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

Add command-line parser #123

Merged
merged 39 commits into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d7d64e2
Add command-line parser
Xrayez Aug 7, 2021
c2fc422
Remove override keywords in `CommandLineParser`
Xrayez Aug 7, 2021
c7ff6ab
Fix shadowed variable when comparing option names
Xrayez Aug 7, 2021
6f8d80a
Formatting and style changes to `CommandLineParser`
Xrayez Aug 7, 2021
e10edfd
Fix typo in `get_occurrence_count` in `CommandLineParser`
Xrayez Aug 7, 2021
416d1e2
Documentation fixes for command-line parser related classes
Xrayez Aug 7, 2021
807c142
Rename `parse_args` to `parse` in `CommandLineParser`
Xrayez Aug 8, 2021
4fe0eb3
Add `CommandLineParser` test suite
Xrayez Aug 8, 2021
02d8645
Rename `get_error()` to `get_error_text()` in `CommandLineParser`
Xrayez Aug 8, 2021
7be1c72
Add test cases for `CommandLineParser`
Xrayez Aug 8, 2021
df0a2b3
Rename more method in `CommandLineParser`
Xrayez Aug 9, 2021
db474d3
Add short option and positional test cases in `CommandLineParser`
Xrayez Aug 9, 2021
d074e05
Remove dead code related to checkers in `CommandLineParser`
Xrayez Aug 9, 2021
9c02f16
Make help format parameter optional in `CommandLineParser.get_help_te…
Xrayez Aug 9, 2021
e0f102a
Move one-line implementation to declaration in `CommandLineParser`
Xrayez Aug 9, 2021
66e37de
Update docs for `CommandLineParser`
Xrayez Aug 9, 2021
7141f6e
Use `ERR_PARSE_ERROR` for `CommandLineParser`
Xrayez Aug 9, 2021
8b01b14
Merge `validate()` into `parse()` in `CommandLineParser`
Xrayez Aug 9, 2021
dcf414e
Add `new_option()` to `CommandLineParser`
Xrayez Aug 9, 2021
d6cb740
Change getter names to follow naming conventions in command line parser
Xrayez Aug 9, 2021
1a0fa2d
Use `index` over `idx` for CLI option arguments
Xrayez Aug 9, 2021
093b4d2
Declare `CommandLineParser` dependencies
Xrayez Aug 9, 2021
76a7e5c
Add more test cases for command line parser
Xrayez Aug 10, 2021
b66bbef
Rename `add_option()` to `append_option()`, `new_option()` to `add_op…
Xrayez Aug 10, 2021
05f4b44
Fix usage example of command-line parser
Xrayez Aug 10, 2021
1b77d73
Further improve docs for command-line parser
Xrayez Aug 11, 2021
b05da1d
Add prefix test cases for command-line parser
Xrayez Aug 11, 2021
514d73b
Fix `ParsedPrefix.is_exists()` name
Xrayez Aug 11, 2021
ff7ee37
Add important TODOs for Godot 4.0 regarding command-line parser
Xrayez Aug 11, 2021
1a41e9d
Add utility methods for `CommandLineOption`
Xrayez Aug 12, 2021
2eeadba
Validate disallowed default arguments in `CommandLineOption`
Xrayez Aug 12, 2021
8e4595c
Add test case for unrecognized options
Xrayez Aug 12, 2021
558dd3d
Move some variable closer to use in command-line parser
Xrayez Aug 12, 2021
37b7478
Mark `help` and `version` as special meta options
Xrayez Aug 12, 2021
f539979
Make sure meta option was parsed on the command-line
Xrayez Aug 12, 2021
a4859a6
Expose `meta` property in `CommandLineOption`
Xrayez Aug 12, 2021
20d3a30
Add positional options test case for command-line parser
Xrayez Aug 13, 2021
90ea98a
Simplify implementation of `CommandLineParser.add_option()` method
Xrayez Aug 13, 2021
9340536
Fix precedence of long prefix parsing in `CommandLineParser`
Xrayez Aug 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ See also a full list of
98teg
Andrii Doroshenko (Xrayez)
Daw11
Hennadii Chernyshchyk (Shatur)
lupoDharkael
Mariano Suligoy (MarianoGnu)
Rafał Mikrut (qarmin)
Twarit Waikar (ChronicallySerious)
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

### Added
- Built-in implementation of Git version control plugin.
- A `CommandLineParser` class which allows to parse arguments from `OS.get_cmdline_args()`.
- An experimental support for cross-language mixin using `MixinScript` (aka `MultiScript`).
- A `PolyPath2D` node, which takes `Path2D` nodes to buffer curves into polygons.
- A `Stopwatch` node, which complements Godot's `Timer` node.
Expand Down
906 changes: 906 additions & 0 deletions core/command_line_parser.cpp

Large diffs are not rendered by default.

245 changes: 245 additions & 0 deletions core/command_line_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
#pragma once

#include "core/ordered_hash_map.h"
#include "core/reference.h"

class CommandLineOption : public Reference {
GDCLASS(CommandLineOption, Reference);

// Names for the options. e.g --help or -h.
PoolStringArray _names;
// List of default values for each argument, empty if any value is allowed.
PoolStringArray _default_args;
// List of allowed values for each argument, empty if any value is allowed.
PoolStringArray _allowed_args;
// Option's description that will be displayed in help.
String _description;
// Option's category, options sharing the same category are grouped together in help.
String _category;
// Name for the option's arguments that will be displayed in help.
String _arg_text = RTR("<arg>");
// Make the option visible in help.
bool _hidden = false;
// If true, arguments can be specified without an option name.
bool _positional = false;
// If true, argument must always be provided.
bool _required = false;
// If true, the option can be specified several times.
bool _multitoken = false;
// Arguments count required for the option, -1 for all arguments left.
int _arg_count = 1;
// Options marked as meta, such as --help, --version etc.
// Allows to skip some validation checks such as required options.
bool _meta = false;

protected:
static void _bind_methods();

public:
void set_names(const PoolStringArray &p_names);
PoolStringArray get_names() const { return _names; }

void set_default_args(const PoolStringArray &p_args) { _default_args = p_args; }
PoolStringArray get_default_args() const { return _default_args; }

void set_allowed_args(const PoolStringArray &p_args) { _allowed_args = p_args; }
PoolStringArray get_allowed_args() const { return _allowed_args; }

void set_description(const String &p_description) { _description = p_description; }
String get_description() const { return _description; }

void set_category(const String &p_category) { _category = p_category; }
String get_category() const { return _category; }

void set_arg_text(const String &p_arg_text) { _arg_text = p_arg_text; }
String get_arg_text() const { return _arg_text; }

void set_arg_count(int p_count) { _arg_count = p_count; }
int get_arg_count() const { return _arg_count; }

void set_hidden(bool p_hidden) { _hidden = p_hidden; }
bool is_hidden() const { return _hidden; }

void set_positional(bool p_positional) { _positional = p_positional; }
bool is_positional() const { return _positional; }

void set_required(bool p_required) { _required = p_required; }
bool is_required() const { return _required; }

void set_multitoken(bool p_multitoken) { _multitoken = p_multitoken; }
bool is_multitoken() const { return _multitoken; }

void set_as_meta(bool p_meta) { _meta = p_meta; }
bool is_meta() const { return _meta; }

// Utility methods.
void add_name(const String &p_name);
void add_default_arg(const String &p_arg);
void add_allowed_arg(const String &p_arg);

CommandLineOption() = default;
explicit CommandLineOption(const PoolStringArray &p_names, int p_arg_count = 1);
};

class CommandLineHelpFormat : public Reference {
GDCLASS(CommandLineHelpFormat, Reference);

String _help_header;
String _help_footer;
String _usage_title;

int _left_help_pad = 2;
int _right_help_pad = 4;
int _help_line_length = 80;
int _min_description_length = _help_line_length / 2;

bool _autogenerate_usage = true;

protected:
static void _bind_methods();

public:
void set_header(const String &p_header) { _help_header = p_header; }
String get_header() const { return _help_header; }

void set_footer(const String &p_footer) { _help_footer = p_footer; }
String get_footer() const { return _help_footer; }

void set_usage_title(const String &p_title) { _usage_title = p_title; }
String get_usage_title() const { return _usage_title; }

void set_left_pad(int p_size) { _left_help_pad = p_size; }
int get_left_pad() const { return _left_help_pad; }

void set_right_pad(int p_size) { _right_help_pad = p_size; }
int get_right_pad() const { return _right_help_pad; }

void set_line_length(int p_length) { _help_line_length = p_length; }
int get_line_length() const { return _help_line_length; }

void set_min_description_length(int p_length) { _min_description_length = p_length; }
int get_min_description_length() const { return _min_description_length; }

void set_autogenerate_usage(bool p_generate) { _autogenerate_usage = p_generate; }
bool is_usage_autogenerated() const { return _autogenerate_usage; }
};

class CommandLineParser : public Reference {
GDCLASS(CommandLineParser, Reference);

struct ParsedPrefix;

Vector<Ref<CommandLineOption>> _options;

PoolStringArray _args;
PoolStringArray _forwarding_args;

PoolStringArray _long_prefixes;
PoolStringArray _short_prefixes;

Map<const CommandLineOption *, PoolStringArray> _parsed_values;
Map<const CommandLineOption *, PoolStringArray> _parsed_prefixes;
Map<const CommandLineOption *, int> _parsed_count;

String _error_text;

float _similarity_bias = 0.3;

bool _allow_forwarding_args = false;
bool _allow_adjacent = true;
bool _allow_sticky = true;
bool _allow_compound = true;

// Parser main helpers.
bool _are_options_valid() const;
void _read_default_args();
int _validate_arguments(int p_current_idx); // Returns number of arguments taken, -1 on validation error.

// Helpers for the function above that parse a specific case.
int _validate_positional(const String &p_arg, int p_current_idx);
int _validate_adjacent(const String &p_arg, const String &p_prefix, int p_separator);
int _validate_short(const String &p_arg, const String &p_prefix, int p_current_idx);
int _validate_long(const String &p_arg, const String &p_prefix, int p_current_idx);

// Validation helpers.
const CommandLineOption *_validate_option(const String &p_name, const String &p_prefix);
int _validate_option_args(const CommandLineOption *p_option, const String &p_display_name, int p_current_idx, bool p_skip_first = false);
bool _validate_option_arg(const CommandLineOption *p_option, const String &p_display_name, const String &p_arg);

// Save information about parsed option.
void _save_parsed_option(const CommandLineOption *p_option, const String &p_prefix, int p_idx, int p_arg_count, const String &p_additional_value = String());
void _save_parsed_option(const CommandLineOption *p_option, const String &p_prefix, const String &p_value = String());
void _save_parsed_option(const CommandLineOption *p_option, int p_idx, int p_arg_count);

// Help text printers.
String _get_usage(const Vector<Pair<const CommandLineOption *, String>> &p_printable_options, const String &p_title) const;
String _get_options_description(const OrderedHashMap<String, PoolStringArray> &p_categories_data) const;

// Other utilies.
String _to_string(const PoolStringArray &p_names) const; // Returns all option names separated by commas with all prefix variants.
String _get_prefixed_longest_name(const PoolStringArray &p_names) const; // Returns longest name with first available prefix (long or short).
ParsedPrefix _parse_prefix(const String &p_arg) const;
String _find_most_similar(const String &p_name) const;
static bool _contains_optional_options(const Vector<Pair<const CommandLineOption *, String>> &p_printable_options);

protected:
static void _bind_methods();

public:
Error parse(const PoolStringArray &p_args);

Ref<CommandLineOption> add_option(const String &p_name, const String &p_description = "", const String &p_default_value = "", const PoolStringArray &p_allowed_values = PoolStringArray());
Ref<CommandLineOption> add_help_option();
Ref<CommandLineOption> add_version_option();

void append_option(const Ref<CommandLineOption> &p_option);
void set_option(int p_idx, const Ref<CommandLineOption> &p_option);
Ref<CommandLineOption> get_option(int p_idx) const;
int get_option_count() const;
void remove_option(int p_idx);
Ref<CommandLineOption> find_option(const String &p_name) const;

bool is_set(const Ref<CommandLineOption> &p_option) const;

String get_value(const Ref<CommandLineOption> &p_option) const;
PoolStringArray get_value_list(const Ref<CommandLineOption> &p_option) const;

String get_prefix(const Ref<CommandLineOption> &p_option) const;
PoolStringArray get_prefix_list(const Ref<CommandLineOption> &p_option) const;

int get_occurrence_count(const Ref<CommandLineOption> &p_option) const;

PoolStringArray get_forwarding_args() const { return _forwarding_args; }
PoolStringArray get_args() const { return _args; }

String get_help_text(const Ref<CommandLineHelpFormat> &p_format = Ref<CommandLineHelpFormat>()) const;
String get_error_text() const { return _error_text; }

void set_long_prefixes(const PoolStringArray &p_prefixes) { _long_prefixes = p_prefixes; }
PoolStringArray get_long_prefixes() const { return _long_prefixes; }

void set_short_prefixes(const PoolStringArray &p_prefixes) { _short_prefixes = p_prefixes; }
PoolStringArray get_short_prefixes() const { return _short_prefixes; }

void set_similarity_bias(float p_bias) { _similarity_bias = CLAMP(p_bias, 0.0f, 1.0f); }
float get_similarity_bias() const { return _similarity_bias; }

void set_allow_forwarding_args(bool p_allow) { _allow_forwarding_args = p_allow; }
bool are_forwarding_args_allowed() const { return _allow_forwarding_args; }

void set_allow_adjacent(bool p_allow) { _allow_adjacent = p_allow; }
bool is_adjacent_allowed() const { return _allow_adjacent; }

void set_allow_sticky(bool p_allow) { _allow_sticky = p_allow; }
bool is_sticky_allowed() const { return _allow_sticky; }

void set_allow_compound(bool p_allow) { _allow_compound = p_allow; }
bool is_compound_allowed() const { return _allow_compound; }

void clear();

CommandLineParser() {
_long_prefixes.push_back("--");
_short_prefixes.push_back("-");
};
};
5 changes: 5 additions & 0 deletions core/register_core_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ static void _variant_resource_preview_init();
#endif

void register_core_types() {
ClassDB::register_class<CommandLineOption>();
ClassDB::register_class<CommandLineHelpFormat>();
ClassDB::register_class<CommandLineParser>();

#ifdef GOOST_GoostEngine
_goost = memnew(GoostEngine);
ClassDB::register_class<GoostEngine>();
Expand All @@ -38,6 +42,7 @@ void register_core_types() {

ClassDB::register_class<VariantMap>();
ClassDB::register_class<VariantResource>();

#if defined(TOOLS_ENABLED) && defined(GOOST_VariantResource)
EditorNode::add_init_callback(_variant_resource_preview_init);
#endif
Expand Down
41 changes: 41 additions & 0 deletions doc/CommandLineHelpFormat.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="CommandLineHelpFormat" inherits="Reference" version="3.4">
<brief_description>
This class contains formatting options for constructing a help message in [CommandLineParser].
</brief_description>
<description>
The class is used to pass formatting parameters to the [method CommandLineParser.get_help_text] method.
</description>
<tutorials>
</tutorials>
<methods>
</methods>
<members>
<member name="autogenerate_usage" type="bool" setter="set_autogenerate_usage" getter="is_usage_autogenerated" default="true">
If [code]true[/code], the usage text will be automatically generated according to passed options.
</member>
<member name="footer" type="String" setter="set_footer" getter="get_footer" default="&quot;&quot;">
Contains text to be displayed at the end of the help text.
</member>
<member name="header" type="String" setter="set_header" getter="get_header" default="&quot;&quot;">
Contains text to be displayed at the beginning of the help text.
</member>
<member name="left_pad" type="int" setter="set_left_pad" getter="get_left_pad" default="2">
The amount of indentation in spaces to the left of the options in the help text.
</member>
<member name="line_length" type="int" setter="set_line_length" getter="get_line_length" default="80">
The maximum length of the line with option and description in help text. If the line exceeds this length, then its description will be split into several lines. See also [member min_description_length].
</member>
<member name="min_description_length" type="int" setter="set_min_description_length" getter="get_min_description_length" default="40">
The minimum description size used to split description when the line with option and description is too large. See also [member line_length].
</member>
<member name="right_pad" type="int" setter="set_right_pad" getter="get_right_pad" default="4">
The amount of indentation in spaces to the right of the options in the help text.
</member>
<member name="usage_title" type="String" setter="set_usage_title" getter="get_usage_title" default="&quot;&quot;">
Title that will be displayed in usage text. If empty, the name of the current executable file will be used.
</member>
</members>
<constants>
</constants>
</class>
Loading