rvgp 0.3.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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +23 -0
  4. data/LICENSE +504 -0
  5. data/README.md +223 -0
  6. data/Rakefile +32 -0
  7. data/bin/rvgp +8 -0
  8. data/lib/rvgp/application/config.rb +159 -0
  9. data/lib/rvgp/application/descendant_registry.rb +122 -0
  10. data/lib/rvgp/application/status_output.rb +139 -0
  11. data/lib/rvgp/application.rb +170 -0
  12. data/lib/rvgp/base/command.rb +457 -0
  13. data/lib/rvgp/base/grid.rb +531 -0
  14. data/lib/rvgp/base/reader.rb +29 -0
  15. data/lib/rvgp/base/reconciler.rb +434 -0
  16. data/lib/rvgp/base/validation.rb +261 -0
  17. data/lib/rvgp/commands/cashflow.rb +160 -0
  18. data/lib/rvgp/commands/grid.rb +70 -0
  19. data/lib/rvgp/commands/ireconcile.rb +95 -0
  20. data/lib/rvgp/commands/new_project.rb +296 -0
  21. data/lib/rvgp/commands/plot.rb +41 -0
  22. data/lib/rvgp/commands/publish_gsheets.rb +83 -0
  23. data/lib/rvgp/commands/reconcile.rb +58 -0
  24. data/lib/rvgp/commands/rotate_year.rb +202 -0
  25. data/lib/rvgp/commands/validate_journal.rb +59 -0
  26. data/lib/rvgp/commands/validate_system.rb +44 -0
  27. data/lib/rvgp/commands.rb +160 -0
  28. data/lib/rvgp/dashboard.rb +252 -0
  29. data/lib/rvgp/fakers/fake_feed.rb +245 -0
  30. data/lib/rvgp/fakers/fake_journal.rb +57 -0
  31. data/lib/rvgp/fakers/fake_reconciler.rb +88 -0
  32. data/lib/rvgp/fakers/faker_helpers.rb +25 -0
  33. data/lib/rvgp/gem.rb +80 -0
  34. data/lib/rvgp/journal/commodity.rb +453 -0
  35. data/lib/rvgp/journal/complex_commodity.rb +214 -0
  36. data/lib/rvgp/journal/currency.rb +101 -0
  37. data/lib/rvgp/journal/journal.rb +141 -0
  38. data/lib/rvgp/journal/posting.rb +156 -0
  39. data/lib/rvgp/journal/pricer.rb +267 -0
  40. data/lib/rvgp/journal.rb +24 -0
  41. data/lib/rvgp/plot/gnuplot.rb +478 -0
  42. data/lib/rvgp/plot/google-drive/output_csv.rb +44 -0
  43. data/lib/rvgp/plot/google-drive/output_google_sheets.rb +434 -0
  44. data/lib/rvgp/plot/google-drive/sheet.rb +67 -0
  45. data/lib/rvgp/plot.rb +293 -0
  46. data/lib/rvgp/pta/hledger.rb +237 -0
  47. data/lib/rvgp/pta/ledger.rb +308 -0
  48. data/lib/rvgp/pta.rb +311 -0
  49. data/lib/rvgp/reconcilers/csv_reconciler.rb +424 -0
  50. data/lib/rvgp/reconcilers/journal_reconciler.rb +41 -0
  51. data/lib/rvgp/reconcilers/shorthand/finance_gem_hacks.rb +48 -0
  52. data/lib/rvgp/reconcilers/shorthand/international_atm.rb +152 -0
  53. data/lib/rvgp/reconcilers/shorthand/investment.rb +144 -0
  54. data/lib/rvgp/reconcilers/shorthand/mortgage.rb +195 -0
  55. data/lib/rvgp/utilities/grid_query.rb +190 -0
  56. data/lib/rvgp/utilities/yaml.rb +131 -0
  57. data/lib/rvgp/utilities.rb +44 -0
  58. data/lib/rvgp/validations/balance_validation.rb +68 -0
  59. data/lib/rvgp/validations/duplicate_tags_validation.rb +48 -0
  60. data/lib/rvgp/validations/uncategorized_validation.rb +15 -0
  61. data/lib/rvgp.rb +66 -0
  62. data/resources/README.MD/2022-cashflow-google.png +0 -0
  63. data/resources/README.MD/2022-cashflow.png +0 -0
  64. data/resources/README.MD/all-wealth-growth-google.png +0 -0
  65. data/resources/README.MD/all-wealth-growth.png +0 -0
  66. data/resources/gnuplot/default.yml +80 -0
  67. data/resources/i18n/en.yml +192 -0
  68. data/resources/iso-4217-currencies.json +171 -0
  69. data/resources/skel/Rakefile +5 -0
  70. data/resources/skel/app/grids/cashflow_grid.rb +27 -0
  71. data/resources/skel/app/grids/monthly_income_and_expenses_grid.rb +25 -0
  72. data/resources/skel/app/grids/wealth_growth_grid.rb +35 -0
  73. data/resources/skel/app/plots/cashflow.yml +33 -0
  74. data/resources/skel/app/plots/monthly-income-and-expenses.yml +17 -0
  75. data/resources/skel/app/plots/wealth-growth.yml +20 -0
  76. data/resources/skel/config/csv-format-acme-checking.yml +9 -0
  77. data/resources/skel/config/google-secrets.yml +5 -0
  78. data/resources/skel/config/rvgp.yml +0 -0
  79. data/resources/skel/journals/prices.db +0 -0
  80. data/rvgp.gemspec +6 -0
  81. data/test/assets/ledger_total_monthly_liabilities_with_empty.xml +383 -0
  82. data/test/assets/ledger_total_monthly_liabilities_with_empty2.xml +428 -0
  83. data/test/test_command_base.rb +61 -0
  84. data/test/test_commodity.rb +270 -0
  85. data/test/test_csv_reconciler.rb +60 -0
  86. data/test/test_currency.rb +24 -0
  87. data/test/test_fake_feed.rb +228 -0
  88. data/test/test_fake_journal.rb +98 -0
  89. data/test/test_fake_reconciler.rb +60 -0
  90. data/test/test_journal_parse.rb +545 -0
  91. data/test/test_ledger.rb +102 -0
  92. data/test/test_plot.rb +133 -0
  93. data/test/test_posting.rb +50 -0
  94. data/test/test_pricer.rb +139 -0
  95. data/test/test_pta_adapter.rb +575 -0
  96. data/test/test_utilities.rb +45 -0
  97. metadata +268 -0
@@ -0,0 +1,457 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../rvgp'
4
+ require_relative '../application/descendant_registry'
5
+
6
+ module RVGP
7
+ module Base
8
+ # If you're looking to write your own rvgp commands, or if you wish to add a rake task - this is the start of that
9
+ # endeavor.
10
+ #
11
+ # All of the built-in rvgp commands are descendants of this Base class. And, the easiest way to get started in
12
+ # writing your own, is simply to emulate one of these examples. You can see links to these examples listed under
13
+ # {RVGP::Commands}.
14
+ #
15
+ # When you're ready to start typing out your code, just place this code in a .rb file under the app/commands
16
+ # directory of your project - and rvgp will pick it up from there. An instance of a Command, that inherits from
17
+ # this base, is initialized with the parsed contents of the command line, for any case when a user invokes the
18
+ # command by its name on the CLI.
19
+ #
20
+ # The content below documents the argument handling, rake workflow, and related functionality available to you
21
+ # in your commands.
22
+ # @attr_reader [Array<String>] errors This array contains any errors that were encountered, when attempting to
23
+ # initialize this command.
24
+ # @attr_reader [Hash<Symbol,TrueClass, String>] options A hash of pairs, with keys being set to the 'long' form
25
+ # of any options that were passed on the command line. And
26
+ # with values consisting of either 'string' (for the case
27
+ # of a ''--option=value') or 'True' for the prescense of
28
+ # an option in the short or long form ("-l" or "--long")
29
+ # @attr_reader [<Object>] targets The parsed targets, that were encountered, for this command. Note that this Array
30
+ # may contain just about any object whatsoever, depending on how the Target for
31
+ # a command is written.
32
+ class Command
33
+ # Targets are, as the name would imply, a command line argument, that isn't prefixed with one or more dashes.
34
+ # Whereas some arguments are program options, targets are typically a specific subject or destination, which
35
+ # the command is applied to.
36
+ #
37
+ # This base class offers common functions for navigating targets, and identifying targets on the command line.
38
+ # This is a base class, which would generally find an inheritor inside a specific command's implementation.
39
+ # @attr_reader [String] name The target name, as it would be expected to be found on the CLI
40
+ # @attr_reader [String] status_name The target name, as it would be expected to appear in the status output,
41
+ # which is generally displayed during the processing of this target during
42
+ # the rake process and/or during an rvgp-triggered process.
43
+ # @attr_reader [String] description A description of this target. Mostly this is used by rake, to describe
44
+ # this target in the 'rake -T' output.
45
+ class Target
46
+ attr_reader :name, :status_name, :description
47
+
48
+ # Create a new Target
49
+ # @param [String] name see {RVGP::Base::Command::Target#name}
50
+ # @param [String] status_name see {RVGP::Base::Command::Target#status_name}
51
+ def initialize(name, status_name = nil)
52
+ @name = name
53
+ @status_name = status_name
54
+ end
55
+
56
+ # Returns true, if the provided identifier matches this target
57
+ # @param [String] identifier A target that was encountered on the CLI
58
+ # @return [TrueClass, FalseClass] whether we're the target specified
59
+ def matches?(identifier)
60
+ File.fnmatch? identifier, name
61
+ end
62
+
63
+ # Find the target that matches the provided string
64
+ # @param [String] str A string which expresses which needle, we want to find, in this haystack.
65
+ # @return [Target] The target we matched this string against.
66
+ def self.from_s(str)
67
+ all.find_all { |target| target.matches? str }
68
+ end
69
+ end
70
+
71
+ # This is an implementation of Target, that matches Reconcilers.
72
+ #
73
+ # This class allows any of the current project's reconcilers to match a target. And, such targets can be selected
74
+ # by way of a:
75
+ # - full reconciler path
76
+ # - reconciler file basename (without the full path)
77
+ # - the reconciler's from field
78
+ # - the reconciler's label field
79
+ # - the reconciler's input file
80
+ # - the reconciler's output file
81
+ #
82
+ # Any class that operates by way of a reconciler-defined target, can use this implementation, in lieu of
83
+ # re-implementing the wheel.
84
+ class ReconcilerTarget < RVGP::Base::Command::Target
85
+ # Create a new ReconcilerTarget
86
+ # @param [RVGP::Base::Reconciler] reconciler An instance of either {RVGP::Reconcilers::CsvReconciler}, or
87
+ # {RVGP::Reconcilers::JournalReconciler}, to use as the basis
88
+ # for this target.
89
+ def initialize(reconciler)
90
+ super reconciler.as_taskname, reconciler.label
91
+ @reconciler = reconciler
92
+ end
93
+
94
+ # (see RVGP::Base::Command::Target#matches?)
95
+ def matches?(identifier)
96
+ @reconciler.matches_argument? identifier
97
+ end
98
+
99
+ # (see RVGP::Base::Command::Target#description)
100
+ def description
101
+ I18n.t format('commands.%s.target_description', self.class.command), input_file: @reconciler.input_file
102
+ end
103
+
104
+ # All possible Reconciler Targets that the project has defined.
105
+ # @return [Array<RVGP::Base::Command::ReconcilerTarget>] A collection of targets.
106
+ def self.all
107
+ RVGP.app.reconcilers.map { |reconciler| new reconciler }
108
+ end
109
+
110
+ # This is a little goofy. But, it exists as a hack to support dispatching this target via the
111
+ # {RVGP::Base::Command::ReconcilerTarget.command} method. You can see an example of this at work in the
112
+ # {https://github.com/brighton36/rvgp/blob/main/lib/rvgp/commands/reconcile.rb reconcile.rb} file.
113
+ # @param [Symbol] underscorized_command_name The command to return, when
114
+ # {RVGP::Base::Command::ReconcilerTarget.command} is called.
115
+ def self.for_command(underscorized_command_name)
116
+ @for_command = underscorized_command_name
117
+ end
118
+
119
+ # Returns which command this class is defined for. See the note in
120
+ # #{RVGP::Base::Command::ReconcilerTarget.for_command}.
121
+ # @return [Symbol] The command this target is relevant for.
122
+ def self.command
123
+ @for_command
124
+ end
125
+ end
126
+
127
+ # This is an implementation of Target, that matches Plots.
128
+ #
129
+ # This class allows any of the current project's plots, to match a target, based on their name and variants.
130
+ #
131
+ # Any class that operates by way of a plot-defined target, can use this implementation, in lieu of
132
+ # re-implementing the wheel.
133
+ # @attr_reader [RVGP::Plot] plot An instance of the plot that offers our :name variant
134
+ class PlotTarget < RVGP::Base::Command::Target
135
+ attr_reader :plot
136
+
137
+ # Create a new PlotTarget
138
+ # @param [String] name A plot variant
139
+ # @param [RVGP::Plot] plot A plot instance which will handle this variant
140
+ def initialize(name, plot)
141
+ super name, name
142
+ @plot = plot
143
+ end
144
+
145
+ # @!visibility private
146
+ def description
147
+ I18n.t 'commands.plot.target_description', name: name
148
+ end
149
+
150
+ # @!visibility private
151
+ def uptodate?
152
+ # I'm not crazy about listing the extension here. Possibly that should come
153
+ # from the plot object. It's conceivable in the future, that we'll use
154
+ # more than one extension here...
155
+ FileUtils.uptodate? @plot.output_file(@name, 'gpi'), [@plot.path] + @plot.variant_files(@name)
156
+ end
157
+
158
+ # All possible Plot Targets that the project has defined.
159
+ # @return [Array<RVGP::Base::Command::PlotTarget>] A collection of targets.
160
+ def self.all
161
+ RVGP::Plot.all(RVGP.app.config.project_path('app/plots')).map do |plot|
162
+ plot.variants.map { |params| new params[:name], plot }
163
+ end.flatten
164
+ end
165
+ end
166
+
167
+ # Option(s) are, as the name would imply, a command line option, that is prefixed with one or more dashes.
168
+ # Whereas some arguments are program targets, options typically expresses a global program setting, to take
169
+ # effect during this execution.
170
+ #
171
+ # Some options are binaries, and are presumed 'off' if unspecified. Other options are key/value pairs, separated
172
+ # by an equal sign or space, in a form such as "-d ~/ledger" or "--dir=~/ledger". Option keys are expected to
173
+ # exist in both a short and long form. In the previous example, both the "-d" and "--dir" examples are identical.
174
+ # The "-d" form is a short form and "--dir" is a long form, of the same Option.
175
+ #
176
+ # This class offers common functions for specifying and parsing options on the command line, as well as
177
+ # for producing the documentation on an option.
178
+ # @attr_reader [Symbol] short A one character code, which identifies this option
179
+ # @attr_reader [Symbol] long A multi-character code, which identifies this option
180
+ class Option
181
+ # This error is raised when an option is encountered on the CLI, and the string terminated, before a value
182
+ # could be parsed.
183
+ class UnexpectedEndOfArgs < StandardError; end
184
+
185
+ attr_reader :short, :long
186
+
187
+ # Create a new Option
188
+ # @param [String] short see {RVGP::Base::Command::Option#short}
189
+ # @param [String] long see {RVGP::Base::Command::Option#long}
190
+ # @param [Hash] options additional parameters to configure this Option with
191
+ # @option options [TrueClass,FalseClass] :has_value (false) This flag indicates that this option is expected to
192
+ # have a corresponding value, for its key.
193
+ def initialize(long, short, options = {})
194
+ @short = short.to_sym
195
+ @long = long.to_sym
196
+ @has_value = options[:has_value] if options.key? :has_value
197
+ end
198
+
199
+ # Returns true, if either our short or long form, equals the provided string
200
+ # @param [String] str an option. This is expected to include one or more dashes.
201
+ # @return [TrueClass,FalseClass] Whether or not we can handle the provided option.
202
+ def matches?(str)
203
+ ["--#{long}", "-#{short}"].include? str
204
+ end
205
+
206
+ # Returns true, if we expect our key to be paired with a value. This property is specified in the :has_value
207
+ # option in the constructor.
208
+ # @return [TrueClass,FalseClass] Whether or not we expect a pair
209
+ def value?
210
+ !@has_value.nil?
211
+ end
212
+
213
+ # Given program arguments, and an array of options that we wish to support, return the options and arguments
214
+ # that were encountered.
215
+ # @param [Array<RVGP::Base::Command::Option>] options The options to that we want to parse, from out of the
216
+ # provided args
217
+ # @param [Array<String>] args Program arguments, as would be provided by a typical ARGV
218
+ # @return [Array<Hash<Symbol,Object>,Array<String>>] A two-element array. The first element is a Hash of Symbols
219
+ # To Objects (Either TrueClass or String). The second is an
220
+ # Array of Strings. The first element represents what options
221
+ # were parsed, with the key for those options being
222
+ # represented by their :long form (regardless of what was
223
+ # encountered) The second element contains the targets that
224
+ # were encountered.
225
+ def self.remove_options_from_args(options, args)
226
+ ret_args = []
227
+ ret_options = {}
228
+
229
+ i = 0
230
+ until i >= args.length
231
+ arg = args[i]
232
+ arg_value = nil
233
+
234
+ if /\A([^=]+)=([^ ]+)/.match arg
235
+ arg = ::Regexp.last_match 1
236
+ arg_value = ::Regexp.last_match 2
237
+ end
238
+
239
+ option = options.find { |opt| opt.matches? arg }
240
+
241
+ if option
242
+ ret_options[option.long] = if option.value?
243
+ if arg_value.nil?
244
+ if i + 1 >= args.length
245
+ raise UnexpectedEndOfArgs, I18n.t('error.end_of_args')
246
+ end
247
+
248
+ i += 1
249
+ args[i]
250
+ else
251
+ arg_value
252
+ end
253
+ else
254
+ true
255
+ end
256
+ else
257
+ ret_args << args[i]
258
+ end
259
+
260
+ i += 1
261
+ end
262
+
263
+ [ret_options, ret_args]
264
+ end
265
+ end
266
+
267
+ include RVGP::Application::DescendantRegistry
268
+
269
+ register_descendants RVGP, :commands
270
+
271
+ attr_reader :errors, :options, :targets
272
+
273
+ # This is shortcut to a --all/-a option, which is common across the built-in rvgp commands
274
+ OPTION_ALL = %i[all a].freeze
275
+ # This is shortcut to a --list/-l option, which is common across the built-in rvgp commands
276
+ OPTION_LIST = %i[list l].freeze
277
+
278
+ # Create a new Command, suitable for execution, and initialized with command line arguments.
279
+ # @param [Array<String>] args The arguments that will govern this command's execution, as they would be expected
280
+ # to be found in ARGV.
281
+ def initialize(*args)
282
+ @errors = []
283
+ @options = {}
284
+ @targets = []
285
+
286
+ # We'll cast the arguments to one of these, instead of storing strings
287
+ target_klass = self.class.const_get('Target')
288
+
289
+ @options, remainders = Option.remove_options_from_args self.class.options, args
290
+
291
+ missing_targets = []
292
+ remainders.each do |remainder|
293
+ if target_klass
294
+ targets = target_klass.from_s remainder
295
+
296
+ if targets
297
+ @targets += targets
298
+ else
299
+ missing_targets << remainder
300
+ end
301
+ else
302
+ @targets << remainder
303
+ end
304
+ end
305
+
306
+ if options[:list] && target_klass
307
+ indent = I18n.t 'status.indicators.indent'
308
+ puts ([RVGP.pastel.bold(I18n.t(format('commands.%s.list_targets', self.class.name)))] +
309
+ target_klass.all.map { |target| indent + target.name }).join("\n")
310
+ exit
311
+ end
312
+
313
+ @targets = target_klass.all if options[:all] && target_klass
314
+
315
+ @errors << I18n.t('error.no_targets') if @targets.empty?
316
+ @errors << I18n.t('error.missing_target', targets: missing_targets.join(', ')) unless missing_targets.empty?
317
+ end
318
+
319
+ # Indicates whether we can execute this command, given the provided arguments
320
+ # @return [TrueClass,FalseClass] Returns true if there were no problems during initialization
321
+ def valid?
322
+ errors.empty?
323
+ end
324
+
325
+ # Executes the command, using the provided options, for each of the targets provided.
326
+ # @return [void]
327
+ def execute!
328
+ execute_each_target
329
+ end
330
+
331
+ private
332
+
333
+ def execute_each_target
334
+ # This keeps things DRY for the case of commands such as reconcile, which
335
+ # use the stdout option
336
+ targets.each { |target| target.execute options }
337
+ end
338
+
339
+ class << self
340
+ # This method exists as a shortcut for inheriting classes, to use, in defining what options their command
341
+ # supports. This method expects a variable amount of arrays. With, each of those arrays
342
+ # expected to contain a :short and :long symbol, and optionally a third Hash element, specifying initialize
343
+ # options.
344
+ #
345
+ # Each of these arguments are supplied to {RVGP::Base::Command::Option#initialize}.
346
+ # {RVGP::Base::Command::OPTION_ALL} and {RVGP::Base::Command::OPTION_LIST} are common parameters to supply as
347
+ # arguments to this method.
348
+ # @param [Array<Array<Symbol,Hash>>] args An array, of pairs of [:long, :short] Symbol(s).
349
+ def accepts_options(*args)
350
+ @options = args.map { |option_args| Option.new(*option_args) }
351
+ end
352
+
353
+ # Return the options that have been defined for this command
354
+ # @return [Array<RVGP::Base::Command::Option] the options this command handles
355
+ def options
356
+ @options || []
357
+ end
358
+ end
359
+
360
+ # This module contains helpers methods, for commands, that want to be inserted into the rake process. By including
361
+ # this module in your command, you'll gain access to {RVGP::Base::Command::RakeTask::ClassMethods#rake_tasks},
362
+ # which will append the Target(s) of your command, to the rake process.
363
+ #
364
+ # If custom rake declarations are necessary for your command the
365
+ # {RVGP::Base::Command::RakeTask::ClassMethods#initialize_rake} method can be overridden, in order to make those
366
+ # declarations.
367
+ #
368
+ # Probably you should just head over to {RVGP::Base::Command::RakeTask::ClassMethods} to learn more about this
369
+ # module.
370
+ module RakeTask
371
+ # @!visibility private
372
+ def execute!
373
+ targets.map do |target|
374
+ RVGP.app.logger.info self.class.name, target.status_name do
375
+ warnings, errors = target.execute options
376
+ warnings ||= []
377
+ errors ||= []
378
+ { warnings: warnings, errors: errors }
379
+ end
380
+ end
381
+ end
382
+
383
+ # @!visibility private
384
+ def self.included(klass)
385
+ klass.extend ClassMethods
386
+ end
387
+
388
+ # These methods are automatically included by the RakeTask module, and provide
389
+ # helper methods, to the class itself, of the command that RakeTask was included
390
+ # in.
391
+ module ClassMethods
392
+ # The namespace in which this command's targets are defined. This value is
393
+ # set by {RVGP::Base::Command::RakeTask::ClassMethods#rake_tasks}.
394
+ attr_reader :rake_namespace
395
+
396
+ # This method is provided for classes that include this module. Calling this method, with a namespace,
397
+ # ensures that all the targets in the command, are setup as rake tasks inside the provided namespace.
398
+ # @param [Symbol] namespace A prefix, under which this command's targets will be declared in rake.
399
+ # @return [void]
400
+ def rake_tasks(namespace)
401
+ @rake_namespace = namespace
402
+ end
403
+
404
+ # @!visibility private
405
+ def task_exec(target)
406
+ error_count = 0
407
+ command = new target.name
408
+
409
+ unless target.uptodate?
410
+ rets = command.execute!
411
+ raise StandardError, 'This should never happen' if rets.length > 1
412
+
413
+ if rets.empty?
414
+ raise StandardError, format('The %<command>s command aborted when trying to run the %<task>s task',
415
+ command: command.class.name,
416
+ task: task.name)
417
+
418
+ end
419
+
420
+ error_count += rets[0][:errors].length
421
+ end
422
+
423
+ # NOTE: It would be kind of nice, IMO, if the namespace continued
424
+ # to run, and then failed. Instead of having all tasks in the
425
+ # namespace halt, on an error. I don't know how to do this, without
426
+ # a lot of monkey patching and such.
427
+ # Or, maybe, we could just not use multitask() and instead write
428
+ # our own multitasking loop, which, is a similar pita
429
+ abort if error_count.positive?
430
+ end
431
+
432
+ # This method initializes rake tasks in the provided context. This method exists as a default implementation
433
+ # for commands, with which to initialize their rake tasks. Feel free to overload this default behavior in your
434
+ # commands.
435
+ # @param [main] rake_main Typically this is the environment of a Rakefile that was passed onto us via self.
436
+ # @return [void]
437
+ def initialize_rake(rake_main)
438
+ command_klass = self
439
+
440
+ if rake_namespace
441
+ rake_main.instance_eval do
442
+ namespace command_klass.rake_namespace do
443
+ command_klass.const_get('Target').all.each do |target|
444
+ unless Rake::Task.task_defined?(target.name)
445
+ desc target.description
446
+ task(target.name) { |_task, _task_args| command_klass.task_exec(target) }
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end