rake-commander 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 79568da769a6523e7024fa641eb8d792b2fd4dfcc600f11f0cbf56e6bb1f45d9
4
+ data.tar.gz: e8e55e77085b67e37fd9befdb1b0b77cd0f8403ff1c0ab939160c3659c0cd67f
5
+ SHA512:
6
+ metadata.gz: fe77e58170a2e2bed3c9d52202e8deaa039422bda0ded1284f0d8fb316c83a6609fffb6753d2387d573b0ed70c7d5a2c74b5d868e8a89f5ba96dfa2d472b8587
7
+ data.tar.gz: f75fa6017995dd942cd37cc6494db0a0c9d5273eb28e1b9ef9fe7d909fa39abffc25b6aa681c05ba1dce868b2265fb0bf54f01c55a31fd4298aaf6f70f3b435c
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ # it's a gem, ignore the lockfile
2
+ Gemfile.lock
3
+
4
+ # build artifacts
5
+ *.gem
6
+ /.bundle/
7
+ /vendor/bundle
8
+ /spec/reports/
9
+ /tmp/
10
+ /pkg/
11
+
12
+ # docs
13
+ /.yardoc
14
+ /_yardoc/
15
+ /coverage/
16
+ /doc/
17
+
18
+ # rspec failure tracking
19
+ .rspec_status
20
+ scratch.rb
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,74 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7.2
3
+ Exclude:
4
+ - 'config/routes.rb'
5
+
6
+ Naming/VariableNumber:
7
+ EnforcedStyle: snake_case
8
+ Naming/FileName:
9
+ Enabled: false
10
+
11
+ Metrics/LineLength:
12
+ Enabled: false
13
+ Metrics/BlockLength:
14
+ ExcludedMethods: [context, describe]
15
+ Max: 50
16
+ Metrics/MethodLength:
17
+ Max: 50
18
+ Metrics/ClassLength:
19
+ Max: 200
20
+ Metrics/AbcSize:
21
+ Max: 50
22
+ Metrics/CyclomaticComplexity:
23
+ Max: 15
24
+ Metrics/PerceivedComplexity:
25
+ Max: 15
26
+
27
+ ParameterLists:
28
+ Max: 5
29
+ CountKeywordArgs: false
30
+
31
+ Style/StringLiterals:
32
+ Enabled: false
33
+ Style/FrozenStringLiteralComment:
34
+ Enabled: false
35
+ Style/CommentedKeyword:
36
+ Enabled: false
37
+ Style/MultilineBlockChain:
38
+ Enabled: false
39
+ Style/Documentation:
40
+ Enabled: false
41
+ Style/StringLiteralsInInterpolation:
42
+ Enabled: false
43
+ Style/AndOr:
44
+ Enabled: false
45
+ Style/SlicingWithRange:
46
+ Enabled: false
47
+ Style/ClassAndModuleChildren:
48
+ Enabled: false
49
+ Style/OptionalBooleanParameter:
50
+ Enabled: false
51
+
52
+ Layout/SpaceInsideHashLiteralBraces:
53
+ Enabled: false
54
+ Layout/SpaceInsideBlockBraces:
55
+ Enabled: false
56
+ Layout/SpaceAroundOperators:
57
+ Enabled: false
58
+ Layout/ExtraSpacing:
59
+ AllowForAlignment: true
60
+ Layout/AccessModifierIndentation:
61
+ EnforcedStyle: indent
62
+ Layout/DotPosition:
63
+ EnforcedStyle: trailing
64
+ Layout/MultilineMethodCallIndentation:
65
+ EnforcedStyle: indented
66
+ Layout/FirstHashElementIndentation:
67
+ Enabled: false
68
+ Layout/EmptyLineAfterGuardClause:
69
+ Enabled: false
70
+ Layout/LeadingCommentSpace:
71
+ Enabled: false
72
+
73
+ Lint/AssignmentInCondition:
74
+ Enabled: false
data/.yardopts ADDED
@@ -0,0 +1,10 @@
1
+ --readme README.md
2
+ --charset utf-8
3
+ --markup-provider=redcarpet
4
+ --markup=markdown
5
+ --no-private
6
+ --output-dir ./doc
7
+ 'lib/**/*.rb'
8
+ -
9
+ CHANGELOG.md
10
+ LICENSE
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ ## TO DO
5
+ - Rake task parameters (see: https://stackoverflow.com/a/825832/4352306)
6
+ - `OptionParser#parse` result (what to do with unknown ARGV `leftovers`)
7
+
8
+ ## [0.1.2] - 2023-04-19
9
+
10
+ ### Added
11
+ - First commit
12
+
13
+ ### Fixed
14
+ ### Changed
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ecoportal-api.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Ecoportal::API
2
+
3
+ Another way to define re-usable rake tasks and samples.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rake-commander', require: %w[rake-commander]
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install rake-commander
20
+
21
+ ## Usage
22
+
23
+
24
+ ## Development
25
+
26
+ 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).
27
+
28
+ For more info on available `Rake` tasks: `rake -T`
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "yard"
4
+ require "redcarpet"
5
+
6
+ desc "run the specs"
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ desc "run rspec showing backtrace"
10
+ RSpec::Core::RakeTask.new(:spec_trace) do |task|
11
+ task.rspec_opts = ['--backtrace']
12
+ end
13
+
14
+ desc "run rspec stopping on first fail, and show backtrace"
15
+ RSpec::Core::RakeTask.new(:spec_fast) do |task|
16
+ task.rspec_opts = ['--fail-fast', '--backtrace']
17
+ end
18
+
19
+ # default task name is yard
20
+ desc "Yard: generate all the documentation"
21
+ YARD::Rake::YardocTask.new(:doc) do |t|
22
+ #t.files = ['lib/**/*.rb']
23
+ end
24
+
25
+ desc "Examples: Run examples (rake examples[basic] -- -h)"
26
+ task :examples, [:sample] do |t, args|
27
+ require_relative "examples/#{args[:sample]}"
28
+ end
29
+
30
+ task default: [:spec]
31
+ task rspec_trace: :spec_trace
32
+ task rspec_fast: :spec_fast
data/examples/basic.rb ADDED
@@ -0,0 +1,30 @@
1
+ require_relative File.join(__dir__, '../lib/rake-commander')
2
+ class RakeCommander::Custom::Basic < RakeCommander
3
+ namespace :examples
4
+
5
+ desc 'A simple example to get started'
6
+ task :basic
7
+
8
+ #banner "Usage: basic:example -- [options]"
9
+ option '-s', '--say [SOMETHING]', "It says 'something'", default: %q(I don't know what to "say"...)
10
+ option :d, '--folder NAME', default: '.', desc: 'Source local folder', required: true
11
+ option '-e', '--enviro ENV', 'The target environment to run this task', required: true
12
+ option '-t', :show_time, TrueClass, desc: 'Displays the local time'
13
+ option :v, :debug, TrueClass, 'Shows the parsed options'
14
+
15
+ def task(*_args)
16
+ if options[:v]
17
+ puts 'We got these options:'
18
+ pp options
19
+ end
20
+ puts Time.now.strftime('%d %b at %H:%M') if options[:t]
21
+ puts options[:s] if options.key?(:s)
22
+ end
23
+ end
24
+
25
+ RakeCommander.self_load
26
+ Rake::Task[:'examples:basic'].invoke
27
+ # ruby basic.rb -- -v -d /some/folder -t
28
+
29
+ #RakeCommander::Custom::Basic.parse_options %w[--help]
30
+ #RakeCommander::Custom::Basic.parse_options %w[-d]
@@ -0,0 +1,112 @@
1
+ class RakeCommander
2
+ module Base
3
+ # Helpers for dynamic object loading based on class declaration
4
+ # @note
5
+ # - this helpers aim to boost the usage of the ruby language in complex api configurations.
6
+ module ClassAutoLoader
7
+ include RakeCommander::Base::ClassHelpers
8
+
9
+ # To enable the class autoloader, you should use this method
10
+ def autoloads_children_of(klass)
11
+ class_resolver :autoloader_class, klass
12
+ @autoloaded_class = klass
13
+ end
14
+
15
+ # Resolves the class `autoloader_class` if it has been defined via `autoloads_children_of`
16
+ def autoloaded_class
17
+ return nil unless @autoloaded_class
18
+ autoloader_class
19
+ end
20
+
21
+ # To which restricted namespaces this class autoloads from
22
+ def autoloaded_namespaces(type = :include)
23
+ @autoloaded_namespaces ||= {}
24
+ @autoloaded_namespaces[type] ||= []
25
+ end
26
+
27
+ # To restrict which namespaces it is allowed to load from
28
+ def autoload_namespace(*namespaces)
29
+ _autoload_namespace(:include, *namespaces)
30
+ end
31
+
32
+ # To ignore certain namespaces this class should not autoload from
33
+ def autoload_namespace_ignore(*namespaces)
34
+ _autoload_namespace(:ignore, *namespaces)
35
+ end
36
+
37
+ def _autoload_namespace(type, *namespaces)
38
+ autoloaded_namespaces(type).concat(namespaces) unless namespaces.empty?
39
+ end
40
+
41
+ # @param constant [Class, String] a class or namespace we want to check auto-load entitlement thereof.
42
+ # @return [Boolean] determines if a given namespace is entitled for autoloading
43
+ def autoload_class?(constant)
44
+ constants = constant.to_s.split("::").compact
45
+ autoload = true
46
+ unless autoloaded_namespaces(:include).empty?
47
+ autoload = autoloaded_namespaces(:include).any? do |ns|
48
+ ns.to_s.split("::").compact.zip(constants).all? {|(r, c)| r == c}
49
+ end
50
+ end
51
+ unless autoloaded_namespaces(:ignore).empty?
52
+ autoload &&= autoloaded_namespaces(:ignore).none? do |ns|
53
+ ns.to_s.split("::").compact.zip(constants).all? {|(r, c)| r == c}
54
+ end
55
+ end
56
+ autoload
57
+ end
58
+
59
+ # As children are loaded as they are declared, we should not load twice same children.
60
+ def autoloaded_children
61
+ @autoloaded_children ||= []
62
+ end
63
+
64
+ # Children classes of `autoloader_class` that have not been created an instance of.
65
+ def unloaded_children
66
+ return [] unless autoloaded_class
67
+ new_detected = new_classes
68
+ known_class!(*new_detected)
69
+ 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
72
+ end
73
+
74
+ # It loads/creates a new instance of children classes pending to be loaded.
75
+ # @return [Boolean] `true` if there were children loaded, `false` otherwise.
76
+ def autoload_children(object = nil)
77
+ return false if !autoloaded_class || @loading_children
78
+ pending_children = unloaded_children
79
+ return false if pending_children.empty?
80
+ @loading_children = true
81
+ pending_children.each do |klass|
82
+ child = object ? klass.new(object) : klass.new
83
+ yield(child) if block_given?
84
+
85
+ rescue TypeError
86
+ # Can't create from this class (must be the singleton class)
87
+ # Just ignore
88
+ ensure
89
+ autoloaded_children.push(klass)
90
+ end
91
+ @loading_children = false
92
+ true
93
+ end
94
+
95
+ # Known namespaces serves the purpose to discover recently added namespaces
96
+ # provided that the namespace discovery is optimized
97
+ def known_classes
98
+ @known_classes ||= []
99
+ end
100
+
101
+ # Add to known namespaces
102
+ def known_class!(*classes)
103
+ known_classes.concat(classes)
104
+ end
105
+
106
+ # List all new namespaces
107
+ def new_classes
108
+ ObjectSpace.each_object(::Class).to_a - known_classes
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,168 @@
1
+ class RakeCommander
2
+ module Base
3
+ module ClassHelpers
4
+ NOT_USED = 'no_used!'.freeze
5
+
6
+ # Helper to determine if a paramter has been used
7
+ # @note to effectivelly use this helper, you should initialize your target
8
+ # paramters with the constant `NOT_USED`
9
+ # @param val [] the value of the paramter
10
+ # @return [Boolean] `true` if value other than `NOT_USED`, `false` otherwise
11
+ def used_param?(val)
12
+ val != NOT_USED
13
+ end
14
+
15
+ # Redefines constant `const` with `value` without triggering a warning.
16
+ def redef_without_warning(const, value)
17
+ self.class.send(:remove_const, const) if self.class.const_defined?(const)
18
+ self.class.const_set(const, value)
19
+ end
20
+
21
+ # Defines a class and instance method for lazy resolving a class.
22
+ def class_resolver(name, klass)
23
+ define_singleton_method(name) { resolve_class(klass) }
24
+ define_method(name) { self.class.resolve_class(klass) }
25
+ end
26
+
27
+ # Class resolver
28
+ # @note it caches the resolved `klass`es
29
+ # @raise [Exception] when could not resolve if `exception` is `true`
30
+ # @param klass [Class, String, Symbol] the class to resolve
31
+ # @param source_class [Class] when the reference to `klass` belongs to a different inheritance chain.
32
+ # @param exception [Boolean] if it should raise exception when could not resolve
33
+ # @return [Class] the `Class` constant
34
+ def resolve_class(klass, source_class: self, exception: true)
35
+ @resolved ||= {}
36
+ @resolved[klass] ||=
37
+ case klass
38
+ when Class
39
+ klass
40
+ when String
41
+ begin
42
+ Kernel.const_get(klass)
43
+ rescue NameError
44
+ raise if exception
45
+ end
46
+ when Symbol
47
+ source_class.resolve_class(source_class.send(klass))
48
+ when Hash
49
+ referrer, referred = klass.first
50
+ resolve_class(referred, source_class: referrer, exception: exception)
51
+ else
52
+ raise "Unknown class: #{klass}" if exception
53
+ end
54
+ end
55
+
56
+ # Helper to normalize `key` into a correct `ruby` **constant name**
57
+ # @note it removes namespace syntax `::`
58
+ # @param key [String, Symbol] to be normalized
59
+ # @return [String] a correct constant name
60
+ def to_constant(key)
61
+ key.to_s.strip.split(/::/).compact.map do |str|
62
+ str.slice(0).upcase + str.slice(1..-1)
63
+ end.join("").split(/[-_ :]+/i).compact.map do |str|
64
+ str.slice(0).upcase + str.slice(1..-1)
65
+ end.join("")
66
+ end
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
+ # If the class for `name` exists, it returns it. Otherwise it generates it.
78
+ # @param name [String, Symbol] the name of the new class
79
+ # @param inherits [Class] the parent class to _inherit_ from
80
+ # @param namespace [Class, String] an existing `constant` (class or module) the new class will be namespaced on
81
+ # @yield [child_class] configure the new class
82
+ # @yieldparam child_class [Class] the new class
83
+ # @return [Class] the new generated class
84
+ def new_class(name = "Child#{uid}", inherits: self, namespace: inherits)
85
+ name = name.to_s.to_sym.freeze
86
+ class_name = to_constant(name)
87
+
88
+ unless target_class = resolve_class("#{namespace}::#{class_name}", exception: false)
89
+ target_class = Class.new(inherits)
90
+ Kernel.const_get(namespace.to_s).const_set class_name, target_class
91
+ end
92
+
93
+ target_class.tap do |klass|
94
+ yield(klass) if block_given?
95
+ end
96
+ end
97
+
98
+ # Finds all child classes of the current class.
99
+ # @param parent_class [Class] the parent class we want to find children of.
100
+ # @param direct [Boolean] it will only include direct child classes.
101
+ # @param scope [nil, Array] to only look for descendants among the ones in `scope`.
102
+ # @return [Arrary<Class>] the child classes in hierarchy order.
103
+ def descendants(parent_class: self, direct: false, scope: nil)
104
+ scope ||= ObjectSpace.each_object(::Class)
105
+ 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
118
+ end
119
+ end
120
+
121
+ # @param parent_class [Class] the parent class we want to find children of.
122
+ # @param direct [Boolean] it will only include direct child classes.
123
+ # @return [Boolean] `true` if the current class has child classes, and `false` otherwise.
124
+ def descendants?(parent_class: self, direct: false)
125
+ !descendants(parent_class: parent_class, direct: direct).empty?
126
+ 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
+ end
167
+ end
168
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'base/class_helpers'
2
+ require_relative 'base/class_auto_loader'
3
+ require_relative 'rake_task'
4
+ require_relative 'options'
5
+
6
+ class RakeCommander
7
+ module Base
8
+ class << self
9
+ def included(base)
10
+ super(base)
11
+ base.extend RakeCommander::Base::ClassHelpers
12
+ base.extend RakeCommander::Base::ClassAutoLoader
13
+ base.autoloads_children_of RakeCommander
14
+
15
+ base.extend ClassMethods
16
+ base.send :include, RakeTask
17
+
18
+ base.send :include, Options
19
+ #autoload_namespace_ignore "RakeCommander::Samples"
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def self_load
25
+ autoload_children
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ # Provides a namespace to declare rake tasks classes
2
+ module RakeCommander::Custom
3
+ end