eac_cli 0.8.0 → 0.12.0
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 +49 -22
- data/lib/eac_cli/definition/alternative.rb +83 -0
- data/lib/eac_cli/definition/base_option.rb +17 -1
- data/lib/eac_cli/{parser/options_collection.rb → definition/help_formatter.rb} +20 -49
- data/lib/eac_cli/definition/positional_argument.rb +21 -4
- 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/docopt/runner_extension.rb +1 -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/parser/collector.rb +4 -0
- data/lib/eac_cli/patches/object/runner_with.rb +2 -1
- data/lib/eac_cli/runner.rb +6 -2
- data/lib/eac_cli/runner/context.rb +19 -2
- data/lib/eac_cli/runner_with/help.rb +1 -1
- data/lib/eac_cli/runner_with/subcommands.rb +96 -0
- data/lib/eac_cli/version.rb +1 -1
- metadata +13 -7
- data/lib/eac_cli/parser/parse_result.rb +0 -21
- data/lib/eac_cli/parser/positional_collection.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22f7790dfca37c3414a3552155806d47808bdee4b6eb6630a07b2eb8b928db59
|
4
|
+
data.tar.gz: 8b6826e7f1f7d4980516ad8d53d2b681890b326f513df49f6394c3f2115f3b64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61d6bf83f3e147b7fd9761f6369347c91c208599eacf7ca0a5762e671e86a7eea30758b6101c639a4775acb376988e0ecd3d8dd31d1950fcebeaabc1b8275360
|
7
|
+
data.tar.gz: 66777c73453633a5af6fe8048818d7e07428dd958676b2d9a1cdc800c286910a73140e3a0526cb0ef5e87102a227be0873fcd14146405c33b73e644351e0070f
|
data/lib/eac_cli/definition.rb
CHANGED
@@ -8,57 +8,66 @@ require 'eac_ruby_utils/core_ext'
|
|
8
8
|
module EacCli
|
9
9
|
class Definition
|
10
10
|
require_sub __FILE__
|
11
|
+
|
12
|
+
MAIN_ALTERNATIVE_KEY = :main
|
13
|
+
SUBCOMMAND_NAME_ARG = 'subcommand'
|
14
|
+
SUBCOMMAND_ARGS_ARG = 'subcommand_args'
|
15
|
+
|
11
16
|
attr_accessor :description
|
12
|
-
attr_accessor :options_argument
|
13
17
|
|
14
18
|
def initialize
|
15
19
|
self.description = '-- NO DESCRIPTION SET --'
|
16
|
-
|
20
|
+
alternatives_set[MAIN_ALTERNATIVE_KEY] = main_alternative
|
17
21
|
end
|
18
22
|
|
19
23
|
def alt(&block)
|
20
|
-
r = ::EacCli::Definition.new
|
24
|
+
r = ::EacCli::Definition::Alternative.new
|
21
25
|
r.instance_eval(&block)
|
22
|
-
|
26
|
+
alternatives_set[new_alternative_key] = r
|
23
27
|
r
|
24
28
|
end
|
25
29
|
|
26
30
|
def alternatives
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def arg_opt(short, long, description, option_options = {})
|
31
|
-
options << ::EacCli::Definition::ArgumentOption.new(
|
32
|
-
short, long, description, option_options
|
33
|
-
)
|
31
|
+
alternatives_set.values
|
34
32
|
end
|
35
33
|
|
36
|
-
def
|
37
|
-
|
34
|
+
def alternative(key)
|
35
|
+
alternatives_set.fetch(key)
|
38
36
|
end
|
39
37
|
|
40
38
|
def desc(description)
|
41
39
|
self.description = description
|
42
40
|
end
|
43
41
|
|
42
|
+
def help_formatter
|
43
|
+
@help_formatter ||= ::EacCli::Definition::HelpFormatter.new(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def main_alternative
|
47
|
+
@main_alternative ||= begin
|
48
|
+
r = ::EacCli::Definition::Alternative.new
|
49
|
+
r.options_argument(true)
|
50
|
+
r
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
44
54
|
def options_arg(options_argument)
|
45
55
|
self.options_argument = options_argument
|
46
56
|
end
|
47
57
|
|
48
|
-
def
|
49
|
-
|
58
|
+
def options_argument
|
59
|
+
main_alternative.options_argument?
|
50
60
|
end
|
51
61
|
|
52
|
-
def
|
53
|
-
|
62
|
+
def options_argument=(enable)
|
63
|
+
main_alternative.options_argument(enable)
|
54
64
|
end
|
55
65
|
|
56
|
-
|
57
|
-
|
58
|
-
end
|
66
|
+
delegate :arg_opt, :bool_opt, :options, :pos_arg,
|
67
|
+
:positional, :subcommands, to: :main_alternative
|
59
68
|
|
60
|
-
def subcommands
|
61
|
-
|
69
|
+
def subcommands?
|
70
|
+
alternatives.any?(&:subcommands?)
|
62
71
|
end
|
63
72
|
|
64
73
|
def options_first(enable = true)
|
@@ -68,5 +77,23 @@ module EacCli
|
|
68
77
|
def options_first?
|
69
78
|
@options_first ? true : false
|
70
79
|
end
|
80
|
+
|
81
|
+
private
|
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
|
+
|
95
|
+
def pos_set
|
96
|
+
@pos_set ||= []
|
97
|
+
end
|
71
98
|
end
|
72
99
|
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
|
@@ -5,19 +5,35 @@ require 'eac_ruby_utils/core_ext'
|
|
5
5
|
module EacCli
|
6
6
|
class Definition
|
7
7
|
class BaseOption
|
8
|
+
DEFAULT_REQUIRED = false
|
9
|
+
|
10
|
+
enable_listable
|
11
|
+
lists.add_symbol :option, :optional, :usage, :required
|
8
12
|
attr_reader :short, :long, :description, :options
|
9
13
|
|
10
14
|
def initialize(short, long, description, options = {})
|
11
15
|
@short = short
|
12
16
|
@long = long
|
13
17
|
@description = description
|
14
|
-
@options = options.
|
18
|
+
@options = options.symbolize_keys
|
19
|
+
@options.assert_valid_keys(::EacCli::Definition::BaseOption.lists.option.values)
|
15
20
|
end
|
16
21
|
|
17
22
|
def identifier
|
18
23
|
long.to_s.variableize.to_sym
|
19
24
|
end
|
20
25
|
|
26
|
+
def required?
|
27
|
+
return true if options.key?(:required) && options.fetch(:required)
|
28
|
+
return false if options.key?(:optional) && options.fetch(:optional)
|
29
|
+
|
30
|
+
DEFAULT_REQUIRED
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"#{self.class.name.demodulize}[#{identifier}]"
|
35
|
+
end
|
36
|
+
|
21
37
|
def show_on_usage?
|
22
38
|
options[:usage]
|
23
39
|
end
|
@@ -1,49 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'eac_ruby_utils/core_ext'
|
4
|
-
require 'optparse'
|
5
4
|
|
6
5
|
module EacCli
|
7
|
-
class
|
8
|
-
class
|
6
|
+
class Definition
|
7
|
+
class HelpFormatter
|
9
8
|
SEP = ' '
|
10
9
|
IDENT = SEP * 2
|
11
10
|
OPTION_DESC_SEP = IDENT * 2
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def collect
|
20
|
-
build_banner
|
21
|
-
build_options
|
22
|
-
@arguments = option_parser.parse(argv)
|
23
|
-
end
|
24
|
-
|
25
|
-
def option_parser_uncached
|
26
|
-
::OptionParser.new
|
27
|
-
end
|
28
|
-
|
29
|
-
def build_banner
|
30
|
-
option_parser.banner = "#{definition.description}\n\n#{section('usage')}"
|
31
|
-
end
|
32
|
-
|
33
|
-
def build_options
|
34
|
-
definition.options.each do |option|
|
35
|
-
build_option(option)
|
12
|
+
class << self
|
13
|
+
def option_long(option)
|
14
|
+
b = option.long
|
15
|
+
b += '=VALUE' if option.argument?
|
16
|
+
b
|
36
17
|
end
|
37
|
-
end
|
38
18
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
collector.collect(option, value)
|
19
|
+
def option_short(option)
|
20
|
+
b = option.short
|
21
|
+
b += 'VALUE' if option.argument?
|
22
|
+
b
|
44
23
|
end
|
45
24
|
end
|
46
25
|
|
26
|
+
common_constructor :definition
|
27
|
+
|
47
28
|
def positional_argument(positional)
|
48
29
|
if positional.subcommand?
|
49
30
|
::EacRubyUtils::Console::DocoptRunner::SUBCOMMANDS_MACRO
|
@@ -55,22 +36,6 @@ module EacCli
|
|
55
36
|
end
|
56
37
|
end
|
57
38
|
|
58
|
-
def option_argument(option)
|
59
|
-
option_long(option)
|
60
|
-
end
|
61
|
-
|
62
|
-
def option_long(option)
|
63
|
-
b = option.long
|
64
|
-
b += '=VALUE' if option.argument?
|
65
|
-
b
|
66
|
-
end
|
67
|
-
|
68
|
-
def option_short(option)
|
69
|
-
b = option.short
|
70
|
-
b += 'VALUE' if option.argument?
|
71
|
-
b
|
72
|
-
end
|
73
|
-
|
74
39
|
def section(header, include_header = true)
|
75
40
|
b = include_header ? "#{header.humanize}:\n" : ''
|
76
41
|
b += send("self_#{header}") + "\n"
|
@@ -94,12 +59,18 @@ module EacCli
|
|
94
59
|
end
|
95
60
|
|
96
61
|
def self_usage_arguments_options
|
97
|
-
definition.options.select(&:show_on_usage?).map
|
62
|
+
definition.options.select(&:show_on_usage?).map do |option|
|
63
|
+
self.class.option_long(option)
|
64
|
+
end
|
98
65
|
end
|
99
66
|
|
100
67
|
def self_usage_arguments_positional
|
101
68
|
definition.positional.map { |p| positional_argument(p) }
|
102
69
|
end
|
70
|
+
|
71
|
+
def to_banner
|
72
|
+
"#{definition.description}\n\n#{section('usage')}"
|
73
|
+
end
|
103
74
|
end
|
104
75
|
end
|
105
76
|
end
|
@@ -5,22 +5,39 @@ require 'eac_ruby_utils/core_ext'
|
|
5
5
|
module EacCli
|
6
6
|
class Definition
|
7
7
|
class PositionalArgument
|
8
|
-
|
8
|
+
DEFAULT_REQUIRED = true
|
9
|
+
|
10
|
+
enable_listable
|
11
|
+
lists.add_symbol :option, :optional, :repeat, :required, :subcommand
|
12
|
+
common_constructor :name, :options, default: [{}] do
|
13
|
+
options.assert_valid_keys(self.class.lists.option.values)
|
14
|
+
end
|
9
15
|
|
10
16
|
def identifier
|
11
17
|
name.to_s.variableize.to_sym
|
12
18
|
end
|
13
19
|
|
14
20
|
def optional?
|
15
|
-
|
21
|
+
!required?
|
16
22
|
end
|
17
23
|
|
18
24
|
def repeat?
|
19
|
-
options[
|
25
|
+
options[OPTION_REPEAT]
|
26
|
+
end
|
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
|
20
33
|
end
|
21
34
|
|
22
35
|
def subcommand?
|
23
|
-
options[
|
36
|
+
options[OPTION_SUBCOMMAND]
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"#{self.class.name.demodulize}[#{identifier}]"
|
24
41
|
end
|
25
42
|
end
|
26
43
|
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
|
@@ -5,13 +5,14 @@ require 'eac_cli/runner'
|
|
5
5
|
require 'eac_cli/runner_with'
|
6
6
|
|
7
7
|
class Object
|
8
|
-
def runner_with(*runners)
|
8
|
+
def runner_with(*runners, &block)
|
9
9
|
include ::EacCli::Runner
|
10
10
|
enable_simple_cache
|
11
11
|
enable_console_speaker
|
12
12
|
runners.each do |runner|
|
13
13
|
include runner_with_to_module(runner)
|
14
14
|
end
|
15
|
+
runner_definition(&block) if block
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
data/lib/eac_cli/runner.rb
CHANGED
@@ -18,6 +18,10 @@ module EacCli
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
def runner?(object)
|
22
|
+
object.is_a?(::Class) && object.included_modules.include?(::EacCli::Runner)
|
23
|
+
end
|
24
|
+
|
21
25
|
private
|
22
26
|
|
23
27
|
def alias_class_method(klass, from, to)
|
@@ -45,7 +49,7 @@ module EacCli
|
|
45
49
|
module AfterClassMethods
|
46
50
|
def create(*runner_context_args)
|
47
51
|
r = new
|
48
|
-
r.runner_context = ::EacCli::Runner::Context.new(*runner_context_args)
|
52
|
+
r.runner_context = ::EacCli::Runner::Context.new(r, *runner_context_args)
|
49
53
|
r
|
50
54
|
end
|
51
55
|
|
@@ -80,7 +84,7 @@ module EacCli
|
|
80
84
|
end
|
81
85
|
|
82
86
|
def parsed
|
83
|
-
@parsed ||= ::EacCli::Parser.new(self.class.runner_definition
|
87
|
+
@parsed ||= ::EacCli::Parser.new(self.class.runner_definition, runner_context.argv).parsed
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|
@@ -5,13 +5,30 @@ require 'eac_ruby_utils/core_ext'
|
|
5
5
|
module EacCli
|
6
6
|
module Runner
|
7
7
|
class Context
|
8
|
-
attr_reader :argv, :parent, :program_name
|
8
|
+
attr_reader :argv, :parent, :program_name, :runner
|
9
9
|
|
10
|
-
def initialize(*context_args)
|
10
|
+
def initialize(runner, *context_args)
|
11
11
|
options = context_args.extract_options!
|
12
12
|
@argv = (context_args[0] || options.delete(:argv) || ARGV).dup.freeze
|
13
13
|
@parent = context_args[1] || options.delete(:parent)
|
14
14
|
@program_name = options.delete(:program_name)
|
15
|
+
@runner = runner
|
16
|
+
end
|
17
|
+
|
18
|
+
# Call a method in the runner or in one of it ancestors.
|
19
|
+
def call(method_name, *args)
|
20
|
+
return runner.send(method_name, *args) if runner.respond_to?(method_name)
|
21
|
+
return parent_call(method_name, *args) if parent.present?
|
22
|
+
|
23
|
+
raise ::NameError, "No method \"#{method_name}\" found in #{runner} or in its ancestors"
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def parent_call(method_name, *args)
|
29
|
+
return parent.context(method_name, *args) if parent.respond_to?(:context)
|
30
|
+
|
31
|
+
parent.runner_context.call(method_name, *args)
|
15
32
|
end
|
16
33
|
end
|
17
34
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'eac_cli/runner'
|
4
|
+
require 'eac_ruby_utils/core_ext'
|
5
|
+
|
6
|
+
module EacCli
|
7
|
+
module RunnerWith
|
8
|
+
module Subcommands
|
9
|
+
common_concern do
|
10
|
+
include ::EacCli::Runner
|
11
|
+
end
|
12
|
+
|
13
|
+
EXTRA_AVAILABLE_SUBCOMMANDS_METHOD_NAME = :extra_available_subcommands
|
14
|
+
|
15
|
+
def available_subcommands
|
16
|
+
@available_subcommands ||= available_subcommands_auto.merge(available_subcommands_extra)
|
17
|
+
end
|
18
|
+
|
19
|
+
def available_subcommands_auto
|
20
|
+
self.class.constants
|
21
|
+
.map { |name| [name.to_s.underscore.gsub('_', '-'), self.class.const_get(name)] }
|
22
|
+
.select { |c| ::EacCli::Runner.runner?(c[1]) }
|
23
|
+
.to_h.with_indifferent_access
|
24
|
+
end
|
25
|
+
|
26
|
+
def available_subcommands_extra
|
27
|
+
if respond_to?(EXTRA_AVAILABLE_SUBCOMMANDS_METHOD_NAME, true)
|
28
|
+
send(EXTRA_AVAILABLE_SUBCOMMANDS_METHOD_NAME)
|
29
|
+
else
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(method_name, *arguments, &block)
|
35
|
+
return run_with_subcommand(*arguments, &block) if
|
36
|
+
run_with_subcommand_alias_run?(method_name)
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def respond_to_missing?(method_name, include_private = false)
|
42
|
+
run_with_subcommand_alias_run?(method_name) || super
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_with_subcommand
|
46
|
+
if subcommand_name
|
47
|
+
subcommand_runner.run
|
48
|
+
else
|
49
|
+
run_without_subcommand
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def run_with_subcommand_alias_run?(method_name)
|
54
|
+
subcommands? && method_name.to_sym == :run
|
55
|
+
end
|
56
|
+
|
57
|
+
def run_without_subcommand
|
58
|
+
"Method #{__method__} should be overrided in #{self.class.name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def subcommands?
|
62
|
+
self.class.runner_definition.subcommands?
|
63
|
+
end
|
64
|
+
|
65
|
+
def subcommand_args
|
66
|
+
parsed.fetch(::EacCli::Definition::SUBCOMMAND_ARGS_ARG)
|
67
|
+
end
|
68
|
+
|
69
|
+
def subcommand_class
|
70
|
+
available_subcommands[subcommand_name].if_present { |v| return v }
|
71
|
+
|
72
|
+
raise(::EacCli::Parser::Error.new(
|
73
|
+
self.class.runner_definition, runner_context.argv,
|
74
|
+
"Subcommand \"#{subcommand_name}\" not found " \
|
75
|
+
"(Available: #{available_subcommands.keys}"
|
76
|
+
))
|
77
|
+
end
|
78
|
+
|
79
|
+
def subcommand_name
|
80
|
+
parsed.fetch(::EacCli::Definition::SUBCOMMAND_NAME_ARG)
|
81
|
+
end
|
82
|
+
|
83
|
+
def subcommand_program
|
84
|
+
subcommand_name
|
85
|
+
end
|
86
|
+
|
87
|
+
def subcommand_runner
|
88
|
+
@subcommand_runner ||= subcommand_class.create(
|
89
|
+
argv: subcommand_args,
|
90
|
+
program_name: subcommand_program,
|
91
|
+
parent: self
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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.0
|
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-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: eac_ruby_utils
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.50'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.50'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: eac_ruby_gem_support
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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
|
@@ -75,6 +80,7 @@ files:
|
|
75
80
|
- lib/eac_cli/runner_with.rb
|
76
81
|
- lib/eac_cli/runner_with/help.rb
|
77
82
|
- lib/eac_cli/runner_with/output_file.rb
|
83
|
+
- lib/eac_cli/runner_with/subcommands.rb
|
78
84
|
- lib/eac_cli/version.rb
|
79
85
|
homepage:
|
80
86
|
licenses: []
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'eac_ruby_utils/core_ext'
|
4
|
-
|
5
|
-
module EacCli
|
6
|
-
class Parser
|
7
|
-
class ParseResult
|
8
|
-
common_constructor :definition, :argv
|
9
|
-
|
10
|
-
def result
|
11
|
-
::EacCli::Parser::Collector.to_data(definition) do |collector|
|
12
|
-
::EacCli::Parser::PositionalCollection.new(
|
13
|
-
definition,
|
14
|
-
::EacCli::Parser::OptionsCollection.new(definition, argv, collector).arguments,
|
15
|
-
collector
|
16
|
-
)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,49 +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 collected
|
14
|
-
@collected ||= ::Set.new
|
15
|
-
end
|
16
|
-
|
17
|
-
def collect
|
18
|
-
argv.each { |argv_value| colect_argv_value(argv_value) }
|
19
|
-
return unless pending_required_positional?
|
20
|
-
|
21
|
-
raise ::EacCli::Parser::Error.new(
|
22
|
-
definition, argv, 'No value for required positional ' \
|
23
|
-
"\"#{current_positional.identifier}\""
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
|
-
def colect_argv_value(argv_value)
|
28
|
-
collector.collect(current_positional, argv_value)
|
29
|
-
collected << current_positional
|
30
|
-
positional_enumerator.next unless current_positional.repeat?
|
31
|
-
end
|
32
|
-
|
33
|
-
def pending_required_positional?
|
34
|
-
!(current_positional.blank? || current_positional.optional? ||
|
35
|
-
collected.include?(current_positional))
|
36
|
-
end
|
37
|
-
|
38
|
-
def positional_enumerator
|
39
|
-
@positional_enumerator ||= definition.positional.each
|
40
|
-
end
|
41
|
-
|
42
|
-
def current_positional
|
43
|
-
positional_enumerator.peek
|
44
|
-
rescue ::StopIteration
|
45
|
-
nil
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|