lock-gemfile 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 70164942502370d3ca05b5e8ed259e798dd21b6891d7e0bb6324daf444312dc1
4
+ data.tar.gz: cd2765434d8e9cbb9b73d657d0e36bbc78e10d3fb5d32254c4b8c9f3cf632553
5
+ SHA512:
6
+ metadata.gz: 24b9c62a24cc58031f3575abdc194a6bcb0621144e92e22e7c81194333a4220bdb15f00d30b9d996e70ec4bd20cc84c79bb5a100303d98b9948537e3e25eb98d
7
+ data.tar.gz: bd3338d1f6d64ac2d223884aeb5f9ec59fa185b53e5c00f576ac4aae27fb3d38ffd1f040d17ac96416aac532ccec9600c4442c89ec656c4531bdfed75b124c82
data/.rubocop.yml ADDED
@@ -0,0 +1,25 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Enabled: false
14
+ Metrics/MethodLength:
15
+ Enabled: false
16
+ Metrics/AbcSize:
17
+ Enabled: false
18
+ Metrics/CyclomaticComplexity:
19
+ Enabled: false
20
+ Metrics/PerceivedComplexity:
21
+ Enabled: false
22
+
23
+ Style/StringConcatenation:
24
+ Enabled: false
25
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-07-05
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Durable Programming Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # Lock::Gemfile
2
+
3
+ Lock::Gemfile is a Ruby library that provides functionality to update a Gemfile with locked versions - typically, from a corresponding Gemfile.lock file, but you can also provide arbitrary versions as well.
4
+
5
+ ## Installation
6
+
7
+ Install using RubyGems:
8
+
9
+ ```
10
+ $ gem install lock-gemfile
11
+ ```
12
+
13
+ Alternatively, if you intend to use this as a library, you can add this to your Gemfile:
14
+
15
+ ```
16
+ $ bundle add lock-gemfile
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Command-line Interface
22
+
23
+ Lock::Gemfile provides a command-line interface (CLI) through the `bin/lock-gemfile` script. You can run the script with the following command:
24
+
25
+ ```
26
+ $ lock-gemfile update GEMFILE [options]
27
+ ```
28
+
29
+ Replace `GEMFILE` with the path to your Gemfile.
30
+
31
+ #### Options
32
+
33
+ - `-w`, `--[no-]write`: Write the updated Gemfile back to disk (default: `false`).
34
+ - `-p`, `--[no-]pessimistic`: Use pessimistic version constraints (`~>`) (default: `true`).
35
+
36
+ #### Examples
37
+
38
+ Update a Gemfile and print the result to the console:
39
+
40
+ ```
41
+ $ lock-gemfile update Gemfile
42
+ ```
43
+
44
+ Update a Gemfile and write the changes back to the file:
45
+
46
+ ```
47
+ $ lock-gemfile update Gemfile --write
48
+ ```
49
+
50
+ Update a Gemfile using exact version constraints:
51
+
52
+ ```
53
+ $ lock-gemfile update Gemfile --no-pessimistic
54
+ ```
55
+
56
+ ### Library API
57
+
58
+ You can also use Lock::Gemfile as a library in your own Ruby code.
59
+
60
+ ```ruby
61
+ require 'lock/gemfile'
62
+
63
+ # Read the content of the Gemfile
64
+ gemfile_content = File.read('Gemfile')
65
+
66
+ # Parse the corresponding Gemfile.lock using Bundler's LockfileParser
67
+ lockfile = Bundler::LockfileParser.new(Bundler.read_file('Gemfile.lock'))
68
+
69
+ # Create a hash to store the desired versions of each gem
70
+ desired_versions = {}
71
+ lockfile.specs.each do |spec|
72
+ desired_versions[spec.name] = spec.version
73
+ end
74
+
75
+ # Create a buffer to hold the Gemfile content
76
+ buffer = Parser::Source::Buffer.new('(gemfile)')
77
+ buffer.source = gemfile_content
78
+
79
+ # Create a new Ruby parser
80
+ parser = Parser::CurrentRuby.new
81
+ # Parse the Gemfile content into an Abstract Syntax Tree (AST)
82
+ ast = parser.parse(buffer)
83
+
84
+ # Create a new instance of the Lock::Gemfile::Rewriter
85
+ rewriter = Lock::Gemfile::Rewriter.new
86
+ # Set the desired versions from the lockfile
87
+ rewriter.lockfile = desired_versions
88
+ # Set the pessimistic option
89
+ rewriter.pessimistic = true
90
+
91
+ # Rewrite the Gemfile AST with the locked versions
92
+ transformed_code = rewriter.rewrite(buffer, ast)
93
+
94
+ # Print the transformed Gemfile content
95
+ puts transformed_code
96
+ ```
97
+
98
+ ## How It Works
99
+
100
+ Lock::Gemfile uses the following steps to update a Gemfile with locked versions:
101
+
102
+ 1. Read the content of the specified Gemfile.
103
+ 2. Parse the corresponding Gemfile.lock using Bundler's LockfileParser.
104
+ 3. Create a hash to store the desired versions of each gem based on the lockfile.
105
+ 4. Create a buffer to hold the Gemfile content and parse it into an AST using the Parser gem.
106
+ 5. Create an instance of the Lock::Gemfile::Rewriter and set the desired versions and pessimistic option.
107
+ 6. Rewrite the Gemfile AST with the locked versions using the rewriter.
108
+ 7. Transform the modified AST back into source code.
109
+ 8. Print the transformed Gemfile content to the console or write it back to the file, depending on the options.
110
+
111
+ The core of the library is the Lock::Gemfile::Rewriter class, which is a subclass of Parser::TreeRewriter. It traverses the AST and looks for `gem` method calls. For each `gem` call found, it checks if a version specifier is already present. If not, it retrieves the locked version from the provided lockfile hash and inserts a version specifier string after the gem name. The version specifier can be either pessimistic or exact, depending on the value of the `pessimistic` attribute.
112
+
113
+ ## Development
114
+
115
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
116
+
117
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
118
+
119
+
120
+ ## License
121
+
122
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
123
+
124
+ ## Commercial Support
125
+
126
+ Commercial support for lock-gemfile and related tools is available from Durable Programming, LLC. You can contact us at [durableprogramming.com](https://www.durableprogramming.com).
127
+
128
+ ![Durable Programming, LLC Logo](https://durableprogramming.com/wp-content/uploads/2022/04/Full-logo-2color-1024x392.png)
129
+
130
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
data/bin/lock-gemfile ADDED
@@ -0,0 +1,98 @@
1
+ #!/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # lock-gemfile
5
+ #
6
+ # This script updates a Gemfile with locked versions from the
7
+ # corresponding Gemfile.lock file. It uses the Parser gem to parse the
8
+ # Gemfile into an Abstract Syntax Tree (AST), and then uses a custom
9
+ # TreeRewriter to modify the AST, inserting the locked versions from
10
+ # the lockfile. The modified AST is then transformed back into source
11
+ # code and either printed to the console or written back to the Gemfile,
12
+ # depending on the provided options.
13
+ #
14
+ # Usage:
15
+ # gemfile_updater.rb update GEMFILE [options]
16
+ #
17
+ # Options:
18
+ # -w, [--write], [--no-write] # Write the updated Gemfile back
19
+ # to disk (default: false) -p, [--pessimistic], [--no-pessimistic] #
20
+ # Use pessimistic version constraints (~>) (default: true)
21
+ #
22
+ # Examples:
23
+ # gemfile_updater.rb update Gemfile gemfile_updater.rb update Gemfile
24
+ # --write gemfile_updater.rb update Gemfile --no-pessimistic
25
+ #
26
+ # Dependencies:
27
+ # - parser - bundler - thor
28
+
29
+ require "parser/current"
30
+ require "bundler"
31
+ require "thor"
32
+
33
+ require_relative "../lib/lock/gemfile/rewriter"
34
+
35
+ class GemfileUpdater < Thor
36
+ #
37
+ # This class update a Gemfile with locked versions - designed to pull
38
+ # from the corresponding Gemfile.lock file, it can also accept an
39
+ # arbitrary hash of versions.. It uses the Parser gem to parse the
40
+ # Gemfile into an Abstract Syntax Tree (AST), and then uses a custom
41
+ # TreeRewriter to modify the AST, inserting the locked versions from
42
+ # the lockfile. The modified AST is then transformed back into source
43
+ # code and either printed to the console or written back to the Gemfile,
44
+ # depending on the provided options.
45
+ #
46
+ desc "update GEMFILE", "Update Gemfile with locked versions"
47
+
48
+ option :write, type: :boolean, default: false, aliases: "-w"
49
+ option :pessimistic, type: :boolean, default: true, aliases: "-p"
50
+
51
+ def update(gemfile)
52
+ # Read the content of the specified Gemfile
53
+ gemfile_content = File.read(gemfile)
54
+
55
+ # Parse the corresponding Gemfile.lock using Bundler's LockfileParser
56
+ lockfile = Bundler::LockfileParser.new(Bundler.read_file(gemfile + ".lock"))
57
+
58
+ # Create a hash to store the desired versions of each gem
59
+ desired_versions = {}
60
+
61
+ # Iterate over each gem specification in the lockfile
62
+ lockfile.specs.each do |spec|
63
+ # Store the gem name and its locked version in the desired_versions hash
64
+ desired_versions[spec.name] = spec.version
65
+ end
66
+
67
+ # Create a buffer to hold the Gemfile content
68
+ buffer = Parser::Source::Buffer.new("(gemfile)")
69
+ buffer.source = gemfile_content
70
+
71
+ # Create a new Ruby parser
72
+ parser = Parser::CurrentRuby.new
73
+ # Parse the Gemfile content into an Abstract Syntax Tree (AST)
74
+ ast = parser.parse(buffer)
75
+
76
+ # Create a new instance of the Lock::Gemfile::Rewriter
77
+ rewriter = Lock::Gemfile::Rewriter.new
78
+ # Set the desired versions from the lockfile
79
+ rewriter.lockfile = desired_versions
80
+ # Set the pessimistic option based on the command-line argument
81
+ rewriter.pessimistic = options[:pessimistic]
82
+
83
+ # Rewrite the Gemfile AST with the locked versions
84
+ transformed_code = rewriter.rewrite(buffer, ast)
85
+
86
+ # Print the transformed Gemfile content
87
+ puts transformed_code
88
+
89
+ # If the write option is not specified, exit the method
90
+ return unless options[:write]
91
+
92
+ # Write the transformed Gemfile content back to the original file
93
+ File.write(gemfile, transformed_code)
94
+ end
95
+ end
96
+
97
+ # Start the GemfileUpdater CLI with the provided command-line arguments
98
+ GemfileUpdater.start(ARGV)
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/current"
4
+
5
+ module Lock
6
+ module Gemfile
7
+ # The Lock::Gemfile::Rewriter class is a subclass of
8
+ # Parser::TreeRewriter that rewrites a Gemfile's Abstract Syntax
9
+ # Tree (AST) to include locked gem versions from a corresponding
10
+ # Gemfile.lock file.
11
+ #
12
+ # The rewriter traverses the AST and looks for `gem` method calls. For
13
+ # each `gem` call found, it checks if a version specifier is already
14
+ # present. If not, it retrieves the locked version from the provided
15
+ # lockfile hash and inserts a version specifier string after the
16
+ # gem name.
17
+ #
18
+ # The version specifier can be either pessimistic or exact, depending
19
+ # on the value of the `pessimistic` attribute. If `pessimistic` is
20
+ # true (default), the version specifier will be prefixed with "~>",
21
+ # otherwise it will be an exact version.
22
+ #
23
+ # Example usage:
24
+ #
25
+ # parser = Parser::CurrentRuby.new ast = parser.parse(buffer)
26
+ #
27
+ # rewriter = Lock::Gemfile::Rewriter.new rewriter.lockfile = {
28
+ # "rails" => "6.1.0", "puma" => "5.0.4"
29
+ # } rewriter.pessimistic = true
30
+ #
31
+ # modified_ast = rewriter.rewrite(buffer, ast)
32
+ #
33
+ # Attributes:
34
+ # lockfile (Hash): A hash containing gem names as keys and their
35
+ # locked versions as values. pessimistic (Boolean): Determines
36
+ # whether to use pessimistic version specifiers. Default is true.
37
+ #
38
+ # Methods:
39
+ # on_send(node): Called when a `:send` node is encountered in the
40
+ # AST. Checks if the node represents a `gem` method call
41
+ # and inserts the locked version specifier if
42
+ # applicable.
43
+ class Rewriter < Parser::TreeRewriter
44
+ attr_accessor :lockfile, :pessimistic
45
+
46
+ # Handles `:send` nodes in the AST, which represent method calls.
47
+ #
48
+ # If the node is a `gem` method call and doesn't already have
49
+ # a version specifier, retrieves the locked version from the
50
+ # `lockfile` hash and inserts a version specifier string.
51
+ #
52
+ # The version specifier can be either pessimistic or exact,
53
+ # depending on the value of `pessimistic`.
54
+ #
55
+ # Arguments:
56
+ #
57
+ # node (Parser::AST::Node): The `:send` node being processed.
58
+ #
59
+ def on_send(node)
60
+ return unless node.type == :send && node.children[1] == :gem
61
+
62
+ gem_name = node.children[2].children[0]
63
+
64
+ old_version_specifier = node.children[3]
65
+ already_has_version_specifier = old_version_specifier && old_version_specifier.type == :str
66
+
67
+ return if already_has_version_specifier
68
+
69
+ lockfile_gem_details = lockfile[gem_name]
70
+ new_version_specifier = lockfile_gem_details&.to_s
71
+ prefix = if pessimistic
72
+ "~> "
73
+ else
74
+ ""
75
+ end
76
+
77
+ return unless new_version_specifier
78
+
79
+ insert_after(node.children[2].location.end, ", '#{prefix}#{new_version_specifier}'")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lock
4
+ module Gemfile
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gemfile/version"
4
+
5
+ module Lock
6
+ module Gemfile
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module Lock
2
+ module Gemfile
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lock-gemfile
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Durable Programming Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ description: This gem provides a command-line tool to update a Gemfile with locked
56
+ versions from the corresponding Gemfile.lock file.
57
+ email:
58
+ - djberube@durableprogramming.com
59
+ executables:
60
+ - lock-gemfile
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".rubocop.yml"
65
+ - CHANGELOG.md
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/lock-gemfile
70
+ - lib/lock/gemfile.rb
71
+ - lib/lock/gemfile/rewriter.rb
72
+ - lib/lock/gemfile/version.rb
73
+ - sig/lock/gemfile.rbs
74
+ homepage: https://github.com/durableprogramming/lock-gemfile
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ homepage_uri: https://github.com/durableprogramming/lock-gemfile
79
+ source_code_uri: https://github.com/durableprogramming/lock-gemfile
80
+ changelog_uri: https://github.com/durableprogramming/lock-gemfile/blob/main/CHANGELOG.md
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.3.26
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: A tool to update Gemfile with locked versions
100
+ test_files: []