diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 07aacc5..2945ba4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: - matrix: { ruby: ['3.2', '3.3', '3.4'] } + matrix: { ruby: ['3.2', '3.3', '3.4', '4.0'] } steps: - name: Check out code diff --git a/lib/victor/cli/commands/render.rb b/lib/victor/cli/commands/render.rb index 35120f5..2e88780 100644 --- a/lib/victor/cli/commands/render.rb +++ b/lib/victor/cli/commands/render.rb @@ -1,4 +1,4 @@ -require 'filewatcher' +require 'listen' module Victor module CLI @@ -56,25 +56,36 @@ def generate end def watch - say "Watching #{ruby_file} for changes" - file_watcher.watch do |changes| - changes.each_value do |event| - yield unless event == :deleted - end + l = listener do |modified, added, _removed| + changes = modified + added + yield unless changes.empty? end + l.start + sleep end def watch_and_generate - watch do - generate - rescue => e - say! "ru`#{e.class}`\n#{e.message}" + say "Watching #{ruby_file} for changes" + safe_generate + begin + watch { safe_generate } + rescue Interrupt + say "\nGoodbye" end end - def file_watcher - @file_watcher ||= Filewatcher.new(ruby_file, immediate: true) + def safe_generate + generate + rescue => e + say! "ru`#{e.class}`\n#{e.message}" end + + def listener(&) + Listen.to(ruby_dir, force_polling: true, latency: 3, only: ruby_glob, &) + end + + def ruby_dir = @ruby_dir ||= File.dirname(ruby_file) + def ruby_glob = @ruby_glob ||= /\A#{Regexp.escape(File.basename(ruby_file))}\z/ end end end diff --git a/spec/approvals/cli/render/watch-interrupt b/spec/approvals/cli/render/watch-interrupt new file mode 100644 index 0000000..14023b2 --- /dev/null +++ b/spec/approvals/cli/render/watch-interrupt @@ -0,0 +1,3 @@ +Watching spec/fixtures/render/pacman_dsl.rb for changes + +Goodbye diff --git a/spec/victor-cli/commands/render_spec.rb b/spec/victor-cli/commands/render_spec.rb index 7d33243..aa78ab2 100644 --- a/spec/victor-cli/commands/render_spec.rb +++ b/spec/victor-cli/commands/render_spec.rb @@ -1,6 +1,6 @@ describe Commands::Render do let(:ruby_file) { 'spec/fixtures/render/pacman_dsl.rb' } - let(:filewatcher) { Filewatcher.new 'path' } + let(:listener) { double(:listener, start: nil) } context 'without arguments' do it 'shows short usage' do @@ -29,29 +29,53 @@ end context 'with RUBY_FILE --watch' do - it 'generates immediately and on change' do - allow(Filewatcher).to receive(:new).and_return(filewatcher) - expect(filewatcher).to receive(:watch) do |_watcher, &block| - changes = { 'some-path' => :updated } - block.call changes - end + it 'generates immediately and watches for changes' do + expect(Listen).to receive(:to).and_return(listener) + expect(listener).to receive(:start) + allow(subject).to receive(:sleep) expect { subject.execute %W[render #{ruby_file} --watch] } .to output_approval('cli/render/watch') end + it 'yields only when changes are present' do + expect(subject).to receive(:listener) + .and_yield([], [], []) + .and_yield(['changed'], [], []) + .and_return(listener) + expect(listener).to receive(:start) + allow(subject).to receive(:sleep) + + count = 0 + subject.send(:watch) { count += 1 } + expect(count).to eq 1 + end + context 'when the script contains an error' do it 'shows it gracefully and continues to watch' do - expect(subject).to receive(:watch) do |_watcher, &block| - allow(subject).to receive(:generate).and_raise('Intentional error') - changes = { 'some-path' => :updated } - block.call changes + call_count = 0 + allow(subject).to receive(:generate) do + call_count += 1 + raise 'Intentional error' if call_count == 2 + end + expect(subject).to receive(:watch) do |&block| + block.call end expect { subject.execute %W[render #{ruby_file} --watch] } .to output_approval('cli/render/watch-error').to_stderr end end + + context 'when interrupted' do + it 'shows a friendly message' do + allow(subject).to receive(:safe_generate) + allow(subject).to receive(:watch).and_raise(Interrupt) + + expect { subject.execute %W[render #{ruby_file} --watch] } + .to output_approval('cli/render/watch-interrupt') + end + end end context 'with RUBY_FILE --save SVG_FILE' do diff --git a/victor-cli.gemspec b/victor-cli.gemspec index 15f6bfc..3e1c97a 100644 --- a/victor-cli.gemspec +++ b/victor-cli.gemspec @@ -17,7 +17,8 @@ Gem::Specification.new do |s| s.add_dependency 'colsole', '~> 1.0' s.add_dependency 'css_parser', '~> 1.7' - s.add_dependency 'filewatcher', '~> 2.0' + s.add_dependency 'listen', '~> 3.9' + s.add_dependency 'logger', '~> 1.7' # required by lietsn s.add_dependency 'mister_bin', '~> 0.7' s.add_dependency 'nokogiri', '~> 1.10' s.add_dependency 'pretty_trace', '~> 0.3' @@ -25,11 +26,6 @@ Gem::Specification.new do |s| s.add_dependency 'rufo', '~> 0.12' s.add_dependency 'victor', '~> 0.4' - # FIXME: Remove when resolved. - # This is a sub-dependency of filewatcher which does not bundle logger. - # ref: https://github.com/filewatcher/filewatcher/pull/272 - s.add_dependency 'logger', '~> 1.6' - s.metadata = { 'bug_tracker_uri' => 'https://github.com/DannyBen/victor-cli/issues', 'changelog_uri' => 'https://github.com/DannyBen/victor-cli/blob/master/CHANGELOG.md',