eac_cli 0.10.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/eac_cli/definition.rb +36 -31
- data/lib/eac_cli/definition/alternative.rb +83 -0
- data/lib/eac_cli/definition/help_formatter.rb +76 -0
- data/lib/eac_cli/definition/positional_argument.rb +15 -2
- data/lib/eac_cli/docopt/doc_builder.rb +18 -40
- data/lib/eac_cli/docopt/doc_builder/alternative.rb +50 -0
- data/lib/eac_cli/parser.rb +21 -3
- data/lib/eac_cli/parser/alternative.rb +88 -0
- data/lib/eac_cli/parser/alternative/argv.rb +17 -0
- data/lib/eac_cli/parser/alternative/double_dash.rb +24 -0
- data/lib/eac_cli/parser/alternative/options.rb +58 -0
- data/lib/eac_cli/parser/alternative/positionals.rb +30 -0
- data/lib/eac_cli/runner.rb +2 -2
- data/lib/eac_cli/runner_with/help.rb +1 -1
- data/lib/eac_cli/runner_with/output_file.rb +5 -1
- data/lib/eac_cli/version.rb +1 -1
- metadata +10 -5
- data/lib/eac_cli/parser/options_collection.rb +0 -127
- data/lib/eac_cli/parser/parse_result.rb +0 -38
- data/lib/eac_cli/parser/positional_collection.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90408a7ab93c85784cf6f5946a034567a222ecbfb66922903302ab50a5a38d94
|
4
|
+
data.tar.gz: 87f6ab796eb1c6734fa615500cb5d7721d3fb1477ce7059bf7da434f60543b01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9a941471808a3827ff4edc8bf6f3e3533e174a62078895514e90fbb32ec53db5f4284da379357fc2bfe64edae36646400688df4386d7344c0cfb087a7887544
|
7
|
+
data.tar.gz: d9e54da85eb7487bba85a30550ffd32065d7b419c5384c81d9724b6d33d0385791b9d0bd07b5733e391fd0030faf59d9b21a9a7ff2c91a384bcfcc4b0d1cff94
|
data/lib/eac_cli/definition.rb
CHANGED
@@ -9,72 +9,65 @@ module EacCli
|
|
9
9
|
class Definition
|
10
10
|
require_sub __FILE__
|
11
11
|
|
12
|
+
MAIN_ALTERNATIVE_KEY = :main
|
12
13
|
SUBCOMMAND_NAME_ARG = 'subcommand'
|
13
14
|
SUBCOMMAND_ARGS_ARG = 'subcommand_args'
|
14
15
|
|
15
16
|
attr_accessor :description
|
16
|
-
attr_accessor :options_argument
|
17
17
|
|
18
18
|
def initialize
|
19
19
|
self.description = '-- NO DESCRIPTION SET --'
|
20
|
-
|
20
|
+
alternatives_set[MAIN_ALTERNATIVE_KEY] = main_alternative
|
21
21
|
end
|
22
22
|
|
23
23
|
def alt(&block)
|
24
|
-
r = ::EacCli::Definition.new
|
24
|
+
r = ::EacCli::Definition::Alternative.new
|
25
25
|
r.instance_eval(&block)
|
26
|
-
|
26
|
+
alternatives_set[new_alternative_key] = r
|
27
27
|
r
|
28
28
|
end
|
29
29
|
|
30
30
|
def alternatives
|
31
|
-
|
31
|
+
alternatives_set.values
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
|
36
|
-
short, long, description, option_options
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
|
-
def bool_opt(short, long, description, option_options = {})
|
41
|
-
options << ::EacCli::Definition::BooleanOption.new(short, long, description, option_options)
|
34
|
+
def alternative(key)
|
35
|
+
alternatives_set.fetch(key)
|
42
36
|
end
|
43
37
|
|
44
38
|
def desc(description)
|
45
39
|
self.description = description
|
46
40
|
end
|
47
41
|
|
48
|
-
def
|
49
|
-
|
42
|
+
def help_formatter
|
43
|
+
@help_formatter ||= ::EacCli::Definition::HelpFormatter.new(self)
|
50
44
|
end
|
51
45
|
|
52
|
-
def
|
53
|
-
@
|
46
|
+
def main_alternative
|
47
|
+
@main_alternative ||= begin
|
48
|
+
r = ::EacCli::Definition::Alternative.new
|
49
|
+
r.options_argument(true)
|
50
|
+
r
|
51
|
+
end
|
54
52
|
end
|
55
53
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
pos_set << ::EacCli::Definition::PositionalArgument.new(name, arg_options)
|
54
|
+
def options_arg(options_argument)
|
55
|
+
self.options_argument = options_argument
|
60
56
|
end
|
61
57
|
|
62
|
-
def
|
63
|
-
|
58
|
+
def options_argument
|
59
|
+
main_alternative.options_argument?
|
64
60
|
end
|
65
61
|
|
66
|
-
def
|
67
|
-
|
62
|
+
def options_argument=(enable)
|
63
|
+
main_alternative.options_argument(enable)
|
68
64
|
end
|
69
65
|
|
70
|
-
|
71
|
-
|
72
|
-
pos_set << ::EacCli::Definition::PositionalArgument.new(SUBCOMMAND_ARGS_ARG,
|
73
|
-
optional: true, repeat: true)
|
74
|
-
end
|
66
|
+
delegate :arg_opt, :bool_opt, :options, :pos_arg,
|
67
|
+
:positional, :subcommands, to: :main_alternative
|
75
68
|
|
76
69
|
def subcommands?
|
77
|
-
|
70
|
+
alternatives.any?(&:subcommands?)
|
78
71
|
end
|
79
72
|
|
80
73
|
def options_first(enable = true)
|
@@ -87,6 +80,18 @@ module EacCli
|
|
87
80
|
|
88
81
|
private
|
89
82
|
|
83
|
+
def alternatives_set
|
84
|
+
@alternatives_set ||= ::ActiveSupport::HashWithIndifferentAccess.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def new_alternative_key
|
88
|
+
@last_key ||= 0
|
89
|
+
loop do
|
90
|
+
@last_key += 1
|
91
|
+
break @last_key unless alternatives_set.key?(@last_key)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
90
95
|
def pos_set
|
91
96
|
@pos_set ||= []
|
92
97
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_cli/definition/argument_option'
|
4
|
+
require 'eac_cli/definition/boolean_option'
|
5
|
+
require 'eac_cli/definition/positional_argument'
|
6
|
+
|
7
|
+
module EacCli
|
8
|
+
class Definition
|
9
|
+
class Alternative
|
10
|
+
SUBCOMMAND_NAME_ARG = :subcommand
|
11
|
+
SUBCOMMAND_ARGS_ARG = :subcommand_args
|
12
|
+
|
13
|
+
def arg_opt(short, long, description, option_options = {})
|
14
|
+
options_set << ::EacCli::Definition::ArgumentOption.new(
|
15
|
+
short, long, description, option_options
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def bool_opt(short, long, description, option_options = {})
|
20
|
+
options_set << ::EacCli::Definition::BooleanOption.new(short, long, description,
|
21
|
+
option_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def options
|
25
|
+
options_set.to_a
|
26
|
+
end
|
27
|
+
|
28
|
+
def options_argument?
|
29
|
+
@options_argument ? true : false
|
30
|
+
end
|
31
|
+
|
32
|
+
def options_argument(enable)
|
33
|
+
@options_argument = enable
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def pos_arg(name, arg_options = {})
|
39
|
+
new_pos_arg = ::EacCli::Definition::PositionalArgument.new(name, arg_options)
|
40
|
+
check_positional_blocked(new_pos_arg)
|
41
|
+
pos_set << new_pos_arg
|
42
|
+
end
|
43
|
+
|
44
|
+
def positional
|
45
|
+
pos_set.to_a
|
46
|
+
end
|
47
|
+
|
48
|
+
def positional_arguments_blocked?(new_pos_arg)
|
49
|
+
last = pos_set.last
|
50
|
+
return false unless last
|
51
|
+
return true if subcommands?
|
52
|
+
return true if last.repeat?
|
53
|
+
return true if last.optional? && new_pos_arg.if_present(&:required?)
|
54
|
+
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def subcommands
|
59
|
+
pos_arg(SUBCOMMAND_NAME_ARG, subcommand: true)
|
60
|
+
pos_set << ::EacCli::Definition::PositionalArgument.new(SUBCOMMAND_ARGS_ARG,
|
61
|
+
optional: true, repeat: true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def subcommands?
|
65
|
+
pos_set.any?(&:subcommand?)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def check_positional_blocked(new_pos_arg)
|
71
|
+
raise 'Positional arguments are blocked' if positional_arguments_blocked?(new_pos_arg)
|
72
|
+
end
|
73
|
+
|
74
|
+
def pos_set
|
75
|
+
@pos_set ||= []
|
76
|
+
end
|
77
|
+
|
78
|
+
def options_set
|
79
|
+
@options_set ||= []
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_ruby_utils/core_ext'
|
4
|
+
|
5
|
+
module EacCli
|
6
|
+
class Definition
|
7
|
+
class HelpFormatter
|
8
|
+
SEP = ' '
|
9
|
+
IDENT = SEP * 2
|
10
|
+
OPTION_DESC_SEP = IDENT * 2
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def option_long(option)
|
14
|
+
b = option.long
|
15
|
+
b += '=VALUE' if option.argument?
|
16
|
+
b
|
17
|
+
end
|
18
|
+
|
19
|
+
def option_short(option)
|
20
|
+
b = option.short
|
21
|
+
b += 'VALUE' if option.argument?
|
22
|
+
b
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
common_constructor :definition
|
27
|
+
|
28
|
+
def positional_argument(positional)
|
29
|
+
if positional.subcommand?
|
30
|
+
::EacRubyUtils::Console::DocoptRunner::SUBCOMMANDS_MACRO
|
31
|
+
else
|
32
|
+
r = "<#{positional.name}>"
|
33
|
+
r += '...' if positional.repeat?
|
34
|
+
r = "[#{r}]" if positional.optional?
|
35
|
+
r
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def section(header, include_header = true)
|
40
|
+
b = include_header ? "#{header.humanize}:\n" : ''
|
41
|
+
b += send("self_#{header}") + "\n"
|
42
|
+
# TO-DO: implement alternatives
|
43
|
+
b
|
44
|
+
end
|
45
|
+
|
46
|
+
def self_options
|
47
|
+
definition.options.map { |option| IDENT + option_definition(option) }.join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def self_usage
|
51
|
+
IDENT + self_usage_arguments.join(SEP)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self_usage_arguments
|
55
|
+
[::EacRubyUtils::Console::DocoptRunner::PROGRAM_MACRO] +
|
56
|
+
definition.options_argument.if_present([]) { |_v| ['[options]'] } +
|
57
|
+
self_usage_arguments_options +
|
58
|
+
self_usage_arguments_positional
|
59
|
+
end
|
60
|
+
|
61
|
+
def self_usage_arguments_options
|
62
|
+
definition.options.select(&:show_on_usage?).map do |option|
|
63
|
+
self.class.option_long(option)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self_usage_arguments_positional
|
68
|
+
definition.positional.map { |p| positional_argument(p) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_banner
|
72
|
+
"#{definition.description}\n\n#{section('usage')}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -5,8 +5,10 @@ require 'eac_ruby_utils/core_ext'
|
|
5
5
|
module EacCli
|
6
6
|
class Definition
|
7
7
|
class PositionalArgument
|
8
|
+
DEFAULT_REQUIRED = true
|
9
|
+
|
8
10
|
enable_listable
|
9
|
-
lists.add_symbol :option, :optional, :repeat, :subcommand
|
11
|
+
lists.add_symbol :option, :optional, :repeat, :required, :subcommand
|
10
12
|
common_constructor :name, :options, default: [{}] do
|
11
13
|
options.assert_valid_keys(self.class.lists.option.values)
|
12
14
|
end
|
@@ -16,16 +18,27 @@ module EacCli
|
|
16
18
|
end
|
17
19
|
|
18
20
|
def optional?
|
19
|
-
|
21
|
+
!required?
|
20
22
|
end
|
21
23
|
|
22
24
|
def repeat?
|
23
25
|
options[OPTION_REPEAT]
|
24
26
|
end
|
25
27
|
|
28
|
+
def required?
|
29
|
+
return true if options.key?(OPTION_REQUIRED) && options.fetch(OPTION_REQUIRED)
|
30
|
+
return false if options.key?(OPTION_OPTIONAL) && options.fetch(OPTION_OPTIONAL)
|
31
|
+
|
32
|
+
DEFAULT_REQUIRED
|
33
|
+
end
|
34
|
+
|
26
35
|
def subcommand?
|
27
36
|
options[OPTION_SUBCOMMAND]
|
28
37
|
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"#{self.class.name.demodulize}[#{identifier}]"
|
41
|
+
end
|
29
42
|
end
|
30
43
|
end
|
31
44
|
end
|
@@ -6,71 +6,49 @@ require 'eac_ruby_utils/console/docopt_runner'
|
|
6
6
|
module EacCli
|
7
7
|
module Docopt
|
8
8
|
class DocBuilder
|
9
|
+
require_sub __FILE__
|
9
10
|
common_constructor :definition
|
10
11
|
|
11
12
|
SEP = ' '
|
12
13
|
IDENT = SEP * 2
|
13
14
|
OPTION_DESC_SEP = IDENT * 2
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
r += '...' if positional.repeat?
|
21
|
-
r = "[#{r}]" if positional.optional?
|
22
|
-
r
|
16
|
+
class << self
|
17
|
+
def option_long(option)
|
18
|
+
b = option.long
|
19
|
+
b += '=<value>' if option.argument?
|
20
|
+
b
|
23
21
|
end
|
24
22
|
end
|
25
23
|
|
26
|
-
def option_argument(option)
|
27
|
-
option_long(option)
|
28
|
-
end
|
29
|
-
|
30
24
|
def option_definition(option)
|
31
|
-
option.short + SEP + option_long(option) + OPTION_DESC_SEP + option.description
|
32
|
-
end
|
33
|
-
|
34
|
-
def option_long(option)
|
35
|
-
b = option.long
|
36
|
-
b += '=<value>' if option.argument?
|
37
|
-
b
|
25
|
+
option.short + SEP + self.class.option_long(option) + OPTION_DESC_SEP + option.description
|
38
26
|
end
|
39
27
|
|
40
28
|
def section(header, include_header = true)
|
41
29
|
b = include_header ? "#{header.humanize}:\n" : ''
|
42
30
|
b += send("self_#{header}") + "\n"
|
43
31
|
definition.alternatives.each do |alternative|
|
44
|
-
b +=
|
32
|
+
b += IDENT + ::EacCli::Docopt::DocBuilder::Alternative.new(alternative).to_s + "\n"
|
45
33
|
end
|
46
34
|
b
|
47
35
|
end
|
48
36
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def self_usage
|
54
|
-
IDENT + self_usage_arguments.join(SEP)
|
55
|
-
end
|
56
|
-
|
57
|
-
def self_usage_arguments
|
58
|
-
[::EacRubyUtils::Console::DocoptRunner::PROGRAM_MACRO] +
|
59
|
-
definition.options_argument.if_present([]) { |_v| ['[options]'] } +
|
60
|
-
self_usage_arguments_options +
|
61
|
-
self_usage_arguments_positional
|
62
|
-
end
|
63
|
-
|
64
|
-
def self_usage_arguments_options
|
65
|
-
definition.options.select(&:show_on_usage?).map { |option| option_argument(option) }
|
37
|
+
def options_section
|
38
|
+
"Options:\n" +
|
39
|
+
definition.alternatives.flat_map(&:options)
|
40
|
+
.map { |option| IDENT + option_definition(option) + "\n" }.join
|
66
41
|
end
|
67
42
|
|
68
|
-
def
|
69
|
-
|
43
|
+
def usage_section
|
44
|
+
"Usage:\n" +
|
45
|
+
definition.alternatives.map do |alternative|
|
46
|
+
IDENT + ::EacCli::Docopt::DocBuilder::Alternative.new(alternative).to_s + "\n"
|
47
|
+
end.join
|
70
48
|
end
|
71
49
|
|
72
50
|
def to_s
|
73
|
-
"#{definition.description}\n\n#{
|
51
|
+
"#{definition.description}\n\n#{usage_section}\n#{options_section}\n"
|
74
52
|
end
|
75
53
|
end
|
76
54
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_ruby_utils/core_ext'
|
4
|
+
require 'eac_ruby_utils/console/docopt_runner'
|
5
|
+
|
6
|
+
module EacCli
|
7
|
+
module Docopt
|
8
|
+
class DocBuilder
|
9
|
+
class Alternative
|
10
|
+
common_constructor :alternative
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
(
|
14
|
+
[::EacRubyUtils::Console::DocoptRunner::PROGRAM_MACRO] +
|
15
|
+
alternative.options_argument?.if_present([]) { |_v| ['[options]'] } +
|
16
|
+
options +
|
17
|
+
positionals
|
18
|
+
).join(::EacCli::Docopt::DocBuilder::SEP)
|
19
|
+
end
|
20
|
+
|
21
|
+
def options
|
22
|
+
alternative.options.select(&:show_on_usage?).map do |option|
|
23
|
+
::EacCli::Docopt::DocBuilder.option_long(option)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def option_argument(option)
|
28
|
+
b = option.long
|
29
|
+
b += '=<value>' if option.argument?
|
30
|
+
b
|
31
|
+
end
|
32
|
+
|
33
|
+
def positionals
|
34
|
+
alternative.positional.map { |p| positional(p) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def positional(positional)
|
38
|
+
if positional.subcommand?
|
39
|
+
::EacRubyUtils::Console::DocoptRunner::SUBCOMMANDS_MACRO
|
40
|
+
else
|
41
|
+
r = "<#{positional.name}>"
|
42
|
+
r += '...' if positional.repeat?
|
43
|
+
r = "[#{r}]" if positional.optional?
|
44
|
+
r
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/eac_cli/parser.rb
CHANGED
@@ -5,10 +5,28 @@ require 'eac_ruby_utils/core_ext'
|
|
5
5
|
module EacCli
|
6
6
|
class Parser
|
7
7
|
require_sub __FILE__
|
8
|
-
|
8
|
+
enable_simple_cache
|
9
|
+
common_constructor :definition, :argv
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
private
|
12
|
+
|
13
|
+
def parsed_uncached
|
14
|
+
raise 'Definition has no alternatives' if alternatives.empty?
|
15
|
+
|
16
|
+
alternatives.each do |alt_parser|
|
17
|
+
return alt_parser.parsed unless alt_parser.error?
|
18
|
+
end
|
19
|
+
|
20
|
+
raise first_error
|
21
|
+
end
|
22
|
+
|
23
|
+
def alternatives_uncached
|
24
|
+
definition.alternatives
|
25
|
+
.map { |alternative| ::EacCli::Parser::Alternative.new(alternative, argv) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def first_error_uncached
|
29
|
+
alternatives.lazy.select(&:error?).map(&:error).first
|
12
30
|
end
|
13
31
|
end
|
14
32
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_cli/parser/collector'
|
4
|
+
require 'eac_cli/parser/error'
|
5
|
+
require 'eac_ruby_utils/core_ext'
|
6
|
+
|
7
|
+
module EacCli
|
8
|
+
class Parser
|
9
|
+
class Alternative
|
10
|
+
require_sub __FILE__, include_modules: true
|
11
|
+
enable_listable
|
12
|
+
lists.add_symbol :phase, :any, :option_argument, :positional
|
13
|
+
attr_reader :error
|
14
|
+
|
15
|
+
common_constructor :alternative, :argv do
|
16
|
+
alternative.assert_argument(::EacCli::Definition::Alternative, :alternative)
|
17
|
+
self.phase = PHASE_ANY
|
18
|
+
collect
|
19
|
+
end
|
20
|
+
|
21
|
+
def error?
|
22
|
+
error.present?
|
23
|
+
end
|
24
|
+
|
25
|
+
def parsed
|
26
|
+
@parsed ||= collector.to_data.freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :phase
|
32
|
+
|
33
|
+
def any_collect_argv_value
|
34
|
+
if argv_current_option?
|
35
|
+
option_collect_argv_value
|
36
|
+
else
|
37
|
+
positional_collect_argv_value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def collector
|
42
|
+
@collector ||= ::EacCli::Parser::Collector.new(alternative)
|
43
|
+
end
|
44
|
+
|
45
|
+
def collect
|
46
|
+
loop do
|
47
|
+
break unless argv_pending?
|
48
|
+
|
49
|
+
collect_argv_value
|
50
|
+
end
|
51
|
+
validate
|
52
|
+
rescue ::EacCli::Parser::Error => e
|
53
|
+
@error = e
|
54
|
+
end
|
55
|
+
|
56
|
+
def collect_argv_value
|
57
|
+
send("#{phase}_collect_argv_value")
|
58
|
+
argv_enum.next
|
59
|
+
end
|
60
|
+
|
61
|
+
def collect_option_argv_value
|
62
|
+
alternative.options.each do |option|
|
63
|
+
end
|
64
|
+
|
65
|
+
raise ::EacCli::Parser::Error.new(
|
66
|
+
alternative, argv, "Invalid option: #{argv_enum.current}"
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def raise_error(message)
|
71
|
+
raise ::EacCli::Parser::Error.new(alternative, argv, message)
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate
|
75
|
+
(alternative.options + alternative.positional).each do |option|
|
76
|
+
validate_option(option)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_option(option)
|
81
|
+
return unless option.required?
|
82
|
+
return if collector.supplied?(option)
|
83
|
+
|
84
|
+
raise_error("Required option/positional #{option} not supplied")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EacCli
|
4
|
+
class Parser
|
5
|
+
class Alternative
|
6
|
+
module DoubleDash
|
7
|
+
DOUBLE_DASH = '--'
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
attr_accessor :double_dash
|
12
|
+
|
13
|
+
def argv_current_double_dash?
|
14
|
+
argv_enum.peek == DOUBLE_DASH && !double_dash
|
15
|
+
end
|
16
|
+
|
17
|
+
def double_dash_collect_argv_value
|
18
|
+
self.phase = PHASE_POSITIONAL
|
19
|
+
self.double_dash = true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EacCli
|
4
|
+
class Parser
|
5
|
+
class Alternative
|
6
|
+
module Options
|
7
|
+
DOUBLE_DASH = '--'
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
attr_accessor :argument_option, :double_dash
|
12
|
+
|
13
|
+
def argument_option_collect_argv(option)
|
14
|
+
self.argument_option = option
|
15
|
+
self.phase = PHASE_OPTION_ARGUMENT
|
16
|
+
end
|
17
|
+
|
18
|
+
def argv_current_option?
|
19
|
+
phase == PHASE_ANY && argv_enum.peek.start_with?('-')
|
20
|
+
end
|
21
|
+
|
22
|
+
def argv_current_double_dash?
|
23
|
+
argv_enum.peek == DOUBLE_DASH && !double_dash
|
24
|
+
end
|
25
|
+
|
26
|
+
def boolean_option_collect_argv(option)
|
27
|
+
collector.collect(option, true)
|
28
|
+
end
|
29
|
+
|
30
|
+
def option_argument_collect_argv_value
|
31
|
+
collector.collect(argument_option, argv_enum.peek)
|
32
|
+
self.argument_option = nil
|
33
|
+
self.phase = PHASE_ANY
|
34
|
+
end
|
35
|
+
|
36
|
+
def option_collect_argv_value
|
37
|
+
return double_dash_collect_argv_value if argv_current_double_dash?
|
38
|
+
|
39
|
+
alternative.options.any? do |option|
|
40
|
+
next false unless [option.short, option.long].include?(argv_enum.peek)
|
41
|
+
|
42
|
+
if option.argument?
|
43
|
+
argument_option_collect_argv(option)
|
44
|
+
else
|
45
|
+
boolean_option_collect_argv(option)
|
46
|
+
end
|
47
|
+
|
48
|
+
true
|
49
|
+
end || raise_argv_current_invalid_option
|
50
|
+
end
|
51
|
+
|
52
|
+
def raise_argv_current_invalid_option
|
53
|
+
raise_error "Invalid option: \"#{argv_enum.peek}\""
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EacCli
|
4
|
+
class Parser
|
5
|
+
class Alternative
|
6
|
+
module Positionals
|
7
|
+
private
|
8
|
+
|
9
|
+
def positional_collect_argv_value
|
10
|
+
positional_check
|
11
|
+
collector.collect(positional_enum.peek, argv_enum.peek)
|
12
|
+
positional_next
|
13
|
+
end
|
14
|
+
|
15
|
+
def positional_enum
|
16
|
+
@positional_enum ||= alternative.positional.each
|
17
|
+
end
|
18
|
+
|
19
|
+
def positional_check
|
20
|
+
raise_error("Invalid positional: #{argv_enum.peek}") if positional_enum.stopped?
|
21
|
+
end
|
22
|
+
|
23
|
+
def positional_next
|
24
|
+
self.phase = PHASE_POSITIONAL if positional_enum.peek.subcommand?
|
25
|
+
positional_enum.next unless positional_enum.peek.repeat?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/eac_cli/runner.rb
CHANGED
@@ -28,7 +28,7 @@ module EacCli
|
|
28
28
|
sklass = klass.singleton_class
|
29
29
|
return unless sklass.method_defined?(from)
|
30
30
|
|
31
|
-
sklass.alias_method to, from
|
31
|
+
sklass.send(:alias_method, to, from)
|
32
32
|
end
|
33
33
|
|
34
34
|
def build_method_name(name, suffix)
|
@@ -84,7 +84,7 @@ module EacCli
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def parsed
|
87
|
-
@parsed ||= ::EacCli::Parser.new(self.class.runner_definition
|
87
|
+
@parsed ||= ::EacCli::Parser.new(self.class.runner_definition, runner_context.argv).parsed
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'eac_cli/runner'
|
4
4
|
require 'eac_ruby_utils/core_ext'
|
5
|
+
require 'eac_ruby_utils/abstract_methods'
|
5
6
|
|
6
7
|
module EacCli
|
7
8
|
module RunnerWith
|
8
9
|
module OutputFile
|
9
10
|
common_concern do
|
10
11
|
include ::EacCli::Runner
|
12
|
+
include ::EacRubyUtils::AbstractMethods
|
13
|
+
|
14
|
+
abstract_methods :output_content
|
11
15
|
|
12
16
|
runner_definition do
|
13
17
|
arg_opt '-o', '--output-file', 'Output to file.'
|
@@ -18,7 +22,7 @@ module EacCli
|
|
18
22
|
if parsed.output_file.present?
|
19
23
|
::File.write(parsed.output_file, output_content)
|
20
24
|
else
|
21
|
-
|
25
|
+
$stdout.write(output_content)
|
22
26
|
end
|
23
27
|
end
|
24
28
|
end
|
data/lib/eac_cli/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eac_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Esquilo Azul Company
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eac_ruby_utils
|
@@ -55,18 +55,23 @@ files:
|
|
55
55
|
- lib/eac_cli/core_ext.rb
|
56
56
|
- lib/eac_cli/default_runner.rb
|
57
57
|
- lib/eac_cli/definition.rb
|
58
|
+
- lib/eac_cli/definition/alternative.rb
|
58
59
|
- lib/eac_cli/definition/argument_option.rb
|
59
60
|
- lib/eac_cli/definition/base_option.rb
|
60
61
|
- lib/eac_cli/definition/boolean_option.rb
|
62
|
+
- lib/eac_cli/definition/help_formatter.rb
|
61
63
|
- lib/eac_cli/definition/positional_argument.rb
|
62
64
|
- lib/eac_cli/docopt/doc_builder.rb
|
65
|
+
- lib/eac_cli/docopt/doc_builder/alternative.rb
|
63
66
|
- lib/eac_cli/docopt/runner_extension.rb
|
64
67
|
- lib/eac_cli/parser.rb
|
68
|
+
- lib/eac_cli/parser/alternative.rb
|
69
|
+
- lib/eac_cli/parser/alternative/argv.rb
|
70
|
+
- lib/eac_cli/parser/alternative/double_dash.rb
|
71
|
+
- lib/eac_cli/parser/alternative/options.rb
|
72
|
+
- lib/eac_cli/parser/alternative/positionals.rb
|
65
73
|
- lib/eac_cli/parser/collector.rb
|
66
74
|
- lib/eac_cli/parser/error.rb
|
67
|
-
- lib/eac_cli/parser/options_collection.rb
|
68
|
-
- lib/eac_cli/parser/parse_result.rb
|
69
|
-
- lib/eac_cli/parser/positional_collection.rb
|
70
75
|
- lib/eac_cli/patches.rb
|
71
76
|
- lib/eac_cli/patches/object.rb
|
72
77
|
- lib/eac_cli/patches/object/runner_with.rb
|
@@ -1,127 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'eac_ruby_utils/core_ext'
|
4
|
-
require 'optparse'
|
5
|
-
|
6
|
-
module EacCli
|
7
|
-
class Parser
|
8
|
-
class OptionsCollection
|
9
|
-
SEP = ' '
|
10
|
-
IDENT = SEP * 2
|
11
|
-
OPTION_DESC_SEP = IDENT * 2
|
12
|
-
|
13
|
-
enable_simple_cache
|
14
|
-
common_constructor(:definition, :argv, :collector) { collect }
|
15
|
-
attr_reader :arguments
|
16
|
-
|
17
|
-
def options_first?
|
18
|
-
definition.options_first? || definition.subcommands?
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def check_required_options
|
24
|
-
definition.options.each do |option|
|
25
|
-
next unless option.required?
|
26
|
-
next if collector.supplied?(option)
|
27
|
-
|
28
|
-
raise ::EacCli::Parser::Error.new(
|
29
|
-
definition, argv, "Option \"#{option}\" is required and a value was not supplied"
|
30
|
-
)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def collect
|
35
|
-
build_banner
|
36
|
-
build_options
|
37
|
-
parse_argv
|
38
|
-
check_required_options
|
39
|
-
end
|
40
|
-
|
41
|
-
def option_parser_uncached
|
42
|
-
::OptionParser.new
|
43
|
-
end
|
44
|
-
|
45
|
-
def parse_argv
|
46
|
-
@arguments = options_first? ? option_parser.order(argv) : option_parser.parse(argv)
|
47
|
-
rescue ::OptionParser::InvalidOption => e
|
48
|
-
raise ::EacCli::Parser::Error.new(definition, argv, e.message)
|
49
|
-
end
|
50
|
-
|
51
|
-
def build_banner
|
52
|
-
option_parser.banner = "#{definition.description}\n\n#{section('usage')}"
|
53
|
-
end
|
54
|
-
|
55
|
-
def build_options
|
56
|
-
definition.options.each do |option|
|
57
|
-
build_option(option)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def build_option(option)
|
62
|
-
option_parser.on(
|
63
|
-
*[option_short(option), option_long(option), option.description].reject(&:blank?)
|
64
|
-
) do |value|
|
65
|
-
collector.collect(option, value)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def positional_argument(positional)
|
70
|
-
if positional.subcommand?
|
71
|
-
::EacRubyUtils::Console::DocoptRunner::SUBCOMMANDS_MACRO
|
72
|
-
else
|
73
|
-
r = "<#{positional.name}>"
|
74
|
-
r += '...' if positional.repeat?
|
75
|
-
r = "[#{r}]" if positional.optional?
|
76
|
-
r
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def option_argument(option)
|
81
|
-
option_long(option)
|
82
|
-
end
|
83
|
-
|
84
|
-
def option_long(option)
|
85
|
-
b = option.long
|
86
|
-
b += '=VALUE' if option.argument?
|
87
|
-
b
|
88
|
-
end
|
89
|
-
|
90
|
-
def option_short(option)
|
91
|
-
b = option.short
|
92
|
-
b += 'VALUE' if option.argument?
|
93
|
-
b
|
94
|
-
end
|
95
|
-
|
96
|
-
def section(header, include_header = true)
|
97
|
-
b = include_header ? "#{header.humanize}:\n" : ''
|
98
|
-
b += send("self_#{header}") + "\n"
|
99
|
-
# TO-DO: implement alternatives
|
100
|
-
b
|
101
|
-
end
|
102
|
-
|
103
|
-
def self_options
|
104
|
-
definition.options.map { |option| IDENT + option_definition(option) }.join("\n")
|
105
|
-
end
|
106
|
-
|
107
|
-
def self_usage
|
108
|
-
IDENT + self_usage_arguments.join(SEP)
|
109
|
-
end
|
110
|
-
|
111
|
-
def self_usage_arguments
|
112
|
-
[::EacRubyUtils::Console::DocoptRunner::PROGRAM_MACRO] +
|
113
|
-
definition.options_argument.if_present([]) { |_v| ['[options]'] } +
|
114
|
-
self_usage_arguments_options +
|
115
|
-
self_usage_arguments_positional
|
116
|
-
end
|
117
|
-
|
118
|
-
def self_usage_arguments_options
|
119
|
-
definition.options.select(&:show_on_usage?).map { |option| option_argument(option) }
|
120
|
-
end
|
121
|
-
|
122
|
-
def self_usage_arguments_positional
|
123
|
-
definition.positional.map { |p| positional_argument(p) }
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'eac_cli/parser/error'
|
4
|
-
require 'eac_ruby_utils/core_ext'
|
5
|
-
|
6
|
-
module EacCli
|
7
|
-
class Parser
|
8
|
-
class ParseResult
|
9
|
-
common_constructor :definition, :argv
|
10
|
-
|
11
|
-
def result
|
12
|
-
result_or_error = parse(definition)
|
13
|
-
return result_or_error unless result_or_error.is_a?(::Exception)
|
14
|
-
|
15
|
-
definition.alternatives.each do |alternative|
|
16
|
-
alt_result_or_error = parse(alternative)
|
17
|
-
return alt_result_or_error unless alt_result_or_error.is_a?(::Exception)
|
18
|
-
end
|
19
|
-
|
20
|
-
raise result_or_error
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def parse(definition)
|
26
|
-
::EacCli::Parser::Collector.to_data(definition) do |collector|
|
27
|
-
::EacCli::Parser::PositionalCollection.new(
|
28
|
-
definition,
|
29
|
-
::EacCli::Parser::OptionsCollection.new(definition, argv, collector).arguments,
|
30
|
-
collector
|
31
|
-
)
|
32
|
-
end
|
33
|
-
rescue ::EacCli::Parser::Error => e
|
34
|
-
e
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'eac_ruby_utils/core_ext'
|
4
|
-
require 'eac_cli/parser/error'
|
5
|
-
|
6
|
-
module EacCli
|
7
|
-
class Parser
|
8
|
-
class PositionalCollection
|
9
|
-
common_constructor(:definition, :argv, :collector) { collect }
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def argv_enum
|
14
|
-
@argv_enum ||= argv.each
|
15
|
-
end
|
16
|
-
|
17
|
-
def argv_pending?
|
18
|
-
argv_enum.ongoing?
|
19
|
-
end
|
20
|
-
|
21
|
-
def argv_pending_check
|
22
|
-
return unless argv_pending?
|
23
|
-
return unless positional_enum.stopped?
|
24
|
-
|
25
|
-
raise ::EacCli::Parser::Error.new(
|
26
|
-
definition, argv, "No positional left for argv \"#{argv_enum.current}\""
|
27
|
-
)
|
28
|
-
end
|
29
|
-
|
30
|
-
def collected
|
31
|
-
@collected ||= ::Set.new
|
32
|
-
end
|
33
|
-
|
34
|
-
def collect
|
35
|
-
loop do
|
36
|
-
break unless enums('pending?').any?
|
37
|
-
|
38
|
-
enums('pending_check')
|
39
|
-
collect_argv_value
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def collect_argv_value
|
44
|
-
collector.collect(*enums('enum', &:peek))
|
45
|
-
collected << positional_enum.peek
|
46
|
-
positional_enum.next unless positional_enum.peek.repeat?
|
47
|
-
argv_enum.next
|
48
|
-
end
|
49
|
-
|
50
|
-
def enums(method_suffix, &block)
|
51
|
-
%w[positional argv].map do |method_prefix|
|
52
|
-
v = send("#{method_prefix}_#{method_suffix}")
|
53
|
-
block ? block.call(v) : v
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def positional_enum
|
58
|
-
@positional_enum ||= definition.positional.each
|
59
|
-
end
|
60
|
-
|
61
|
-
def positional_pending?
|
62
|
-
!(positional_enum.stopped? || positional_enum.current.optional? ||
|
63
|
-
collected.include?(positional_enum.current))
|
64
|
-
end
|
65
|
-
|
66
|
-
def positional_pending_check
|
67
|
-
return unless positional_pending?
|
68
|
-
return unless argv_enum.stopped?
|
69
|
-
|
70
|
-
raise ::EacCli::Parser::Error.new(
|
71
|
-
definition, argv, 'No value for required positional ' \
|
72
|
-
"\"#{positional_enum.current.identifier}\""
|
73
|
-
)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|