diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1506c1d..07aacc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: - matrix: { ruby: ['3.1', '3.2', '3.3', '3.4'] } + matrix: { ruby: ['3.2', '3.3', '3.4'] } steps: - name: Check out code diff --git a/.rubocop.yml b/.rubocop.yml index b5809ea..ce44a99 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,4 @@ -require: +plugins: - rubocop-rspec - rubocop-performance @@ -9,10 +9,11 @@ inherit_gem: AllCops: SuggestExtensions: false - TargetRubyVersion: 3.1 + TargetRubyVersion: 3.2 Exclude: - debug.rb - 'dev/**/*' + - 'tmp/**/*' - 'spec/approvals/**/*' - 'spec/tmp/**/*' # This file contains errors on purpose diff --git a/bin/victor b/bin/victor index 115cbe5..bb92a5f 100755 --- a/bin/victor +++ b/bin/victor @@ -3,6 +3,7 @@ require 'victor/cli' require 'pretty_trace/enable-trim' include Colsole + PrettyTrace.filter [/gem/, /lib/, %r{bin/victor}, %r{bin/bundle}] PrettyTrace.debug_tip diff --git a/lib/victor/cli/commands/convert.rb b/lib/victor/cli/commands/convert.rb index c551547..3be634b 100644 --- a/lib/victor/cli/commands/convert.rb +++ b/lib/victor/cli/commands/convert.rb @@ -4,7 +4,7 @@ module Commands class Convert < Base summary 'Convert SVG to Ruby code' - usage 'victor convert SVG_FILE [RUBY_FILE --template NAME]' + usage 'victor convert SVG_FILE [--save RUBY_FILE --template NAME]' usage 'victor convert (-h|--help)' option '-t, --template NAME', <<~USAGE @@ -14,21 +14,15 @@ class Convert < Base cli a Victor CLI compatible DSL script (default) USAGE - param 'SVG_FILE', 'Input SVG file' + option '-o, --save RUBY_FILE', 'Save to RUBY file instead of printing to stdout' + param 'RUBY_FILE', 'Output Ruby file. Leave empty to write to stdout' - example 'victor convert example.svg example.rb' + example 'victor convert example.svg --save example.rb' def run - svg_file = args['SVG_FILE'] - template = args['--template'] - svg_node = SVGNode.load_file svg_file, layout: template - validate_template template if template - code = svg_node.render - ruby_file = args['RUBY_FILE'] - if ruby_file File.write ruby_file, code say "Saved #{ruby_file}" @@ -37,6 +31,17 @@ def run end end + protected + + def svg_file = args['SVG_FILE'] + def template = args['--template'] + def ruby_file = args['--save'] + + def svg_node = @svg_node ||= SVGNode.load_file(svg_file, layout: template) + def code = @code ||= svg_node.render + + private + def validate_template(template) allowed = %w[cli dsl standalone] return if allowed.include? template diff --git a/lib/victor/cli/commands/render.rb b/lib/victor/cli/commands/render.rb index f4e1fe0..35120f5 100644 --- a/lib/victor/cli/commands/render.rb +++ b/lib/victor/cli/commands/render.rb @@ -4,9 +4,11 @@ module Victor module CLI module Commands class Render < Base + using PairSplit + summary 'Render Ruby code to SVG' - usage 'victor render RUBY_FILE [SVG_FILE] [options]' + usage 'victor render RUBY_FILE [options] [PARAMS...]' usage 'victor render (-h|--help)' option '-t, --template TEMPLATE', <<~USAGE @@ -15,28 +17,33 @@ class Render < Base USAGE option '-w, --watch', 'Watch the source file and regenerate on change' + option '-o, --save SVG_FILE', 'Save to SVG file instead of printing to stdout' param 'RUBY_FILE', 'Input Ruby file' - param 'SVG_FILE', 'Output SVG file. Leave empty to write to stdout' + param 'PARAMS', 'One or more key=value pairs that will be available in the `params` hash for the Ruby script' - example 'victor render input.rb output.svg' - example 'victor render input.rb output.svg --watch' + example 'victor render input.rb -o output.svg' + example 'victor render input.rb --save output.svg --watch' example 'victor render input.rb --template minimal' + example 'victor render input.rb color=black "text=Hello World"' def run - if args['--watch'] - watch_and_generate - else - generate - end + args['--watch'] ? watch_and_generate : generate end + protected + + def ruby_file = args['RUBY_FILE'] + def svg_file = args['--save'] + def template = args['--template'] + def params = @params ||= args['PARAMS'].pair_split + private def generate code = File.read ruby_file - ruby_source = RubySource.new code, ruby_file + ruby_source = RubySource.new code, filename: ruby_file, params: params ruby_source.evaluate ruby_source.template template if template @@ -68,18 +75,6 @@ def watch_and_generate def file_watcher @file_watcher ||= Filewatcher.new(ruby_file, immediate: true) end - - def ruby_file - args['RUBY_FILE'] - end - - def svg_file - args['SVG_FILE'] - end - - def template - args['--template'] - end end end end diff --git a/lib/victor/cli/refinements/pair_split.rb b/lib/victor/cli/refinements/pair_split.rb new file mode 100644 index 0000000..5739ad8 --- /dev/null +++ b/lib/victor/cli/refinements/pair_split.rb @@ -0,0 +1,13 @@ +module Victor + module CLI + module PairSplit + refine Array do + def pair_split(operator = '=') + return {} if empty? + + to_h { |pair| pair.split(operator, 2) }.transform_keys(&:to_sym) + end + end + end + end +end diff --git a/lib/victor/cli/ruby_source.rb b/lib/victor/cli/ruby_source.rb index 73b99f1..5280f8c 100644 --- a/lib/victor/cli/ruby_source.rb +++ b/lib/victor/cli/ruby_source.rb @@ -2,14 +2,18 @@ module Victor module CLI class RubySource include Victor::DSL - attr_reader :code, :filename - def initialize(code, filename = nil) + attr_reader :code, :filename, :global_params, :params + + def initialize(code, filename: nil, params: nil) @code = code @filename = filename + @global_params = params || {} + @params = global_params end - def evaluate + def evaluate(local_params = nil) + @params = local_params || global_params if filename instance_eval code, filename else diff --git a/spec/approvals/cli/convert/help b/spec/approvals/cli/convert/help index 82d62fe..7bc034b 100644 --- a/spec/approvals/cli/convert/help +++ b/spec/approvals/cli/convert/help @@ -1,7 +1,7 @@ Convert SVG to Ruby code Usage: - victor convert SVG_FILE [RUBY_FILE --template NAME] + victor convert SVG_FILE [--save RUBY_FILE --template NAME] victor convert (-h|--help) Options: @@ -11,15 +11,15 @@ Options: dsl a Victor DSL script cli a Victor CLI compatible DSL script (default) + -o, --save RUBY_FILE + Save to RUBY file instead of printing to stdout + -h --help Show this help Parameters: - SVG_FILE - Input SVG file - RUBY_FILE Output Ruby file. Leave empty to write to stdout Examples: - victor convert example.svg example.rb + victor convert example.svg --save example.rb diff --git a/spec/approvals/cli/convert/usage b/spec/approvals/cli/convert/usage index 0ae92f2..1a3a995 100644 --- a/spec/approvals/cli/convert/usage +++ b/spec/approvals/cli/convert/usage @@ -1,3 +1,3 @@ Usage: - victor convert SVG_FILE [RUBY_FILE --template NAME] + victor convert SVG_FILE [--save RUBY_FILE --template NAME] victor convert (-h|--help) diff --git a/spec/approvals/cli/render/help b/spec/approvals/cli/render/help index 64037e0..d463a39 100644 --- a/spec/approvals/cli/render/help +++ b/spec/approvals/cli/render/help @@ -1,7 +1,7 @@ Render Ruby code to SVG Usage: - victor render RUBY_FILE [SVG_FILE] [options] + victor render RUBY_FILE [options] [PARAMS...] victor render (-h|--help) Options: @@ -12,6 +12,9 @@ Options: -w, --watch Watch the source file and regenerate on change + -o, --save SVG_FILE + Save to SVG file instead of printing to stdout + -h --help Show this help @@ -19,10 +22,12 @@ Parameters: RUBY_FILE Input Ruby file - SVG_FILE - Output SVG file. Leave empty to write to stdout + PARAMS + One or more key=value pairs that will be available in the `params` hash for + the Ruby script Examples: - victor render input.rb output.svg - victor render input.rb output.svg --watch + victor render input.rb -o output.svg + victor render input.rb --save output.svg --watch victor render input.rb --template minimal + victor render input.rb color=black "text=Hello World" diff --git a/spec/approvals/cli/render/svg-code-blue.svg b/spec/approvals/cli/render/svg-code-blue.svg new file mode 100644 index 0000000..86063a4 --- /dev/null +++ b/spec/approvals/cli/render/svg-code-blue.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spec/approvals/cli/render/usage b/spec/approvals/cli/render/usage index 1b84a95..a6ec482 100644 --- a/spec/approvals/cli/render/usage +++ b/spec/approvals/cli/render/usage @@ -1,3 +1,3 @@ Usage: - victor render RUBY_FILE [SVG_FILE] [options] + victor render RUBY_FILE [options] [PARAMS...] victor render (-h|--help) diff --git a/spec/fixtures/render/pacman_dsl.rb b/spec/fixtures/render/pacman_dsl.rb index d61305c..08481ec 100644 --- a/spec/fixtures/render/pacman_dsl.rb +++ b/spec/fixtures/render/pacman_dsl.rb @@ -1,11 +1,13 @@ +color = params[:color] || :yellow + setup width: '140', height: '100', style: 'background:#ddd' build do rect x: '10', y: '10', width: '120', height: '80', rx: '10', fill: '#666' - circle cx: '50', cy: '50', r: '30', fill: 'yellow' + circle cx: '50', cy: '50', r: '30', fill: color circle cx: '58', cy: '32', r: '4', fill: 'black' polygon points: '45,50 80,30 80,70', fill: '#666' - circle cx: '80', cy: '50', r: '4', fill: 'yellow' - circle cx: '98', cy: '50', r: '4', fill: 'yellow' - circle cx: '116', cy: '50', r: '4', fill: 'yellow' + circle cx: '80', cy: '50', r: '4', fill: color + circle cx: '98', cy: '50', r: '4', fill: color + circle cx: '116', cy: '50', r: '4', fill: color end diff --git a/spec/victor-cli/commands/convert_spec.rb b/spec/victor-cli/commands/convert_spec.rb index 57f817d..687c2c4 100644 --- a/spec/victor-cli/commands/convert_spec.rb +++ b/spec/victor-cli/commands/convert_spec.rb @@ -17,17 +17,17 @@ context 'with SVG_FILE' do it 'outputs the converted ruby code to stdout' do - expect { subject.run ['convert', svg_file] }.to output_approval('cli/convert/ruby-code.rb') + expect { subject.run %W[convert #{svg_file}] }.to output_approval('cli/convert/ruby-code.rb') end end - context 'with SVG_FILE RUBY_FILE' do + context 'with SVG_FILE --save RUBY_FILE' do let(:ruby_file) { 'spec/tmp/code.rb' } before { File.unlink ruby_file if File.exist? ruby_file } it 'saves the converted ruby code' do - expect { subject.run ['convert', svg_file, ruby_file] }.to output_approval('cli/convert/save') + expect { subject.run %W[convert #{svg_file} --save #{ruby_file}] }.to output_approval('cli/convert/save') expect(File.read ruby_file).to match_approval('cli/convert/ruby-code.rb') end end diff --git a/spec/victor-cli/commands/render_spec.rb b/spec/victor-cli/commands/render_spec.rb index d110cee..7d33243 100644 --- a/spec/victor-cli/commands/render_spec.rb +++ b/spec/victor-cli/commands/render_spec.rb @@ -16,7 +16,15 @@ context 'with RUBY_FILE' do it 'outputs the converted SVG code to stdout' do - expect { subject.execute ['render', ruby_file] }.to output_approval('cli/render/svg-code.svg') + expect { subject.execute %W[render #{ruby_file}] } + .to output_approval('cli/render/svg-code.svg') + end + end + + context 'with RUBY_FILE PARAMS...' do + it 'passes param pairs to the DSL' do + expect { subject.execute %W[render #{ruby_file} color=blue] } + .to output_approval('cli/render/svg-code-blue.svg') end end @@ -46,13 +54,15 @@ end end - context 'with RUBY_FILE SVG_FILE' do + context 'with RUBY_FILE --save SVG_FILE' do let(:svg_file) { 'spec/tmp/svg.svg' } before { File.unlink svg_file if File.exist? svg_file } it 'saves the converted SVG code' do - expect { subject.execute ['render', ruby_file, svg_file] }.to output_approval('cli/render/save') + expect { subject.execute %W[render #{ruby_file} --save #{svg_file}] } + .to output_approval('cli/render/save') + expect(File.read(svg_file)).to match_approval('cli/render/svg-code.svg') end end diff --git a/spec/victor-cli/refinements/pair_split_spec.rb b/spec/victor-cli/refinements/pair_split_spec.rb new file mode 100644 index 0000000..91ee174 --- /dev/null +++ b/spec/victor-cli/refinements/pair_split_spec.rb @@ -0,0 +1,13 @@ +describe PairSplit do + using described_class + + subject { ['color=blue', 'text=Hello World'] } + + describe Array do + describe '#pair_split' do + it 'splits key=value pairs and returns a hash' do + expect(subject.pair_split).to eq({ color: 'blue', text: 'Hello World' }) + end + end + end +end diff --git a/spec/victor-cli/ruby_source_spec.rb b/spec/victor-cli/ruby_source_spec.rb index 184c781..352ab11 100644 --- a/spec/victor-cli/ruby_source_spec.rb +++ b/spec/victor-cli/ruby_source_spec.rb @@ -1,5 +1,5 @@ describe RubySource do - subject { described_class.new code, filename } + subject { described_class.new code, filename: filename } let(:code) { "puts 'hello'" } let(:filename) { nil } diff --git a/victor-cli.gemspec b/victor-cli.gemspec index 750d994..15f6bfc 100644 --- a/victor-cli.gemspec +++ b/victor-cli.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |s| s.executables = ['victor'] s.homepage = 'https://github.com/dannyben/victor-cli' s.license = 'MIT' - s.required_ruby_version = '>= 3.1' + s.required_ruby_version = '>= 3.2' s.add_dependency 'colsole', '~> 1.0' s.add_dependency 'css_parser', '~> 1.7' @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency 'requires', '~> 1.0' 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