spec_forge 0.5.0 → 0.6.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -3
  3. data/CHANGELOG.md +106 -1
  4. data/README.md +34 -22
  5. data/flake.lock +3 -3
  6. data/flake.nix +8 -2
  7. data/lib/spec_forge/attribute/chainable.rb +208 -20
  8. data/lib/spec_forge/attribute/factory.rb +91 -14
  9. data/lib/spec_forge/attribute/faker.rb +62 -13
  10. data/lib/spec_forge/attribute/global.rb +96 -0
  11. data/lib/spec_forge/attribute/literal.rb +15 -2
  12. data/lib/spec_forge/attribute/matcher.rb +186 -11
  13. data/lib/spec_forge/attribute/parameterized.rb +45 -12
  14. data/lib/spec_forge/attribute/regex.rb +55 -5
  15. data/lib/spec_forge/attribute/resolvable.rb +48 -5
  16. data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
  17. data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
  18. data/lib/spec_forge/attribute/store.rb +65 -0
  19. data/lib/spec_forge/attribute/transform.rb +33 -5
  20. data/lib/spec_forge/attribute/variable.rb +37 -6
  21. data/lib/spec_forge/attribute.rb +166 -66
  22. data/lib/spec_forge/backtrace_formatter.rb +26 -3
  23. data/lib/spec_forge/callbacks.rb +79 -0
  24. data/lib/spec_forge/cli/actions.rb +27 -0
  25. data/lib/spec_forge/cli/command.rb +78 -24
  26. data/lib/spec_forge/cli/init.rb +11 -1
  27. data/lib/spec_forge/cli/new.rb +54 -3
  28. data/lib/spec_forge/cli/run.rb +20 -0
  29. data/lib/spec_forge/cli.rb +16 -5
  30. data/lib/spec_forge/configuration.rb +94 -22
  31. data/lib/spec_forge/context/callbacks.rb +91 -0
  32. data/lib/spec_forge/context/global.rb +72 -0
  33. data/lib/spec_forge/context/store.rb +148 -0
  34. data/lib/spec_forge/context/variables.rb +91 -0
  35. data/lib/spec_forge/context.rb +36 -0
  36. data/lib/spec_forge/core_ext/rspec.rb +22 -4
  37. data/lib/spec_forge/error.rb +267 -113
  38. data/lib/spec_forge/factory.rb +33 -14
  39. data/lib/spec_forge/filter.rb +87 -0
  40. data/lib/spec_forge/forge.rb +170 -0
  41. data/lib/spec_forge/http/backend.rb +99 -29
  42. data/lib/spec_forge/http/client.rb +23 -13
  43. data/lib/spec_forge/http/request.rb +74 -62
  44. data/lib/spec_forge/http/verb.rb +79 -0
  45. data/lib/spec_forge/http.rb +105 -0
  46. data/lib/spec_forge/loader.rb +254 -0
  47. data/lib/spec_forge/matchers.rb +130 -0
  48. data/lib/spec_forge/normalizer/configuration.rb +24 -11
  49. data/lib/spec_forge/normalizer/constraint.rb +21 -8
  50. data/lib/spec_forge/normalizer/expectation.rb +31 -12
  51. data/lib/spec_forge/normalizer/factory.rb +24 -11
  52. data/lib/spec_forge/normalizer/factory_reference.rb +27 -13
  53. data/lib/spec_forge/normalizer/global_context.rb +88 -0
  54. data/lib/spec_forge/normalizer/spec.rb +39 -16
  55. data/lib/spec_forge/normalizer.rb +255 -41
  56. data/lib/spec_forge/runner/callbacks.rb +246 -0
  57. data/lib/spec_forge/runner/debug_proxy.rb +213 -0
  58. data/lib/spec_forge/runner/listener.rb +54 -0
  59. data/lib/spec_forge/runner/metadata.rb +58 -0
  60. data/lib/spec_forge/runner/state.rb +99 -0
  61. data/lib/spec_forge/runner.rb +132 -123
  62. data/lib/spec_forge/spec/expectation/constraint.rb +91 -20
  63. data/lib/spec_forge/spec/expectation.rb +43 -51
  64. data/lib/spec_forge/spec.rb +83 -96
  65. data/lib/spec_forge/type.rb +36 -4
  66. data/lib/spec_forge/version.rb +4 -1
  67. data/lib/spec_forge.rb +161 -76
  68. metadata +20 -5
  69. data/spec_forge/factories/user.yml +0 -4
  70. data/spec_forge/forge_helper.rb +0 -48
  71. data/spec_forge/specs/users.yml +0 -65
@@ -5,18 +5,21 @@ require_relative "attribute/parameterized"
5
5
  require_relative "attribute/chainable"
6
6
  require_relative "attribute/resolvable"
7
7
 
8
- # Doesn't matter
9
- require_relative "attribute/factory"
10
- require_relative "attribute/faker"
11
- require_relative "attribute/literal"
12
- require_relative "attribute/matcher"
13
- require_relative "attribute/regex"
14
- require_relative "attribute/resolvable_array"
15
- require_relative "attribute/resolvable_hash"
16
- require_relative "attribute/transform"
17
- require_relative "attribute/variable"
18
-
19
8
  module SpecForge
9
+ #
10
+ # Base class for all attribute types in SpecForge.
11
+ # Attributes represent values that can be transformed, resolved, or have special meaning
12
+ # in the context of specs and expectations.
13
+ #
14
+ # The Attribute system handles dynamic data generation, variable references,
15
+ # matchers, transformations and other special values in YAML specs.
16
+ #
17
+ # @example Basic usage in YAML
18
+ # username: faker.internet.username # A dynamic faker attribute
19
+ # email: /\w+@\w+\.\w+/ # A regex attribute
20
+ # status: kind_of.integer # A matcher attribute
21
+ # user_id: variables.user.id # A variable reference
22
+ #
20
23
  class Attribute
21
24
  include Resolvable
22
25
 
@@ -66,7 +69,7 @@ module SpecForge
66
69
  end
67
70
 
68
71
  #
69
- # Creates an Attribute instance from a string, handling any macros
72
+ # Creates an Attribute instance from a string
70
73
  #
71
74
  # @param string [String] The input string
72
75
  #
@@ -75,24 +78,31 @@ module SpecForge
75
78
  # @private
76
79
  #
77
80
  def self.from_string(string)
78
- case string
79
- when Faker::KEYWORD_REGEX
80
- Faker.new(string)
81
- when Variable::KEYWORD_REGEX
82
- Variable.new(string)
83
- when Matcher::KEYWORD_REGEX
84
- Matcher.new(string)
85
- when Factory::KEYWORD_REGEX
86
- Factory.new(string)
87
- when Regex::KEYWORD_REGEX
88
- Regex.new(string)
89
- else
90
- Literal.new(string)
91
- end
81
+ klass =
82
+ case string
83
+ when Factory::KEYWORD_REGEX
84
+ Factory
85
+ when Faker::KEYWORD_REGEX
86
+ Faker
87
+ when Global::KEYWORD_REGEX
88
+ Global
89
+ when Matcher::KEYWORD_REGEX
90
+ Matcher
91
+ when Regex::KEYWORD_REGEX
92
+ Regex
93
+ when Store::KEYWORD_REGEX
94
+ Store
95
+ when Variable::KEYWORD_REGEX
96
+ Variable
97
+ else
98
+ Literal
99
+ end
100
+
101
+ klass.new(string)
92
102
  end
93
103
 
94
104
  #
95
- # Creates an Attribute instance from a hash, handling any macros
105
+ # Creates an Attribute instance from a hash
96
106
  #
97
107
  # @param hash [Hash] The input hash
98
108
  #
@@ -118,66 +128,156 @@ module SpecForge
118
128
  end
119
129
  end
120
130
 
131
+ #
132
+ # The original input value
133
+ #
134
+ # @return [Object]
135
+ #
121
136
  attr_reader :input
122
137
 
123
138
  #
124
- # @param input [Object] Anything
139
+ # Creates a new attribute
140
+ #
141
+ # @param input [Object] The original input value
125
142
  #
126
143
  def initialize(input)
127
144
  @input = input
128
145
  end
129
146
 
130
147
  #
131
- # Returns the processed value of the input
148
+ # Compares this attributes input to other
149
+ #
150
+ # @param other [Object, Attribute] If another Attribute, the input will be compared
151
+ #
152
+ # @return [Boolean]
153
+ #
154
+ def ==(other)
155
+ other =
156
+ if other.is_a?(Attribute)
157
+ other.input
158
+ else
159
+ other
160
+ end
161
+
162
+ input == other
163
+ end
164
+
165
+ #
166
+ # Returns the processed value of this attribute.
167
+ # Recursively calls #value on underlying attributes, but does NOT resolve
168
+ # all nested structures completely.
132
169
  #
133
- # For literals, this is the input itself.
134
- # For generated values (Faker, Transform), this is the result of their operations.
170
+ # This returns an intermediate representation - for fully resolved values, use #resolve instead.
135
171
  #
136
172
  # @return [Object] The processed value of this attribute
137
173
  #
138
174
  # @raise [RuntimeError] if not implemented by subclass
139
175
  #
176
+ # @example
177
+ # variable_attr = Attribute::Variable.new("variables.user")
178
+ # variable_attr.value # => User instance, but any attributes of User remain
179
+ # as Attribute objects
180
+ #
140
181
  def value
141
182
  raise "not implemented"
142
183
  end
143
184
 
144
185
  #
145
- # Returns the fully evaluated result, recursively resolving any nested attributes
186
+ # Returns the fully evaluated result with complete recursive resolution.
187
+ # Calls #value internally and then resolves all nested attributes, caching the result.
146
188
  #
147
- # @return [Object] The resolved value
189
+ # Use this when you need the final, fully-resolved value with all nested attributes
190
+ # fully evaluated to their primitive values.
148
191
  #
149
- # @example Simple literal
150
- # attr = Attribute::Literal.new("hello")
151
- # attr.resolve # => "hello"
192
+ # @return [Object] The completely resolved value with cached results
152
193
  #
153
- # @example Nested array with faker
154
- # attr = Attribute::Literal.new(["faker.number.positive", ["faker.name.first_name"]])
155
- # attr.resolve # => [42, ["Jane"]]
194
+ # @example
195
+ # faker_attr = Attribute::Faker.new("faker.name.first_name")
196
+ # faker_attr.resolved # => "Jane" (result is cached in @resolved)
197
+ # faker_attr.resolved # => "Jane" (returns same cached value)
156
198
  #
157
- def resolve
158
- @resolved ||= resolve_value
199
+ def resolved
200
+ @resolved ||= resolve
159
201
  end
160
202
 
161
- def resolve_value
162
- __resolve(value)
203
+ #
204
+ # Performs recursive resolution of the attribute's value.
205
+ # Handles nested arrays and hashes by recursively resolving their elements.
206
+ #
207
+ # Unlike #resolved, this method doesn't cache results and can be used
208
+ # when fresh resolution is needed each time.
209
+ #
210
+ # @return [Object] The recursively resolved value without caching
211
+ #
212
+ # @example
213
+ # hash_attr = Attribute::ResolvableHash.new({name: Attribute::Faker.new("faker.name.name")})
214
+ # hash_attr.resolve # => {name: "John Smith"}
215
+ # hash_attr.resolve # => {name: "Jane Doe"} (different value on each call)
216
+ #
217
+ def resolve
218
+ case value
219
+ when ArrayLike
220
+ value.map(&resolved_proc)
221
+ when HashLike
222
+ value.transform_values(&resolved_proc)
223
+ else
224
+ value
225
+ end
163
226
  end
164
227
 
165
228
  #
166
- # Compares this attributes input to other
229
+ # Converts this attribute to an appropriate RSpec matcher.
230
+ # Handles different types of values by creating the right matcher type:
231
+ # - Arrays become contain_exactly matchers
232
+ # - Hashes become include matchers
233
+ # - Regexp become match matchers
234
+ # - Existing matchers are passed through
235
+ # - Other values become eq matchers
167
236
  #
168
- # @param other [Object, Attribute] If another Attribute, the input will be compared
237
+ # This method is crucial for nested matcher structures and compound matchers
238
+ # like matcher.and that require all values to be proper matchers.
169
239
  #
170
- # @return [Boolean]
240
+ # @return [RSpec::Matchers::BuiltIn::BaseMatcher] A matcher representing this attribute
171
241
  #
172
- def ==(other)
173
- other =
174
- if other.is_a?(Attribute)
175
- other.input
242
+ # @example Converting different values to matchers
243
+ # literal_attr = Attribute::Literal.new("hello")
244
+ # literal_attr.resolve_as_matcher # => eq("hello")
245
+ #
246
+ # array_attr = Attribute::ResolvableArray.new([1, 2, 3])
247
+ # array_attr.resolve_as_matcher # => contain_exactly(eq(1), eq(2), eq(3))
248
+ #
249
+ # hash_attr = Attribute::ResolvableHash.new({name: "Test"})
250
+ # hash_attr.resolve_as_matcher # => include("name" => eq("Test"))
251
+ #
252
+ def resolve_as_matcher
253
+ methods = Attribute::Matcher::MATCHER_METHODS
254
+
255
+ case resolved
256
+ when Array, ArrayLike
257
+ resolved_array = resolved.map(&resolve_as_matcher_proc)
258
+
259
+ if resolved_array.size > 0
260
+ methods.contain_exactly(*resolved_array)
176
261
  else
177
- other
262
+ methods.eq([])
178
263
  end
264
+ when Hash, HashLike
265
+ resolved_hash = resolved.transform_values(&resolve_as_matcher_proc).stringify_keys
179
266
 
180
- input == other
267
+ if resolved_hash.size > 0
268
+ methods.include(**resolved_hash)
269
+ else
270
+ methods.eq({})
271
+ end
272
+ when Attribute::Matcher, Regexp
273
+ methods.match(resolved)
274
+ when RSpec::Matchers::BuiltIn::BaseMatcher,
275
+ RSpec::Matchers::DSL::Matcher,
276
+ Class
277
+ resolved # Pass through
278
+ else
279
+ methods.eq(resolved)
280
+ end
181
281
  end
182
282
 
183
283
  #
@@ -185,20 +285,20 @@ module SpecForge
185
285
  #
186
286
  # @param variables [Hash] A hash of variable attributes
187
287
  #
188
- def bind_variables(_variables)
189
- end
190
-
191
- protected
192
-
193
- def __resolve(value)
194
- case value
195
- when ArrayLike
196
- value.map(&resolvable_proc)
197
- when HashLike
198
- value.transform_values(&resolvable_proc)
199
- else
200
- value
201
- end
288
+ def bind_variables(variables)
202
289
  end
203
290
  end
204
291
  end
292
+
293
+ # Order doesn't matter
294
+ require_relative "attribute/factory"
295
+ require_relative "attribute/faker"
296
+ require_relative "attribute/global"
297
+ require_relative "attribute/literal"
298
+ require_relative "attribute/matcher"
299
+ require_relative "attribute/regex"
300
+ require_relative "attribute/resolvable_array"
301
+ require_relative "attribute/resolvable_hash"
302
+ require_relative "attribute/store"
303
+ require_relative "attribute/transform"
304
+ require_relative "attribute/variable"
@@ -2,18 +2,41 @@
2
2
 
3
3
  module SpecForge
4
4
  #
5
- # Used internally by RSpec
6
- # This class handles formatting backtraces, hence the name ;)
5
+ # Used internally by RSpec to format backtraces for test failures
6
+ # Customizes error output to make it more readable and useful for SpecForge
7
7
  #
8
8
  module BacktraceFormatter
9
+ #
10
+ # Returns the RSpec backtrace formatter instance
11
+ # Lazily initializes the formatter on first access
12
+ #
13
+ # @return [RSpec::Core::BacktraceFormatter] The backtrace formatter
14
+ #
9
15
  def self.formatter
10
16
  @formatter ||= RSpec::Core::BacktraceFormatter.new
11
17
  end
12
18
 
19
+ #
20
+ # Formats a single backtrace line
21
+ # Delegates to the RSpec formatter
22
+ #
23
+ # @param line [String] The backtrace line to format
24
+ #
25
+ # @return [String] The formatted backtrace line
26
+ #
13
27
  def self.backtrace_line(line)
14
28
  formatter.backtrace_line(line)
15
29
  end
16
30
 
31
+ #
32
+ # Formats a complete backtrace for an example
33
+ # Adds the YAML location to the front of the backtrace for better context
34
+ #
35
+ # @param backtrace [Array<String>] The raw backtrace lines
36
+ # @param example_metadata [Hash] Metadata about the failing example
37
+ #
38
+ # @return [Array<String>] The formatted backtrace with YAML location first
39
+ #
17
40
  def self.format_backtrace(backtrace, example_metadata)
18
41
  backtrace = SpecForge.backtrace_cleaner.clean(backtrace)
19
42
 
@@ -21,7 +44,7 @@ module SpecForge
21
44
  line_number = example_metadata[:example_group][:line_number]
22
45
 
23
46
  # Add the yaml location to the front so it's the first thing people see
24
- ["#{location}:#{line_number}"] + backtrace
47
+ ["#{location}:#{line_number}"] + backtrace[0..50]
25
48
  end
26
49
  end
27
50
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ #
5
+ # Manages user-defined callbacks for test lifecycle events
6
+ #
7
+ # This singleton class stores and executes callback functions that
8
+ # users can register to run at specific points in the test lifecycle.
9
+ # Each callback receives a context object containing relevant state
10
+ # information for that point in execution.
11
+ #
12
+ # @example Registering and using a callback
13
+ # SpecForge::Callbacks.register(:my_callback) do |context|
14
+ # puts "Running test: #{context.expectation_name}"
15
+ # end
16
+ #
17
+ class Callbacks < Hash
18
+ include Singleton
19
+
20
+ class << self
21
+ #
22
+ # Registers a new callback for a specific event
23
+ #
24
+ # @param name [String, Symbol] The name of the callback event
25
+ # @param block [Proc] The callback function to execute
26
+ #
27
+ # @raise [ArgumentError] If no block is provided
28
+ #
29
+ def register(name, &block)
30
+ raise ArgumentError, "A block must be provided" unless block.is_a?(Proc)
31
+
32
+ if registered?(name)
33
+ warn("Callback #{name.in_quotes} is already registered. It will be overwritten")
34
+ end
35
+
36
+ instance[name.to_s] = block
37
+ end
38
+
39
+ #
40
+ # Checks if a callback is registered for the given event
41
+ #
42
+ # @param name [String, Symbol] The name of the callback event
43
+ #
44
+ # @return [Boolean] True if the callback exists
45
+ #
46
+ def registered?(name)
47
+ instance.key?(name.to_s)
48
+ end
49
+
50
+ #
51
+ # Returns all registered callback names
52
+ #
53
+ # @return [Array<String>] List of registered callback names
54
+ #
55
+ def registered_names
56
+ instance.keys
57
+ end
58
+
59
+ #
60
+ # Executes a named callback with the provided context
61
+ #
62
+ # @param name [String, Symbol] The name of the callback to run
63
+ # @param context [Object] Context object containing state data
64
+ #
65
+ # @raise [ArgumentError] If the callback is not registered
66
+ #
67
+ def run(name, context)
68
+ callback = instance[name.to_s]
69
+ raise ArgumentError, "Callback #{name.in_quotes} is not defined" if callback.nil?
70
+
71
+ if callback.arity == 0
72
+ callback.call
73
+ else
74
+ callback.call(context)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -2,17 +2,44 @@
2
2
 
3
3
  module SpecForge
4
4
  class CLI
5
+ #
6
+ # Provides helper methods for CLI actions such as file generation
7
+ # and template rendering through Thor::Actions integration.
8
+ #
9
+ # @example Using actions in a command
10
+ # actions.template("my_template.tt", "destination/path.rb")
11
+ #
5
12
  module Actions
13
+ #
14
+ # Internal Ruby hook, called when the module is included in another file
15
+ #
16
+ # @param base [Class] The class that included this module
17
+ #
6
18
  def self.included(base)
19
+ #
20
+ # Returns an ActionContext instance for performing file operations
21
+ #
22
+ # @return [ActionContext] The action context for this command
23
+ #
7
24
  base.define_method(:actions) do
8
25
  @actions ||= ActionContext.new
9
26
  end
10
27
  end
11
28
  end
12
29
 
30
+ #
31
+ # Provides a context for Thor actions that configures paths and options
32
+ #
33
+ # @private
34
+ #
13
35
  class ActionContext < Thor
14
36
  include Thor::Actions
15
37
 
38
+ #
39
+ # Creates a new action context with SpecForge template paths configured
40
+ #
41
+ # @return [ActionContext] A new context for Thor actions
42
+ #
16
43
  def initialize(...)
17
44
  self.class.source_root(File.expand_path("../../templates", __dir__))
18
45
  self.destination_root = SpecForge.root
@@ -2,20 +2,55 @@
2
2
 
3
3
  module SpecForge
4
4
  class CLI
5
+ #
6
+ # Base class for CLI commands that provides common functionality and
7
+ # defines the DSL for declaring command properties.
8
+ #
9
+ # @example Defining a simple command
10
+ # class MyCommand < Command
11
+ # command_name "my_command"
12
+ # syntax "my_command [options]"
13
+ # summary "Does something awesome"
14
+ # description "A longer description of what this command does"
15
+ #
16
+ # option "-f", "--force", "Force the operation"
17
+ #
18
+ # def call
19
+ # # Command implementation
20
+ # end
21
+ # end
22
+ #
5
23
  class Command
6
24
  include CLI::Actions
7
25
 
8
26
  class << self
9
- attr_writer(*%i[
10
- command_name
11
- syntax
12
- description
13
- summary
14
- options
15
- ])
27
+ #
28
+ # Sets the command's name
29
+ #
30
+ attr_writer :command_name
31
+
32
+ #
33
+ # Sets the command's syntax string
34
+ #
35
+ attr_writer :syntax
36
+
37
+ #
38
+ # Sets the command's detailed description
39
+ #
40
+ attr_writer :description
41
+
42
+ #
43
+ # Sets a brief summary of the command
44
+ #
45
+ attr_writer :summary
46
+
47
+ #
48
+ # Sets the command's available options
49
+ #
50
+ attr_writer :options
16
51
 
17
52
  #
18
- # The command's name
53
+ # Sets the command's name
19
54
  #
20
55
  # @param name [String] The name of the command
21
56
  #
@@ -24,37 +59,37 @@ module SpecForge
24
59
  end
25
60
 
26
61
  #
27
- # The command's syntax
62
+ # Sets the command's syntax
28
63
  #
29
- # @param syntax [String]
64
+ # @param syntax [String] The command syntax to display in help
30
65
  #
31
66
  def syntax(syntax)
32
67
  self.syntax = syntax
33
68
  end
34
69
 
35
70
  #
36
- # The command's description, long form
71
+ # Sets the command's description, displayed in detailed help
37
72
  #
38
- # @param description [String]
73
+ # @param description [String] The detailed command description
39
74
  #
40
75
  def description(description)
41
76
  self.description = description
42
77
  end
43
78
 
44
79
  #
45
- # The command's summary, short form
80
+ # Sets the command's summary, displayed in command list
46
81
  #
47
- # @param summary [String]
82
+ # @param summary [String] The short command summary
48
83
  #
49
84
  def summary(summary)
50
85
  self.summary = summary
51
86
  end
52
87
 
53
88
  #
54
- # Defines an example on how to use the command
89
+ # Adds an example of how to use the command
55
90
  #
56
- # @param command [String] The example
57
- # @param description [String] Description of the example
91
+ # @param command [String] The example command
92
+ # @param description [String] Description of what the example does
58
93
  #
59
94
  def example(command, description)
60
95
  @examples ||= []
@@ -64,7 +99,10 @@ module SpecForge
64
99
  end
65
100
 
66
101
  #
67
- # Defines a command flag (-f, --force)
102
+ # Adds a command line option
103
+ #
104
+ # @param args [Array<String>] The option flags (e.g., "-f", "--force")
105
+ # @yield [value] Block to handle the option value
68
106
  #
69
107
  def option(*args, &block)
70
108
  @options ||= []
@@ -73,9 +111,9 @@ module SpecForge
73
111
  end
74
112
 
75
113
  #
76
- # Defines any aliases for this command
114
+ # Adds command aliases
77
115
  #
78
- # @param *aliases [Array<String>]
116
+ # @param aliases [Array<String>] Alias names for this command
79
117
  #
80
118
  def aliases(*aliases)
81
119
  @aliases ||= []
@@ -86,7 +124,7 @@ module SpecForge
86
124
  #
87
125
  # Registers the command with Commander
88
126
  #
89
- # @param context [Commander::Command]
127
+ # @param context [Commander::Command] The Commander context
90
128
  #
91
129
  # @private
92
130
  #
@@ -112,11 +150,27 @@ module SpecForge
112
150
  end
113
151
  end
114
152
 
115
- attr_reader :arguments, :options
153
+ #
154
+ # Command arguments passed from the command line
155
+ #
156
+ # @return [Array] The positional arguments
157
+ #
158
+ attr_reader :arguments
159
+
160
+ #
161
+ # Command options passed from the command line
162
+ #
163
+ # @return [Hash] The flag arguments
164
+ #
165
+ attr_reader :options
116
166
 
117
167
  #
118
- # @param arguments [Array] Any positional arguments
119
- # @param options [Hash] Any flag arguments
168
+ # Creates a new command instance
169
+ #
170
+ # @param arguments [Array] Any positional arguments from the command line
171
+ # @param options [Hash] Any flag arguments from the command line
172
+ #
173
+ # @return [Command] A new command instance
120
174
  #
121
175
  def initialize(arguments, options)
122
176
  @arguments = arguments
@@ -2,13 +2,23 @@
2
2
 
3
3
  module SpecForge
4
4
  class CLI
5
+ #
6
+ # Command for initializing a new SpecForge project structure
7
+ #
8
+ # @example Creating a new SpecForge project
9
+ # spec_forge init
10
+ #
5
11
  class Init < Command
6
12
  command_name "init"
7
13
  syntax "init"
8
14
  summary "Initializes directory structure and configuration files"
9
15
 
16
+ #
17
+ # Creates the "spec_forge", "spec_forge/factories", and "spec_forge/specs" directories
18
+ # Also creates the "spec_forge.rb" initialization file
19
+ #
10
20
  def call
11
- base_path = SpecForge.forge
21
+ base_path = SpecForge.forge_path
12
22
  actions.empty_directory "#{base_path}/factories"
13
23
  actions.empty_directory "#{base_path}/specs"
14
24