Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
# Change Log

## TODO
- Diff:
- Gems:
- benchmark
- brakeman
- json (not using CHANGES.md?)
- nio4r not using releases.md?
- parser not using CHANGELOG.md?
- actioncable-next uses release tag names?
- paper_trail not using CHANGELOG.md?
- playwright-ruby-client uses release tags?
- bundler itself
- use changelog files from installed gems where present

## Unreleased

- Added `gemstar server`, your interactive Gemfile.lock explorer and more.
- Default location for `diff` is now a tmp file.
- Removed Railtie from this gem.
- Improve how git root dir is determined.


## 0.0.2

- Diff: Fix regex warnings shown in terminal.
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ To examine a specific Gemfile.lock, pass it like this:
gemstar diff --lockfile=~/MyProject/Gemfile.lock
```

### gemstar server

Start the interactive web UI:

```shell
gemstar server
```

By default, the server listens to http://127.0.0.1:2112/

## Contributing

Bug reports and pull requests are welcome on GitHub at [https://github.com/FDj/gemstar](https://github.com/FDj/gemstar).
Expand Down
23 changes: 23 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## Gemstar TODO
### Diff
- Gems:
- benchmark
- brakeman
- json (not using CHANGES.md?)
- nio4r not using releases.md?
- parser not using CHANGELOG.md?
- actioncable-next uses release tag names?
- paper_trail not using CHANGELOG.md?
- playwright-ruby-client uses release tags?
- bundler itself
- use changelog files from installed gems where present
- use 'gh' tool to fetch GitHub releases
- support downgrading pinned gems, i.e. minitest 6.0 -> 5.x
- read release notes from locally installed gems
- for each gem, show why it's included (Gemfile or 2nd dependency)

### Server
- Add... project in web ui
- bundle install, bundle update, etc.
- possibly add gem in web ui
- upgrade/downgrade/pin specific versions
4 changes: 4 additions & 0 deletions gemstar.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ Gem::Specification.new do |s|
s.add_development_dependency "combustion", "~> 1.5"
s.add_development_dependency "rake", "~> 13.0"
s.add_development_dependency "minitest", "~> 5.0"
s.add_development_dependency "rerun", "~> 0.14"

s.add_dependency "kramdown", "~> 2.0"
s.add_dependency "kramdown-parser-gfm", "~> 1.0"
s.add_dependency "rouge", "~> 4"
s.add_dependency "concurrent-ruby", "~> 1.0"
s.add_dependency "thor", "~> 1.4"
s.add_dependency "nokogiri", "~> 1.18"
s.add_dependency "roda", "~> 3.90"
s.add_dependency "rackup", "~> 2.2"
s.add_dependency "webrick", "~> 1.9"
end
6 changes: 6 additions & 0 deletions lib/gemstar.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# frozen_string_literal: true

require "gemstar/version"
require "gemstar/cache_cli"
require "gemstar/cli"
require "gemstar/cache_warmer"
require "gemstar/commands/command"
require "gemstar/commands/cache"
require "gemstar/commands/diff"
require "gemstar/commands/server"
require "gemstar/config"
require "gemstar/outputs/basic"
require "gemstar/outputs/html"
require "gemstar/cache"
require "gemstar/change_log"
require "gemstar/git_hub"
require "gemstar/lock_file"
require "gemstar/project"
require "gemstar/remote_repository"
require "gemstar/utils"
require "gemstar/ruby_gems_metadata"
Expand Down
57 changes: 47 additions & 10 deletions lib/gemstar/cache.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
require_relative "config"
require "fileutils"
require "digest"

module Gemstar
class Cache
MAX_CACHE_AGE = 60 * 60 * 24 * 7 # 1 week
CACHE_DIR = ".gem_changelog_cache"
CACHE_DIR = File.join(Gemstar::Config.home_directory, "cache")

@@initialized = false

Expand All @@ -18,15 +19,12 @@ def self.init
def self.fetch(key, &block)
init

path = File.join(CACHE_DIR, Digest::SHA256.hexdigest(key))
path = path_for(key)

if File.exist?(path)
age = Time.now - File.mtime(path)
if age <= MAX_CACHE_AGE
content = File.read(path)
return nil if content == "__404__"
return content
end
if fresh?(path)
content = File.read(path)
return nil if content == "__404__"
return content
end

begin
Expand All @@ -39,11 +37,50 @@ def self.fetch(key, &block)
end
end

def self.peek(key)
init

path = path_for(key)
return nil unless fresh?(path)

content = File.read(path)
return nil if content == "__404__"

content
end

def self.path_for(key)
File.join(CACHE_DIR, Digest::SHA256.hexdigest(key))
end

def self.fresh?(path)
return false unless File.exist?(path)

(Time.now - File.mtime(path)) <= MAX_CACHE_AGE
end

def self.flush!
init

flush_directory(CACHE_DIR)
end

def self.flush_directory(directory)
return 0 unless Dir.exist?(directory)

entries = Dir.children(directory)
entries.each do |entry|
FileUtils.rm_rf(File.join(directory, entry))
end

entries.count
end

end

def edit_gitignore
gitignore_path = ".gitignore"
ignore_entries = %w[.gem_changelog_cache/ gem_update_changelog.html]
ignore_entries = %w[gem_update_changelog.html]

existing_lines = File.exist?(gitignore_path) ? File.read(gitignore_path).lines.map(&:chomp) : []

Expand Down
12 changes: 12 additions & 0 deletions lib/gemstar/cache_cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require "thor"

module Gemstar
class CacheCLI < Thor
package_name "gemstar cache"

desc "flush", "Clear all gemstar cache entries"
def flush
Gemstar::Commands::Cache.new({}).flush
end
end
end
120 changes: 120 additions & 0 deletions lib/gemstar/cache_warmer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
require "set"
require "thread"

module Gemstar
class CacheWarmer
DEFAULT_THREADS = 10

def initialize(io: $stderr, debug: false, thread_count: DEFAULT_THREADS)
@io = io
@debug = debug
@thread_count = thread_count
@mutex = Mutex.new
@condition = ConditionVariable.new
@queue = []
@queued = Set.new
@in_progress = Set.new
@completed = Set.new
@workers = []
@started = false
@total = 0
@completed_count = 0
end

def enqueue_many(gem_names)
names = gem_names.uniq

@mutex.synchronize do
names.each do |gem_name|
next if @completed.include?(gem_name) || @queued.include?(gem_name) || @in_progress.include?(gem_name)

@queue << gem_name
@queued << gem_name
end
@total += names.count
start_workers_unlocked unless @started
end

log "Background cache refresh queued for #{names.count} gems."
@condition.broadcast
self
end

def prioritize(gem_name)
@mutex.synchronize do
return if @completed.include?(gem_name) || @in_progress.include?(gem_name)

if @queued.include?(gem_name)
@queue.delete(gem_name)
else
@queued << gem_name
@total += 1
end

@queue.unshift(gem_name)
start_workers_unlocked unless @started
end

log "Prioritized #{gem_name}"
@condition.broadcast
end

private

def start_workers_unlocked
return if @started

@started = true
@thread_count.times do
@workers << Thread.new { worker_loop }
end
end

def worker_loop
Thread.current.name = "gemstar-cache-worker" if Thread.current.respond_to?(:name=)

loop do
gem_name = @mutex.synchronize do
while @queue.empty?
@condition.wait(@mutex)
end

next_gem = @queue.shift
@queued.delete(next_gem)
@in_progress << next_gem
next_gem
end

warm_cache_for_gem(gem_name)

current = @mutex.synchronize do
@in_progress.delete(gem_name)
@completed << gem_name
@completed_count += 1
end

log_progress(gem_name, current)
end
end

def warm_cache_for_gem(gem_name)
metadata = Gemstar::RubyGemsMetadata.new(gem_name)
metadata.meta
metadata.repo_uri
Gemstar::ChangeLog.new(metadata).sections
rescue StandardError => e
log "Cache refresh failed for #{gem_name}: #{e.class}: #{e.message}"
end

def log_progress(gem_name, current)
return unless @debug
return unless current <= 5 || (current % 25).zero?

log "Background cache refresh #{current}/#{@total}: #{gem_name}"
end

def log(message)
@io.puts(message)
end
end
end
Loading
Loading