cuprum-cli 0.1.0 → 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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +10 -0
  4. data/lib/cuprum/cli/argument.rb +2 -2
  5. data/lib/cuprum/cli/command.rb +2 -4
  6. data/lib/cuprum/cli/commands/ci/rspec_each_command.rb +34 -8
  7. data/lib/cuprum/cli/commands/ci.rb +8 -0
  8. data/lib/cuprum/cli/commands/file/new_command.rb +42 -49
  9. data/lib/cuprum/cli/commands/file.rb +8 -5
  10. data/lib/cuprum/cli/dependencies/clock/mock.rb +28 -0
  11. data/lib/cuprum/cli/dependencies/clock.rb +29 -0
  12. data/lib/cuprum/cli/dependencies/file_system/mock.rb +150 -18
  13. data/lib/cuprum/cli/dependencies/file_system.rb +145 -10
  14. data/lib/cuprum/cli/dependencies.rb +2 -0
  15. data/lib/cuprum/cli/errors.rb +0 -1
  16. data/lib/cuprum/cli/{commands/file/generate_file.rb → files/create_file.rb} +6 -48
  17. data/lib/cuprum/cli/{commands/file → files/engines}/render_erb.rb +6 -6
  18. data/lib/cuprum/cli/files/engines.rb +76 -0
  19. data/lib/cuprum/cli/{errors/files → files/errors}/file_not_writeable.rb +3 -3
  20. data/lib/cuprum/cli/files/errors/generator_error.rb +46 -0
  21. data/lib/cuprum/cli/{errors/files → files/errors}/missing_parameter.rb +5 -5
  22. data/lib/cuprum/cli/{errors/files → files/errors}/missing_template.rb +3 -3
  23. data/lib/cuprum/cli/{errors/files → files/errors}/template_error.rb +3 -3
  24. data/lib/cuprum/cli/files/errors.rb +19 -0
  25. data/lib/cuprum/cli/files/generator.rb +431 -0
  26. data/lib/cuprum/cli/files/generators/basic_generator.rb +15 -0
  27. data/lib/cuprum/cli/files/generators/rspec_generator.rb +19 -0
  28. data/lib/cuprum/cli/files/generators/ruby_generator.rb +32 -0
  29. data/lib/cuprum/cli/files/generators.rb +25 -0
  30. data/lib/cuprum/cli/files/template.rb +62 -0
  31. data/lib/cuprum/cli/files/templates/basic_template.rb +39 -0
  32. data/lib/cuprum/cli/files/templates/file_template.rb +60 -0
  33. data/lib/cuprum/cli/files/templates/string_template.rb +29 -0
  34. data/lib/cuprum/cli/files/templates.rb +59 -0
  35. data/lib/cuprum/cli/files.rb +18 -0
  36. data/lib/cuprum/cli/integrations/async/commands/rspec_each_command.rb +32 -0
  37. data/lib/cuprum/cli/integrations/async/commands.rb +11 -0
  38. data/lib/cuprum/cli/integrations/async.rb +21 -0
  39. data/lib/cuprum/cli/integrations/thor/registry.rb +7 -29
  40. data/lib/cuprum/cli/integrations/thor/task.rb +37 -7
  41. data/lib/cuprum/cli/metadata.rb +54 -5
  42. data/lib/cuprum/cli/option.rb +10 -4
  43. data/lib/cuprum/cli/options/class_methods.rb +19 -1
  44. data/lib/cuprum/cli/registry.rb +131 -55
  45. data/lib/cuprum/cli/rspec/deferred/dependencies/file_system_examples.rb +1303 -1001
  46. data/lib/cuprum/cli/rspec/deferred/generators_examples.rb +112 -0
  47. data/lib/cuprum/cli/rspec/deferred/metadata_examples.rb +300 -0
  48. data/lib/cuprum/cli/rspec/deferred/options_examples.rb +8 -2
  49. data/lib/cuprum/cli/rspec/deferred/registry_examples.rb +343 -108
  50. data/lib/cuprum/cli/rspec/deferred/templates_examples.rb +183 -0
  51. data/lib/cuprum/cli/version.rb +1 -1
  52. data/lib/cuprum/cli.rb +1 -0
  53. metadata +31 -14
  54. data/lib/cuprum/cli/commands/file/resolve_template.rb +0 -136
  55. data/lib/cuprum/cli/commands/file/templates.rb +0 -71
  56. data/lib/cuprum/cli/errors/files/template_not_resolved.rb +0 -54
  57. data/lib/cuprum/cli/errors/files.rb +0 -19
  58. /data/lib/cuprum/cli/{commands/file → files}/templates/rspec.rb.erb +0 -0
  59. /data/lib/cuprum/cli/{commands/file → files}/templates/ruby.rb.erb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0569f2b0f6fd69ffae0141d299421c0a42d91182e8d795151ab0d8994838cf5
4
- data.tar.gz: d585735c4712bd25a0729c09fb8547e46b0e4a2222d3cf1476c178ea17d54bc1
3
+ metadata.gz: '053079e009641ced526992e4638869d248642d3bb52d03ce3eec21ce936684fd'
4
+ data.tar.gz: 0e2fe126bc581aec7b90b778b1f21d618883bcd0cfad321d92af0190af7f1c93
5
5
  SHA512:
6
- metadata.gz: b65e0e51ef3d04c4adb58998dc62e3a153e9e8cb2c44f21fd540798993bf13633e7079c07df56a03e30d0d0d5300f31b3eeacf3b2575ae4c88f513ecd60f5e1f
7
- data.tar.gz: d0b99e59cc83645160bad0fec52432cee1d10b1f7efaf4f5771dff0c7a1df747dd2e9364ac27994ba73b17aaa6c74070711ff048e4185512a085a2d85346d6ee
6
+ metadata.gz: f73d5753de14f610d59c47bfc193ec6202e8bd5ba1f252a4f7ce38d6682d72688807508f9ae2c1c16b659c2ba7163e2b7b8f42e3d191ef3b382ea99e3c9172ad
7
+ data.tar.gz: 507e66d118165c0ae0dfbad7bfe6a2465cd61de01719eb0c4a8d716546754b028faad08cd2cb13fa2cb68e997629dade961d6e257dbd3cefd279a9648de0e79d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ## Commands
6
+
7
+ - Implemented support for resolving aliased options.
8
+ - Normalized option keys for variadic options.
9
+
10
+ ### Built-In Commands
11
+
12
+ - Fixed directory expansion for `Ci::RSpecEachCommand`.
13
+ - Fixed `Ci::RSpecEachCommand` reporting failing specs as errored.
14
+ - Updated `Ci::RSpecEachCommand` output to report total elapsed time.
15
+ - Updated `File::NewCommand` to use generators instead of raw templates.
16
+
17
+ ### Dependencies
18
+
19
+ - Implemented `Clock` dependency.
20
+ - Implemented `FileSystem#copy_file` helper.
21
+ - Implemented `FileSystem#delete_directory` helper.
22
+ - Improved handling of file parameters for `FileSystem::Mock`.
23
+ - Updated `FileSystem#each_file` enumerated values to match the given pattern - values will only be converted to absolute paths if the input pattern is an absolute path.
24
+ - Fixed a `FileSystem#each_file` bug when matching a template that included the root path.
25
+ - Fixed a `FileSystem::Mock` bug that would wipe existing mock directories.
26
+ - Fixed a `FileSystem::Mock` bug that did not include directories in `each_file`.
27
+ - Fixed a `FileSystem::Mock` bug in handling globbed file patterns ending in `**`.
28
+
29
+ ### Files
30
+
31
+ - Implemented file generators, a more robust solution for generating files or groups of files.
32
+ - Implemented file templates for defining the outputs of generators.
33
+ - Refactored `Errors::Files` to `Files::Errors`.
34
+
35
+ ## Integrations
36
+
37
+ - Added `Async` integration.
38
+ - Added `Async::Commands::RSpecEachCommand`, which allows running spec files in parallel.
39
+ - Fixed error handling for `Thor` integration.
40
+ - Enabled passing variadic options through `Thor` tasks.
41
+
42
+ ## Registries
43
+
44
+ - Added support for registering groups of commands.
45
+
3
46
  ## 0.1.0
4
47
 
5
48
  Initial version.
data/README.md CHANGED
@@ -89,11 +89,17 @@ Cuprum::Cli.initializer.call
89
89
 
90
90
  registry = Cuprum::Cli::Integrations::Thor::Registry.new
91
91
 
92
+ # Register an individual command.
92
93
  registry.register Cuprum::Cli::Commands::Ci::RSpecCommand
94
+
95
+ # Register a command with configuration options.
93
96
  registry.register Cuprum::Cli::Commands::Ci::RSpecCommand,
94
97
  full_name: 'ci:rspec:sinatra4',
95
98
  description: 'Runs the RSpec tests against Sinatra 4.X',
96
99
  options: { gemfile: 'gemfiles/sinatra_4.gemfile' }
100
+
101
+ # Register a group of commands.
102
+ registry.register Cuprum::Cli::Commands::File.commands
97
103
  ```
98
104
 
99
105
  Finally, you can call the commands from your CLI tool:
@@ -104,6 +110,10 @@ ci
104
110
  --
105
111
  thor ci:rspec ...FILE_PATTERNS # Runs an RSpec command.
106
112
  thor ci:rspec:sinatra4 ...FILE_PATTERNS # Runs the RSpec tests against Sinatra 4.X
113
+
114
+ file
115
+ ----
116
+ thor file:new FILE_PATH # Generates a new templated file or files.
107
117
  ```
108
118
 
109
119
  ### Defining Commands
@@ -35,9 +35,9 @@ module Cuprum::Cli # rubocop:disable Metrics/ModuleLength
35
35
  default: nil,
36
36
  description: nil,
37
37
  parameter_name: nil,
38
- required: false,
38
+ required: false, # rubocop:disable Layout/ExtraSpacing
39
39
  type: :string,
40
- variadic: false
40
+ variadic: false # rubocop:disable Layout/ExtraSpacing
41
41
  )
42
42
  name = name.to_sym
43
43
  required = required ? true : false
@@ -81,10 +81,8 @@ module Cuprum::Cli
81
81
  #
82
82
  # @return [Cuprum::Result] the command result.
83
83
  def call(*, resolved_arguments: nil, resolved_options: nil, **)
84
- @arguments =
85
- resolved_arguments || self.class.resolve_arguments(*)
86
- @options =
87
- resolved_options || self.class.resolve_options(**)
84
+ @arguments = resolved_arguments || self.class.resolve_arguments(*)
85
+ @options = resolved_options || self.class.resolve_options(**)
88
86
 
89
87
  super()
90
88
  end
@@ -6,12 +6,16 @@ require 'cuprum/cli/commands/ci/rspec_command'
6
6
  module Cuprum::Cli::Commands::Ci
7
7
  # Command for running each RSpec file in its own process.
8
8
  class RSpecEachCommand < Cuprum::Cli::Command # rubocop:disable Metrics/ClassLength
9
+ dependency :clock
9
10
  dependency :file_system
10
11
  dependency :standard_io
11
12
 
12
13
  include Cuprum::Cli::Dependencies::StandardIo::Helpers
13
14
  include Cuprum::Cli::Options::Quiet
14
15
 
16
+ FILE_PATTERN = /\.\w+\z/
17
+ private_constant :FILE_PATTERN
18
+
15
19
  arguments :file_patterns
16
20
 
17
21
  option :color, type: :boolean, default: true
@@ -32,12 +36,14 @@ module Cuprum::Cli::Commands::Ci
32
36
 
33
37
  attr_reader :report
34
38
 
35
- def aggregate_file_results(filename:, result:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
39
+ attr_reader :total_time
40
+
41
+ def aggregate_file_results(filename:, result:)
36
42
  if result.value.is_a?(Cuprum::Cli::Commands::Ci::Report)
37
43
  merge_report(result)
38
44
  end
39
45
 
40
- if result.value&.errored? || result.failure?
46
+ if errored?(result)
41
47
  errored_files << filename
42
48
  elsif result.value&.failure?
43
49
  failing_files << filename
@@ -55,13 +61,25 @@ module Cuprum::Cli::Commands::Ci
55
61
  def configured_file_patterns
56
62
  patterns = arguments.fetch(:file_patterns, [])
57
63
 
58
- return patterns unless patterns.empty?
64
+ return ['spec/**{,/*/**}/*_spec.rb'] if patterns.empty?
65
+
66
+ patterns.map do |pattern|
67
+ next pattern if wildcard_pattern?(pattern) || file_pattern?(pattern)
68
+
69
+ File.join(pattern, '**', '*')
70
+ end
71
+ end
72
+
73
+ def errored?(result)
74
+ return true unless result.value.is_a?(Cuprum::Cli::Commands::Ci::Report)
59
75
 
60
- ['spec/**{,/*/**}/*_spec.rb']
76
+ result.value.errored?
61
77
  end
62
78
 
79
+ def file_pattern?(pattern) = pattern.match?(FILE_PATTERN)
80
+
63
81
  def format_status(result)
64
- if result.failure? || result.value.errored?
82
+ if errored?(result)
65
83
  color('Errored', 'red')
66
84
  elsif result.value.failure?
67
85
  color('Failing', 'red')
@@ -78,7 +96,9 @@ module Cuprum::Cli::Commands::Ci
78
96
  all_files = Set.new
79
97
 
80
98
  configured_file_patterns.each do |pattern|
81
- file_system.each_file(pattern) { |filename| all_files << filename }
99
+ file_system.each_file(pattern) do |filename|
100
+ all_files << filename if file_system.file?(filename)
101
+ end
82
102
  end
83
103
 
84
104
  @matching_files = all_files.sort
@@ -92,7 +112,7 @@ module Cuprum::Cli::Commands::Ci
92
112
  say "Running #{matching_files.count} spec files...\n"
93
113
  say "\n" unless matching_files.none?
94
114
 
95
- matching_files.each { |filename| run_file(filename) }
115
+ @total_time = clock.measure { run_matching_files }
96
116
 
97
117
  summarize_report
98
118
 
@@ -126,6 +146,10 @@ module Cuprum::Cli::Commands::Ci
126
146
  result
127
147
  end
128
148
 
149
+ def run_matching_files
150
+ matching_files.each { |filename| run_file(filename) }
151
+ end
152
+
129
153
  def say_errored
130
154
  return if errored_files.empty?
131
155
 
@@ -157,7 +181,7 @@ module Cuprum::Cli::Commands::Ci
157
181
  end
158
182
 
159
183
  def say_summary # rubocop:disable Metrics/AbcSize
160
- say "\nFinished in #{report.duration.round(2)} seconds"
184
+ say "\nFinished in #{total_time.round(2)} seconds"
161
185
 
162
186
  summary_color =
163
187
  if report.errored? || report.failure? || errored_files.any?
@@ -181,5 +205,7 @@ module Cuprum::Cli::Commands::Ci
181
205
  def trim_filename(filename)
182
206
  filename.sub(%r{\A#{file_system.root_path}/?}, '')
183
207
  end
208
+
209
+ def wildcard_pattern?(pattern) = pattern.include?('*')
184
210
  end
185
211
  end
@@ -8,5 +8,13 @@ module Cuprum::Cli::Commands
8
8
  autoload :Report, 'cuprum/cli/commands/ci/report'
9
9
  autoload :RSpecCommand, 'cuprum/cli/commands/ci/rspec_command'
10
10
  autoload :RSpecEachCommand, 'cuprum/cli/commands/ci/rspec_each_command'
11
+
12
+ # @return [Array<Class>] the default commands defined for running CI steps.
13
+ def self.commands
14
+ [
15
+ RSpecCommand,
16
+ RSpecEachCommand
17
+ ]
18
+ end
11
19
  end
12
20
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'cuprum/cli/command'
4
4
  require 'cuprum/cli/commands/file'
5
- require 'cuprum/cli/commands/file/render_erb'
6
- require 'cuprum/cli/commands/file/resolve_template'
7
5
 
8
6
  module Cuprum::Cli::Commands::File
9
7
  # Command for generating a templated file or files.
@@ -14,73 +12,68 @@ module Cuprum::Cli::Commands::File
14
12
  include Cuprum::Cli::Options::Quiet
15
13
  include Cuprum::Cli::Options::Verbose
16
14
 
15
+ class << self
16
+ # @return [Array<Class>] the default generators configured for the
17
+ # command.
18
+ def default_generators
19
+ [
20
+ Cuprum::Cli::Files::Generators::RubyGenerator,
21
+ Cuprum::Cli::Files::Generators::RSpecGenerator
22
+ ]
23
+ end
24
+ end
25
+
17
26
  argument :file_path, type: String, required: true
18
27
 
19
- option :directories, type: :boolean, default: true
20
- option :dry_run, type: :boolean, default: false
21
- option :parent_class, type: String
22
- option :templates,
23
- type: Array,
24
- default: Cuprum::Cli::Commands::File::Templates::DEFAULT_TEMPLATES
25
- option :extra_flags,
26
- type: :boolean,
27
- variadic: true
28
+ option :directories, type: :boolean, default: true
29
+ option :dry_run, type: :boolean, default: false
30
+ option :generators, type: :array, default: default_generators
31
+ option :params, type: :object, variadic: true
28
32
 
29
33
  description 'Generates a new templated file or files.'
30
34
 
31
35
  private
32
36
 
33
- def excluded_tags
34
- excluded = []
37
+ def build_generator
38
+ generator = generators.reverse_each.find do |generator_class|
39
+ generator_class.match?(file_path, **params)
40
+ end
35
41
 
36
- extra_flags.each do |flag, value|
37
- excluded << flag if value == false
42
+ if generator
43
+ return generator.new(file_path, **params, **generator_options)
38
44
  end
39
45
 
40
- excluded
46
+ details = 'no generator matches the file path and options'
47
+ failure(generator_error(details:))
41
48
  end
42
49
 
43
- def generate_file(file_path:, parameters:, template_path:) # rubocop:disable Metrics/MethodLength
44
- file_path = resolve_file_path(file_path, **parameters)
45
- command = GenerateFile.new(
46
- file_system:,
47
- standard_io:,
50
+ def generator_error(details: nil)
51
+ message = "unable to generate file #{file_path}"
52
+ message = "#{message} - #{details}" if details
53
+
54
+ Cuprum::Cli::Files::Errors::GeneratorError.new(
55
+ details:,
56
+ file_path:,
57
+ message:,
58
+ options:
59
+ )
60
+ end
61
+
62
+ def generator_options
63
+ {
48
64
  directories: directories?,
49
65
  dry_run: dry_run?,
66
+ file_system:,
50
67
  quiet: quiet?,
68
+ standard_io:,
51
69
  verbose: verbose?
52
- )
53
-
54
- parameters = parameters.merge(parent_class:)
55
-
56
- step { command.call(file_path:, parameters:, template_path:) }
57
-
58
- file_path
70
+ }
59
71
  end
60
72
 
61
73
  def process
62
- templates, parameters = step { resolve_template }
63
-
64
- templates.map do |template|
65
- file_path = template.fetch(:file_path, self.file_path)
66
- template_path = template.fetch(:template)
67
-
68
- step { generate_file(file_path:, parameters:, template_path:) }
69
- end
70
- end
71
-
72
- def resolve_file_path(file_path, **params)
73
- params = tools.hash_tools.convert_keys_to_symbols(params)
74
-
75
- format(file_path, params)
76
- end
77
-
78
- def resolve_template
79
- command = Cuprum::Cli::Commands::File::ResolveTemplate.new(templates:)
74
+ generator = step { build_generator }
80
75
 
81
- command.call(file_path, except: excluded_tags)
76
+ generator.call
82
77
  end
83
-
84
- def tools = @tools ||= SleepingKingStudios::Tools::Toolbelt.instance
85
78
  end
86
79
  end
@@ -5,10 +5,13 @@ require 'cuprum/cli/commands'
5
5
  module Cuprum::Cli::Commands
6
6
  # Namespace for commands that create or manage files.
7
7
  module File
8
- autoload :GenerateFile, 'cuprum/cli/commands/file/generate_file'
9
- autoload :NewCommand, 'cuprum/cli/commands/file/new_command'
10
- autoload :RenderErb, 'cuprum/cli/commands/file/render_erb'
11
- autoload :ResolveTemplate, 'cuprum/cli/commands/file/resolve_template'
12
- autoload :Templates, 'cuprum/cli/commands/file/templates'
8
+ autoload :NewCommand, 'cuprum/cli/commands/file/new_command'
9
+
10
+ # @return [Array<Class>] the default commands defined for file management.
11
+ def self.commands
12
+ [
13
+ NewCommand
14
+ ]
15
+ end
13
16
  end
14
17
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/dependencies/clock'
4
+
5
+ module Cuprum::Cli::Dependencies
6
+ # Mock implementation of Clock for testing purposes.
7
+ class Clock::Mock < Cuprum::Cli::Dependencies::Clock
8
+ # @param current_time [Time] the current time in UTC time zone.
9
+ # @param monotonic_time [Numeric] the monotonic server time.
10
+ def initialize(current_time: nil, monotonic_time: nil)
11
+ super()
12
+
13
+ @current_time = current_time || Time.now(in: 'Z')
14
+ @monotonic_time =
15
+ monotonic_time || Process.clock_gettime(Process::CLOCK_MONOTONIC)
16
+ end
17
+
18
+ # @return [Numeric] the mocked monotonic time.
19
+ def get_monotonic_time # rubocop:disable Naming/AccessorMethodName
20
+ @monotonic_time
21
+ end
22
+
23
+ # @return [Time] the mocked current time.
24
+ def get_time = @current_time # rubocop:disable Naming/AccessorMethodName
25
+ alias current_time get_time
26
+ alias now get_time
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/cli/dependencies'
4
+
5
+ module Cuprum::Cli::Dependencies
6
+ # Utility wrapping the current time and time calculations.
7
+ class Clock
8
+ autoload :Mock, 'cuprum/cli/dependencies/clock/mock'
9
+
10
+ # @return [Numeric] a monotonically increasing system clock time.
11
+ def get_monotonic_time # rubocop:disable Naming/AccessorMethodName
12
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
13
+ end
14
+
15
+ # @return [Time] the current time in the UTC time zone.
16
+ def get_time = Time.now(in: 'Z') # rubocop:disable Naming/AccessorMethodName
17
+ alias current_time get_time
18
+ alias now get_time
19
+
20
+ # Measures the time elapsed while the block is run.
21
+ def measure
22
+ start_time = get_monotonic_time
23
+
24
+ yield
25
+
26
+ get_monotonic_time - start_time
27
+ end
28
+ end
29
+ end