diff_gem 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 73ae8c129ddec8ddc5fb106a8d34d9bf7bdc42cabf49a076e70918d006cf4e72
4
+ data.tar.gz: ceb5f41c3b67cfd6d3f7b58f5bdf9ed599e7cdc1ed122eb8536819cb395a7dc5
5
+ SHA512:
6
+ metadata.gz: ff5a6fe8679e51eacb93bf8de2d9a8de23101c4bc13963b171d8fd0bdbff0c9fab8b4e131a71cae08bfd07ed5128ff595286000456bf0cd9c39f89a2cf0efa2b
7
+ data.tar.gz: d6162df393f490ec848d6f80a67432bb11ee5d203ae4ebf0655f6aa5df866a31642297703a3d098513e1c27f608059b05b89e09cdb229706eb7683b2ee2108ab
@@ -0,0 +1,23 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(mkdir:*)",
5
+ "Bash(chmod:*)",
6
+ "Bash(bundle install:*)",
7
+ "Bash(bundle exec ruby:*)",
8
+ "Bash(curl:*)",
9
+ "Bash(gem build:*)",
10
+ "Bash(git init:*)",
11
+ "Bash(git add:*)",
12
+ "Bash(git commit:*)",
13
+ "Bash(git rm:*)",
14
+ "Bash(gem install:*)",
15
+ "Bash(gemcompare minitest:*)",
16
+ "Bash(gemcompare --help:*)",
17
+ "Bash(gemcompare cache info:*)",
18
+ "Bash(git mv:*)"
19
+ ],
20
+ "deny": [],
21
+ "ask": []
22
+ }
23
+ }
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ diff_gem (0.1.0)
5
+ thor (~> 1.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ coderay (1.1.3)
11
+ diff-lcs (1.6.2)
12
+ method_source (1.1.0)
13
+ pry (0.15.2)
14
+ coderay (~> 1.1)
15
+ method_source (~> 1.0)
16
+ rspec (3.13.2)
17
+ rspec-core (~> 3.13.0)
18
+ rspec-expectations (~> 3.13.0)
19
+ rspec-mocks (~> 3.13.0)
20
+ rspec-core (3.13.6)
21
+ rspec-support (~> 3.13.0)
22
+ rspec-expectations (3.13.5)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.13.0)
25
+ rspec-mocks (3.13.7)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.13.0)
28
+ rspec-support (3.13.6)
29
+ thor (1.4.0)
30
+
31
+ PLATFORMS
32
+ arm64-darwin-22
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ diff_gem!
37
+ pry (~> 0.14)
38
+ rspec (~> 3.12)
39
+
40
+ BUNDLED WITH
41
+ 2.7.1
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # DiffGem
2
+
3
+ Compare the source code and metadata between two versions of a Ruby gem.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install diff_gem
9
+ ```
10
+
11
+ Or install locally:
12
+
13
+ ```bash
14
+ gem build diff_gem.gemspec
15
+ gem install diff_gem-0.1.0.gem
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Compare Source Code
21
+
22
+ Compare two versions of a gem:
23
+
24
+ ```bash
25
+ diff_gem GEM_NAME OLD_VERSION NEW_VERSION
26
+ ```
27
+
28
+ Example:
29
+
30
+ ```bash
31
+ diff_gem rails 7.0.0 7.0.1
32
+ ```
33
+
34
+ Or use the explicit command:
35
+
36
+ ```bash
37
+ diff_gem compare rails 7.0.0 7.0.1
38
+ ```
39
+
40
+ ### Compare Metadata
41
+
42
+ Compare gem metadata (dependencies, version info, file counts, etc.) without comparing source:
43
+
44
+ ```bash
45
+ diff_gem metadata rails 7.0.0 7.0.1
46
+ ```
47
+
48
+ ### Compare Both
49
+
50
+ Compare metadata and source code together:
51
+
52
+ ```bash
53
+ diff_gem compare --metadata rails 7.0.0 7.0.1
54
+ ```
55
+
56
+ ### Cache Management
57
+
58
+ View cache information:
59
+
60
+ ```bash
61
+ diff_gem cache info
62
+ ```
63
+
64
+ Clear the cache:
65
+
66
+ ```bash
67
+ diff_gem cache clear
68
+ ```
69
+
70
+ ### Custom Cache Directory
71
+
72
+ You can specify a custom cache directory:
73
+
74
+ ```bash
75
+ diff_gem --cache-dir /tmp/gem_cache rails 7.0.0 7.0.1
76
+ ```
77
+
78
+ Or set the environment variable:
79
+
80
+ ```bash
81
+ export DIFF_GEM_CACHE_DIR=/tmp/gem_cache
82
+ diff_gem rails 7.0.0 7.0.1
83
+ ```
84
+
85
+ ## How It Works
86
+
87
+ **Source Comparison:**
88
+ 1. Downloads both gem versions from rubygems.org
89
+ 2. Extracts the gem source code to a cache directory
90
+ 3. Runs `diff -r -u -N` to compare the source trees
91
+ 4. Displays the unified diff output
92
+
93
+ **Metadata Comparison:**
94
+ 1. Downloads gem metadata from rubygems.org
95
+ 2. Extracts and parses the gemspec information
96
+ 3. Compares dependencies, file counts, version requirements, and other metadata
97
+ 4. Displays a structured comparison showing added, removed, and changed fields
98
+
99
+ Downloaded gems are cached in `~/.diff_gem_cache` by default, so subsequent comparisons are faster.
100
+
101
+ ## Similar Tools
102
+
103
+ If `diff_gem` doesn't meet your needs, you might want to check out these alternatives:
104
+
105
+ - **[gemdiff](https://github.com/teeparham/gemdiff)** - Compare gem versions with GitHub integration, showing commits between releases and opening diffs in the browser
106
+ - **[gem-compare](https://github.com/sj26/gem-compare)** - Another tool for comparing gem versions with a focus on simplicity
107
+
108
+ `diff_gem` focuses on providing both detailed source code diffs and comprehensive metadata comparisons in a command-line interface.
109
+
110
+ ## License
111
+
112
+ MIT
data/exe/diff_gem ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/diff_gem'
4
+
5
+ # If first argument is not a known command, assume it's a gem name and prepend 'compare'
6
+ KNOWN_COMMANDS = %w[compare metadata version cache help]
7
+ if ARGV.length >= 3 && !KNOWN_COMMANDS.include?(ARGV[0])
8
+ ARGV.unshift('compare')
9
+ end
10
+
11
+ DiffGem::CLI.start(ARGV)
@@ -0,0 +1,150 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+
4
+ module DiffGem
5
+ class CacheCommands < Thor
6
+ desc "clear", "Clear the cache directory"
7
+ def clear
8
+ cache_dir = ENV['DIFF_GEM_CACHE_DIR'] || File.expand_path('~/.diff_gem_cache')
9
+
10
+ if File.exist?(cache_dir)
11
+ FileUtils.rm_rf(cache_dir)
12
+ puts "Cache cleared: #{cache_dir}"
13
+ else
14
+ puts "Cache directory does not exist: #{cache_dir}"
15
+ end
16
+ end
17
+
18
+ desc "info", "Show cache directory information"
19
+ def info
20
+ cache_dir = ENV['DIFF_GEM_CACHE_DIR'] || File.expand_path('~/.diff_gem_cache')
21
+
22
+ puts "Cache directory: #{cache_dir}"
23
+
24
+ if File.exist?(cache_dir)
25
+ gem_dirs = Dir.glob(File.join(cache_dir, '*', '*')).select { |f| File.directory?(f) }
26
+
27
+ size = `du -sh "#{cache_dir}" 2>/dev/null`.split.first rescue "unknown"
28
+
29
+ puts "Status: exists"
30
+ puts "Size: #{size}"
31
+ puts "Cached gem versions: #{gem_dirs.length}"
32
+
33
+ if gem_dirs.any?
34
+ puts "\nCached gems:"
35
+ gem_dirs.each do |dir|
36
+ parts = dir.split('/')
37
+ version = parts[-1]
38
+ name = parts[-2]
39
+ puts " #{name} #{version}"
40
+ end
41
+ end
42
+ else
43
+ puts "Status: does not exist"
44
+ end
45
+ end
46
+ end
47
+
48
+ class CLI < Thor
49
+ def self.exit_on_failure?
50
+ true
51
+ end
52
+
53
+ desc "compare GEM_NAME OLD_VERSION NEW_VERSION", "Compare two versions of a Ruby gem"
54
+ long_desc <<-LONGDESC
55
+ Compare the source trees of two different versions of a Ruby gem.
56
+
57
+ Downloads both gem versions, extracts their source code, and displays
58
+ the differences in unified diff format.
59
+
60
+ Example:
61
+ $ diff_gem rails 7.0.0 7.1.0
62
+
63
+ $ diff_gem compare rails 7.0.0 7.1.0
64
+
65
+ With metadata comparison:
66
+ $ diff_gem compare --metadata rails 7.0.0 7.1.0
67
+ LONGDESC
68
+ option :cache_dir,
69
+ type: :string,
70
+ desc: "Custom cache directory for downloaded gems"
71
+ option :metadata,
72
+ type: :boolean,
73
+ default: false,
74
+ desc: "Also compare gem metadata before showing source diff"
75
+
76
+ def compare(gem_name, old_version, new_version)
77
+ begin
78
+ # Show metadata comparison if requested
79
+ if options[:metadata]
80
+ metadata_comparer = MetadataComparer.new(
81
+ gem_name: gem_name,
82
+ old_version: old_version,
83
+ new_version: new_version
84
+ )
85
+ metadata_comparer.compare
86
+ puts "\n" + "=" * 80
87
+ puts "Source Code Comparison"
88
+ puts "=" * 80 + "\n\n"
89
+ end
90
+
91
+ # Show source comparison
92
+ comparer = Comparer.new(
93
+ gem_name: gem_name,
94
+ old_version: old_version,
95
+ new_version: new_version,
96
+ cache_dir: options[:cache_dir]
97
+ )
98
+
99
+ comparer.compare
100
+
101
+ rescue DiffGem::Error => e
102
+ puts "Error: #{e.message}"
103
+ exit 1
104
+ rescue => e
105
+ puts "Unexpected error: #{e.message}"
106
+ puts e.backtrace
107
+ exit 1
108
+ end
109
+ end
110
+
111
+ desc "metadata GEM_NAME OLD_VERSION NEW_VERSION", "Compare metadata of two gem versions"
112
+ long_desc <<-LONGDESC
113
+ Compare the metadata (dependencies, version info, etc.) of two different
114
+ versions of a Ruby gem without comparing source code.
115
+
116
+ Example:
117
+ $ diff_gem metadata rails 7.0.0 7.1.0
118
+ LONGDESC
119
+
120
+ def metadata(gem_name, old_version, new_version)
121
+ begin
122
+ metadata_comparer = MetadataComparer.new(
123
+ gem_name: gem_name,
124
+ old_version: old_version,
125
+ new_version: new_version
126
+ )
127
+ metadata_comparer.compare
128
+
129
+ rescue DiffGem::Error => e
130
+ puts "Error: #{e.message}"
131
+ exit 1
132
+ rescue => e
133
+ puts "Unexpected error: #{e.message}"
134
+ puts e.backtrace
135
+ exit 1
136
+ end
137
+ end
138
+
139
+ desc "version", "Show version"
140
+ def version
141
+ puts DiffGem::VERSION
142
+ end
143
+
144
+ desc "cache", "Manage cache directory"
145
+ subcommand "cache", CacheCommands
146
+
147
+ # Set compare as the default command
148
+ default_task :compare
149
+ end
150
+ end
@@ -0,0 +1,50 @@
1
+ require 'fileutils'
2
+
3
+ module DiffGem
4
+ class Comparer
5
+ attr_reader :gem_name, :old_version, :new_version, :cache_dir
6
+
7
+ def initialize(gem_name:, old_version:, new_version:, cache_dir: nil)
8
+ @gem_name = gem_name
9
+ @old_version = old_version
10
+ @new_version = new_version
11
+ @cache_dir = cache_dir || default_cache_dir
12
+ end
13
+
14
+ def compare
15
+ puts "Comparing #{gem_name}: #{old_version} -> #{new_version}\n\n"
16
+
17
+ extractor = GemExtractor.new(cache_dir)
18
+
19
+ # Extract both versions
20
+ old_path = extractor.extract_gem(gem_name, old_version)
21
+ new_path = extractor.extract_gem(gem_name, new_version)
22
+
23
+ puts "\nGenerating diff...\n\n"
24
+
25
+ # Generate and display diff
26
+ generate_diff(old_path, new_path)
27
+ end
28
+
29
+ private
30
+
31
+ def generate_diff(old_path, new_path)
32
+ # Use system diff command to generate comprehensive diff
33
+ # The diff command will:
34
+ # - Compare directories recursively (-r)
35
+ # - Use unified format (-u) for better readability
36
+ # - Show files that exist in only one directory (-N)
37
+ system("diff -r -u -N '#{old_path}' '#{new_path}'")
38
+
39
+ # diff returns non-zero exit status when there are differences,
40
+ # which is expected behavior
41
+ if $?.exitstatus > 1
42
+ raise Error, "Failed to generate diff (exit status: #{$?.exitstatus})"
43
+ end
44
+ end
45
+
46
+ def default_cache_dir
47
+ ENV['DIFF_GEM_CACHE_DIR'] || File.expand_path('~/.diff_gem_cache')
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,140 @@
1
+ require 'fileutils'
2
+ require 'rubygems/package'
3
+ require 'zlib'
4
+ require 'stringio'
5
+ require 'open-uri'
6
+ require 'net/http'
7
+
8
+ module DiffGem
9
+ class GemExtractor
10
+ attr_reader :cache_dir
11
+
12
+ def initialize(cache_dir)
13
+ @cache_dir = cache_dir
14
+ ensure_cache_dir_exists
15
+ end
16
+
17
+ def extract_gem(gem_name, version)
18
+ gem_cache_dir = File.join(cache_dir, gem_name, version)
19
+
20
+ # Return existing path if already downloaded and extracted
21
+ if File.exist?(gem_cache_dir) && !Dir.empty?(gem_cache_dir)
22
+ puts " Using cached: #{gem_name} #{version}"
23
+ return gem_cache_dir
24
+ end
25
+
26
+ puts " Downloading: #{gem_name} #{version}"
27
+
28
+ # Ensure directory exists
29
+ FileUtils.mkdir_p(gem_cache_dir)
30
+
31
+ # Download gem file
32
+ gem_file_path = download_gem(gem_name, version)
33
+
34
+ # Extract gem contents
35
+ extract_gem_archive(gem_file_path, gem_cache_dir)
36
+
37
+ # Clean up downloaded gem file
38
+ File.delete(gem_file_path) if File.exist?(gem_file_path)
39
+
40
+ gem_cache_dir
41
+ end
42
+
43
+ private
44
+
45
+ def download_gem(gem_name, version)
46
+ gem_filename = "#{gem_name}-#{version}.gem"
47
+ gem_file_path = File.join(cache_dir, gem_filename)
48
+
49
+ # Try to download from rubygems.org
50
+ gem_url = "https://rubygems.org/downloads/#{gem_filename}"
51
+
52
+ begin
53
+ uri = URI(gem_url)
54
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
55
+ request = Net::HTTP::Get.new(uri)
56
+ response = http.request(request)
57
+
58
+ if response.code == '200'
59
+ File.open(gem_file_path, 'wb') do |file|
60
+ file.write(response.body)
61
+ end
62
+ else
63
+ raise Error, "Failed to download #{gem_name} #{version}: HTTP #{response.code}"
64
+ end
65
+ end
66
+
67
+ gem_file_path
68
+ rescue => e
69
+ raise Error, "Failed to download #{gem_name} #{version}: #{e.message}"
70
+ end
71
+ end
72
+
73
+ def extract_gem_archive(gem_file_path, extract_dir)
74
+ begin
75
+ # Modern gems can be either gzipped tar or plain tar
76
+ File.open(gem_file_path, 'rb') do |gem_file|
77
+ # Try to read as gzipped first
78
+ tar_reader = begin
79
+ Gem::Package::TarReader.new(Zlib::GzipReader.new(gem_file))
80
+ rescue Zlib::GzipFile::Error
81
+ # If not gzipped, rewind and read as plain tar
82
+ gem_file.rewind
83
+ Gem::Package::TarReader.new(gem_file)
84
+ end
85
+
86
+ tar_reader.each do |entry|
87
+ next unless entry.file?
88
+
89
+ # Extract data.tar.gz which contains the actual gem files
90
+ if entry.full_name == 'data.tar.gz'
91
+ extract_data_tar(entry, extract_dir, compressed: true)
92
+ break
93
+ # Some gems might have uncompressed data.tar
94
+ elsif entry.full_name == 'data.tar'
95
+ extract_data_tar(entry, extract_dir, compressed: false)
96
+ break
97
+ end
98
+ end
99
+
100
+ tar_reader.close
101
+ end
102
+ rescue => e
103
+ raise Error, "Failed to extract #{gem_file_path}: #{e.message}"
104
+ end
105
+ end
106
+
107
+ def extract_data_tar(data_tar_entry, extract_dir, compressed:)
108
+ data = data_tar_entry.read
109
+
110
+ tar_reader = if compressed
111
+ Gem::Package::TarReader.new(Zlib::GzipReader.new(StringIO.new(data)))
112
+ else
113
+ Gem::Package::TarReader.new(StringIO.new(data))
114
+ end
115
+
116
+ tar_reader.each do |entry|
117
+ next if entry.directory?
118
+
119
+ file_path = File.join(extract_dir, entry.full_name)
120
+
121
+ # Ensure directory exists
122
+ FileUtils.mkdir_p(File.dirname(file_path))
123
+
124
+ # Write file contents
125
+ File.open(file_path, 'wb') do |file|
126
+ file.write(entry.read)
127
+ end
128
+
129
+ # Preserve file permissions
130
+ File.chmod(entry.header.mode, file_path) if entry.header.mode
131
+ end
132
+
133
+ tar_reader.close
134
+ end
135
+
136
+ def ensure_cache_dir_exists
137
+ FileUtils.mkdir_p(cache_dir)
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,240 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'rubygems/package'
4
+ require 'zlib'
5
+ require 'net/http'
6
+
7
+ module DiffGem
8
+ class MetadataComparer
9
+ attr_reader :gem_name, :old_version, :new_version
10
+
11
+ def initialize(gem_name:, old_version:, new_version:)
12
+ @gem_name = gem_name
13
+ @old_version = old_version
14
+ @new_version = new_version
15
+ end
16
+
17
+ def compare
18
+ puts "Fetching metadata for #{gem_name}..."
19
+
20
+ old_spec = fetch_gem_spec(gem_name, old_version)
21
+ new_spec = fetch_gem_spec(gem_name, new_version)
22
+
23
+ puts "\n" + "=" * 80
24
+ puts "Metadata Comparison: #{gem_name} #{old_version} → #{new_version}"
25
+ puts "=" * 80 + "\n"
26
+
27
+ compare_basic_info(old_spec, new_spec)
28
+ compare_dependencies(old_spec, new_spec)
29
+ compare_files(old_spec, new_spec)
30
+ compare_metadata(old_spec, new_spec)
31
+ end
32
+
33
+ private
34
+
35
+ def fetch_gem_spec(gem_name, version)
36
+ gem_filename = "#{gem_name}-#{version}.gem"
37
+ gem_url = "https://rubygems.org/downloads/#{gem_filename}"
38
+
39
+ begin
40
+ uri = URI(gem_url)
41
+
42
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
43
+ request = Net::HTTP::Get.new(uri)
44
+ request['User-Agent'] = 'DiffGem Ruby Gem Comparator'
45
+ gem_data = http.request(request)
46
+
47
+ if gem_data.code != '200'
48
+ raise Error, "Failed to download #{gem_name} #{version}: HTTP #{gem_data.code}"
49
+ end
50
+
51
+ # Parse the gem file to extract metadata
52
+ io = StringIO.new(gem_data.body)
53
+
54
+ tar_reader = begin
55
+ Gem::Package::TarReader.new(Zlib::GzipReader.new(io))
56
+ rescue Zlib::GzipFile::Error
57
+ io.rewind
58
+ Gem::Package::TarReader.new(io)
59
+ end
60
+
61
+ metadata = nil
62
+ tar_reader.each do |entry|
63
+ if entry.full_name == 'metadata.gz'
64
+ metadata = Zlib::GzipReader.new(StringIO.new(entry.read)).read
65
+ break
66
+ elsif entry.full_name == 'metadata'
67
+ metadata = entry.read
68
+ break
69
+ end
70
+ end
71
+ tar_reader.close
72
+
73
+ raise Error, "No metadata found in gem" unless metadata
74
+
75
+ # Use safe_load with permitted classes for security
76
+ permitted_classes = [
77
+ Gem::Specification,
78
+ Gem::Version,
79
+ Gem::Requirement,
80
+ Gem::Dependency,
81
+ Time,
82
+ Symbol,
83
+ Date,
84
+ DateTime
85
+ ]
86
+
87
+ YAML.safe_load(metadata, permitted_classes: permitted_classes, aliases: true)
88
+ end
89
+
90
+ rescue => e
91
+ raise Error, "Failed to fetch metadata for #{gem_name} #{version}: #{e.message}"
92
+ end
93
+ end
94
+
95
+ def compare_basic_info(old_spec, new_spec)
96
+ puts "Basic Information:"
97
+ puts "-" * 80
98
+
99
+ compare_field("Name", old_spec.name, new_spec.name)
100
+ compare_field("Version", old_spec.version.to_s, new_spec.version.to_s)
101
+ compare_field("Authors", old_spec.authors.join(', '), new_spec.authors.join(', '))
102
+ compare_field("Email", old_spec.email, new_spec.email)
103
+ compare_field("Homepage", old_spec.homepage, new_spec.homepage)
104
+ compare_field("License", old_spec.license || old_spec.licenses.join(', '),
105
+ new_spec.license || new_spec.licenses.join(', '))
106
+ compare_field("Summary", old_spec.summary, new_spec.summary)
107
+ compare_field("Description", old_spec.description, new_spec.description)
108
+ compare_field("Ruby Version Required", old_spec.required_ruby_version.to_s,
109
+ new_spec.required_ruby_version.to_s)
110
+ compare_field("RubyGems Version Required", old_spec.required_rubygems_version.to_s,
111
+ new_spec.required_rubygems_version.to_s)
112
+
113
+ puts
114
+ end
115
+
116
+ def compare_dependencies(old_spec, new_spec)
117
+ puts "Runtime Dependencies:"
118
+ puts "-" * 80
119
+
120
+ old_deps = old_spec.dependencies.select { |d| d.type == :runtime }
121
+ new_deps = new_spec.dependencies.select { |d| d.type == :runtime }
122
+
123
+ compare_dependency_list(old_deps, new_deps)
124
+
125
+ puts "\nDevelopment Dependencies:"
126
+ puts "-" * 80
127
+
128
+ old_dev_deps = old_spec.dependencies.select { |d| d.type == :development }
129
+ new_dev_deps = new_spec.dependencies.select { |d| d.type == :development }
130
+
131
+ compare_dependency_list(old_dev_deps, new_dev_deps)
132
+
133
+ puts
134
+ end
135
+
136
+ def compare_dependency_list(old_deps, new_deps)
137
+ old_dep_hash = old_deps.each_with_object({}) { |d, h| h[d.name] = d }
138
+ new_dep_hash = new_deps.each_with_object({}) { |d, h| h[d.name] = d }
139
+
140
+ all_dep_names = (old_dep_hash.keys + new_dep_hash.keys).uniq.sort
141
+
142
+ if all_dep_names.empty?
143
+ puts " (none)"
144
+ return
145
+ end
146
+
147
+ all_dep_names.each do |name|
148
+ old_dep = old_dep_hash[name]
149
+ new_dep = new_dep_hash[name]
150
+
151
+ if old_dep && !new_dep
152
+ puts " - #{name} #{old_dep.requirement} (REMOVED)"
153
+ elsif !old_dep && new_dep
154
+ puts " + #{name} #{new_dep.requirement} (ADDED)"
155
+ elsif old_dep.requirement.to_s != new_dep.requirement.to_s
156
+ puts " ~ #{name}: #{old_dep.requirement} → #{new_dep.requirement}"
157
+ else
158
+ puts " #{name} #{new_dep.requirement}"
159
+ end
160
+ end
161
+ end
162
+
163
+ def compare_files(old_spec, new_spec)
164
+ puts "Files:"
165
+ puts "-" * 80
166
+
167
+ old_files = old_spec.files.sort
168
+ new_files = new_spec.files.sort
169
+
170
+ removed = old_files - new_files
171
+ added = new_files - old_files
172
+ unchanged = old_files & new_files
173
+
174
+ puts " Total files: #{old_files.length} → #{new_files.length}"
175
+
176
+ if added.any?
177
+ puts "\n Added (#{added.length}):"
178
+ added.first(10).each { |f| puts " + #{f}" }
179
+ puts " ... and #{added.length - 10} more" if added.length > 10
180
+ end
181
+
182
+ if removed.any?
183
+ puts "\n Removed (#{removed.length}):"
184
+ removed.first(10).each { |f| puts " - #{f}" }
185
+ puts " ... and #{removed.length - 10} more" if removed.length > 10
186
+ end
187
+
188
+ puts "\n Unchanged: #{unchanged.length} files"
189
+ puts
190
+ end
191
+
192
+ def compare_metadata(old_spec, new_spec)
193
+ puts "Additional Metadata:"
194
+ puts "-" * 80
195
+
196
+ old_metadata = old_spec.metadata || {}
197
+ new_metadata = new_spec.metadata || {}
198
+
199
+ all_keys = (old_metadata.keys + new_metadata.keys).uniq.sort
200
+
201
+ if all_keys.empty?
202
+ puts " (none)"
203
+ puts
204
+ return
205
+ end
206
+
207
+ all_keys.each do |key|
208
+ old_value = old_metadata[key]
209
+ new_value = new_metadata[key]
210
+
211
+ if old_value && !new_value
212
+ puts " - #{key}: #{old_value}"
213
+ elsif !old_value && new_value
214
+ puts " + #{key}: #{new_value}"
215
+ elsif old_value != new_value
216
+ puts " ~ #{key}:"
217
+ puts " #{old_value}"
218
+ puts " → #{new_value}"
219
+ else
220
+ puts " #{key}: #{new_value}"
221
+ end
222
+ end
223
+
224
+ puts
225
+ end
226
+
227
+ def compare_field(label, old_value, new_value)
228
+ old_value = old_value.to_s.strip
229
+ new_value = new_value.to_s.strip
230
+
231
+ if old_value != new_value && !old_value.empty?
232
+ puts " ~ #{label}:"
233
+ puts " #{old_value}"
234
+ puts " → #{new_value}"
235
+ else
236
+ puts " #{label}: #{new_value}"
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,3 @@
1
+ module DiffGem
2
+ VERSION = "0.1.0"
3
+ end
data/lib/diff_gem.rb ADDED
@@ -0,0 +1,9 @@
1
+ require_relative "diff_gem/version"
2
+ require_relative "diff_gem/gem_extractor"
3
+ require_relative "diff_gem/comparer"
4
+ require_relative "diff_gem/metadata_comparer"
5
+ require_relative "diff_gem/cli"
6
+
7
+ module DiffGem
8
+ class Error < StandardError; end
9
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: diff_gem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Developer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
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: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.14'
55
+ description: A tool to download and compare the source trees of two different versions
56
+ of a Ruby gem, displaying differences in diff format
57
+ email:
58
+ - developer@example.com
59
+ executables:
60
+ - diff_gem
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".claude/settings.local.json"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - README.md
68
+ - exe/diff_gem
69
+ - lib/diff_gem.rb
70
+ - lib/diff_gem/cli.rb
71
+ - lib/diff_gem/comparer.rb
72
+ - lib/diff_gem/gem_extractor.rb
73
+ - lib/diff_gem/metadata_comparer.rb
74
+ - lib/diff_gem/version.rb
75
+ homepage: https://github.com/example/diff_gem
76
+ licenses:
77
+ - MIT
78
+ metadata:
79
+ homepage_uri: https://github.com/example/diff_gem
80
+ source_code_uri: https://github.com/example/diff_gem
81
+ changelog_uri: https://github.com/example/diff_gem/CHANGELOG.md
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 2.6.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.4.10
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Compare source code between two versions of a Ruby gem
101
+ test_files: []