diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index 32c9d0b..ae3fe97 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.5 + ruby-version: 3.2 - name: Install dependencies run: bundle install - name: Checking offenses @@ -28,7 +28,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: 2.5 + ruby-version: 3.2 - name: Install dependencies run: bundle install - name: Testing docs @@ -38,7 +38,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.5, 2.6, 2.7.2, head] + ruby: [3.2, 3.3, 3.4, 4.0, head] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -73,7 +73,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.5, 2.6, 2.7.2, head] + ruby: [3.2, 3.3, 3.4, 4.0, head] env: CI_PLATFORM: "macos" runs-on: macos-latest diff --git a/.rubocop.yml b/.rubocop.yml index 6121c2f..95efa75 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -98,6 +98,6 @@ RSpec/MultipleMemoizedHelpers: Enabled: false AllCops: - TargetRubyVersion: 2.5 + TargetRubyVersion: 3.2 Exclude: - '**/*_example.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index 8463579..81ae4eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,20 @@ # Changelog +## [0.3.2-beta2] +Ruby 3.5 compatibility release. +- Add gem `benchmark` to gemspec +- Add gem `ostruct` to gemspec +- Update dependency `byebug` + +## [0.3.2-beta1] +Ruby 3.4 compatibility release for testing. + +### Features +- Ruby 3.4 support and compatibility +- Updated CI to test against Ruby 3.0-3.4 + +### Bug fixes +- Fix Ruby 3.4.6 Reline interceptor compatibility +- Fix Ruby 3.4.6 compatibility in path classifier ## [0.3.1] This release fixes bunch of bugs, and performance issues reported by the users after beta launch. No new features are introduced. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..065127e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Ruby Jard is a terminal-based debugger for Ruby that provides a rich UI wrapping around Byebug. It offers features like highlighted source code, stacktrace visualization, variable exploration, and multi-thread debugging. + +## Development Commands + +### Testing +- `bundle exec rspec` - Run all tests +- `bundle exec rspec spec/path/to/specific_spec.rb` - Run specific test file +- `bundle exec parallel_rspec spec/` - Run tests in parallel + +### Code Quality +- `bundle exec rubocop` - Run linting +- `bundle exec rubocop -a` - Auto-fix linting issues + +### Build & Install +- `bundle install` - Install dependencies +- `rake build` - Build the gem +- `rake install` - Install the gem locally +- `rake release` - Release the gem (maintainers only) + +## Architecture Overview + +### Core Components + +**Session Management (`lib/ruby_jard/session.rb`)** +- Entry point for debugging sessions +- Manages the lifecycle of debugging contexts + +**REPL System** +- `repl_manager.rb` - Manages REPL interactions +- `repl_processor.rb` - Processes commands and input +- `repl_interceptor.rb` - Intercepts and handles debugging flow +- `repl_state.rb` - Tracks REPL state + +**Screen & Layout System** +- `screen_manager.rb` - Manages multiple debug screens +- `screen_renderer.rb` - Renders screen content +- `layout_*.rb` files - Different layout configurations (wide, narrow, tiny) +- `screens/` directory - Individual screen implementations (source, variables, backtrace, etc.) + +**Inspection System** (`lib/ruby_jard/inspectors/`) +- Modular object inspection with specialized inspectors for different data types +- `base.rb` - Base inspector class +- Type-specific inspectors for arrays, hashes, objects, strings, etc. + +**Command System** (`lib/ruby_jard/commands/`) +- Debugging commands like step, next, continue, frame navigation +- Each command is a separate class inheriting from `base_command.rb` + +### Key Design Patterns + +- **Configuration-driven**: Uses `config.rb` for user customization +- **Modular screens**: Each debug view is a separate screen class +- **Responsive layouts**: Multiple layout templates adapt to terminal size +- **Command pattern**: Debug commands are individual classes +- **Decorator pattern**: Used for colorizing and formatting output + +### Integration Points + +- **Byebug Integration**: Core debugging functionality via Byebug gem +- **Pry Integration**: REPL functionality via Pry gem +- **TTY Integration**: Terminal UI via tty-screen gem + +### Development Notes + +- The codebase uses frozen string literals throughout +- Main entry point is the `jard` method added to Kernel module +- Debugging state is managed through thread-local variables +- UI rendering is optimized for different terminal sizes and capabilities \ No newline at end of file diff --git a/Gemfile b/Gemfile index 1b1c841..60401b4 100644 --- a/Gemfile +++ b/Gemfile @@ -9,12 +9,12 @@ if Gem.path.all? { |path| Dir[File.join(path, 'gems/jard_merge_sort*')].empty? } puts `gem install ./spec/examples/jard_merge_sort/jard_merge_sort-0.1.0.gem` end -gem 'byebug', '~> 11.1.0' +gem 'byebug', '~> 12.0' gem 'jard_merge_sort', require: false gem 'rake', '~> 12.0' gem 'rspec', '~> 3.0' -gem 'rubocop', '~> 0.89.1' -gem 'rubocop-rspec', '~> 1.43.1', require: false +gem 'rubocop', '~> 1.81', '>= 1.81.7' +gem 'rubocop-rspec', '~> 3.7', require: false gem 'tty-markdown', '~>0.7.0' group :test do diff --git a/lib/ruby_jard/path_classifier.rb b/lib/ruby_jard/path_classifier.rb index c78b2ff..42c1f43 100644 --- a/lib/ruby_jard/path_classifier.rb +++ b/lib/ruby_jard/path_classifier.rb @@ -12,7 +12,7 @@ class PathClassifier GEM_PATTERN = /(.*)-(\d+\.\d+[.\d]*[.\d]*[-.\w]*)/i.freeze STDLIB_PATTERN = /(.*)\.rb$/.freeze INTERNAL_PATTERN = /]+>/.freeze - EVALUATION_SIGNATURE = '(eval)' + EVALUATION_PATTERN = /^\(eval.*\)$/.freeze RUBY_SCRIPT_SIGNATURE = '-e' TYPES = [ @@ -80,27 +80,33 @@ def try_classify_internal(path) end def try_classify_stdlib(path) - lib_dir = RbConfig::CONFIG['rubylibdir'].to_s.strip + lib_dirs = [ + RbConfig::CONFIG['rubylibdir'], + RbConfig::CONFIG['sitelibdir'] + ].compact.map(&:strip).reject(&:empty?) - return false if lib_dir.empty? - return false unless path.start_with?(lib_dir) + lib_dirs.each do |lib_dir| + next unless path.start_with?(lib_dir) - splitted_path = - path[lib_dir.length..-1] - .split('/') - .reject(&:empty?) - lib_name = splitted_path.first - match = STDLIB_PATTERN.match(lib_name) - lib_name = match[1] if match + splitted_path = + path[lib_dir.length..-1] + .split('/') + .reject(&:empty?) + lib_name = splitted_path.first + match = STDLIB_PATTERN.match(lib_name) + lib_name = match[1] if match - [true, lib_name, splitted_path.join('/')] + return [true, lib_name, splitted_path.join('/')] + end + + false rescue NameError # RbConfig is not available false end def try_classify_evaluation(path) - path == EVALUATION_SIGNATURE + path =~ EVALUATION_PATTERN end def try_classify_ruby_script(path) diff --git a/lib/ruby_jard/repl_interceptor.rb b/lib/ruby_jard/repl_interceptor.rb index 5bbf5c3..a5c873e 100644 --- a/lib/ruby_jard/repl_interceptor.rb +++ b/lib/ruby_jard/repl_interceptor.rb @@ -104,9 +104,22 @@ def redirected_output def interceptable? return false unless defined?(PTY) - return false if defined?(Reline) && Readline == Reline - return false if RubyJard::Reflection.instance.call_method(::Readline, :input=).source_location != nil - return false if RubyJard::Reflection.instance.call_method(::Readline, :output=).source_location != nil + + # In Ruby 3.4+, Reline is the default Readline implementation and should work fine + if RUBY_VERSION >= '3.4.0' + # Allow forwardable delegation in Ruby 3.4+ (methods will have source_location in forwardable.rb) + input_location = RubyJard::Reflection.instance.call_method(::Readline, :input=).source_location + output_location = RubyJard::Reflection.instance.call_method(::Readline, :output=).source_location + + # Only reject if patched by non-standard sources (not forwardable.rb) + return false if input_location && !input_location[0].include?('forwardable.rb') + return false if output_location && !output_location[0].include?('forwardable.rb') + else + # Original logic for older Ruby versions + return false if defined?(Reline) && Readline == Reline + return false if RubyJard::Reflection.instance.call_method(::Readline, :input=).source_location != nil + return false if RubyJard::Reflection.instance.call_method(::Readline, :output=).source_location != nil + end true end diff --git a/lib/ruby_jard/version.rb b/lib/ruby_jard/version.rb index 7c99e1b..4b67a08 100644 --- a/lib/ruby_jard/version.rb +++ b/lib/ruby_jard/version.rb @@ -2,5 +2,5 @@ # Semantic versionn module RubyJard - VERSION = '0.3.1' + VERSION = '0.3.2.beta2' end diff --git a/ruby_jard.gemspec b/ruby_jard.gemspec index 75a1e71..2d73c0c 100644 --- a/ruby_jard.gemspec +++ b/ruby_jard.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |spec| editors.' spec.homepage = 'https://github.com/nguyenquangminh0711/ruby_jard' spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.2.0', '< 4.1.0') spec.metadata['allowed_push_host'] = 'https://rubygems.org' @@ -32,7 +32,11 @@ Gem::Specification.new do |spec| spec.executables = [] spec.require_paths = ['lib'] - spec.add_runtime_dependency 'byebug', '>= 9.1', '< 12.0' - spec.add_runtime_dependency 'pry', '~> 0.13.0' + spec.add_runtime_dependency 'benchmark', '~> 0.5.0' + spec.add_runtime_dependency 'bigdecimal', '~> 3.2', '>= 3.2.3' + spec.add_runtime_dependency 'byebug', '~> 12.0' + spec.add_runtime_dependency 'mutex_m', '~> 0.3.0' + spec.add_runtime_dependency 'ostruct', '~> 0.6.3' + spec.add_runtime_dependency 'pry', '~> 0.14.0' spec.add_runtime_dependency 'tty-screen', '~> 0.8.1' end diff --git a/spec/helpers/integration_helper.rb b/spec/helpers/integration_helper.rb index aa95166..fa88561 100644 --- a/spec/helpers/integration_helper.rb +++ b/spec/helpers/integration_helper.rb @@ -45,6 +45,7 @@ def start '-c', @dir, '-t', @target, '-n', 'main', + '-e', 'RUBYOPT=-W0', @command ) sleep 0.5 diff --git a/spec/ruby_jard/path_classifier_spec.rb b/spec/ruby_jard/path_classifier_spec.rb index 6531f57..b392b52 100644 --- a/spec/ruby_jard/path_classifier_spec.rb +++ b/spec/ruby_jard/path_classifier_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'uri' +require 'unicode_normalize/normalize' RSpec.describe RubyJard::PathClassifier do subject(:classifier) { described_class.new } @@ -28,7 +28,7 @@ end context 'when input path is in current dir which is also gem path' do - let(:dir) { Gem.path.first } + let(:dir) { Gem.path.find { |path| Dir.exist?(path) } } it 'returns source tree' do Dir.chdir(dir) do @@ -99,8 +99,8 @@ context 'when input path is a standard lib sub folder' do it 'returns stdlib, and relative path' do - expect(classifier.classify(URI::HTTP.method(:build).source_location.first)).to eq( - [:stdlib, 'uri', 'uri/http.rb'] + expect(classifier.classify(UnicodeNormalize.method(:normalize).source_location.first)).to eq( + [:stdlib, 'unicode_normalize', 'unicode_normalize/normalize.rb'] ) end end