From 0c8a9281de3f720045400b6432b5ca7256e82aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Mari=C3=A9?= Date: Wed, 3 Apr 2024 08:41:56 +0200 Subject: [PATCH] upgrade template-ruby --- .github/workflows/ci.yml | 38 ++++++++++ bin/template | 133 +++++++++++++++++++--------------- lib/template.rb | 59 +++++++-------- lib/template/node/template.rb | 6 +- spec/spec_helper.rb | 3 +- spec/template_spec.rb | 8 +- 6 files changed, 149 insertions(+), 98 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..90730c7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI +on: push +jobs: + bundler-audit: + name: Bundler Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - run: bin/bundler-audit check --update + rspec: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + - run: bin/test + rubocop: + name: Rubocop + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - run: bin/rubocop + yarn-audit: + name: Yarn Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: yarn audit diff --git a/bin/template b/bin/template index f8a4dbe..83123d2 100755 --- a/bin/template +++ b/bin/template @@ -4,76 +4,95 @@ require "optparse" require_relative "../lib/template-ruby" -options = { timeout: 0, profile: false, profiler: "text" } - -OptionParser - .new do |opts| - opts.banner = "Usage: template [options]" - - opts.on("-v", "--version", "Version of template") do |_input| - puts Template::Version - exit - end - - opts.on( - "-i INPUT", - "--input=INPUT", - "Input in the template language (String or File)" - ) do |input| - input = File.read(input) if File.exist?(input) - - options[:input] = input +options = { + timeout: 0, + profile: false, + profiler: "text", + input: "", + parse: false +} + +argv = + OptionParser + .new do |opts| + opts.banner = "Usage: template INPUT\n\n" + + opts.on("-v", "--version", "Version of Template") do |_input| + puts Code::Version + exit + end + + opts.on( + "-i INPUT", + "--input INPUT", + "Input in the Template language (String or File)" + ) do |input| + input = File.read(input) if File.exist?(input) + + options[:input] = input + end + + opts.on("-p", "--parse", "Parser tree for input") do |parse| + options[:parse] = parse + end + + opts.on( + "-t TIMEOUT", + "--timeout TIMEOUT", + "Set timeout in seconds" + ) { |timeout| options[:timeout] = timeout.to_f } + + opts.on( + "-z TIME_ZONE", + "--time-zone TIME_ZONE", + "Set time zone" + ) { |time_zone| Time.zone = time_zone } + + opts.on("--profile", "Profile Ruby code") do |_timeout| + require "ruby-prof" + options[:profile] = true + end + + opts.on( + "--profiler TYPE", + "Profiler output type (text (default) or html)" + ) do |profiler| + require "ruby-prof" + options[:profile] = true + options[:profiler] = profiler + end end + .parse! - opts.on( - "-c CONTEXT", - "--context=CONTEXT", - "Context in the code language (String or File)" - ) do |context| - context = File.read(context) if File.exist?(context) - - options[:context] = context - end +options[:input] = argv.join(" ") if options[:input].empty? - opts.on("-p", "--parse", "Get parser results for input") do |parse| - options[:parse] = parse - end +abort <<~HELP if options[:input].empty? + Usage: template INPUT - opts.on( - "-t TIMEOUT", - "--timeout=TIMEOUT", - "Set timeout in seconds" - ) { |timeout| options[:timeout] = timeout.to_f } - - opts.on("--profile", "Profile Ruby code") do |_timeout| - require "ruby-prof" - options[:profile] = true - end - - opts.on( - "--profiler TYPE", - "Profiler output type (text (default) or html)" - ) do |profiler| - require "ruby-prof" - options[:profile] = true - options[:profiler] = profiler - end - end - .parse! - -input = options.fetch(:input, "") -context = options.fetch(:context, "") + -v, --version Version of Template + -i, --input INPUT Input in the Template language (String or File) + -p, --parse Parser tree for input + -t, --timeout TIMEOUT Set timeout in seconds + --profile Profile Ruby code + --profiler TYPE Profiler output type (text (default) or html) +HELP RubyProf.start if options[:profile] if options[:parse] - pp ::Template::Parser.parse(input).to_raw + pp Template::Parser.parse(options[:input]).to_raw else - Template.evaluate(input, context, io: $stdout, timeout: options[:timeout]) + print Template.evaluate( + options[:input], + output: $stdout, + error: $stderr, + timeout: options[:timeout] + ) end if options[:profile] result = RubyProf.stop + if options[:profiler] == "text" printer = RubyProf::FlatPrinter.new(result) printer.print($stdout) diff --git a/lib/template.rb b/lib/template.rb index 9bc23b4..c874337 100644 --- a/lib/template.rb +++ b/lib/template.rb @@ -1,54 +1,49 @@ # frozen_string_literal: true class Template - EMPTY_STRING = "" - GLOBALS = %i[io context object].freeze + GLOBALS = %i[output error context object].freeze DEFAULT_TIMEOUT = 0 - def initialize(input, io: ::StringIO.new, timeout: DEFAULT_TIMEOUT, ruby: {}) + def initialize( + input, + output: StringIO.new, + error: StringIO.new, + timeout: DEFAULT_TIMEOUT + ) @input = input - @parsed = - Timeout.timeout(timeout) { ::Template::Parser.parse(@input).to_raw } - @io = io + @output = output + @error = error @timeout = timeout || DEFAULT_TIMEOUT - @ruby = - Timeout.timeout(timeout) do - ::Code::Ruby.to_code(ruby || {}).code_to_context - end + @context = ::Code::Object::Context.new + end + + def self.parse(input, timeout: DEFAULT_TIMEOUT) + Timeout.timeout(timeout) { Parser.parse(input).to_raw } end def self.evaluate( input, - context = "", - io: ::StringIO.new, - timeout: DEFAULT_TIMEOUT, - ruby: {} + output: StringIO.new, + error: StringIO.new, + timeout: DEFAULT_TIMEOUT ) - new(input, io:, timeout:, ruby:).evaluate(context) + new(input, output:, error:, timeout:).evaluate end - def evaluate(context = "") + def evaluate Timeout.timeout(timeout) do - context = - if context == EMPTY_STRING - ::Code::Object::Context.new - else - ::Code.evaluate(context, timeout:, io:, ruby:).code_to_context - end - - unless context.is_a?(::Code::Object::Context) - raise(Error::IncompatibleContext) - end - - context = context.merge(ruby) + parsed = Template.parse(input) + Node::Template.new(parsed).evaluate(context:, output:, error:) - ::Template::Node::Template.new(parsed).evaluate(context:, io:) - - io.is_a?(::StringIO) ? io.string : nil + if output.is_a?(StringIO) + output.string + else + "" + end end end private - attr_reader :input, :parsed, :timeout, :io, :ruby + attr_reader :input, :timeout, :output, :error, :context end diff --git a/lib/template/node/template.rb b/lib/template/node/template.rb index b242d48..0b241e4 100644 --- a/lib/template/node/template.rb +++ b/lib/template/node/template.rb @@ -8,9 +8,9 @@ def initialize(parts) end def evaluate(**args) - io = args.fetch(:io) - - @parts.each { |part| io.print(part.evaluate(**args)) } + output = args.fetch(:output) + @parts.each { |part| output.print(part.evaluate(**args)) } + ::Code::Object::Nothing.new end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4cfd2e0..b3f31aa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,2 @@ -# frozen_string_literal: true - +require "rspec" require_relative "../lib/template-ruby" diff --git a/spec/template_spec.rb b/spec/template_spec.rb index 0dfd9ca..4d92fb5 100644 --- a/spec/template_spec.rb +++ b/spec/template_spec.rb @@ -4,10 +4,10 @@ RSpec.describe Code do [ - ["Hello {name}", "{name: :Dorian}", "Hello Dorian"] - ].each do |input, context, expected| - it "#{input} (#{context}) == #{expected}" do - expect(Template.evaluate(input, context)).to eq(expected) + ["{name = :Dorian nothing}Hello {name}", "Hello Dorian"] + ].each do |input, expected| + it "#{input} == #{expected}" do + expect(Template.evaluate(input)).to eq(expected) end end end