eac_cli 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d373480947d50978fb88bb6e55a1296d644fcae95eb3b39a7a4127250cf14e2
4
- data.tar.gz: 350598588e6cb04bf5fca9cc626f8a1d662167dc5dee9fbc60bd231e2279aa0b
3
+ metadata.gz: 607e900ac4f5c7b6e7531c4ce9f9f6cfc8fd8ffce30f7b4d2a22ff91cdfd99bb
4
+ data.tar.gz: 0f49af96a89a527390ba34d2e6ecc17d2942aea7200bebbab1ba1f540398f413
5
5
  SHA512:
6
- metadata.gz: 074cf7d6fe38050884bb0578d1dcd6a7b02bb384d0634bfec15d8d53b03945dc789bf7654aba4a491145e4c9bb8a7366e1a2a799ce00709e0af2940ca97004ba
7
- data.tar.gz: 4da66cb205f1e58cb0c4fffb491d0d4e7aa38f7a6eeb7d7d67caa7c0f0e2177e2e98366639de29f9e588fc329c21051334d6696515e1c82711d1b2b8ec6e95b7
6
+ metadata.gz: 1e2a23e42fb5df31819b499f8dc1448f6ecc9d095fc22c9a0ceabe1acadf9ba6398a62d04a09943b233feeeb5e07325f62069ee883fb388b29424e7656ebe500
7
+ data.tar.gz: 1fefcf204e43d2ccfcf45c8507be13be885d46da72f0d6eda2ea3c3e6b7b845592e3ed989fc29b8c687a3727b795f8ea3a2d2a6526c3cda61bbf6b6f7da8df86
@@ -8,6 +8,10 @@ require 'eac_ruby_utils/core_ext'
8
8
  module EacCli
9
9
  class Definition
10
10
  require_sub __FILE__
11
+
12
+ SUBCOMMAND_NAME_ARG = 'subcommand'
13
+ SUBCOMMAND_ARGS_ARG = 'subcommand_args'
14
+
11
15
  attr_accessor :description
12
16
  attr_accessor :options_argument
13
17
 
@@ -50,15 +54,27 @@ module EacCli
50
54
  end
51
55
 
52
56
  def pos_arg(name, arg_options = {})
53
- positional << ::EacCli::Definition::PositionalArgument.new(name, arg_options)
57
+ raise 'Positional arguments are blocked' if positional_arguments_blocked?
58
+
59
+ pos_set << ::EacCli::Definition::PositionalArgument.new(name, arg_options)
54
60
  end
55
61
 
56
62
  def positional
57
- @positional ||= []
63
+ pos_set.to_a
64
+ end
65
+
66
+ def positional_arguments_blocked?
67
+ pos_set.any? { |e| e.optional? || e.repeat? }
58
68
  end
59
69
 
60
70
  def subcommands
61
- positional << ::EacCli::Definition::PositionalArgument.new('subcommand', subcommand: true)
71
+ pos_arg(SUBCOMMAND_NAME_ARG, subcommand: true)
72
+ pos_set << ::EacCli::Definition::PositionalArgument.new(SUBCOMMAND_ARGS_ARG,
73
+ optional: true, repeat: true)
74
+ end
75
+
76
+ def subcommands?
77
+ pos_set.any?(&:subcommand?)
62
78
  end
63
79
 
64
80
  def options_first(enable = true)
@@ -68,5 +84,11 @@ module EacCli
68
84
  def options_first?
69
85
  @options_first ? true : false
70
86
  end
87
+
88
+ private
89
+
90
+ def pos_set
91
+ @pos_set ||= []
92
+ end
71
93
  end
72
94
  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.with_indifferent_access
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
@@ -5,22 +5,26 @@ require 'eac_ruby_utils/core_ext'
5
5
  module EacCli
6
6
  class Definition
7
7
  class PositionalArgument
8
- common_constructor :name, :options, default: [{}]
8
+ enable_listable
9
+ lists.add_symbol :option, :optional, :repeat, :subcommand
10
+ common_constructor :name, :options, default: [{}] do
11
+ options.assert_valid_keys(self.class.lists.option.values)
12
+ end
9
13
 
10
14
  def identifier
11
15
  name.to_s.variableize.to_sym
12
16
  end
13
17
 
14
18
  def optional?
15
- options[:optional]
19
+ options[OPTION_OPTIONAL]
16
20
  end
17
21
 
18
22
  def repeat?
19
- options[:repeat]
23
+ options[OPTION_REPEAT]
20
24
  end
21
25
 
22
26
  def subcommand?
23
- options[:subcommand]
27
+ options[OPTION_SUBCOMMAND]
24
28
  end
25
29
  end
26
30
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'eac_cli/docopt/doc_builder'
4
+ require 'eac_cli/runner'
4
5
  require 'eac_ruby_utils/console/docopt_runner'
5
6
 
6
7
  module EacCli
@@ -31,6 +31,10 @@ module EacCli
31
31
  end
32
32
  end
33
33
 
34
+ def supplied?(option)
35
+ data[option].present?
36
+ end
37
+
34
38
  private
35
39
 
36
40
  def data
@@ -14,18 +14,40 @@ module EacCli
14
14
  common_constructor(:definition, :argv, :collector) { collect }
15
15
  attr_reader :arguments
16
16
 
17
+ def options_first?
18
+ definition.options_first? || definition.subcommands?
19
+ end
20
+
17
21
  private
18
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
+
19
34
  def collect
20
35
  build_banner
21
36
  build_options
22
- @arguments = option_parser.parse(argv)
37
+ parse_argv
38
+ check_required_options
23
39
  end
24
40
 
25
41
  def option_parser_uncached
26
42
  ::OptionParser.new
27
43
  end
28
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
+
29
51
  def build_banner
30
52
  option_parser.banner = "#{definition.description}\n\n#{section('usage')}"
31
53
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'eac_cli/parser/error'
3
4
  require 'eac_ruby_utils/core_ext'
4
5
 
5
6
  module EacCli
@@ -8,6 +9,20 @@ module EacCli
8
9
  common_constructor :definition, :argv
9
10
 
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)
11
26
  ::EacCli::Parser::Collector.to_data(definition) do |collector|
12
27
  ::EacCli::Parser::PositionalCollection.new(
13
28
  definition,
@@ -15,6 +30,8 @@ module EacCli
15
30
  collector
16
31
  )
17
32
  end
33
+ rescue ::EacCli::Parser::Error => e
34
+ e
18
35
  end
19
36
  end
20
37
  end
@@ -10,39 +10,67 @@ module EacCli
10
10
 
11
11
  private
12
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
+
13
30
  def collected
14
31
  @collected ||= ::Set.new
15
32
  end
16
33
 
17
34
  def collect
18
- argv.each { |argv_value| colect_argv_value(argv_value) }
19
- return unless pending_required_positional?
35
+ loop do
36
+ break unless enums('pending?').any?
20
37
 
21
- raise ::EacCli::Parser::Error.new(
22
- definition, argv, 'No value for required positional ' \
23
- "\"#{current_positional.identifier}\""
24
- )
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
25
48
  end
26
49
 
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?
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
31
55
  end
32
56
 
33
- def pending_required_positional?
34
- !(current_positional.blank? || current_positional.optional? ||
35
- collected.include?(current_positional))
57
+ def positional_enum
58
+ @positional_enum ||= definition.positional.each
36
59
  end
37
60
 
38
- def positional_enumerator
39
- @positional_enumerator ||= definition.positional.each
61
+ def positional_pending?
62
+ !(positional_enum.stopped? || positional_enum.current.optional? ||
63
+ collected.include?(positional_enum.current))
40
64
  end
41
65
 
42
- def current_positional
43
- positional_enumerator.peek
44
- rescue ::StopIteration
45
- nil
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
+ )
46
74
  end
47
75
  end
48
76
  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
@@ -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)
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EacCli
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
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.9.0
4
+ version: 0.10.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-10-14 00:00:00.000000000 Z
11
+ date: 2020-11-15 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.45'
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.45'
26
+ version: '0.50'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: eac_ruby_gem_support
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +75,7 @@ files:
75
75
  - lib/eac_cli/runner_with.rb
76
76
  - lib/eac_cli/runner_with/help.rb
77
77
  - lib/eac_cli/runner_with/output_file.rb
78
+ - lib/eac_cli/runner_with/subcommands.rb
78
79
  - lib/eac_cli/version.rb
79
80
  homepage:
80
81
  licenses: []