Class: Bundler::GemBytes::Actions::Gemspec

Inherits:
Parser::TreeRewriter
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/bundler/gem_bytes/actions/gemspec.rb,
lib/bundler/gem_bytes/actions/gemspec/attribute.rb,
lib/bundler/gem_bytes/actions/gemspec/dependency.rb,
lib/bundler/gem_bytes/actions/gemspec/attribute_node.rb,
lib/bundler/gem_bytes/actions/gemspec/dependency_node.rb,
lib/bundler/gem_bytes/actions/gemspec/delete_dependency.rb,
lib/bundler/gem_bytes/actions/gemspec/gem_specification.rb,
lib/bundler/gem_bytes/actions/gemspec/upsert_dependency.rb

Overview

Updates a gemspec according to the given block

This class enables you to programmatically update a gemspec file by adding or removing dependencies, updating configuration parameters, and manipulating metadata. It processes the gemspec file as an Abstract Syntax Tree (AST), which allows granular control over gemspec updates.

Key terms:

  • ‘gemspec_block`: The AST node representing the Gem::Specification block in the gemspec file.

  • ‘receiver_name`: The receiver of the Gem::Specification block (e.g., ’spec’ in ‘spec.add_dependency`).

  • ‘dependency declarations`: Calls to methods like `add_dependency` or `add_runtime_dependency` within the gemspec.

Examples:

gemspec_path = Dir['*.gemspec'].first
gemspec_content = File.read(gemspec_path)
updated_gemspec_content = Gemspec.new.call(gemspec_content, path: gemspec_path) do
  add_dependency 'activesupport', '~> 7.0'
  add_runtime_dependency 'process_executer', '~> 1.1'
  add_development_dependency 'rubocop', '~> 1.68'
  remove_dependency 'byebug'
  config 'required_ruby_version', '>= 2.5.0'
  remove_config 'required_ruby_version'
   'homepage', 'https://example.com'
   'homepage'
end

Defined Under Namespace

Classes: Attribute, AttributeNode, DeleteDependency, Dependency, DependencyNode, GemSpecification, UpsertDependency

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context:) ⇒ Gemspec

Create a new Gemspec action

Examples:

Gemspec.new(context: self)

Parameters:

  • context (Bundler::GemBytes::ScriptExecuter)

    The context in which the action is being run



51
52
53
54
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 51

def initialize(context:)
  @context = context
  super()
end

Instance Attribute Details

#action_blockProc (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The block passed to #call containing the instructions to update the gemspec

Returns:

  • (Proc)


92
93
94
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 92

def action_block
  @action_block
end

#contextBundler::GemBytes::ScriptExecuter (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The ScriptExecuter object that called this action (used for testing)

Returns:

  • (Bundler::GemBytes::ScriptExecuter)


82
83
84
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 82

def context
  @context
end

#dataGemSpecification (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The GemSpecification object containing information about the gemspec file

Returns:



97
98
99
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 97

def data
  @data
end

#processing_gemspec_blockBoolean (readonly) Also known as: processing_gemspec_block?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Indicates that the gemspec block was found and is being processed

Returns:

  • (Boolean)


87
88
89
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 87

def processing_gemspec_block
  @processing_gemspec_block
end

Instance Method Details

#add_dependency(gem_name, *version_constraints, method_name: :add_dependency) ⇒ void

This method returns an undefined value.

Adds or updates a dependency to the Gem::Specification block

TODO: just pass data to DeleteDependency.new

Examples:

add_dependency 'rails', '~> 7.0'
# Adds (or updates) the following line to the Gem::Specification block:
# spec.add_dependency 'rails', '~> 7.0'

Parameters:

  • gem_name (String)

    the name of the gem to add a dependency on

  • version_constraints (Array[String])

    one or more version constraints on the gem

  • method_name (String) (defaults to: :add_dependency)

    the name of the method to use to add the dependency



168
169
170
171
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 168

def add_dependency(gem_name, *version_constraints, method_name: :add_dependency)
  new_dependency = Dependency.new(method_name, gem_name, version_constraints)
  UpsertDependency.new(self, gemspec_ast, gemspec_object_name, dependencies).call(new_dependency)
end

#add_development_dependency(gem_name, version_constraint) ⇒ void

This method returns an undefined value.

Adds or updates a development dependency to the Gem::Specification block

Examples:

add_runtime_development_dependency 'rubocop', '~> 1.68'
# Adds (or updates) the following line to the Gem::Specification block:
# spec.add_development_dependency 'rubocop', '~> 1.68'

Parameters:

  • gem_name (String)

    the name of the gem to add a dependency on

  • version_constraint (String)

    the version constraint on the gem



197
198
199
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 197

def add_development_dependency(gem_name, version_constraint)
  add_dependency(gem_name, version_constraint, method_name: :add_development_dependency)
end

#add_runtime_dependency(gem_name, version_constraint) ⇒ void

This method returns an undefined value.

Adds or updates a dependency to the Gem::Specification block

Examples:

add_runtime_dependency 'rails', '~> 7.0'
# Adds (or updates) the following line to the Gem::Specification block:
# spec.add_runtime_dependency 'rails', '~> 7.0'

Parameters:

  • gem_name (String)

    the name of the gem to add a dependency on

  • version_constraint (String)

    the version constraint on the gem



183
184
185
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 183

def add_runtime_dependency(gem_name, version_constraint)
  add_dependency(gem_name, version_constraint, method_name: :add_runtime_dependency)
end

#call(source, source_path: '(string)', &action_block) ⇒ String

Processes the given gemspec file and returns the updated content

Examples:

updated_gemspec_content = Gemspec.new.call(gemspec_content, source_path: gemspec_path) do
  add_dependency 'activesupport', '~> 7.0'
end

Parameters:

  • code (String)

    The content of the gemspec file

  • path (String)

    The path to the gemspec file (used for error reporting)

Returns:

  • (String)

    The updated gemspec file content

Raises:

  • (ArgumentError)

    if the Gem Specification block is not found in the given gemspec content



70
71
72
73
74
75
76
77
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 70

def call(source, source_path: '(string)', &action_block)
  @data = GemSpecification.new(source, source_path)
  @action_block = action_block
  @processing_gemspec_block = false
  rewrite(source_buffer, source_ast).tap do |_result|
    raise ArgumentError, 'Gem::Specification block not found' unless gemspec_ast.present?
  end
end

#on_block(node) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Handles block nodes within the AST to locate the Gem Specification block

Parameters:

  • node (Parser::AST::Node)

    The block node within the AST



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 106

def on_block(node)
  # If already processing the Gem Specification block, this must be some other nested block
  return if processing_gemspec_block?

  data.gemspec_object_name = gem_specification_pattern.match(node)

  return unless gemspec_object_name

  @processing_gemspec_block = true
  data.gemspec_ast = node

  super # process the children of this node to find interesting parts of the Gem::Specification block

  @processing_gemspec_block = false

  # Call the action_block to do requested modifications the Gem::Specification block.
  # The default receiver in the block is this object.
  # receiver_name and gem_specification are passed as arguments.
  return unless action_block

  instance_exec(gemspec_object_name, gemspec_object, &action_block)
end

#on_send(node) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Handles ‘send` nodes within the AST to locate dependency calls

Only processes ‘send` nodes within the Gem::Specification block.

Parameters:

  • node (Parser::AST::Node)

    The ‘send` node to check for dependency patterns



136
137
138
139
140
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 136

def on_send(node)
  return unless processing_gemspec_block?

  handle_dependency(node) || handle_attribute(node)
end

#remove_dependency(gem_name) ⇒ void

This method returns an undefined value.

Removes a dependency from the Gem::Specification block

TODO: just pass data to DeleteDependency.new

Examples:

remove_dependency 'rubocop'
# Removes the dependency on 'rubocop' from the Gem::Specification block:
# spec.add_development_dependency 'rubocop', '~> 1.68'

Parameters:

  • gem_name (String)

    the name of the gem to remove a dependency on



152
153
154
# File 'lib/bundler/gem_bytes/actions/gemspec.rb', line 152

def remove_dependency(gem_name)
  DeleteDependency.new(self, gemspec_ast, gemspec_object_name, dependencies).call(gem_name)
end