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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e081d49425f6d1bb155e06f8a535da63de74ccdc28a694131a116c93dcf5bb18
4
- data.tar.gz: 04a13ce95b1d620ca6897ff356718fc7886b13ccc087c2661d9101f31bb3aa5b
3
+ metadata.gz: 2ee84435b5dbc4349dbdf9f87c825cf933dba8509a33acb7ac2f8a65516cdd91
4
+ data.tar.gz: 0db994491848b0cd41a83ab1b7cd5dd176446571da8e9bd9d573015d08d81cb3
5
5
  SHA512:
6
- metadata.gz: 861709bf72507d147e63ff1c3cf8b34159ec3ecc8ec12a1a70172afcc1e23d569bc1995db97db14e2eb1c3c5a87a9859a29ea3b9fc366c3780766b64b148c276
7
- data.tar.gz: ba3a92619c32912a7d4abb5aee9ab0c0f089a2d6d82e19b819641c7c0ed331b42e5b67f6b79b4e7c8b8374270174d8b2e6fd53ee9948751385cd0825c86fd33a
6
+ metadata.gz: a865449d239e5af18b17b6349d6e303f554661aacef7e5304a5863ae1c620a85e389fe8edd3838b70f15d7b29515885057b3c3a536d776f350ef4d582726c06a
7
+ data.tar.gz: 12a4873de4fa57dfd396e0f272368a26e50220b1e6af767053fecd9feb0e95fc227de34905043e0beb1999010e618fad6849949dc421ee1e07cbdd52b937f41a
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.3"
3
+ }
data/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ All notable changes to the process_executer gem will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.3](https://github.com/main-branch/bundler-gem_bytes/compare/v0.2.2...v0.2.3) (2025-04-17)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Do not trigger build workflows after merging to main or for release PRs ([aa7539c](https://github.com/main-branch/bundler-gem_bytes/commit/aa7539c344a900375597a707d1be2256bb19775c))
14
+
15
+ ## [0.2.2](https://github.com/main-branch/bundler-gem_bytes/compare/v0.2.1...v0.2.2) (2025-04-16)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * Automate commit-to-publish workflow version file path wrong ([4220bd4](https://github.com/main-branch/bundler-gem_bytes/commit/4220bd4428cc4841d56c65fb86540d9237b0944b))
21
+
22
+ ## [0.2.1](https://github.com/main-branch/bundler-gem_bytes/compare/v0.2.0...v0.2.1) (2025-04-16)
23
+
24
+
25
+ ### Features
26
+
27
+ * Add a gemspec action to make modification to a project gemspec ([d83d145](https://github.com/main-branch/bundler-gem_bytes/commit/d83d14598922fe8b54de12b92232e8cae6e6e668))
28
+ * Add the remove_dependency sub-action to the gemspec action ([ba1ff7b](https://github.com/main-branch/bundler-gem_bytes/commit/ba1ff7b8401ff7dd29c37f457da14452ce55c8f2))
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * Allow dependencies to have multiple version constraints ([4217151](https://github.com/main-branch/bundler-gem_bytes/commit/421715118af964e5e53ae35cda445e5b2dfd4f48))
34
+ * Automate commit-to-publish workflow ([3d018da](https://github.com/main-branch/bundler-gem_bytes/commit/3d018da4b2ac3caceaa81dd12a0b7771fc03db18))
35
+
8
36
  ## v0.2.0 (2024-10-30)
9
37
 
10
38
  [Full Changelog](https://github.com/main-branch/bundler-gem_bytes/compare/v0.1.0..v0.2.0)
data/README.md CHANGED
@@ -7,9 +7,6 @@ Version](https://badge.fury.io/rb/bundler-gem_bytes.svg)](https://badge.fury.io/
7
7
  Log](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://rubydoc.info/gems/bundler-gem_bytes/file/CHANGELOG.md)
8
8
  [![Build
9
9
  Status](https://github.com/main-branch/bundler-gem_bytes/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/main-branch/bundler-gem_bytes/actions/workflows/continuous-integration.yml)
10
- [![Maintainability](https://api.codeclimate.com/v1/badges/2468fc247e5d66fc179f/maintainability)](https://codeclimate.com/github/main-branch/bundler-gem_bytes/maintainability)
11
- [![Test
12
- Coverage](https://api.codeclimate.com/v1/badges/2468fc247e5d66fc179f/test_coverage)](https://codeclimate.com/github/main-branch/bundler-gem_bytes/test_coverage)
13
10
  [![Conventional
14
11
  Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
15
12
  [![Slack](https://img.shields.io/badge/slack-main--branch/bundler--gem_bytes-yellow.svg?logo=slack)](https://main-branch.slack.com/archives/C07RKRKTLDT)
@@ -36,14 +33,15 @@ own script**
36
33
 
37
34
  * [Installation](#installation)
38
35
  * [Usage](#usage)
39
- * [Example](#example)
40
- * [Handling Errors](#handling-errors)
36
+ * [Example](#example)
37
+ * [Handling Errors](#handling-errors)
41
38
  * [Development](#development)
42
- * [Debugging](#debugging)
43
- * [Releasing](#releasing)
39
+ * [How this gem works](#how-this-gem-works)
40
+ * [Debugging](#debugging)
41
+ * [Releasing](#releasing)
44
42
  * [Contributing](#contributing)
45
- * [Commit message guidelines](#commit-message-guidelines)
46
- * [Pull request guidelines](#pull-request-guidelines)
43
+ * [Commit message guidelines](#commit-message-guidelines)
44
+ * [Pull request guidelines](#pull-request-guidelines)
47
45
  * [License](#license)
48
46
  * [Code of Conduct](#code-of-conduct)
49
47
 
@@ -81,6 +79,55 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
81
79
  workflow. You can also run `bin/console` for an interactive prompt that will allow
82
80
  you to experiment.
83
81
 
82
+ ### How this gem works
83
+
84
+ 1. The user runs the `bundler gem-bytes` command from the command line, passing the
85
+ path or URL to a GemBytes script:
86
+
87
+ ```shell
88
+ bundler gem-bytes [SCRIPT]
89
+ ```
90
+
91
+ 2. The `plugins.rb` file (in the root directory of this project) defines
92
+ `Bundler::GemBytes::BundlerCommand` class as the handler for the `gem-bytes`
93
+ bundler command:
94
+
95
+ ```ruby
96
+ require 'bundler/gem_bytes'
97
+
98
+ # Register Bundler::GemBytes::BundlerCommand as the handler for the `gem-bytes`
99
+ # bundler command
100
+
101
+ Bundler::Plugin::API.command('gem-bytes', Bundler::GemBytes::BundlerCommand)
102
+ ```
103
+
104
+ 3. Bundler invokes the gem-bytes plugin by creating an instance of
105
+ `Bundler::GemBytes::BundlerCommand` and then calling `#exec(command, args)` on
106
+ that instance. Where:
107
+
108
+ * `command` is the bundler command given on the command line. It will always be
109
+ "gem-bytes".
110
+ * `args` is the array of any other arguments given on the command line after the
111
+ command. In this case, we expect the script path or URI.
112
+
113
+ 4. The `BundlerCommand` instance creates a `Bundler::GemBytes::ScriptExecutor`
114
+ instance and calls `#execute(path_or_uri)` on that instance. This method in turn
115
+ calls `Thor::Actions#apply(path_or_uri)` to load the external script and execute
116
+ it in the context of the `ScriptExecutor` instance.
117
+
118
+ The `#apply` method, part of the `Thor::Actions` module, loads and executes the
119
+ script within the context of the `ScriptExecutor` instance.
120
+
121
+ If an error occurs during script execution, `BundlerCommand` catches the error, outputs an error message to `stderr`, and exits with a status code of `1`.
122
+
123
+ 5. The `ScriptExecutor` class provides the environment/binding in which the GemBytes
124
+ script is executed, allowing the script to use instance methods and context from
125
+ `ScriptExecutor`. In addition to core Ruby and Active Support, the API available
126
+ to this script includes methods from both the
127
+ [`Thor::Actions`](https://github.com/rails/thor/wiki/Actions) and
128
+ `Bundler::GemBytes::Actions` modules, which provide utilities for file
129
+ manipulation, template generation, and other tasks essential for script execution.
130
+
84
131
  ### Debugging
85
132
 
86
133
  To debug this gem it is recommended that you create a test project and install
@@ -100,7 +147,9 @@ BUNDLE_IGNORE_CONFIG=TRUE bundle plugin install --path ../.. bundler-gem_bytes
100
147
 
101
148
  # 4. Create a gembytes script to add a development dependency on rubocop
102
149
  cat <<SCRIPT > gem_bytes_script.rb
103
- add_dependency :development, "rubocop", "~> 1.6"
150
+ gemspec do
151
+ add_development_dependency "rubocop", "~> 1.68"
152
+ end
104
153
  SCRIPT
105
154
 
106
155
  # 5. Modify code, set breakpoints, or add binding.{irb|pry} calls to the source
@@ -173,3 +222,36 @@ License](https://opensource.org/licenses/MIT).
173
222
  Everyone interacting in the Bundler::GemBytes project's codebases, issue trackers,
174
223
  chat rooms and mailing lists is expected to follow the [code of
175
224
  conduct](https://github.com/main-branch/bundler-gem_bytes/blob/main/CODE_OF_CONDUCT.md).
225
+
226
+ gemspec path do |spec_var, spec|
227
+ add_dependency 'example', '~> 1.1'
228
+ add_runtime_dependency 'example', '~> 1.1', '>= 1.1.4'
229
+ add_development_dependency "rubocop", "~> 1.68"
230
+
231
+ remove_dependency "example"
232
+
233
+ attr "description", "#{spec.description}. Enhanced by GemBytes."
234
+ attr "author", ENV['USER']
235
+ attr "files", "Dir['lib/**/*.rb'] + Dir['bin/*']", quote: false
236
+ attr "authors", <% spec.authors %>.append('GemBytes').inspect, quote: false
237
+
238
+ remove_attr "license"
239
+
240
+ metadata "homepage_url", "https://github.com/example/"
241
+
242
+ remove_metadata "wiki_uri"
243
+
244
+ in_block("if RUBY_PLATFORM != 'java'", "end") do
245
+ development_dependency 'redcarpet', '~> 3.5'
246
+ development_dependency 'yard', '~> 0.9'
247
+ development_dependency 'yardstick', '~> 0.9'
248
+ end
249
+
250
+ code <<~CODE
251
+ if RUBY_PLATFORM != 'java'
252
+ <%= spec_var %>.add_development_dependency 'redcarpet', '~> 3.5'
253
+ <%= spec_var %>.add_development_dependency 'yard', '~> 0.9'
254
+ <%= spec_var %>.add_development_dependency 'yardstick', '~> 0.9'
255
+ end
256
+ CODE
257
+ end
data/Rakefile CHANGED
@@ -25,6 +25,13 @@ rescue Bundler::BundlerError => e
25
25
  exit e.status_code
26
26
  end
27
27
 
28
+ # Make it so that calling `rake release` just calls `rake release:rubygems_push` to
29
+ # avoid creating and pushing a new tag.
30
+
31
+ Rake::Task['release'].clear
32
+ desc 'Customized release task to avoid creating a new tag'
33
+ task release: 'release:rubygem_push'
34
+
28
35
  CLEAN << 'pkg'
29
36
  CLOBBER << 'Gemfile.lock'
30
37
 
@@ -56,6 +63,7 @@ unless RUBY_PLATFORM == 'java'
56
63
 
57
64
  YARD::Rake::YardocTask.new(:build) do |t|
58
65
  t.files = %w[lib/**/*.rb examples/**/*]
66
+ t.options = ['--markup-provider', 'redcarpet', '--markup', 'markdown']
59
67
  t.stats_options = ['--list-undoc']
60
68
  end
61
69
 
@@ -0,0 +1,51 @@
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
+ # Holds the components of a attribute: a name and a value
12
+ #
13
+ # @api public
14
+ #
15
+ # Attributes in the gemspec look like the following:
16
+ #
17
+ # ```ruby
18
+ # Gem::Specification.new do |spec|
19
+ # spec.name = 'test'
20
+ # end
21
+ # ```
22
+ #
23
+ # @!attribute [r] name
24
+ # The name of the attribute
25
+ # @example
26
+ # attribute.name #=> "test"
27
+ #
28
+ # @return [String]
29
+ # @api public
30
+ #
31
+ # @!attribute [r] value
32
+ # The value of the attribute expressed as an AST tree
33
+ # @example
34
+ # attribute.value.to_sexp #=> 's(:str, "my_description")'
35
+ # @return [Parser::AST::Node]
36
+ # @api public
37
+ #
38
+ # @!method to_a()
39
+ # Converts the attribute into an array
40
+ # @example
41
+ # dependency.to_a #=> ["description", [:str, "my_description"]]
42
+ # @return [Array]
43
+ # @api public
44
+ #
45
+ Attribute = Struct.new(:name, :value) do
46
+ def to_a = [name, value.to_sexp_array]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
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
+ # Maps a dependency declaration to the AST that represents it
12
+ # @api public
13
+ #
14
+ # @!attribute [r] node
15
+ # The AST node for the attribute
16
+ # @example
17
+ # attribute_node.node #=> ...
18
+ # @return [Parser::AST::Node]
19
+ # @api public
20
+ #
21
+ # @!attribute [r] attribute
22
+ # The components of the attribute from the AST node
23
+ # @example
24
+ # attribute_node.attribute.to_a #=> ["description", [:str, "My deescription"]]
25
+ # @return [Dependency]
26
+ # @api public
27
+ #
28
+ AttributeNode = Struct.new(:node, :attribute)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,106 @@
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
+ # Remove a dependency in a gemspec
12
+ #
13
+ # If a dependency on the given gem is not found, this action does nothing.
14
+ #
15
+ # If one or more dependencies are found on the same gem as gem_name,
16
+ # the are removed from the gemspec.
17
+ #
18
+ # The gemspec is updated via calls to the tree_rewriter object.
19
+ #
20
+ # @!attribute [r] tree_rewriter
21
+ # The object that updates the source
22
+ # @return [Parser::TreeRewriter]
23
+ # @api private
24
+ #
25
+ # @!attribute [r] gemspec_block
26
+ # The root AST node of the Gem::Specification block from the gemspec
27
+ # @return [Parser::AST::Node]
28
+ # @api private
29
+ #
30
+ # @!attribute [r] receiver_name
31
+ # The name of the receiver for the Gem::Specification block
32
+ # @return [Symbol]
33
+ # @api private
34
+ #
35
+ # @!attribute [r] dependencies
36
+ # The dependency declarations found in the gemspec file
37
+ # @return [Array<DependencyNode>]
38
+ # @api private
39
+ #
40
+ # @!attribute [r] gem_name
41
+ # The name of the gem to remove dependency on
42
+ # @return [String]
43
+ # @api private
44
+ #
45
+ # @api public
46
+ class DeleteDependency
47
+ # Initializes the delete dependency action
48
+ # @param tree_rewriter [Parser::TreeRewriter] The object that updates the source
49
+ # @param gemspec_block [Parser::AST::Node] The Gem::Specification block
50
+ # @param receiver_name [Symbol] The name of the receiver for the Gem::Specification block
51
+ # @param dependencies [Array<DependencyNode>] The dependency declarations found in the gemspec file
52
+ # @api private
53
+ def initialize(tree_rewriter, gemspec_block, receiver_name, dependencies)
54
+ @tree_rewriter = tree_rewriter
55
+ @gemspec_block = gemspec_block
56
+ @receiver_name = receiver_name
57
+ @dependencies = dependencies
58
+ end
59
+
60
+ attr_reader :tree_rewriter, :gemspec_block, :receiver_name, :dependencies, :gem_name
61
+
62
+ # Adds or updates a dependency to the Gem::Specification block
63
+ #
64
+ # @example
65
+ # delete_dependency = DeleteDependency.new(tree_rewriter, gemspec_block, receiver_name, dependencies)
66
+ # gem_name = 'rubocop'
67
+ # depete_dependency.call(gem_name)
68
+ # @param gem_name [String] The name of the gem to remove dependency on
69
+ # @return [void]
70
+ # @api public
71
+ def call(gem_name)
72
+ @gem_name = gem_name
73
+ matching_dependencies = dependencies.select { |d| d.dependency.gem_name == gem_name }
74
+
75
+ delete_dependencies(matching_dependencies) if matching_dependencies.any?
76
+ end
77
+
78
+ # Removes the matching dependencies from the gemspec
79
+ # @param matching_dependencies [Array<DependencyNode>] The existing dependencies that match gem_name
80
+ # @return [void]
81
+ # @api private
82
+ def delete_dependencies(matching_dependencies)
83
+ matching_dependencies.each do |found_dependency|
84
+ tree_rewriter.replace(full_line_range(found_dependency), '')
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Expand the range for a node to include any leading whitespace and newline
91
+ # @param dependency_node [DependencyNode] The node to remove
92
+ # @return [Parser::Source::Range] The range of the whole line including whitespace
93
+ # @api private
94
+ def full_line_range(dependency_node)
95
+ range = dependency_node.node.loc.expression
96
+ source_buffer = range.source_buffer
97
+ # The whole line including leading and trailing whitespace
98
+ line_range = source_buffer.line_range(range.line)
99
+ # Expand the range to include the leading newline
100
+ line_range.with(begin_pos: line_range.begin_pos - 1)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,73 @@
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
+ # Holds the components of a dependency declaration
12
+ #
13
+ # @api public
14
+ #
15
+ # A dependency declaration is a call to `add_dependency`,
16
+ # `add_runtime_dependency`, or `add_development_dependency` in the
17
+ # Gem::Specification block.
18
+ #
19
+ # For example, the following is a dependency declaration:
20
+ #
21
+ # ```ruby
22
+ # spec.add_dependency 'rubocop', '~> 1.0'
23
+ # ```
24
+ #
25
+ # Would be represented by a Dependency object with the following
26
+ # attributes:
27
+ # * method_name: :add_dependency
28
+ # * gem_name: 'rubocop'
29
+ # * version_constraint: '~> 1.0'
30
+ #
31
+ # @!attribute [r] method_name
32
+ # The name of the method called to add the dependency
33
+ #
34
+ # Must be one of the following:
35
+ #
36
+ # * :add_dependency
37
+ # * :add_runtime_dependency
38
+ # * :add_development_dependency
39
+ #
40
+ # @example
41
+ # dependency.method_name #=> :add_dependency
42
+ #
43
+ # @return [Symbol]
44
+ # @api public
45
+ #
46
+ # @!attribute [r] gem_name
47
+ # The name of the gem being depended on
48
+ # @example
49
+ # dependency.gem_name #=> "rubocop"
50
+ # @return [String]
51
+ # @api public
52
+ #
53
+ # @!attribute [r] version_constraints
54
+ # The version constraints for the dependency
55
+ # @example
56
+ # dependency.version_constraints #=> ["~> 1.68", ">= 1.68.3"]
57
+ # @return [Array<String>]
58
+ # @api public
59
+ #
60
+ # @!method to_a()
61
+ # Converts the dependency into an array
62
+ # @example
63
+ # dependency.to_a #=> [:add_runtime_dependency, "rubocop", "~> 1.68"]
64
+ # @return [Array]
65
+ # @api public
66
+ #
67
+ Dependency = Struct.new(:method_name, :gem_name, :version_constraints) do
68
+ def to_a = [method_name, gem_name, *version_constraints]
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
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
+ # Maps a dependency declaration to the AST that represents it
12
+ # @api public
13
+ #
14
+ # @!attribute [r] node
15
+ # The AST node for the dependency declaration
16
+ # @example
17
+ # dependency_node.node #=> ...
18
+ # @return [Parser::AST::Node]
19
+ # @api public
20
+ #
21
+ # @!attribute [r] dependency
22
+ # The components of the dependency declaration from the AST node
23
+ # @example
24
+ # dependency_node.dependency.to_a #=> [:add_dependency, 'rubocop', '~> 1.68']
25
+ # @return [Dependency]
26
+ # @api public
27
+ #
28
+ DependencyNode = Struct.new(:node, :dependency)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module GemBytes
5
+ module Actions
6
+ class Gemspec < Parser::TreeRewriter
7
+ # Holds information about the gemspec file
8
+ # @!attribute [r] gemspec_object
9
+ # @api private
10
+ class GemSpecification
11
+ # Create a new GemSpecification object
12
+ #
13
+ # @param source [String] The contents of the gemspec file
14
+ # @param source_path [String] The path to the gemspec file
15
+ # @api private
16
+ #
17
+ def initialize(source, source_path)
18
+ @source = source
19
+ @source_path = source_path
20
+
21
+ @dependencies = []
22
+ @attributes = []
23
+
24
+ @source_buffer, @source_ast = parse(source, source_path)
25
+ end
26
+
27
+ # The contents of the gemspec file
28
+ # @example
29
+ # data.source # => "Gem::Specification.new do |spec|\n spec.name = 'example'\nend"
30
+ # @return [String]
31
+ attr_reader :source
32
+
33
+ # The path to the gemspec file (used for error reporting)
34
+ # @example
35
+ # data.source_path # => "example.gemspec"
36
+ # @return [String]
37
+ attr_reader :soure_path
38
+
39
+ # The source buffer used when updating the gemspec file
40
+ # @return [Parser::Source::Buffer]
41
+ # @api private
42
+ attr_reader :source_buffer
43
+
44
+ # The parsed AST for the gemspec file source
45
+ # @example
46
+ # data.source_ast # => (send nil :puts)
47
+ # @return [Parser::AST::Node]
48
+ attr_reader :source_ast
49
+
50
+ # The Gem::Specification object
51
+ # @example
52
+ # data.gemspec_object # => #<Gem::Specification:0x00007f9b1b8b3f10>
53
+ # @return [Gem::Specification]
54
+ def gemspec_object
55
+ @gemspec_object ||= load_gem_specification
56
+ end
57
+
58
+ # The name of the Gem::Specification object within the gemspec block
59
+ # @example When the gemspec block starts `Gem::Specification.new do |spec|`
60
+ # data.gemspec_object_name # => :spec
61
+ # @return [Symbol]
62
+ attr_accessor :gemspec_object_name
63
+
64
+ # The AST node for the Gem::Specification block within the source
65
+ # @return [Parser::AST::Node]
66
+ attr_accessor :gemspec_ast
67
+
68
+ # The dependencies found in the gemspec file
69
+ # @return [Array<Dependency>]
70
+ attr_reader :dependencies
71
+
72
+ # The attributes found in the gemspec file
73
+ # @return [Array<Attribute>]
74
+ attr_reader :attributes
75
+
76
+ private
77
+
78
+ # Parses the given code into an AST
79
+ # @param source [String] The code to parse
80
+ # @param source_path [String] The path to the file being parsed (used for error messages only)
81
+ # @return [Array<Parser::AST::Node, Parser::Source::Buffer>] The AST and buffer
82
+ # @api private
83
+ def parse(source, source_path)
84
+ source_buffer = Parser::Source::Buffer.new(source_path, source: source)
85
+ processed_source = RuboCop::AST::ProcessedSource.new(source, ruby_version, source_path)
86
+ unless processed_source.valid_syntax?
87
+ raise "Invalid syntax in #{source_path}\n#{processed_source.diagnostics.map(&:render).join("\n")}"
88
+ end
89
+
90
+ source_ast = processed_source.ast
91
+ [source_buffer, source_ast]
92
+ end
93
+
94
+ # The currently running Ruby version as a float (MAJOR.MINOR only)
95
+ #
96
+ # @return [Float] The Ruby version number, e.g., 3.0
97
+ # @api private
98
+ def ruby_version = RUBY_VERSION.match(/^(?<version>\d+\.\d+)/)['version'].to_f
99
+
100
+ # Load the gemspec file into a Gem::Specification object
101
+ # @return [Gem::Specification] The Gem::Specification object
102
+ # @api private
103
+ def load_gem_specification
104
+ # Store the current $LOAD_PATH
105
+ original_load_path = $LOAD_PATH.dup
106
+
107
+ # Temporarily add 'lib' to the $LOAD_PATH
108
+ lib_path = File.expand_path('lib', Dir.pwd)
109
+ $LOAD_PATH.unshift(lib_path)
110
+
111
+ # Evaluate the gemspec file
112
+ eval(source, binding, '.').tap do # rubocop:disable Security/Eval
113
+ # Restore the original $LOAD_PATH
114
+ $LOAD_PATH.replace(original_load_path)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end