Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Update template code, documentation page for the DNF5 Command Template #1994

Merged
merged 11 commits into from
Jan 23, 2025
5 changes: 1 addition & 4 deletions doc/templates/command/arguments.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
#ifndef DNF5_COMMANDS_TEMPLATE_ARGUMENTS_HPP
#define DNF5_COMMANDS_TEMPLATE_ARGUMENTS_HPP


#include <libdnf5-cli/session.hpp>
#include <libdnf5/utils/bgettext/bgettext-lib.h>


namespace dnf5 {

// This implementation is needed only if you are using unique_ptr as the type
Expand All @@ -21,5 +19,4 @@ class BarOption : public libdnf5::cli::session::BoolOption {

} // namespace dnf5


#endif // DNF_COMMANDS_DOWNLOAD_TEMPLATE_ARGUMENTS_HPP
#endif // DNF_COMMANDS_TEMPLATE_ARGUMENTS_HPP
42 changes: 29 additions & 13 deletions doc/templates/command/template.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Adjust :lines: in template-command.rst if removing/modifying these comment lines!
// clang-format off
#include "template.hpp"

#include <iostream>
Expand All @@ -6,6 +8,15 @@ namespace dnf5 {

using namespace libdnf5::cli;

void TemplateCommand::set_parent_command() {
auto * parent_cmd = get_session().get_argument_parser().get_root_command();
auto * this_cmd = get_argument_parser_command();
parent_cmd->register_command(this_cmd);

auto & group = parent_cmd->get_group("software_management_commands");
group.register_argument(this_cmd);
}

void TemplateCommand::set_argument_parser() {
// Context is the main object in dnf5.
// It contains useful functions and pieces of information necessary to run
Expand All @@ -16,37 +27,42 @@ void TemplateCommand::set_argument_parser() {
auto & ctx = get_context();
auto & parser = ctx.get_argument_parser();

auto & cmd = *get_argument_parser_command();
auto & this_cmd = *get_argument_parser_command();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to rename this to match the variable name in the set_parent_command() method, to more clearly show that they represent the same object.


// Add template command to the Parser
// Configure 'template' command in the Parser
//
// Set a description that would be displayed in the second column of the
// help message would also add the command to the parser
cmd.set_short_description("A command that prints its name and arguments' name");
// Set a description that would be displayed in the second column of
// the main help message.
this_cmd.set_description("A command that prints its name and arguments' name");

// Add foo and bar options
// Add '--foo' and '--bar' options
//
// Option 1: as said in the header file here you should handle the pointer
// by giving up the ownership to dnf5's parser.
// Set the default value here.
foo_option = dynamic_cast<libdnf5::OptionBool *>(
parser.add_init_value(std::unique_ptr<libdnf5::OptionBool>(new libdnf5::OptionBool(false))));
parser.add_init_value(
std::unique_ptr<libdnf5::OptionBool>(
new libdnf5::OptionBool(false))));
Comment on lines 43 to +46
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kontura My only problem with this (which the pre-commit formatter wants to change back to the un-wrapped format:)

    foo_option = dynamic_cast<libdnf5::OptionBool *>(
       parser.add_init_value(std::unique_ptr<libdnf5::OptionBool>(new libdnf5::OptionBool(false))));

...is that it won't fit when it's embedded on the documentation page, without forcing the reader to scroll horizontally.

Is there an override comment that can stop the formatter from re-wrapping it? I really think it reads better, for the documentation, if the line length is kept down to fit the page format.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do this:

int formatted_code;
// clang-format off
    void    unformatted_code  ;
// clang-format on
void formatted_code_again;

If the disable comment was at the very top of the page we could hide it by changing the include lines again so it doesn't complicate the template.

(I have tested it only briefly but I think if the // clang-format on is omitted it disables the formatting for the one file only.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kontura Done, thanks! I think that looks much better. I also made some other changes, I'll add some review comments about those.


// Create an option by giving it a name. It will be shown in the help message.
// Set long name, description and constant value.
// Link the option to the TemplateCommand's class member.
auto foo = parser.add_new_named_arg("foo");
// Set the long name for the option.
foo->set_long_name("foo");
foo->set_short_description("print foo");
// Set the option's description (for the help message).
foo->set_description("print foo");
// Set the constant value. This is the value assigned when an option
// which takes no value arguments appears on the command line.
foo->set_const_value("true");
// Link the option to the TemplateCommand's class member.
foo->link_value(foo_option);
Comment on lines 48 to 58
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, I fleshed out these comments after realizing, as I was reading the code, that I actually had no idea what the "const_value" was — and had to go digging through include/libdnf5-cli/argument_parser.hpp to find out.


// Register the argument to the command template.
cmd.register_named_arg(foo);
// Register the '--foo' argument to the 'template' command.
this_cmd.register_named_arg(foo);

// Option 2: create a unique_ptr of type BarOption.
// The long name, description, and other values were set in
// dnf5/command/arguments.hpp
// template.hpp when we derived the BarOption class.
bar_option = std::make_unique<BarOption>(*this);
}

Expand Down
44 changes: 35 additions & 9 deletions doc/templates/command/template.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <memory>
#include <vector>


namespace dnf5 {

// This template aims to explain how to add a new command to dnf5.
Expand All @@ -18,38 +17,65 @@ namespace dnf5 {
class TemplateCommand : public Command {
public:
// The command name is set directly in the constructor.
explicit TemplateCommand(Command & parent) : Command(parent, "template") {}
explicit TemplateCommand(Context & context) : Command(context, "template") {}

// This method needs to be overridden to register the command with
// the dnf5 command-line parser, and place the command in one of
// the standard command groups.
//
// Every top-level command will have a set_parent_command method.
// Subcommands will not, their parent command's register_subcommands()
// method is used to register them.
void set_parent_command() override;

// This method needs to be overridden to define the structure of the
// command such as description, options or sub-commands.
// command such as description and options.
void set_argument_parser() override;

// This method MAY be overridden in a top-level command which has
// subcommands, to register them using register_subcommand().
// void register_subcommands() override;

// This method needs to be overridden to run the command.
void run() override;

// This method MAY be overridden to perform any configuration
// needed by the command.
// void configure() override;

// This method MAY be overridden to perform any pre-configuration
// needed by the command.
// void pre_configure() override;

// Not every command will need a pre_configure or configure method,
// and few will need both.
//
// A common pattern in commands that have subcommands is to define
// the top-level command's pre_configure as containing only
// throw_missing_command(). This ensures that using the command
// without one of its subcommands will be treated as an error.

private:
// There are two ways to specify options for a command.

// Option 1: C++ Pointer.
// Create a OptionBool pointer for the option you need.
// Create an OptionBool pointer for the option you need.
//
// Using a pure C++ pointer is safe here since the OptionBool class
// will take care of moving the pointer ownership to the parser, which will
// be in charge of handling the memory deallocation.
libdnf5::OptionBool * foo_option{nullptr};

// Option 2: STL Unique Pointer
// Create a unique_ptr<BarOption>
// Create a std::unique_ptr<BarOption>
//
// This might be needed in case you need full control over the options and
// do not want dnf5's parser to handle it.
// To use a unique_ptr, BarOption has to be defined (see the file
// dnf5/commands/arguments.hpp)
// To use a unique_ptr, BarOption has to be defined.
// A boolean BarOption is defined in the accompanying "arguments.hpp".
std::unique_ptr<BarOption> bar_option{nullptr};
};


} // namespace dnf5


#endif // DNF5_COMMANDS_TEMPLATE_TEMPLATE_HPP
38 changes: 28 additions & 10 deletions doc/templates/template-command.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,42 @@ This page focuses on how to write a command with two options in dnf5.
the snippets, following the directory structure and naming shown above
each one.

``dnf5/command/template.hpp``

The command header
------------------

.. literalinclude:: command/template.hpp
:language: c++
:linenos:
:lines: 2,4-
:caption: ``dnf5/command/template.hpp``

``dnf5/command/template.cpp``
The command source
------------------

.. literalinclude:: command/template.cpp
:language: c++
:linenos:
:lines: 2,4-
:lines: 3-
:caption: ``dnf5/command/template.cpp``

``dnf5/command/arguments.hpp``
The argument class(es)
----------------------

.. literalinclude:: command/arguments.hpp
:language: c++
:linenos:
:lines: 2,4-
:caption: ``dnf5/command/arguments.hpp``

Direct integration into dnf5 codebase
-------------------------------------

.. CAUTION::

If you are writing an external command to be included in a dnf5
plugin, **STOP** here and move on to the :ref:`dnf5 plugin template`.
The remainder of this page is only applicable when writing commands
to be included directly in the dnf5 codebase (in the ``dnf5/commands/``
subdirectory).

The command must be included and registered in ``dnf5/main.cpp``

Expand All @@ -39,8 +55,9 @@ The command must be included and registered in ``dnf5/main.cpp``
// new commands must be included in main.cpp
#include "commands/template/template.hpp"

// commands must be registered like this
register_subcommand(std::make_unique<TemplateCommand>(*this), software_management_commands_group);
// commands are registered in the add_commands() function
context.add_and_initialize_command(
std::make_unique<TemplateCommand>(context));

Following this example you should have an output like this.

Expand All @@ -52,13 +69,14 @@ Following this example you should have an output like this.
install Install software
upgrade Upgrade software
...
template A command that prints its name and arguments' name
template A command that prints its name and arguments'
name

.. code-block::

$ dnf5 template --help
Usage:
dnf5 template [GLOBAL OPTIONS] [OPTIONS] [ARGUMENTS]
dnf5 [GLOBAL OPTIONS] template [OPTIONS]

Options:
--bar print bar
Expand Down
2 changes: 2 additions & 0 deletions doc/templates/template-dnf5-plugin.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _dnf5 plugin template:

DNF5 Plugin Template
====================

Expand Down
Loading