Skip to content

Commit

Permalink
upgrade template-ruby
Browse files Browse the repository at this point in the history
  • Loading branch information
dorianmariecom committed Apr 3, 2024
1 parent 7737275 commit 0c8a928
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 98 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
133 changes: 76 additions & 57 deletions bin/template
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
59 changes: 27 additions & 32 deletions lib/template.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions lib/template/node/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# frozen_string_literal: true

require "rspec"
require_relative "../lib/template-ruby"
8 changes: 4 additions & 4 deletions spec/template_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 0c8a928

Please sign in to comment.