bundler-gem_bytes 0.2.0 → 0.2.3

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.
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parser/current'
4
+ require 'rubocop-ast'
5
+ require 'active_support/core_ext/object'
6
+
7
+ module Bundler
8
+ module GemBytes
9
+ module Actions
10
+ class Gemspec < Parser::TreeRewriter
11
+ # Add or update a dependency in a gemspec
12
+ #
13
+ # If a dependency on the given gem is not found, a new dependency is added to
14
+ # the end of the Gem::Specification block.
15
+ #
16
+ # If one or more dependencies are found on the same gem as new_dependency,
17
+ # the version constraint is updated to the new_dependency version constraint.
18
+ #
19
+ # The gemspec is updated via calls to the tree_rewriter object.
20
+ #
21
+ # @!attribute [r] tree_rewriter
22
+ # The object that updates the source
23
+ # @return [Parser::TreeRewriter]
24
+ # @api private
25
+ #
26
+ # @!attribute [r] gemspec_block
27
+ # The root AST node of the Gem::Specification block from the gemspec
28
+ # @return [Parser::AST::Node]
29
+ # @api private
30
+ #
31
+ # @!attribute [r] receiver_name
32
+ # The name of the receiver for the Gem::Specification block
33
+ # @return [Symbol]
34
+ # @api private
35
+ #
36
+ # @!attribute [r] dependencies
37
+ # The dependency declarations found in the gemspec file
38
+ # @return [Array<DependencyNode>]
39
+ # @api private
40
+ #
41
+ # @!attribute [r] new_dependency
42
+ # The dependency declaration to add or update
43
+ # @return [Dependency]
44
+ # @api private
45
+ #
46
+ # @api public
47
+ class UpsertDependency
48
+ # Initializes the upsert dependency action
49
+ # @param tree_rewriter [Parser::TreeRewriter] The object that updates the source
50
+ # @param gemspec_block [Parser::AST::Node] The Gem::Specification block
51
+ # @param receiver_name [Symbol] The name of the receiver for the Gem::Specification block
52
+ # @param dependencies [Array<DependencyNode>] The dependency declarations found in the gemspec file
53
+ # @api private
54
+ def initialize(tree_rewriter, gemspec_block, receiver_name, dependencies)
55
+ @tree_rewriter = tree_rewriter
56
+ @gemspec_block = gemspec_block
57
+ @receiver_name = receiver_name
58
+ @dependencies = dependencies
59
+ end
60
+
61
+ attr_reader :tree_rewriter, :gemspec_block, :receiver_name, :dependencies, :new_dependency
62
+
63
+ # Adds or updates a dependency to the Gem::Specification block
64
+ #
65
+ # @example
66
+ # upsert_dependency = UpsertDependency.new(tree_rewriter, gemspec_block, receiver_name, dependencies)
67
+ # new_dependency = Dependency.new(:add_runtime_dependency, 'rubocop', '~> 1.68')
68
+ # upsert_dependency.call(new_dependency)
69
+ # @param new_dependency [Dependency] The dependency declaration to add or update
70
+ # @return [void]
71
+ # @api public
72
+ def call(new_dependency)
73
+ @new_dependency = new_dependency
74
+ matching_dependencies = dependencies.select { |d| d.dependency.gem_name == new_dependency.gem_name }
75
+
76
+ if matching_dependencies.any?
77
+ update_dependencies(matching_dependencies)
78
+ else
79
+ add_dependency
80
+ end
81
+ end
82
+
83
+ # Update the version constraint of the existing dependency(s)
84
+ # @param matching_dependencies [Array<DependencyNode>] The existing dependencies that match new_dependency
85
+ # @return [void]
86
+ # @api private
87
+ def update_dependencies(matching_dependencies)
88
+ matching_dependencies.each do |found_dependency|
89
+ raise(dependency_type_conflict_error(found_dependency)) unless dependency_type_match?(found_dependency)
90
+
91
+ tree_rewriter.replace(found_dependency.node.loc.expression, dependency_source_code(found_dependency))
92
+ end
93
+ end
94
+
95
+ # Add the new_dependency to the end of the Gem::Specification block
96
+ # @return [void]
97
+ # @api private
98
+ def add_dependency
99
+ # Add the dependency to the end of the Gem::Specification block
100
+ internal_block = gemspec_block.children[2]
101
+ if internal_block
102
+ tree_rewriter.insert_after(internal_block.children.last.loc.expression, "\n #{dependency_source_code}")
103
+ else
104
+ # When the Gem::Specification block is empty, it require special handling
105
+ add_dependency_to_empty_gemspec_block
106
+ end
107
+ end
108
+
109
+ # Error message for a dependency type conflict
110
+ # @param existing_dependency [DependencyNode] The existing dependency
111
+ # @return [String] The error message
112
+ # @api private
113
+ def dependency_type_conflict_error(existing_dependency)
114
+ # :nocov: JRuby give false positive for this line being uncovered by tests
115
+ <<~MESSAGE.chomp.gsub("\n", ' ')
116
+ Trying to add a
117
+ #{dependency_method_to_type(new_dependency.method_name).upcase}
118
+ dependency on "#{new_dependency.gem_name}" which conflicts with the existing
119
+ #{dependency_method_to_type(existing_dependency.dependency.method_name).upcase}
120
+ dependency.
121
+ MESSAGE
122
+ # :nocov:
123
+ end
124
+
125
+ # The dependency type (:runtime or :development) based on a given method name
126
+ # @param method [Symbol] The method name to convert to a dependency type
127
+ # @return [Symbol] The dependency type
128
+ # @api private
129
+ def dependency_method_to_type(method)
130
+ method == :add_development_dependency ? :development : :runtime
131
+ end
132
+
133
+ # Checks if the new dependency type is the same as the existing dependency type
134
+ #
135
+ # @param existing_dependency [DependencyNode] The existing dependency
136
+ # @return [Boolean] Whether the dependency type conflicts
137
+ # @api private
138
+ def dependency_type_match?(existing_dependency)
139
+ # Either both are :add_development_dependency or both are not
140
+ (existing_dependency.dependency.method_name == :add_development_dependency) ==
141
+ (new_dependency.method_name == :add_development_dependency)
142
+ end
143
+
144
+ # Add new_dependency to an empty Gem::Specification block
145
+ # @return [void]
146
+ # @api private
147
+ def add_dependency_to_empty_gemspec_block
148
+ source = gemspec_block.loc.expression.source
149
+ # :nocov: supress false reporting of no coverage of multiline string literals on JRuby
150
+ tree_rewriter.replace(gemspec_block.loc.expression, <<~GEMSPEC_BLOCK.chomp)
151
+ #{source[0..-5]}
152
+ #{dependency_source_code}
153
+ #{source[-3..]}
154
+ GEMSPEC_BLOCK
155
+ # :nocov:
156
+ end
157
+
158
+ # The source code for the updated dependency declaration
159
+ # @param existing_dependency [DependencyNode] The existing dependency
160
+ # @return [String] The source code for the dependency declaration
161
+ # @api private
162
+ def dependency_source_code(existing_dependency = nil)
163
+ # Use existing quote character for string literals
164
+ q = new_quote_char(existing_dependency)
165
+
166
+ # :nocov: supress false reporting of no coverage of multiline string literals on JRuby
167
+ "#{receiver_name}.#{new_method_name(existing_dependency)} " \
168
+ "#{q}#{new_dependency.gem_name}#{q}, " \
169
+ "#{new_dependency.version_constraints.map { |vc| "#{q}#{vc}#{q}" }.join(', ')}"
170
+ # :nocov:
171
+ end
172
+
173
+ # Use the same quote char as the existing dependency or default to single quote
174
+ # @param existing_dependency [DependencyNode, nil] The existing dependency being updated
175
+ # @return [String] The quote character to use
176
+ # @api private
177
+ def new_quote_char(existing_dependency)
178
+ if existing_dependency
179
+ existing_dependency.node.children[3].loc.expression.source[0]
180
+ else
181
+ "'"
182
+ end
183
+ end
184
+
185
+ # The method to use for the new dependency
186
+ #
187
+ # If `existing_dependency` is given and the dependency type (runtime vs.
188
+ # development) matches, the existing dependency method is used. Otherwise,
189
+ # the new_dependency method is used.
190
+ #
191
+ # The purpose of this method is ensure that an #add_dependency call is not
192
+ # replaced with an #add_runtime_dependency call or vice versa. This
193
+ # maintains consistency within the user's gemspec even though these methods
194
+ # are functionally equivalent.
195
+ #
196
+ # @param existing_dependency [DependencyNode, nil] The existing dependency being updated
197
+ # @return [Symbol] The method to use for the new dependency
198
+ # @api private
199
+ def new_method_name(existing_dependency)
200
+ if existing_dependency && dependency_type_match?(existing_dependency)
201
+ existing_dependency.dependency.method_name
202
+ else
203
+ new_dependency.method_name
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,281 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'parser/current'
5
+ require 'rubocop-ast'
6
+ require 'active_support/core_ext/object'
7
+
8
+ module Bundler
9
+ module GemBytes
10
+ module Actions
11
+ # Updates a gemspec according to the given block
12
+ #
13
+ # This class enables you to programmatically update a gemspec file by adding or
14
+ # removing dependencies, updating configuration parameters, and manipulating
15
+ # metadata. It processes the gemspec file as an Abstract Syntax Tree (AST),
16
+ # which allows granular control over gemspec updates.
17
+ #
18
+ # Key terms:
19
+ # * `gemspec_block`: The AST node representing the Gem::Specification block in
20
+ # the gemspec file.
21
+ # * `receiver_name`: The receiver of the Gem::Specification block (e.g., 'spec'
22
+ # in `spec.add_dependency`).
23
+ # * `dependency declarations`: Calls to methods like `add_dependency` or
24
+ # `add_runtime_dependency` within the gemspec.
25
+ #
26
+ # @example
27
+ # gemspec_path = Dir['*.gemspec'].first
28
+ # gemspec_content = File.read(gemspec_path)
29
+ # updated_gemspec_content = Gemspec.new.call(gemspec_content, path: gemspec_path) do
30
+ # add_dependency 'activesupport', '~> 7.0'
31
+ # add_runtime_dependency 'process_executer', '~> 1.1'
32
+ # add_development_dependency 'rubocop', '~> 1.68'
33
+ # remove_dependency 'byebug'
34
+ # config 'required_ruby_version', '>= 2.5.0'
35
+ # remove_config 'required_ruby_version'
36
+ # config_metadata 'homepage', 'https://example.com'
37
+ # remove_config_metadata 'homepage'
38
+ # end
39
+ #
40
+ # @api public
41
+ #
42
+ class Gemspec < Parser::TreeRewriter
43
+ extend Forwardable
44
+
45
+ # Create a new Gemspec action
46
+ #
47
+ # @example
48
+ # Gemspec.new(context: self)
49
+ # @param context [Bundler::GemBytes::ScriptExecuter] The context in which the action is being run
50
+ #
51
+ def initialize(context:)
52
+ @context = context
53
+ super()
54
+ end
55
+
56
+ def_delegators :data, :attributes, :dependencies, :gemspec_ast, :gemspec_object,
57
+ :gemspec_object_name, :source, :source_path, :source_ast, :source_buffer
58
+
59
+ # Processes the given gemspec file and returns the updated content
60
+ #
61
+ # @example
62
+ # updated_gemspec_content = Gemspec.new.call(gemspec_content, source_path: gemspec_path) do
63
+ # add_dependency 'activesupport', '~> 7.0'
64
+ # end
65
+ # @param code [String] The content of the gemspec file
66
+ # @param path [String] The path to the gemspec file (used for error reporting)
67
+ # @return [String] The updated gemspec file content
68
+ # @raise [ArgumentError] if the Gem Specification block is not found in the given gemspec content
69
+ #
70
+ def call(source, source_path: '(string)', &action_block)
71
+ @data = GemSpecification.new(source, source_path)
72
+ @action_block = action_block
73
+ @processing_gemspec_block = false
74
+ rewrite(source_buffer, source_ast).tap do |_result|
75
+ raise ArgumentError, 'Gem::Specification block not found' unless gemspec_ast.present?
76
+ end
77
+ end
78
+
79
+ # The ScriptExecuter object that called this action (used for testing)
80
+ # @return [Bundler::GemBytes::ScriptExecuter]
81
+ # @api private
82
+ attr_reader :context
83
+
84
+ # Indicates that the gemspec block was found and is being processed
85
+ # @return [Boolean]
86
+ # @api private
87
+ attr_reader :processing_gemspec_block
88
+
89
+ # The block passed to #call containing the instructions to update the gemspec
90
+ # @return [Proc]
91
+ # @api private
92
+ attr_reader :action_block
93
+
94
+ # The GemSpecification object containing information about the gemspec file
95
+ # @return [GemSpecification]
96
+ # @api private
97
+ attr_reader :data
98
+
99
+ alias processing_gemspec_block? processing_gemspec_block
100
+
101
+ # Handles block nodes within the AST to locate the Gem Specification block
102
+ #
103
+ # @param node [Parser::AST::Node] The block node within the AST
104
+ # @return [void]
105
+ # @api private
106
+ def on_block(node)
107
+ # If already processing the Gem Specification block, this must be some other nested block
108
+ return if processing_gemspec_block?
109
+
110
+ data.gemspec_object_name = gem_specification_pattern.match(node)
111
+
112
+ return unless gemspec_object_name
113
+
114
+ @processing_gemspec_block = true
115
+ data.gemspec_ast = node
116
+
117
+ super # process the children of this node to find interesting parts of the Gem::Specification block
118
+
119
+ @processing_gemspec_block = false
120
+
121
+ # Call the action_block to do requested modifications the Gem::Specification block.
122
+ # The default receiver in the block is this object.
123
+ # receiver_name and gem_specification are passed as arguments.
124
+ return unless action_block
125
+
126
+ instance_exec(gemspec_object_name, gemspec_object, &action_block)
127
+ end
128
+
129
+ # Handles `send` nodes within the AST to locate dependency calls
130
+ #
131
+ # Only processes `send` nodes within the Gem::Specification block.
132
+ #
133
+ # @param node [Parser::AST::Node] The `send` node to check for dependency patterns
134
+ # @return [void]
135
+ # @api private
136
+ def on_send(node)
137
+ return unless processing_gemspec_block?
138
+
139
+ handle_dependency(node) || handle_attribute(node)
140
+ end
141
+
142
+ # Removes a dependency from the Gem::Specification block
143
+ #
144
+ # @example
145
+ # remove_dependency 'rubocop'
146
+ # # Removes the dependency on 'rubocop' from the Gem::Specification block:
147
+ # # spec.add_development_dependency 'rubocop', '~> 1.68'
148
+ # @param gem_name [String] the name of the gem to remove a dependency on
149
+ # @return [void]
150
+ #
151
+ # TODO: just pass data to DeleteDependency.new
152
+ def remove_dependency(gem_name)
153
+ DeleteDependency.new(self, gemspec_ast, gemspec_object_name, dependencies).call(gem_name)
154
+ end
155
+
156
+ # Adds or updates a dependency to the Gem::Specification block
157
+ #
158
+ # @example
159
+ # add_dependency 'rails', '~> 7.0'
160
+ # # Adds (or updates) the following line to the Gem::Specification block:
161
+ # # spec.add_dependency 'rails', '~> 7.0'
162
+ # @param gem_name [String] the name of the gem to add a dependency on
163
+ # @param version_constraints [Array[String]] one or more version constraints on the gem
164
+ # @param method_name [String] the name of the method to use to add the dependency
165
+ # @return [void]
166
+ #
167
+ # TODO: just pass data to DeleteDependency.new
168
+ def add_dependency(gem_name, *version_constraints, method_name: :add_dependency)
169
+ new_dependency = Dependency.new(method_name, gem_name, version_constraints)
170
+ UpsertDependency.new(self, gemspec_ast, gemspec_object_name, dependencies).call(new_dependency)
171
+ end
172
+
173
+ # Adds or updates a dependency to the Gem::Specification block
174
+ #
175
+ # @example
176
+ # add_runtime_dependency 'rails', '~> 7.0'
177
+ # # Adds (or updates) the following line to the Gem::Specification block:
178
+ # # spec.add_runtime_dependency 'rails', '~> 7.0'
179
+ # @param gem_name [String] the name of the gem to add a dependency on
180
+ # @param version_constraint [String] the version constraint on the gem
181
+ # @return [void]
182
+ #
183
+ def add_runtime_dependency(gem_name, version_constraint)
184
+ add_dependency(gem_name, version_constraint, method_name: :add_runtime_dependency)
185
+ end
186
+
187
+ # Adds or updates a development dependency to the Gem::Specification block
188
+ #
189
+ # @example
190
+ # add_runtime_development_dependency 'rubocop', '~> 1.68'
191
+ # # Adds (or updates) the following line to the Gem::Specification block:
192
+ # # spec.add_development_dependency 'rubocop', '~> 1.68'
193
+ # @param gem_name [String] the name of the gem to add a dependency on
194
+ # @param version_constraint [String] the version constraint on the gem
195
+ # @return [void]
196
+ #
197
+ def add_development_dependency(gem_name, version_constraint)
198
+ add_dependency(gem_name, version_constraint, method_name: :add_development_dependency)
199
+ end
200
+
201
+ private
202
+
203
+ # Save the dependency if the node is a dependency
204
+ # @param node [Parser::AST::Node] the node to check if it is a dependency
205
+ # @return [Boolean] true if the node is a dependency, false otherwise
206
+ # @api private
207
+ def handle_dependency(node)
208
+ return false unless (match = dependency_pattern.match(node))
209
+
210
+ dependencies << DependencyNode.new(node, Dependency.new(*match))
211
+
212
+ true
213
+ end
214
+
215
+ # Save the attribute if the node is an attribute
216
+ # @param node [Parser::AST::Node] the node to check if it is an attribute
217
+ # @return [Boolean] true if the node is an attribute, false otherwise
218
+ # @api private
219
+ def handle_attribute(node)
220
+ return false unless (match = attribute_pattern.match(node))
221
+ return false unless match[0].end_with?('=')
222
+
223
+ name = match[0][0..-2]
224
+ value = match[1]
225
+ attributes << AttributeNode.new(node, Attribute.new(name, value))
226
+
227
+ true
228
+ end
229
+
230
+ # The pattern to match the Gem::Specification block in the AST
231
+ # @return [RuboCop::AST::NodePattern] The Gem::Specification pattern
232
+ # @api private
233
+ def gem_specification_pattern
234
+ @gem_specification_pattern ||=
235
+ RuboCop::AST::NodePattern.new(<<~PATTERN)
236
+ (block (send (const (const nil? :Gem) :Specification) :new)(args (arg $_)) ...)
237
+ PATTERN
238
+ end
239
+
240
+ # The pattern to match a dependency declaration in the AST
241
+ # @return [RuboCop::AST::NodePattern] The dependency pattern
242
+ # @api private
243
+ def dependency_pattern
244
+ # :nocov: JRuby give false positive for this line being uncovered by tests
245
+ @dependency_pattern ||=
246
+ RuboCop::AST::NodePattern.new(<<~PATTERN)
247
+ (send
248
+ { (send _ :#{gemspec_object_name}) | (lvar :#{gemspec_object_name}) }
249
+ ${ :add_dependency :add_runtime_dependency :add_development_dependency }
250
+ (str $_gem_name)
251
+ <(str $_version_constraint) ...>
252
+ )
253
+ PATTERN
254
+ # :nocov:
255
+ end
256
+
257
+ # The pattern to match an attribute in the AST
258
+ # @return [RuboCop::AST::NodePattern] The attribute pattern
259
+ # @api private
260
+ def attribute_pattern
261
+ # :nocov: JRuby give false positive for this line being uncovered by tests
262
+ @attribute_pattern ||=
263
+ RuboCop::AST::NodePattern.new(<<~PATTERN)
264
+ (send
265
+ (lvar :#{gemspec_object_name}) $_name $_value
266
+ )
267
+ PATTERN
268
+ # :nocov:
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ require_relative 'gemspec/attribute'
276
+ require_relative 'gemspec/attribute_node'
277
+ require_relative 'gemspec/gem_specification'
278
+ require_relative 'gemspec/delete_dependency'
279
+ require_relative 'gemspec/dependency'
280
+ require_relative 'gemspec/dependency_node'
281
+ require_relative 'gemspec/upsert_dependency'
@@ -7,47 +7,37 @@ module Bundler
7
7
  # The API for GemBytes templates
8
8
  # @api public
9
9
  module Actions
10
- # Adds (or updates) a dependency in the project's gemspec file
10
+ # The gemspec at `gemspec_path` is updated per instructions in `action_block`
11
11
  #
12
- # @example
13
- # add_dependency(:development, 'rspec', '~> 3.13')
14
- # add_dependency(:runtime, 'activesupport', '>= 6.0.0')
12
+ # @example Adding a runtime dependency
13
+ # actions = Actions.new
14
+ # actions.gemspec(gemspec_path: 'test.gemspec') do
15
+ # add_runtime_dependency 'rubocop', '~> 1.68'
16
+ # end
15
17
  #
16
- # @param dependency_type [Symbol] the type of dependency to add (either :development or :runtime)
17
- # @param gem_name [String] the name of the gem to add
18
- # @param version_constraint [String] the version constraint for the gem
19
- # @param force [Boolean] whether to overwrite the existing dependency
20
- # @param gemspec [String] the path to the gemspec file
18
+ # @param gemspec_path [String] the path to the gemspec file to process
21
19
  #
22
- # @return [void]
23
- #
24
- # @api public
20
+ # Defaults to the first gemspec file found in the current directory.
25
21
  #
26
- def add_dependency(dependency_type, gem_name, version_constraint, force: false, gemspec: Dir['*.gemspec'].first)
27
- source = File.read(gemspec)
28
- updated_source = Bundler::GemBytes::Gemspec::UpsertDependency.new(
29
- dependency_type, gem_name, version_constraint, force: force
30
- ).call(source, path: gemspec)
31
- File.write(gemspec, updated_source)
32
- end
33
-
34
- # Removes a dependency from the project's gemspec file
22
+ # @yield a block with instructions to modify the gemspec
35
23
  #
36
- # @example
37
- # remove_dependency('rspec')
24
+ # This block is run in the context of a {GemBytes::Actions::Gemspec} instance.
25
+ # The instructions are methods defined by this instance (e.g.
26
+ # `add_dependency`, `remove_dependency`, etc.)
38
27
  #
39
- # @param gem_name [String] the name of the gem to add
40
- # @param gemspec [String] the path to the gemspec file
28
+ # @yieldparam gemspec_name [Symbol] the name of the Gem::Specification varaible used in the gemspec
29
+ # @yieldparam gemspec [Gem::Specification] the evaluated gemspec
30
+ # @yieldreturn [String] the updated gemspec
41
31
  #
42
32
  # @return [void]
43
- #
44
- # @api public
45
- #
46
- def remove_dependency(gem_name, gemspec: Dir['*.gemspec'].first)
47
- source = File.read(gemspec)
48
- updated_source = Bundler::GemBytes::Gemspec::DeleteDependency.new(gem_name).call(source, path: gemspec)
49
- File.write(gemspec, updated_source)
33
+ def gemspec(gemspec_path: Dir['*.gemspec'].first, &action_block)
34
+ source = File.read(gemspec_path)
35
+ action = Bundler::GemBytes::Actions::Gemspec.new(context: self)
36
+ updated_source = action.call(source, source_path: gemspec_path, &action_block)
37
+ File.write(gemspec_path, updated_source)
50
38
  end
51
39
  end
52
40
  end
53
41
  end
42
+
43
+ require_relative 'actions/gemspec'
@@ -5,17 +5,15 @@ require 'bundler'
5
5
  module Bundler
6
6
  module GemBytes
7
7
  # A bundler command that adds features to your existing Ruby Gems project
8
- #
9
8
  # @api public
10
- #
11
9
  class BundlerCommand < Bundler::Plugin::API
12
- # Called when the `gem-bytes` command is invoked
10
+ # Executes the `gem-bytes` command
13
11
  #
14
12
  # @example
15
- # BundlerCommand.new.exec('gem-bytes', ['path_or_uri_to_script'])
16
- # @param _command [String] the command that was invoked (in this case, 'gem-bytes')
17
- # @param args [Array<String>] any additional arguments passed to the command
18
- # @raise [SystemExit] if there was an error executing the command
13
+ # BundlerCommand.new.exec('gem-bytes', ['uri_or_path', *extra_args])
14
+ # @param _command [String] the invoked bundler command (in this case, 'gem-bytes')
15
+ # @param args [Array<String>] command arguments
16
+ # @raise [SystemExit] if an error occurs
19
17
  # @return [void]
20
18
  def exec(_command, args)
21
19
  uri_or_path = validate_args(args)
@@ -5,32 +5,52 @@ require_relative 'actions'
5
5
 
6
6
  module Bundler
7
7
  module GemBytes
8
- # ScriptExecutor is responsible for executing scripts using Thor actions
8
+ # Responsible for executing scripts using Thor and GemBytes actions
9
9
  #
10
- # @api public
10
+ # This class enables the execution of scripts from a file or URI, integrating
11
+ # with `Thor::Actions` to allow advanced file manipulation and action chaining.
12
+ # This can be particularly useful for tasks like creating or modifying files,
13
+ # managing dependencies, and implementing workflows for gem development.
11
14
  #
12
15
  # @example Executing a script from a file or URI
13
16
  # executor = Bundler::GemBytes::ScriptExecutor.new
14
17
  # executor.execute('path_or_uri_to_script')
18
+ # @api public
15
19
  class ScriptExecutor < ::Thor::Group
16
20
  include ::Thor::Actions
17
21
  include Bundler::GemBytes::Actions
18
22
 
19
- # Set the source paths for Thor to use
20
- # @return [Array<String>] the source paths
23
+ # Sets the source paths for `Thor::Actions`
24
+ #
25
+ # This determines where action scripts will be sourced from.
26
+ #
27
+ # By default, the source path is set to the current working directory, which
28
+ # allows scripts to access files from the local file system during execution.
29
+ #
30
+ # @return [Array<String>] the list of source paths
31
+ #
21
32
  # @api private
22
33
  def self.source_paths
23
- # Add the current working directory or the directory of the script
24
34
  [Dir.pwd]
25
35
  end
26
36
 
27
- # Executes the script from a URI or file path
37
+ # Executes a script from a given URI or file path
38
+ #
39
+ # This method loads the script located at the specified `path_or_uri` and
40
+ # executes it within the context of this class which includes `Thor::Actions`
41
+ # and `Bundler::GemBytes::ScriptExecutor`. This allows the script to perform
42
+ # tasks such as modifying files, creating directories, and other common file
43
+ # system operations.
28
44
  #
29
45
  # @param path_or_uri [String] the URI or file path to the script
46
+ #
30
47
  # @return [void]
48
+ #
31
49
  # @raise [RuntimeError] if the script cannot be loaded
50
+ #
32
51
  # @example Execute a script from a path
33
52
  # execute('path/to/script.rb')
53
+ #
34
54
  def execute(path_or_uri)
35
55
  apply(path_or_uri)
36
56
  rescue StandardError => e
@@ -3,6 +3,6 @@
3
3
  module Bundler
4
4
  module GemBytes
5
5
  # The version of this gem
6
- VERSION = '0.2.0'
6
+ VERSION = '0.2.3'
7
7
  end
8
8
  end
@@ -16,7 +16,7 @@ end
16
16
  require 'active_support'
17
17
  require 'thor'
18
18
 
19
+ require_relative 'gem_bytes/actions'
19
20
  require_relative 'gem_bytes/bundler_command'
20
- require_relative 'gem_bytes/gemspec'
21
21
  require_relative 'gem_bytes/script_executor'
22
22
  require_relative 'gem_bytes/version'