rake-commander 0.1.2 → 0.2.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +14 -8
  4. data/CHANGELOG.md +84 -4
  5. data/Gemfile +1 -1
  6. data/LICENSE +21 -0
  7. data/README.md +95 -3
  8. data/Rakefile +11 -13
  9. data/examples/01_basic_example.rb +28 -0
  10. data/examples/02_a_chainer_example.rb +66 -0
  11. data/examples/02_a_chainer_options_set.rb +8 -0
  12. data/examples/02_b_chained_example.rb +13 -0
  13. data/examples/03_a_chainer_plus_example.rb +34 -0
  14. data/examples/03_b_chained_plus_example.rb +17 -0
  15. data/examples/Examples.rake +7 -0
  16. data/examples/README.md +79 -0
  17. data/examples/libs/shell_helpers.rb +81 -0
  18. data/lib/rake-commander/base/class_auto_loader.rb +45 -7
  19. data/lib/rake-commander/base/class_helpers.rb +16 -61
  20. data/lib/rake-commander/base/class_inheritable.rb +122 -0
  21. data/lib/rake-commander/base/custom_error.rb +52 -0
  22. data/lib/rake-commander/base/object_helpers.rb +42 -0
  23. data/lib/rake-commander/base.rb +16 -2
  24. data/lib/rake-commander/option.rb +115 -25
  25. data/lib/rake-commander/options/arguments.rb +206 -94
  26. data/lib/rake-commander/options/description.rb +17 -0
  27. data/lib/rake-commander/options/error/base.rb +86 -0
  28. data/lib/rake-commander/options/error/handling.rb +106 -0
  29. data/lib/rake-commander/options/error/invalid_argument.rb +21 -0
  30. data/lib/rake-commander/options/error/invalid_option.rb +9 -0
  31. data/lib/rake-commander/options/error/missing_argument.rb +10 -0
  32. data/lib/rake-commander/options/error/missing_option.rb +48 -0
  33. data/lib/rake-commander/options/error/unknown_argument.rb +32 -0
  34. data/lib/rake-commander/options/error.rb +75 -10
  35. data/lib/rake-commander/options/name.rb +67 -23
  36. data/lib/rake-commander/options/result.rb +107 -0
  37. data/lib/rake-commander/options/set.rb +7 -1
  38. data/lib/rake-commander/options.rb +175 -102
  39. data/lib/rake-commander/patcher/README.md +79 -0
  40. data/lib/rake-commander/patcher/application/run_method.rb +46 -0
  41. data/lib/rake-commander/patcher/application/top_level_method.rb +74 -0
  42. data/lib/rake-commander/patcher/application.rb +16 -0
  43. data/lib/rake-commander/patcher/base.rb +45 -0
  44. data/lib/rake-commander/patcher/debug.rb +32 -0
  45. data/lib/rake-commander/patcher/helpers.rb +44 -0
  46. data/lib/rake-commander/patcher.rb +26 -0
  47. data/lib/rake-commander/rake_context/wrapper.rb +2 -0
  48. data/lib/rake-commander/rake_task.rb +50 -50
  49. data/lib/rake-commander/version.rb +1 -1
  50. data/lib/rake-commander.rb +4 -0
  51. data/rake-commander.gemspec +5 -2
  52. metadata +75 -7
  53. data/examples/basic.rb +0 -30
  54. data/lib/rake-commander/options/error_rely.rb +0 -58
@@ -0,0 +1,81 @@
1
+ module Examples
2
+ module Libs
3
+ module ShellHelpers
4
+ SHELL_METHODS = %I[system back_quotes x spawn exec fork_exec pipe].freeze
5
+
6
+ # https://stackoverflow.com/a/37329716/4352306
7
+ def shell(cmd, method: :system)
8
+ method = method.to_sym
9
+ case method
10
+ when :system
11
+ success = system(cmd)
12
+ puts "* #{success ? "Succeded in" : "Failed to"} running '#{cmd}'"
13
+ when :back_quotes, :x
14
+ result = `#{cmd}`
15
+ puts result
16
+ #%x(#{cmd})
17
+ when :spawn # like Kernel.system but with no wait
18
+ pid = Process.spawn(cmd)
19
+ puts "* new child process (pid: #{pid}). Will wait..."
20
+ Process.wait(pid)
21
+ puts "* child process finished (pid: #{pid})"
22
+ when :exec
23
+ exec cmd
24
+ # Flow does not reach here
25
+ when :fork_exec
26
+ for_exec(cmd)
27
+ when :pipe # I/O of new process
28
+ pipe(cmd)
29
+ else
30
+ raise "* unknown shell method '#{method}'"
31
+ end
32
+ end
33
+
34
+ def fork_exec(cmd)
35
+ pid = fork do
36
+ shell(cmd, method: :exec)
37
+ end
38
+ puts "* new child process (pid: #{pid})"
39
+ rescue NotImplementedError => e
40
+ puts e
41
+ puts "Redirecting to 'spawn'"
42
+ shell(cmd, method: :spawn)
43
+ end
44
+
45
+ def pipe(cmd)
46
+ IO.popen(host_shell_command, 'r+') do |pipe|
47
+ puts "* new child process (pid: #{pipe.pid})"
48
+ prompt = pipe_prompt(pipe)
49
+ pipe.puts cmd
50
+ pipe.close_write # prevent `gets` to get stuck
51
+ lines = pipe.readlines
52
+ if index = lines.index {|ln| ln.include?(cmd)}
53
+ lines = lines[index+1..]
54
+ end
55
+ lines.reject! {|ln| ln.start_with?(prompt)}
56
+ lines = lines.map {|ln| ">> (pid: #{pipe.pid}) #{ln}"}
57
+ puts lines
58
+ end
59
+ end
60
+
61
+ # Changes the commandline prompt, so we can discard those.
62
+ def pipe_prompt(pipe, prompt: ':>$ ')
63
+ if Gem::Platform.local.os == "mingw32"
64
+ pipe.puts "function prompt {\"#{prompt}\"}"
65
+ else
66
+ pipe.puts "PS1=\"#{prompt}\""
67
+ end
68
+ prompt
69
+ end
70
+
71
+ # Command to open a new shell for `pipe`
72
+ def host_shell_command
73
+ if Gem::Platform.local.os == "mingw32"
74
+ "powershell -noprofile -noninteractive"
75
+ else
76
+ 'sh'
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -25,17 +25,25 @@ class RakeCommander
25
25
  end
26
26
 
27
27
  # To restrict which namespaces it is allowed to load from
28
+ # @note this deletes from the `:ignore` namespaces
28
29
  def autoload_namespace(*namespaces)
29
30
  _autoload_namespace(:include, *namespaces)
30
31
  end
31
32
 
32
33
  # To ignore certain namespaces this class should not autoload from
34
+ # @note this deletes from the `:include` namespaces
33
35
  def autoload_namespace_ignore(*namespaces)
34
36
  _autoload_namespace(:ignore, *namespaces)
35
37
  end
36
38
 
39
+ # Applies a change to the `autoloaded_namespaces`
37
40
  def _autoload_namespace(type, *namespaces)
38
- autoloaded_namespaces(type).concat(namespaces) unless namespaces.empty?
41
+ autoloaded_namespaces(type).tap do |target|
42
+ next if namespaces.empty?
43
+ other_type = type == :include ? :ignore : :include
44
+ namespaces.each {|nm_sp| autoloaded_namespace(other_type).delete(nm_sp)}
45
+ target.concat(namespaces)
46
+ end
39
47
  end
40
48
 
41
49
  # @param constant [Class, String] a class or namespace we want to check auto-load entitlement thereof.
@@ -61,14 +69,36 @@ class RakeCommander
61
69
  @autoloaded_children ||= []
62
70
  end
63
71
 
72
+ # Keep track on actual children that have been ignored by `autoload_namespace_ignore`.
73
+ def ignored_children
74
+ @ignored_children ||= []
75
+ end
76
+
77
+ # Allows to reload
78
+ # @note it may be handy some times.
79
+ def clear_autoloaded_children
80
+ forget_class!(*autoloaded_children, *ignored_children)
81
+ @ignored_children = []
82
+ @autoloaded_children = []
83
+ end
84
+
85
+ # Prevents already excluded children to enter into the loop again.
86
+ def excluded_children
87
+ @excluded_children ||= []
88
+ end
89
+
64
90
  # Children classes of `autoloader_class` that have not been created an instance of.
65
91
  def unloaded_children
66
92
  return [] unless autoloaded_class
67
93
  new_detected = new_classes
68
94
  known_class!(*new_detected)
69
95
  descendants(parent_class: autoloaded_class, scope: new_detected).select do |child_class|
70
- !autoloaded_children.include?(child_class) && autoload_class?(child_class)
71
- end.sort
96
+ !autoloaded_children.include?(child_class) && \
97
+ !excluded_children.include?(child_class) && \
98
+ autoload_class?(child_class).tap do |ignored|
99
+ ignored_children.push(child_class) if ignored
100
+ end
101
+ end
72
102
  end
73
103
 
74
104
  # It loads/creates a new instance of children classes pending to be loaded.
@@ -79,14 +109,15 @@ class RakeCommander
79
109
  return false if pending_children.empty?
80
110
  @loading_children = true
81
111
  pending_children.each do |klass|
82
- child = object ? klass.new(object) : klass.new
112
+ exclude = false
113
+ child = object ? klass.new(object) : klass.new
83
114
  yield(child) if block_given?
84
-
85
115
  rescue TypeError
86
116
  # Can't create from this class (must be the singleton class)
87
- # Just ignore
117
+ exclude = true
118
+ excluded_children.push(klass)
88
119
  ensure
89
- autoloaded_children.push(klass)
120
+ autoloaded_children.push(klass) unless exclude
90
121
  end
91
122
  @loading_children = false
92
123
  true
@@ -101,6 +132,13 @@ class RakeCommander
101
132
  # Add to known namespaces
102
133
  def known_class!(*classes)
103
134
  known_classes.concat(classes)
135
+ self
136
+ end
137
+
138
+ # Forget namespaces
139
+ def forget_class!(*classes)
140
+ @known_classes = known_classes - classes
141
+ self
104
142
  end
105
143
 
106
144
  # List all new namespaces
@@ -1,7 +1,7 @@
1
1
  class RakeCommander
2
2
  module Base
3
3
  module ClassHelpers
4
- NOT_USED = 'no_used!'.freeze
4
+ NOT_USED = :not_used!
5
5
 
6
6
  # Helper to determine if a paramter has been used
7
7
  # @note to effectivelly use this helper, you should initialize your target
@@ -65,15 +65,6 @@ class RakeCommander
65
65
  end.join("")
66
66
  end
67
67
 
68
- # Helper to create an instance variable `name`
69
- # @param [String, Symbol] the name of the variable
70
- # @reutrn [String] the name of the created instance variable
71
- def instance_variable_name(name)
72
- str = name.to_s
73
- str = "@#{str}" unless str.start_with?("@")
74
- str
75
- end
76
-
77
68
  # If the class for `name` exists, it returns it. Otherwise it generates it.
78
69
  # @param name [String, Symbol] the name of the new class
79
70
  # @param inherits [Class] the parent class to _inherit_ from
@@ -95,6 +86,16 @@ class RakeCommander
95
86
  end
96
87
  end
97
88
 
89
+ # @param klasses [Arrary<Class>] the classes to sort.
90
+ # @return [Arrary<Class>] the classes in hierarchy order.
91
+ def sort_classes(*klasses)
92
+ klasses.sort do |k_1, k_2|
93
+ next -1 if k_2 < k_1
94
+ next 1 if k_1 < k_2
95
+ 0
96
+ end
97
+ end
98
+
98
99
  # Finds all child classes of the current class.
99
100
  # @param parent_class [Class] the parent class we want to find children of.
100
101
  # @param direct [Boolean] it will only include direct child classes.
@@ -103,18 +104,11 @@ class RakeCommander
103
104
  def descendants(parent_class: self, direct: false, scope: nil)
104
105
  scope ||= ObjectSpace.each_object(::Class)
105
106
  return [] if scope.empty?
106
- scope.select do |klass|
107
- klass < parent_class
108
- end.sort do |k_1, k_2|
109
- next -1 if k_2 < k_1
110
- next 1 if k_1 < k_2
111
- 0
112
- end.tap do |siblings|
113
- if direct
114
- siblings.reject! do |si|
115
- siblings.any? {|s| si < s}
116
- end
117
- end
107
+ siblings = scope.select {|klass| klass < parent_class}
108
+ siblings = sort_classes(*siblings)
109
+ return siblings unless direct
110
+ siblings.reject! do |si|
111
+ siblings.any? {|s| si < s}
118
112
  end
119
113
  end
120
114
 
@@ -124,45 +118,6 @@ class RakeCommander
124
118
  def descendants?(parent_class: self, direct: false)
125
119
  !descendants(parent_class: parent_class, direct: direct).empty?
126
120
  end
127
-
128
- # Keeps track on class instance variables that should be inherited by child classes.
129
- # @note
130
- # - subclasses will inherit the value as is at that moment
131
- # - any change afterwards will be only on the specific class (in line with class instance variables)
132
- # - adapted from https://stackoverflow.com/a/10729812/4352306
133
- # TODO: this separates the logic of the method to the instance var. Think if would be possible to join them somehow.
134
- def inheritable_class_vars(*vars)
135
- @inheritable_class_vars ||= [:inheritable_class_vars]
136
- @inheritable_class_vars += vars
137
- end
138
-
139
- # Builds the attr_reader and attr_writer of `attrs` and registers the associated instance variable as inheritable.
140
- def inheritable_attrs(*attrs, add_accessors: false)
141
- if add_accessors
142
- attrs.each do |attr|
143
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
144
- class << self; attr_accessor :#{attr} end
145
- RUBY
146
- end
147
- end
148
- inheritable_class_vars(*attrs)
149
- end
150
-
151
- # This callback method is called whenever a subclass of the current class is created.
152
- # @note
153
- # - values of the instance variables are copied as they are (no dups or clones)
154
- # - the above means: avoid methods that change the state of the mutable object on it
155
- # - mutating methods would reflect the changes on other classes as well
156
- # - therefore, `freeze` will be called on the values that are inherited.
157
- def inherited(subclass)
158
- super.tap do
159
- inheritable_class_vars.each do |var|
160
- instance_var = instance_variable_name(var)
161
- value = instance_variable_get(instance_var)
162
- subclass.instance_variable_set(instance_var, value.freeze)
163
- end
164
- end
165
- end
166
121
  end
167
122
  end
168
123
  end
@@ -0,0 +1,122 @@
1
+ class RakeCommander
2
+ module Base
3
+ module ClassInheritable
4
+ include RakeCommander::Base::ObjectHelpers
5
+
6
+ # Builds the attr_reader and attr_writer of `attrs` and registers the associated
7
+ # instance variable as inheritable.
8
+ # @yield [value]
9
+ # @yieldparam value [Variant] the value of the parent class
10
+ # @yieldreturn the value that will be inherited by the child class
11
+ # @param attrs [Array <Symbol>] the variable names that should be inheritable.
12
+ # @param add_accessors [Boolean] whether attr_accessor should be invoked
13
+ # @param deep_dup [Boolean] whether the value of the instance var should be `deep_dup`ed.
14
+ def attr_inheritable(*attrs, add_accessors: false, deep_dup: true, &block)
15
+ attrs = attrs.map(&:to_sym)
16
+ inheritable_class_var(*attrs, deep_dup: deep_dup, &block)
17
+ return unless add_accessors
18
+ attrs.each do |attr|
19
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ class << self; attr_accessor :#{attr} end
21
+ RUBY
22
+ end
23
+ self
24
+ end
25
+
26
+ # @return [Boolean] whether an `var` has been declared as inheritable
27
+ def inheritable_class_var?(var)
28
+ inheritable_class_var.any? do |_method, definitions|
29
+ definitions.key?(var.to_sym)
30
+ end
31
+ end
32
+
33
+ # Removes from the inheritance some class variables.
34
+ # @param attrs [Array <Symbol>] the instance variable names of the class
35
+ # that should NOT be inheritable.
36
+ def attr_not_inheritable(*attrs)
37
+ attrs.each do |attr|
38
+ next unless method = inheritable_class_var_method(attr)
39
+ inheritable_class_var[method].delete(attr)
40
+ end
41
+ self
42
+ end
43
+
44
+ private
45
+
46
+ # This callback method is called whenever a subclass of the current class is created.
47
+ def inherited(subclass)
48
+ super.tap do
49
+ inheritable_class_var.each do |method, definitions|
50
+ definitions.each do |var, action|
51
+ instance_var = instance_variable_name(var)
52
+ value = instance_variable_get(instance_var)
53
+ child_value = inherited_class_value(value, method, action)
54
+ subclass.instance_variable_set(instance_var, child_value)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # @return [Variant] the value that the child class will inherit
61
+ def inherited_class_value(value, method, action)
62
+ case method
63
+ when :mirror
64
+ value
65
+ when :deep_dup
66
+ case action
67
+ when Proc
68
+ action.call(value)
69
+ when :default
70
+ custom_deep_dup(value)
71
+ end
72
+ end
73
+ end
74
+
75
+ # Keeps track on class instance variables that should be inherited by child classes.
76
+ # @note
77
+ # - adapted from https://stackoverflow.com/a/10729812/4352306
78
+ # - subclasses will inherit the value depending on `depep_dup` and `block` if enabled or given (respectivelly)
79
+ # - any change afterwards will be only on the specific class (in line with class instance variables)
80
+ # @param see `#attr_inheritable`
81
+ # @return [Hash] methods and variables to be inherited.
82
+ def inheritable_class_var(*vars, deep_dup: true, &block)
83
+ @inheritable_class_var ||= {
84
+ mirror: {},
85
+ deep_dup: {}
86
+ }.tap do |hash|
87
+ hash[:deep_dup][:inheritable_class_var] = :default
88
+ end
89
+ @inheritable_class_var.tap do |_methods|
90
+ vars.each {|var| inheritable_class_var_add(var, deep_dup: deep_dup, &block)}
91
+ end
92
+ end
93
+
94
+ # Adds var to the `inheritable_class_var`
95
+ # @param var [Symbol] the name of an instance variable of this class that should be inherited.
96
+ def inheritable_class_var_add(var, deep_dup: true, &block)
97
+ # Remove previous definition if present
98
+ attr_not_inheritable(var)
99
+ method = deep_dup || block ? :deep_dup : :mirror
100
+ inheritable_class_var[method][var] = block || :default
101
+ self
102
+ end
103
+
104
+ # @return [Symbol, NilClass]
105
+ def inheritable_class_var_method(var)
106
+ inheritable_class_var.each do |method, definitions|
107
+ return method if definitions.key?(var.to_sym)
108
+ end
109
+ nil
110
+ end
111
+
112
+ # Helper to create an instance variable `name`
113
+ # @param [String, Symbol] the name of the variable
114
+ # @return [String] the name of the created instance variable
115
+ def instance_variable_name(name)
116
+ str = name.to_s
117
+ str = "@#{str}" unless str.start_with?("@")
118
+ str
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,52 @@
1
+ class RakeCommander
2
+ module Base
3
+ # This class allows to use both method calls to `raise` by using additional parameters.
4
+ # @note Although not clearly explained, this is somehow captured here https://stackoverflow.com/a/32481520/4352306
5
+ class CustomError < StandardError
6
+ def initialize(value = nil)
7
+ super(@message = to_message(value)) if @value = value
8
+ end
9
+
10
+ # If @value was already set, ignore the latest message and
11
+ # just return @message
12
+ def to_s
13
+ return @message if @message
14
+ unclassed(super)
15
+ end
16
+
17
+ # If @value was already set, ignore the latest message
18
+ # just return @message
19
+ def message
20
+ return @message if @message
21
+ to_message(unclassed(super))
22
+ end
23
+
24
+ protected
25
+
26
+ # Any **children classes** that want to extend how `value` is transformed
27
+ # into a `message` **should extend this method**.
28
+ # @return [String] `message`
29
+ def to_message(value)
30
+ case value
31
+ when StandardError
32
+ to_message(value.message)
33
+ when String
34
+ value
35
+ when NilClass
36
+ value
37
+ else
38
+ raise ArgumentError, "Expecting String, StandardError or NilClass. Given: #{value.class}"
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # When `value` is `nil` **methods** `to_s` and `message` return the error class of `self`.
45
+ # This helper allows to remove that part to know if the error was raised with `nil`
46
+ # @return [String]
47
+ def unclassed(str)
48
+ str.to_s.gsub(self.class.to_s, '').strip
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ class RakeCommander
2
+ module Base
3
+ module ObjectHelpers
4
+ private
5
+
6
+ # Custom Object#deep_dup for rake commander
7
+ def custom_deep_dup(value, dup_objects: true, &dup_block)
8
+ case value
9
+ when Hash
10
+ custom_hash_deep_dup(value, dup_objects: dup_objects, &dup_block)
11
+ when Array
12
+ value.map {|v| custom_deep_dup(v, dup_objects: dup_objects, &dup_block)}
13
+ else
14
+ custom_object_deep_dup(value, dup_objects: true, &dup_block)
15
+ end
16
+ end
17
+
18
+ # Does the copy of the final object
19
+ def custom_object_deep_dup(value, dup_objects: true)
20
+ return yield(value) if block_given?
21
+ return value unless dup_objects
22
+ return value.deep_dup if value.respond_to?(:deep_dup)
23
+ value.dup
24
+ end
25
+
26
+ # Custom Hash#deep_dup for rake commander
27
+ def custom_hash_deep_dup(original, dup_objects: true, &dup_block)
28
+ raise ArgumentError, "Expecting Hash. Given: #{original.class}" unless original.is_a?(Hash)
29
+ hash = original.dup
30
+ original.each_pair do |key, value|
31
+ unless key.frozen? && key.is_a?(::String)
32
+ hash.delete(key)
33
+ key = custom_deep_dup(key, dup_objects: dup_objects, &dup_block) if dup_objects
34
+ end
35
+ value = dup_objects ? custom_deep_dup(value, dup_objects: dup_objects, &dup_block) : value
36
+ hash[key] = value
37
+ end
38
+ hash
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,8 @@
1
+ require_relative 'base/object_helpers'
1
2
  require_relative 'base/class_helpers'
3
+ require_relative 'base/class_inheritable'
2
4
  require_relative 'base/class_auto_loader'
5
+ require_relative 'base/custom_error'
3
6
  require_relative 'rake_task'
4
7
  require_relative 'options'
5
8
 
@@ -8,22 +11,33 @@ class RakeCommander
8
11
  class << self
9
12
  def included(base)
10
13
  super(base)
11
- base.extend RakeCommander::Base::ClassHelpers
12
14
  base.extend RakeCommander::Base::ClassAutoLoader
13
15
  base.autoloads_children_of RakeCommander
14
16
 
15
17
  base.extend ClassMethods
16
18
  base.send :include, RakeTask
17
-
18
19
  base.send :include, Options
19
20
  #autoload_namespace_ignore "RakeCommander::Samples"
20
21
  end
21
22
  end
22
23
 
23
24
  module ClassMethods
25
+ # Loads children classes by keeping a cache.
24
26
  def self_load
25
27
  autoload_children
26
28
  end
29
+
30
+ # Clears track on any auto-loaded children
31
+ # @note required for reload.
32
+ def self_load_reset
33
+ clear_autoloaded_children
34
+ end
35
+
36
+ # Clears the cache of autoloaded children classes and loads them again.
37
+ def self_reload
38
+ self_load_reset
39
+ autoload_children
40
+ end
27
41
  end
28
42
  end
29
43
  end