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

iox #1067 create command line parser abstraction #1102

Conversation

elfenpiff
Copy link
Contributor

@elfenpiff elfenpiff commented Feb 14, 2022

Pre-Review Checklist for the PR Author

  1. Code follows the coding style of CONTRIBUTING.md
  2. Tests follow the best practice for testing
  3. Changelog updated in the unreleased section including API breaking changes
  4. Branch follows the naming format (iox-#123-this-is-a-branch)
  5. Commits messages are according to this guideline
    • Commit messages have the issue ID (iox-#123 commit text)
    • Commit messages are signed (git commit -s)
    • Commit author matches Eclipse Contributor Agreement (and ECA is signed)
  6. Update the PR title
    • Follow the same conventions as for commit messages
    • Link to the relevant issue
  7. Relevant issues are linked
  8. Add sensible notes for the reviewer
  9. All checks have passed (except task-list-completed)
  10. Assign PR to reviewer

Notes for Reviewer

Required for the Windows command line feature in the upcoming iceoryx 2.0. This provides the building blocks to replace and unify all the command line parsing algorithms in all applications with a simple struct based interface. With this the requirement for the library getopt.h is removed and we have the same simplistic command line parsing approach on all platforms.

Furthermore, this class provides a lot of error handling, boundary checking etc. which is not present at the moment. Invalid arguments are most of the time only poorly checked and this may lead to crashes. With those classes this should be a problem of the past.

Most likely the command line parser will not become a safety certified class therefore the tests most not perform a MC/DC coverage. Nevertheless I tried to be as diligent as possible.

Checklist for the PR Reviewer

  • Commits are properly organized and messages are according to the guideline
  • Code according to our coding style and naming conventions
  • Unit tests have been written for new behavior
    • Each unit test case has a unique UUID
  • Public API changes are documented via doxygen
  • Copyright owner are updated in the changed files
  • PR title describes the changes

Post-review Checklist for the PR Author

  1. All open points are addressed and tracked via issues

References

@elfenpiff elfenpiff force-pushed the iox-#1067-create-command-line-parser-abstraction branch from dec4497 to 10d0ea7 Compare February 14, 2022 13:02
@elfenpiff elfenpiff self-assigned this Feb 14, 2022
@codecov
Copy link

codecov bot commented Feb 14, 2022

Codecov Report

Merging #1102 (ce7f38f) into master (6a58416) will decrease coverage by 1.70%.
The diff coverage is 0.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1102      +/-   ##
==========================================
- Coverage   77.39%   75.68%   -1.71%     
==========================================
  Files         367      375       +8     
  Lines       14248    14566     +318     
  Branches     1992     2072      +80     
==========================================
- Hits        11027    11025       -2     
- Misses       2590     2909     +319     
- Partials      631      632       +1     
Flag Coverage Δ
unittests 75.35% <0.00%> (-1.70%) ⬇️
unittests_timing 15.33% <0.00%> (-0.35%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...st/include/iceoryx_dust/internal/cli/arguments.inl 0.00% <0.00%> (ø)
...de/iceoryx_dust/internal/cli/option_definition.hpp 0.00% <0.00%> (ø)
...clude/iceoryx_dust/internal/cli/option_manager.inl 0.00% <0.00%> (ø)
iceoryx_dust/source/cli/arguments.cpp 0.00% <0.00%> (ø)
iceoryx_dust/source/cli/command_line_parser.cpp 0.00% <0.00%> (ø)
iceoryx_dust/source/cli/option.cpp 0.00% <0.00%> (ø)
iceoryx_dust/source/cli/option_definition.cpp 0.00% <0.00%> (ø)
iceoryx_dust/source/cli/option_manager.cpp 0.00% <0.00%> (ø)
iceoryx_hoofs/source/concurrent/loffli.cpp 80.00% <0.00%> (-5.72%) ⬇️

@elfenpiff elfenpiff force-pushed the iox-#1067-create-command-line-parser-abstraction branch from 0d4c789 to 570da07 Compare February 14, 2022 13:41
@elBoberido
Copy link
Member

I can't promise to look at this anytime soon

@elfenpiff
Copy link
Contributor Author

@elBoberido this is alright ... but I hoped that I maybe can bribe you with coffee, cookies, cake and chocolate.

@elfenpiff elfenpiff force-pushed the iox-#1067-create-command-line-parser-abstraction branch from 83ca102 to 13232d8 Compare February 14, 2022 16:13
Copy link
Contributor

@FerdinandSpitzschnueffler FerdinandSpitzschnueffler left a comment

Choose a reason for hiding this comment

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

Only reviewed command_line.hpp and corresponding .inl and tests so far.

iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/include/iceoryx_hoofs/cxx/command_line.hpp Outdated Show resolved Hide resolved
iceoryx_hoofs/test/moduletests/test_cxx_command_line.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@FerdinandSpitzschnueffler FerdinandSpitzschnueffler left a comment

Choose a reason for hiding this comment

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

CommandLineParser, but not the tests.

iceoryx_hoofs/source/cxx/command_line_parser.cpp Outdated Show resolved Hide resolved
iceoryx_hoofs/source/cxx/command_line_parser.cpp Outdated Show resolved Hide resolved
iceoryx_hoofs/source/cxx/command_line_parser.cpp Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
Copy link
Contributor

@mossmaurice mossmaurice left a comment

Choose a reason for hiding this comment

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

Please don't introduce new direct calls to errorHandler() in iceoryx_hoofs.

Copy link
Contributor

@FerdinandSpitzschnueffler FerdinandSpitzschnueffler left a comment

Choose a reason for hiding this comment

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

I checked if the CommandLineParser tests make sense; will check for missing tests in the next review round.

@dkroenke dkroenke self-requested a review February 17, 2022 12:38
@dkroenke dkroenke added the enhancement New feature label Feb 17, 2022
@elfenpiff elfenpiff force-pushed the iox-#1067-create-command-line-parser-abstraction branch from 2e440d5 to ed271a8 Compare February 28, 2022 11:19
@elfenpiff elfenpiff force-pushed the iox-#1067-create-command-line-parser-abstraction branch from ed271a8 to d113363 Compare March 14, 2022 16:26
@elfenpiff elfenpiff force-pushed the iox-#1067-create-command-line-parser-abstraction branch from e1929cf to be68cad Compare April 5, 2022 09:05
… copyright header

Signed-off-by: Christian Eltzschig <[email protected]>
Copy link
Member

@elBoberido elBoberido left a comment

Choose a reason for hiding this comment

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

We are getting closer but still not there yet.

The tests are still no reviewed. Some fun for later.

/// @brief returns true when it contains a long option which is set
bool hasLongOption() const noexcept;

char shortOption = NO_SHORT_OPTION;
Copy link
Member

Choose a reason for hiding this comment

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

This somehow smells like a cxx::optional<char> shortOption or cxx::string<1> shortOption. It's not a request for change just a food for thought.

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 would stick to this. The reason is that char is also somehow nullable and this I cannot avoid. What if the user explicitly state that the short option should be \0? With this approach I covered it.

Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be a the same with a cxx::string<1> which would be empty for \0? I'm just thinking about this since it would lead to a nice symmetry in the checks with longOption. Maybe something for a future refactoring.

return shortOption == '-';
}

bool Option::hasLongOptionName(const OptionName_t& value) const noexcept
Copy link
Member

Choose a reason for hiding this comment

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

Also just food for thought. matchingLongOptionName/hasMatchingLongOptionName instead of hasLongOptionName

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright but then it would be inconsistent with the other methods. But I could rename all methods to something like matchesLongOptionName, matchesShortOptionName, matchesOptionName but I prefer the current names.

iceoryx_hoofs/source/cli/command_line_argument_parser.cpp Outdated Show resolved Hide resolved
iceoryx_hoofs/source/cli/command_line_argument_parser.cpp Outdated Show resolved Hide resolved
Comment on lines 289 to 313
void CommandLineArgumentParser::setDefaultValuesToUnsetOptions() noexcept
{
for (const auto& r : m_optionSet->m_availableOptions)
{
if (r.details.type != OptionType::OPTIONAL)
{
continue;
}

bool isOptionAlreadySet = false;
for (auto& option : m_optionValue.m_arguments)
{
if (option.isSameOption(r))
{
isOptionAlreadySet = true;
break;
}
}

if (!isOptionAlreadySet)
{
m_optionValue.m_arguments.emplace_back(r);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This also took me a while. Actually this does not set default values to unset options since all specified options are already in m_optionValue.m_arguments. What is does is adding unspecified options to the specified options if actionWhenOptionUnknown is not UnknownOption::TERMINATE. Please keep a consistent terminology. Currently it is a mixture of unknown and unset.

I would even argue to not add the unknown options to m_optionValue.m_arguments. If the developer forgot to specify the option at least it will be revealed in tests and the incorrect behavior can be fixed.

}
case UnknownOption::IGNORE:
{
if (isNextArgumentAValue(i))
Copy link
Member

Choose a reason for hiding this comment

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

Pleas print a message that an unknown option was found and that it will be ignored.

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 explicitly do not want that. Let's assume you do something like ros with the commands and subcommands and parse some arguments internally and the remaining arguments are forwarded to another command line parser which knows what to do with it.

Then it would be absolutely annoying to see messages on the console that some arguments weren't parsed by the first parser.

Copy link
Member

Choose a reason for hiding this comment

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

But what ros does with the subcommands is different that what this does, doesn't it?

Let's assume I have ./foo bar baz caramba. Then I would assume that the parser would stop parsing when the subcommand bar is found and delegate the remaining arguments to the next stage. If there is no output, how do I know that I mistyped the command like ./foo baz baz caramba?

Copy link
Member

Choose a reason for hiding this comment

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

... when thinking about it, there shouldn't be an option to ignore params but just the option to stop parsing when a subcommand is found or maybe call the nesting parser on the remaining arguments

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 removed the feature again. Whenever you type an unknown option you get an error message and the help is shown.

iceoryx_hoofs/source/cli/command_line_argument_parser.cpp Outdated Show resolved Hide resolved
@elBoberido
Copy link
Member

You challenged me with the struct size ... now I throw my code in the ring :)

https://gist.github.com/elBoberido/86ec2d69912a47a5ba7cf09acba9de4e

The resulting struct has only 8 bytes of additional data ... on the plus side, the parser front-end is even simpler ... the secret is once again to prefer factories over constructors :)

elfenpiff added 3 commits July 4, 2022 16:12
…rite methods for better readability, options are only sorted before they are printed in the help, add parameter to doxygen docu

Signed-off-by: Christian Eltzschig <[email protected]>
…truct by creating an explicit factory which holds all the temporary members

Signed-off-by: Christian Eltzschig <[email protected]>
…e can use argv[0] system wide starting from main

Signed-off-by: Christian Eltzschig <[email protected]>
@elfenpiff
Copy link
Contributor Author

You challenged me with the struct size ... now I throw my code in the ring :)

https://gist.github.com/elBoberido/86ec2d69912a47a5ba7cf09acba9de4e

The resulting struct has only 8 bytes of additional data ... on the plus side, the parser front-end is even simpler ... the secret is once again to prefer factories over constructors :)

Alright you mastered the challenge! And you inspired me, I used your idea and reduced the base size of the user defined command line struct to 16 - not to 8 like you. The reason is that this command line struct stores by default the binary name which require additional 8 byte since I store the const char *.

This shouldn't pose any problem since argv[0] is valid as long as one is in main (so for the whole runtime).

elfenpiff and others added 4 commits July 4, 2022 22:48
@flynneva
Copy link

flynneva commented Nov 4, 2022

@elfenpiff @elBoberido @dkroenke any chance we could get this merged in? Would be super useful elsewhere 😄

@elBoberido
Copy link
Member

@elfenpiff @elBoberido @dkroenke any chance we could get this merged in? Would be super useful elsewhere smile

@flynneva with the introduction of iceoryx_dust we have a nice place for code which does not need to fulfill all the strict requirements for code which will be certified. @elfenpiff will continue with the effort to merge this PR

@elfenpiff elfenpiff requested a review from elBoberido November 8, 2022 11:02
Copy link
Member

@elBoberido elBoberido left a comment

Choose a reason for hiding this comment

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

Okay to be merged into dust

@elfenpiff elfenpiff dismissed stale reviews from mossmaurice and dkroenke November 17, 2022 14:53

outdated

@elfenpiff elfenpiff merged commit dafb6fa into eclipse-iceoryx:master Nov 17, 2022
@elfenpiff elfenpiff deleted the iox-#1067-create-command-line-parser-abstraction branch November 17, 2022 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

command line parser abstraction
6 participants