ultra_command_line 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7cd80a8c2740619c3fbba59d3d76fee87f6763c
4
+ data.tar.gz: ab0a550b07ac26bfeae400c9d0a0002c563efe75
5
+ SHA512:
6
+ metadata.gz: c8a55f46171afd5a91ea53301915efb2f5f92f61cc87f91f2c84b7638ffebc5788b49a621c8023282549552ffc146b39f1c0c3d2d2e89f75b88f14682cbc0ff3
7
+ data.tar.gz: 979c60dffeb59988e17a490064352ee18601fc14a02d9fc4674362077bab055cef6433b760c51268faf0b131763d2454cc9648e90bf125f85b71ca890224ba48
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ /.idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.15.4
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://gems.nanonet'
2
+
3
+ # Specify your gem's dependencies in ultra_command_line.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ UltraCommandLine
2
+ ================
3
+
4
+ This gem is basically a wrapper around the `slop` gem which generates slop options from a YAML definition file.
5
+ This is most probably only used by the `climatic` gem
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'ultra_command_line'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install ultra_command_line
23
+
24
+ ## Usage
25
+
26
+ TODO: Write usage instructions here
27
+
28
+ ## Development
29
+
30
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
31
+
32
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
+
34
+ ## Contributing
35
+
36
+ Bug reports and pull requests are welcome on GitHub at https://gitlab.com/lbriais/ultra_command_line.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'ultra_command_line'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,160 @@
1
+ @startuml
2
+
3
+
4
+ skinparam class {
5
+ backgroundColor<<module>> LightGray
6
+ backgroundColor<<external>> Gray
7
+ }
8
+
9
+ set namespaceSeparator ::
10
+
11
+
12
+ namespace Commands {
13
+
14
+ class OptionDefinition {
15
+ Defines one command line option,
16
+ wrapping around slop features.
17
+ +{method}:to_slop_options
18
+ }
19
+
20
+ class SubCommand {
21
+ Represents a a-la-git sub-command
22
+ ..
23
+ +{field}name
24
+ }
25
+
26
+ class MainCommand << (M, #ADD1B2) module>> {
27
+ Represents the main command.
28
+ Basically a Subcommand but
29
+ with an empty name.
30
+ ..
31
+ }
32
+
33
+ class Factory << (M, #ADD1B2) module>> {
34
+ How to create a SubCommand from
35
+ a definition hash.
36
+ ..
37
+ +{method}:from_hash
38
+ }
39
+
40
+ class HelpFormatter << (M, #ADD1B2) module>> {
41
+ Builds help for a command.
42
+ ..
43
+ +{field}help_template
44
+ +{method}:help
45
+ }
46
+
47
+ class Validation << (M, #ADD1B2) module>> {
48
+ Checks the validity of options
49
+ defined.
50
+ ..
51
+ +{method}:valid?
52
+ }
53
+
54
+
55
+ class CommandLineParser <<(M, #ADD1B2) module>> {
56
+ Parses command-line
57
+ ..
58
+ +{field}cmd_line_args
59
+ +{method}:extra_arguments
60
+ +{method}:params_hash
61
+ }
62
+
63
+
64
+ SubCommand "1" *-- "n" OptionDefinition : "contains\nand\ncreates"
65
+ SubCommand -|> Factory : extends
66
+ SubCommand --|> HelpFormatter : includes
67
+ SubCommand -|> Validation : includes
68
+ SubCommand --|> CommandLineParser : includes
69
+
70
+ MainCommand ..|> SubCommand : "included\nfor main command\nat runtime"
71
+
72
+ }
73
+
74
+
75
+ namespace Manager {
76
+
77
+
78
+ class Base {
79
+ Manages completely the list of
80
+ options definition and how to find
81
+ a processor for a list of command
82
+ line parameters
83
+ }
84
+
85
+ class LayeredDefinition <<(M, #ADD1B2) module>> {
86
+ Wrapper around SuperStack::Manager
87
+ to manage "contributions" to the definition
88
+ with cache management.
89
+ ..
90
+ +{method}:definition_hash
91
+ +{method}:contribute_to_definition
92
+ +{method}:[]=
93
+ }
94
+
95
+ class ProcessorMatcher <<(M, #ADD1B2) module>> {
96
+ Determines a suitable list of potential
97
+ processors from the command-line
98
+ parameters
99
+ ..
100
+ +{method}:candidates
101
+ }
102
+
103
+ class Commands <<(M, #ADD1B2) module>> {
104
+ Manages a main command
105
+ and optionally sub commands.
106
+ ..
107
+ +{field}commands
108
+ }
109
+
110
+ class Factory <<(M, #ADD1B2) module>> {
111
+ How to create a Manager from
112
+ a definition hash.
113
+ ..
114
+ +{method}:from_hash
115
+ }
116
+
117
+ Base -|> ProcessorMatcher : includes
118
+ Base ---|> LayeredDefinition : includes
119
+ Base --|> Commands : includes
120
+ Base --|> Factory : extends
121
+ }
122
+
123
+ namespace Processors {
124
+ class Base
125
+
126
+ class Registration<<(M, #ADD1B2) module>>
127
+
128
+ Base -|> Registration : includes
129
+
130
+ }
131
+
132
+
133
+ namespace Utils {
134
+ class BasicLogger<<(M, #ADD1B2) module>>
135
+ class ErrorPropagation<<(M, #ADD1B2) module>>
136
+ class YamlFactory<<(M, #ADD1B2) module>> {
137
+ If your module or class implements
138
+ #from_hash, then including this module
139
+ will bring the ability to load it from
140
+ a yaml string or a yaml file.
141
+ ..
142
+ +{method}:from_yaml
143
+ +{method}:from_yaml_file
144
+ }
145
+ }
146
+
147
+ Manager::Commands "1" *---- "n" Commands::SubCommand : "contains\nand\ncreates"
148
+ Manager::ProcessorMatcher "0" *- "n" Processors::Base
149
+
150
+ Manager::Factory -|> Utils::YamlFactory : includes
151
+ Commands::Factory -|> Utils::YamlFactory : includes
152
+
153
+ Manager::Base -|> Utils::ErrorPropagation : includes
154
+ Commands::SubCommand --|> Utils::ErrorPropagation : includes
155
+
156
+
157
+
158
+
159
+
160
+ @enduml
@@ -0,0 +1,23 @@
1
+ require 'slop'
2
+
3
+ require 'ultra_command_line/version'
4
+ require 'ultra_command_line/error'
5
+ require 'ultra_command_line/utils/basic_logger'
6
+ require 'ultra_command_line/utils/error_propagation'
7
+ require 'ultra_command_line/utils/yaml_factory'
8
+
9
+
10
+ module UltraCommandLine
11
+
12
+ extend UltraCommandLine::Utils::BasicLogger
13
+
14
+ def self.new_slop_options
15
+ Slop::Options.new underscore_flags: false
16
+ end
17
+
18
+ end
19
+
20
+
21
+ require 'ultra_command_line/commands/sub_command'
22
+ require 'ultra_command_line/manager/base'
23
+
@@ -0,0 +1,34 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module CommandLineParser
5
+
6
+ def params_hash
7
+ parse_cmd_line_options.to_hash
8
+ end
9
+
10
+ def extra_arguments
11
+ parse_cmd_line_options.to_hash
12
+ @extra_arguments ||= []
13
+ end
14
+
15
+ private
16
+
17
+ def parse_cmd_line_options(options_definition = UltraCommandLine.new_slop_options)
18
+ parser = build_parser(options_definition)
19
+ hash = parser.parse cmd_line_args
20
+ # @cmd_line_args = cmd_line_args
21
+ @extra_arguments = parser.arguments
22
+ hash
23
+ end
24
+
25
+ def build_parser(options_definition)
26
+ options_definition.banner = banner
27
+ options.each { |option| option.to_slop_options options_definition }
28
+ Slop::Parser.new options_definition
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module Factory
5
+
6
+ DEFAULT_BANNER = ''.freeze
7
+
8
+ include UltraCommandLine::Utils::YamlFactory
9
+
10
+ def from_hash(definition_hash, factory_options = {})
11
+ name = factory_options.fetch :name, ''
12
+ options_definition_hash = definition_hash.fetch(:options, {})
13
+ manager = factory_options[:manager]
14
+ banner = definition_hash.fetch :banner, DEFAULT_BANNER
15
+ create_command name, options_definition_hash, banner, manager
16
+ end
17
+
18
+ private
19
+
20
+ def create_command(name, options_definition_hash, banner, manager)
21
+ options = options_definition_hash.map do |option_name, option_definition_hash|
22
+ option_type = option_definition_hash[:type].to_sym
23
+ UltraCommandLine::Commands::OptionDefinition.new option_name, option_type, option_definition_hash
24
+ end
25
+ new manager, name, banner, options: options
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module HelpFormatter
5
+
6
+ DEFAULT_SEPARATOR_WIDTH = 80
7
+ DEFAULT_SEPARATOR_FILLER = '-'.freeze
8
+ DEFAULT_TITLE = 'Options'.freeze
9
+
10
+ attr_writer :separator_width, :separator_filler
11
+
12
+ def separator_width
13
+ @separator_width ||= DEFAULT_SEPARATOR_WIDTH
14
+ end
15
+
16
+ def title
17
+ name.nil? || name.empty? ? DEFAULT_TITLE : name
18
+ end
19
+
20
+ def separator_filler
21
+ @separator_filler ||= DEFAULT_SEPARATOR_FILLER
22
+ end
23
+
24
+ def help
25
+ output = []
26
+ output << banner
27
+ unless options.empty?
28
+ output << build_separator(title)
29
+ output.concat options.map(&:help_line)
30
+ end
31
+ output
32
+ end
33
+
34
+ private
35
+
36
+ def build_separator(title)
37
+ "#{separator_filler * 2} #{title} ".ljust separator_width, separator_filler
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module MainCommand
5
+
6
+
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,68 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ class OptionDefinition
5
+
6
+ DEFAULT_SUMMARY = 'Option summary not provided.'.freeze
7
+ attr_reader :name, :type
8
+ attr_reader :short_aliases, :long_aliases
9
+ attr_accessor :summary, :default, :description, :help, :dependencies, :incompatibilities
10
+
11
+ def initialize(name, type, options = {})
12
+ @name = name
13
+ @type = type
14
+ @summary = options.fetch(:summary, DEFAULT_SUMMARY)
15
+ @default = options.fetch(:default, nil)
16
+ @description = options.fetch(:description, summary)
17
+ @help = options.fetch(:help, description)
18
+ @global = options.fetch(:global, false)
19
+ @dependencies = options.fetch(:dependencies, [])
20
+ self.dependencies = dependencies.is_a?(Array) ? dependencies : [dependencies]
21
+ @incompatibilities = options.fetch(:incompatibilities, [])
22
+ self.incompatibilities = incompatibilities.is_a?(Array) ? incompatibilities : [incompatibilities]
23
+ short_aliases = options.fetch(:short_aliases, [])
24
+ long_aliases = options.fetch(:long_aliases, [])
25
+ self.short_aliases = (short_aliases.is_a?(Array) ? short_aliases : [short_aliases]).map &:to_sym
26
+ self.long_aliases = (long_aliases.is_a?(Array) ? long_aliases : [long_aliases]).map &:to_sym
27
+ end
28
+
29
+ def short_aliases=(options)
30
+ options.each do |option|
31
+ raise UltraCommandLine::Error, "Invalid short option defined for slop option '#{option}' !" unless option.to_s.size == 1
32
+ end
33
+ @short_aliases = options
34
+ end
35
+
36
+ def long_aliases=(options)
37
+ if options.include? name
38
+ raise UltraCommandLine::Error, "Inconsistent long options for slop option '#{name}' already defined as main option !"
39
+ end
40
+ @long_aliases = options
41
+ # long_aliases.unshift name
42
+ end
43
+
44
+ def global?
45
+ @global
46
+ end
47
+
48
+ def to_slop_options(slop_options = UltraCommandLine.new_slop_options)
49
+ one_letter_options = short_aliases.map.each { |option| "-#{option}" }
50
+ word_options = long_aliases.map.each { |option| "--#{option}" }
51
+ # The main option as last entry before description
52
+ option_def = word_options.concat(one_letter_options) << "--#{name}" << summary
53
+ option_def.concat [{ default: default }] unless default.nil?
54
+ slop_options.send type, *option_def
55
+ slop_options.banner = nil
56
+ slop_options
57
+ rescue NoMethodError => e
58
+ raise UltraCommandLine::Error, "Invalid option type '#{e.name}' for option '#{name}' !"
59
+ end
60
+
61
+ def help_line(slop_options = UltraCommandLine.new_slop_options)
62
+ to_slop_options(slop_options).to_s.gsub /\n/, ''
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ require 'ultra_command_line/commands/validation'
2
+ require 'ultra_command_line/commands/command_line_parser'
3
+ require 'ultra_command_line/commands/main_command'
4
+ require 'ultra_command_line/commands/option_definition'
5
+ require 'ultra_command_line/commands/help_formatter'
6
+ require 'ultra_command_line/commands/factory'
7
+
8
+ module UltraCommandLine
9
+ module Commands
10
+
11
+ class SubCommand
12
+
13
+ include UltraCommandLine::Utils::ErrorPropagation
14
+
15
+ extend UltraCommandLine::Commands::Factory
16
+
17
+ include UltraCommandLine::Commands::Validation
18
+ include UltraCommandLine::Commands::HelpFormatter
19
+ include UltraCommandLine::Commands::CommandLineParser
20
+
21
+
22
+ attr_reader :name, :manager, :options, :aliases
23
+ attr_accessor :banner
24
+
25
+ def initialize(manager,
26
+ name = '',
27
+ banner = UltraCommandLine::Commands::Factory::DEFAULT_BANNER,
28
+ options: [])
29
+ @manager = manager
30
+ @name = name
31
+ if name.empty?
32
+ class << self; include UltraCommandLine::Commands::MainCommand; end
33
+ end
34
+ self.banner = banner
35
+ @options = options
36
+ @aliases = [self.name]
37
+ end
38
+
39
+ def root_command?
40
+ singleton_class.ancestors.include? UltraCommandLine::Commands::MainCommand
41
+ end
42
+
43
+ def cmd_line_args
44
+ self_view_of_manager_cmd_line_args
45
+ end
46
+
47
+ private
48
+
49
+ attr_writer :manager
50
+
51
+ def self_view_of_manager_cmd_line_args
52
+ manager.cmd_line_args_for_command self
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,80 @@
1
+ module UltraCommandLine
2
+ module Commands
3
+
4
+ module Validation
5
+
6
+
7
+ def valid?(raise_error: false)
8
+ # return false_or_raise 'You have to successfully parse cmd line parameters before checking its validity !', raise_error: raise_error if params_hash.nil?
9
+ begin
10
+ raise if params_hash.nil?
11
+ rescue
12
+ return false_or_raise 'You have to successfully parse cmd line parameters before checking its validity !', raise_error: raise_error
13
+ end
14
+ return false unless check_options_dependencies raise_error: raise_error
15
+ return false unless check_options_incompatibilities raise_error: raise_error
16
+ if name.empty?
17
+ unless root_command?
18
+ return false_or_raise 'Only a root command should not have a name !', raise_error: raise_error
19
+ end
20
+ end
21
+ true
22
+ end
23
+
24
+ private
25
+
26
+ def check_options_incompatibilities(raise_error: false)
27
+ options.each do |option|
28
+ if option_eligible_for_incompatibility_test? option
29
+ option.incompatibilities.each do |incompatibility|
30
+ unless params_hash[incompatibility.to_sym].nil? or params_hash[incompatibility.to_sym] == false
31
+ return false_or_raise "Command line option '#{option.name}' is incompatible with option '#{incompatibility}' !",
32
+ raise_error: raise_error,
33
+ error_type: UltraCommandLine::OptionDependencyError
34
+ end
35
+ end
36
+ end
37
+ end
38
+ true
39
+ end
40
+
41
+ def option_eligible_for_incompatibility_test?(option)
42
+ return false if option.incompatibilities.empty?
43
+ keys_str = params_hash.keys.map &:to_s
44
+ if keys_str.include? option.name
45
+ not (params_hash[option.name.to_sym].nil? or params_hash[option.name.to_sym] == false)
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+
52
+ def check_options_dependencies(raise_error: false)
53
+ options.each do |option|
54
+ if option_eligible_for_dependency_test? option
55
+ option.dependencies.each do |dependency|
56
+ if params_hash[dependency.to_sym].nil? or params_hash[dependency.to_sym] == false
57
+ return false_or_raise "Command line option '#{option.name}' requires option '#{dependency}' !",
58
+ raise_error: raise_error,
59
+ error_type: UltraCommandLine::OptionDependencyError
60
+ end
61
+ end
62
+ end
63
+ end
64
+ true
65
+ end
66
+
67
+ def option_eligible_for_dependency_test?(option)
68
+ return false if option.dependencies.empty?
69
+ keys_str = params_hash.keys.map &:to_s
70
+ if keys_str.include? option.name
71
+ not (params_hash[option.name.to_sym].nil? or params_hash[option.name.to_sym] == false)
72
+ else
73
+ false
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,7 @@
1
+ module UltraCommandLine
2
+
3
+ class Error < StandardError; end
4
+
5
+ class OptionDependencyError < Error; end
6
+
7
+ end
@@ -0,0 +1,35 @@
1
+ require 'ultra_command_line/manager/factory'
2
+ require 'ultra_command_line/manager/layered_definition'
3
+ require 'ultra_command_line/manager/cmd_line_args'
4
+ require 'ultra_command_line/manager/commands'
5
+ require 'ultra_command_line/manager/processors'
6
+
7
+ module UltraCommandLine
8
+ module Manager
9
+
10
+ class Base
11
+
12
+ include UltraCommandLine::Utils::ErrorPropagation
13
+
14
+ extend UltraCommandLine::Manager::Factory
15
+
16
+ include UltraCommandLine::Manager::LayeredDefinition
17
+ include UltraCommandLine::Manager::Commands
18
+
19
+ include UltraCommandLine::Manager::Processors
20
+
21
+ def initialize(commands = [])
22
+ @commands = commands
23
+ end
24
+
25
+ def definition_hash_to_commands
26
+ self.class.from_hash(definition_hash) do |commands|
27
+ commands.each {|command| command.send :manager=, self }
28
+ @commands = commands
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module UltraCommandLine
3
+ module Manager
4
+
5
+ module CmdLineArgs
6
+
7
+ def cmd_line_args
8
+ @cmd_line_args ||= []
9
+ end
10
+
11
+ def cmd_line_args=(cmd_line_args)
12
+ cmd_line_args = case cmd_line_args
13
+ when Array
14
+ cmd_line_args
15
+ when String
16
+ cmd_line_args.split ' '
17
+ end
18
+ UltraCommandLine.logger.debug "Cmd line: #{cmd_line_args.inspect}"
19
+ @cmd_line_args = cmd_line_args
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ module UltraCommandLine
2
+ module Manager
3
+
4
+ module Commands
5
+
6
+ include UltraCommandLine::Manager::CmdLineArgs
7
+
8
+ def commands
9
+ @commands ||= []
10
+ end
11
+
12
+ def root_command
13
+ return nil if commands.empty?
14
+ l = commands.select {|c| c.root_command? && c.name.empty? }
15
+ raise UltraCommandLine::Error, 'Wrong commands definition !' unless l.count == 1
16
+ l.first
17
+ end
18
+
19
+ def aliases_consistent?
20
+ # including aliases
21
+ full_names_list = []
22
+ commands.each do |command|
23
+ if command.root_command?
24
+ return false unless command.name.empty? && command.aliases == [command.name]
25
+ else
26
+ command.aliases.each do |alias_name|
27
+ return false if full_names_list.include? alias_name.to_s
28
+ full_names_list << alias_name.to_s
29
+ end
30
+
31
+ end
32
+ end
33
+ true
34
+ end
35
+
36
+ def command(cmd_line_args = self.cmd_line_args)
37
+ command_alias = cmd_line_args.empty? ? '' : cmd_line_args.first
38
+ command_by_alias(command_alias) || root_command
39
+ end
40
+
41
+ def command_by_alias(command_alias)
42
+ raise UltraCommandLine::Error, 'command names/aliases are not consistent !' unless aliases_consistent?
43
+ candidates = commands.select do |command|
44
+ command.aliases.map(&:to_s).include? command_alias.to_s
45
+ end
46
+ # raise UltraCommandLine::Error, "There is no command named '#{command_alias}'!" unless candidates.siez == 1
47
+ candidates.size == 1 ? candidates.first : nil
48
+ end
49
+
50
+ def cmd_line_args_for_command(command)
51
+ if command.root_command?
52
+ cmd_line_args
53
+ else
54
+ clfc = cmd_line_args.dup
55
+ clfc.shift
56
+ clfc
57
+ end
58
+ end
59
+
60
+ protected
61
+
62
+ attr_writer :commands
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ module UltraCommandLine
2
+ module Manager
3
+
4
+ module Factory
5
+
6
+ include UltraCommandLine::Utils::YamlFactory
7
+
8
+ def from_hash(definition_hash, factory_options = {})
9
+ UltraCommandLine.logger.debug 'Starting commands analysis from definition hash.'
10
+ commands = []
11
+ # Create main command
12
+ UltraCommandLine.logger.debug 'Defining main command.'
13
+ main_command = create_command '', definition_hash
14
+ main_command.banner = definition_hash.fetch :banner, ''
15
+ commands << main_command
16
+ # Create sub-commands
17
+ definition_hash.fetch(:subcommands, {}).each do |subcommand_name, subcommand_definition|
18
+ UltraCommandLine.logger.debug "Defining sub-command '#{subcommand_name}'."
19
+ # subcommand_definition ||= {}
20
+ subcommand = create_command subcommand_name, subcommand_definition
21
+ subcommand.banner = subcommand_definition.fetch :banner, ''
22
+ # Let's add global options
23
+ main_command.options.select(&:global?).each do |global_option|
24
+ subcommand.options << global_option
25
+ end
26
+ commands << subcommand
27
+ end
28
+ if block_given?
29
+ yield commands
30
+ else
31
+ # Return a new object supporting a list of commands as parameter
32
+ new_manager = new commands
33
+ new_manager.initialize_definition definition_hash
34
+ commands.each {|c| c.send :manager=, new_manager}
35
+ new_manager
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def create_command(name, options_definition)
42
+ UltraCommandLine::Commands::SubCommand.from_hash options_definition, name: name, manager: self
43
+ end
44
+
45
+
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,86 @@
1
+ require 'super_stack'
2
+
3
+ module UltraCommandLine
4
+ module Manager
5
+
6
+ module LayeredDefinition
7
+
8
+ def definition_hash
9
+ @cached_definition ||= layers_manager[].to_hash
10
+ end
11
+
12
+ def initialize_definition(hash)
13
+ reset_cached_configuration
14
+ @layers_manager = setup_layers_manager hash
15
+ self
16
+ end
17
+
18
+ def contribute_to_definition(hash, layer_name: :unknown_layer)
19
+ reset_cached_configuration
20
+ layers_manager
21
+ new_layer = SuperStack::LayerWrapper.from_hash hash
22
+ new_layer.name = layer_name
23
+ new_layer.priority = definition_counter
24
+ layers_manager.add_layer new_layer
25
+ end
26
+
27
+ def []=(key, value)
28
+ layers_manager[key] = value
29
+ end
30
+
31
+ def refresh
32
+ reset_cached_configuration
33
+ end
34
+
35
+ def clear
36
+ reset_definition_hash
37
+ end
38
+
39
+ private
40
+
41
+ BASE_LAYER_PRIORITY = 10
42
+ DEFINITION_LAYERS_BASE_PRIORITY = 100
43
+ UPDATE_LAYER_PRIORITY = 1000
44
+
45
+ def reset_cached_configuration
46
+ @cached_definition = nil
47
+ end
48
+
49
+ def reset_definition_hash
50
+ reset_cached_configuration
51
+ @layers_manager = nil
52
+ end
53
+
54
+ def definition_counter
55
+ raise UltraCommandLine::Error, 'Maximum number of definition reached !' if @definition_counter+1 >= UPDATE_LAYER_PRIORITY
56
+ @definition_counter += 1
57
+ end
58
+
59
+ def layers_manager
60
+ @layers_manager ||= setup_layers_manager
61
+ end
62
+
63
+ def setup_layers_manager(hash = nil)
64
+ merger = SuperStack::Manager.new
65
+ merger.merge_policy = SuperStack::MergePolicies::FullMergePolicy
66
+ base_layer = if hash.nil?
67
+ SuperStack::Layer.new
68
+ else
69
+ SuperStack::LayerWrapper.from_hash(hash)
70
+ end
71
+ base_layer.name = :base
72
+ base_layer.priority = BASE_LAYER_PRIORITY
73
+ merger.add_layer base_layer
74
+ update_layer = SuperStack::Layer.new
75
+ update_layer.name = :manual_updates
76
+ update_layer.priority = UPDATE_LAYER_PRIORITY
77
+ merger.add_layer update_layer
78
+ merger.write_layer = update_layer
79
+ @definition_counter = DEFINITION_LAYERS_BASE_PRIORITY
80
+ merger
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,75 @@
1
+ module UltraCommandLine
2
+ module Manager
3
+
4
+ module Processors
5
+
6
+ include UltraCommandLine::Manager::Commands
7
+
8
+ MANDATORY_PROCESSOR_METHODS = %i[check_params execute].freeze
9
+
10
+ def processors(for_command: nil)
11
+ if for_command.nil?
12
+ processors_hash.values.inject([]) do |res, value|
13
+ res.concat value
14
+ res
15
+ end .uniq
16
+ else
17
+ processors_hash[for_command]
18
+ end
19
+ end
20
+
21
+ def register_processor(command_name_or_command, processor)
22
+ verify_processor_implementation processor
23
+ if processors.include? processor
24
+ msg = 'Trying to insert a processor already registered: %s' % [processor.inspect]
25
+ UltraCommandLine.logger.debug msg
26
+ end
27
+ command = command_name_or_command_to_command command_name_or_command
28
+ processors_hash[command.name] ||= []
29
+ processors_hash[command.name] << processor
30
+ command_name = command.name.empty? ? 'Root' : "#{command.name}"
31
+ UltraCommandLine.logger.debug "Registered Processor #{processor.inspect} to '#{command_name}' command"
32
+ end
33
+
34
+ def clear_processors
35
+ @processors_hash = nil
36
+ end
37
+
38
+ def processor
39
+ processor_candidates = processors_hash.fetch(command(cmd_line_args).name, []) .select do |processor|
40
+ processor.check_params command.cmd_line_args
41
+ end
42
+ raise UltraCommandLine::Error, 'No processor found for this command line' if processor_candidates.empty?
43
+ raise UltraCommandLine::Error, 'Too many possible processors' unless processor_candidates.size == 1
44
+ processor_candidates.first
45
+ end
46
+
47
+ private
48
+
49
+ def command_name_or_command_to_command(command_name_or_command)
50
+ case command_name_or_command
51
+ when String
52
+ command_by_alias command_name_or_command
53
+ when UltraCommandLine::Commands::SubCommand
54
+ command_name_or_command
55
+ else
56
+ raise UltraCommandLine::Error, "Invalid command or command name: '#{command_name_or_command.inspect}'"
57
+ end
58
+ end
59
+
60
+ def verify_processor_implementation(processor)
61
+ MANDATORY_PROCESSOR_METHODS.each do |method_name|
62
+ unless processor.respond_to? method_name
63
+ raise UltraCommandLine::Error, "Invalid processor '#{processor.inspect}' missing method '#{method_name}'!"
64
+ end
65
+ end
66
+ end
67
+
68
+ def processors_hash
69
+ @processors_hash ||= {}
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,23 @@
1
+ module UltraCommandLine
2
+ module Utils
3
+
4
+ module BasicLogger
5
+
6
+ class NullLogger
7
+ def method_missing(*args)
8
+ # Do nothing
9
+ end
10
+ end
11
+
12
+ def logger=(logger)
13
+ @logger = logger
14
+ end
15
+
16
+ def logger
17
+ @logger ||= NullLogger.new
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module UltraCommandLine
2
+ module Utils
3
+
4
+ module ErrorPropagation
5
+
6
+ DEFAULT_ERROR_MESSAGE = 'Error message not provided!'.freeze
7
+
8
+ private
9
+
10
+ def false_or_raise(message = DEFAULT_ERROR_MESSAGE, raise_error: false, error_type: UltraCommandLine::Error)
11
+ if raise_error
12
+ raise error_type, message
13
+ else
14
+ UltraCommandLine.logger.warn message
15
+ end
16
+ false
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module UltraCommandLine
2
+ module Utils
3
+
4
+ module YamlFactory
5
+
6
+ def from_yaml_file(yaml_file, factory_options = {}, &block)
7
+ yaml_file = File.expand_path yaml_file
8
+ UltraCommandLine.logger.debug "Loading commands definitions from '#{yaml_file}'."
9
+ raise UltraCommandLine::Error, 'Invalid Yaml command file specified !' unless File.exists? yaml_file
10
+ raise UltraCommandLine::Error, 'Cannot read Yaml command file !' unless File.readable? yaml_file
11
+ from_yaml File.read(yaml_file), factory_options, &block
12
+ end
13
+
14
+ def from_yaml(yaml, factory_options = {}, &block)
15
+ from_hash YAML.load(yaml), factory_options, &block
16
+ rescue => e
17
+ UltraCommandLine.logger.error "#{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
18
+ raise UltraCommandLine::Error, 'Invalid Yaml content. Parser error !'
19
+ end
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module UltraCommandLine
2
+ VERSION = '0.4.7'.freeze
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ultra_command_line/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ultra_command_line'
8
+ spec.version = UltraCommandLine::VERSION
9
+ spec.authors = ['Laurent B.']
10
+ spec.email = ['lbnetid+rb@gmail.com']
11
+
12
+ spec.summary = %q{Manage complex sub_command line options.}
13
+ spec.description = %q{Allows to handle complex sub_command line options with subcommands a-la-git.}
14
+ spec.homepage = 'https://gitlab.com/lbriais/ultra_command_line'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.15'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+ spec.add_development_dependency 'simplecov'
27
+
28
+ spec.add_dependency 'super_stack', '~> 1.0'
29
+ spec.add_dependency 'slop', '~> 4.5'
30
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ultra_command_line
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.7
5
+ platform: ruby
6
+ authors:
7
+ - Laurent B.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: super_stack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: slop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.5'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.5'
97
+ description: Allows to handle complex sub_command line options with subcommands a-la-git.
98
+ email:
99
+ - lbnetid+rb@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - README.md
109
+ - Rakefile
110
+ - bin/console
111
+ - bin/setup
112
+ - docs/command_definition.puml
113
+ - lib/ultra_command_line.rb
114
+ - lib/ultra_command_line/commands/command_line_parser.rb
115
+ - lib/ultra_command_line/commands/factory.rb
116
+ - lib/ultra_command_line/commands/help_formatter.rb
117
+ - lib/ultra_command_line/commands/main_command.rb
118
+ - lib/ultra_command_line/commands/option_definition.rb
119
+ - lib/ultra_command_line/commands/sub_command.rb
120
+ - lib/ultra_command_line/commands/validation.rb
121
+ - lib/ultra_command_line/error.rb
122
+ - lib/ultra_command_line/manager/base.rb
123
+ - lib/ultra_command_line/manager/cmd_line_args.rb
124
+ - lib/ultra_command_line/manager/commands.rb
125
+ - lib/ultra_command_line/manager/factory.rb
126
+ - lib/ultra_command_line/manager/layered_definition.rb
127
+ - lib/ultra_command_line/manager/processors.rb
128
+ - lib/ultra_command_line/utils/basic_logger.rb
129
+ - lib/ultra_command_line/utils/error_propagation.rb
130
+ - lib/ultra_command_line/utils/yaml_factory.rb
131
+ - lib/ultra_command_line/version.rb
132
+ - ultra_command_line.gemspec
133
+ homepage: https://gitlab.com/lbriais/ultra_command_line
134
+ licenses: []
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.5.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Manage complex sub_command line options.
156
+ test_files: []