Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Run Specs
on: [push, pull_request]

jobs:
test_embedded_ruby:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
ruby: [ '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', head]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- run: bundle install
working-directory: templatescompiler/erbrenderer/
- run: bundle exec rake
working-directory: templatescompiler/erbrenderer/
continue-on-error: ${{ matrix.ruby == 'head' }}
1 change: 1 addition & 0 deletions templatescompiler/erbrenderer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Gemfile.lock
7 changes: 7 additions & 0 deletions templatescompiler/erbrenderer/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
source "https://rubygems.org"

group :test do
gem "rake"
gem "rspec"
gem "standardrb"
end
Comment on lines +1 to +7
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gemfile does not include the 'ostruct' gem, which is required by erb_renderer.rb. According to the PR description, the code will not pass against Ruby > 3.4 because ostruct is no longer part of the default gemset. To ensure compatibility with Ruby > 3.4, add 'gem "ostruct"' to the Gemfile.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Gemfile is intended only for testing, and can not require gems for "production" execution of the embedded ruby code so this omission is expected, and intentional.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rkoster - would you care to resolve this for the robot, or shall I?

6 changes: 6 additions & 0 deletions templatescompiler/erbrenderer/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "rspec/core/rake_task"
require "standard/rake"

RSpec::Core::RakeTask.new(:spec)

task default: [:standard, :spec]
52 changes: 26 additions & 26 deletions templatescompiler/erbrenderer/erb_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

class Hash
def recursive_merge!(other)
self.merge!(other) do |_, old_value, new_value|
if old_value.class == Hash && new_value.class == Hash
merge!(other) do |_, old_value, new_value|
if old_value.class == Hash && new_value.class == Hash # rubocop:disable Style/ClassEqualityComparison
old_value.recursive_merge!(new_value)
else
new_value
Expand All @@ -27,14 +27,14 @@ def initialize(spec)
@name = spec["job"]["name"] if spec["job"].is_a?(Hash)
@index = spec["index"]

if !spec['job_properties'].nil?
properties1 = spec['job_properties']
properties1 = if !spec["job_properties"].nil?
spec["job_properties"]
else
properties1 = spec['global_properties'].recursive_merge!(spec['cluster_properties'])
spec["global_properties"].recursive_merge!(spec["cluster_properties"])
end

properties = {}
spec['default_properties'].each do |name, value|
spec["default_properties"].each do |name, value|
copy_property(properties, properties1, name, value)
end

Expand Down Expand Up @@ -66,7 +66,7 @@ def if_p(*names)
value
end

yield *values
yield(*values)
InactiveElseBlock.new
end

Expand Down Expand Up @@ -97,13 +97,15 @@ def copy_property(dst, src, name, default = nil)

def openstruct(object)
case object
when Hash
mapped = object.inject({}) { |h, (k,v)| h[k] = openstruct(v); h }
OpenStruct.new(mapped)
when Array
object.map { |item| openstruct(item) }
else
object
when Hash
mapped = object.each_with_object({}) { |(k, v), h|
h[k] = openstruct(v)
}
OpenStruct.new(mapped)
when Array
object.map { |item| openstruct(item) }
else
object
end
end

Expand Down Expand Up @@ -137,13 +139,14 @@ def else
yield
end

def else_if_p(*names, &block)
@context.if_p(*names, &block)
def else_if_p(*names, &block) # rubocop:disable Style/ArgumentsForwarding
@context.if_p(*names, &block) # rubocop:disable Style/ArgumentsForwarding
end
end

class InactiveElseBlock
def else; end
def else
end

def else_if_p(*names)
InactiveElseBlock.new
Expand All @@ -153,7 +156,7 @@ def else_if_p(*names)

# todo do not use JSON in releases
class << JSON
alias dump_array_or_hash dump
alias_method :dump_array_or_hash, :dump

def dump(*args)
arg = args[0]
Expand All @@ -174,18 +177,15 @@ def render(src_path, dst_path)
erb = ERB.new(File.read(src_path), trim_mode: "-")
erb.filename = src_path

context_hash = JSON.load(File.read(@json_context_path))
context_hash = JSON.load_file(@json_context_path)
template_evaluation_context = TemplateEvaluationContext.new(context_hash)

File.open(dst_path, "w") do |f|
f.write(erb.result(template_evaluation_context.get_binding))
end

rescue Exception => e
File.write(dst_path, erb.result(template_evaluation_context.get_binding))
rescue Exception => e # rubocop:disable Lint/RescueException
name = "#{template_evaluation_context&.name}/#{template_evaluation_context&.index}"

line_i = e.backtrace.index { |l| l.include?("#{erb&.filename}") }
line_num = line_i ? e.backtrace[line_i].split(':')[1] : "unknown"
line_i = e.backtrace.index { |l| l.include?(erb&.filename.to_s) }
line_num = line_i ? e.backtrace[line_i].split(":")[1] : "unknown"
location = "(line #{line_num}: #{e.inspect})"

raise("Error filling in template '#{src_path}' for #{name} #{location}")
Expand Down
79 changes: 79 additions & 0 deletions templatescompiler/erbrenderer/spec/erb_renderer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require "spec_helper"
require "erb_renderer"

RSpec.describe "erb_renderer" do
describe "ERBRenderer" do
let(:test_tmpdir) { Dir.tmpdir }
let(:json_context_path) { File.join(test_tmpdir, "context.json") }

before do
File.write(json_context_path, context_hash.to_json)
end

after do
File.unlink(json_context_path) if File.exist?(json_context_path)
end

describe "#initialize" do
let(:context_hash) { {} }

it "does not raise an error" do
expect { ERBRenderer.new(json_context_path) }.not_to raise_error
end
end

describe "#render" do
let(:context_hash) do
{
index: 867_5309,
global_properties: {
property1: "global_value1"
},
cluster_properties: {
property2: "cluster_value1"
},
default_properties: {
property1: "default_value1",
property2: "default_value2",
property3: "default_value3"
}
}
end

let(:erb_template_path) { File.join(test_tmpdir, "template.yml.erb") }
let(:erb_content) do
<<~TEST_TEMPLATE
---
property1: <%= p('property1') %>
property2: <%= p('property2') %>
property3: <%= p('property3') %>
TEST_TEMPLATE
end

let(:rendered_template_path) { File.join(test_tmpdir, "template.yml") }
let(:expected_template_content) do
<<~EXPECTED_TEMPLATE
---
property1: global_value1
property2: cluster_value1
property3: default_value3
EXPECTED_TEMPLATE
end

let(:erb_renderer) { ERBRenderer.new(json_context_path) }

before do
File.write(erb_template_path, erb_content)
end

it "does not raise an error" do
expect { erb_renderer.render(erb_template_path, rendered_template_path) }.not_to raise_error
end

it "renders the expected content" do
erb_renderer.render(erb_template_path, rendered_template_path)
expect(File.read(rendered_template_path)).to eq(expected_template_content)
end
end
end
end
7 changes: 7 additions & 0 deletions templatescompiler/erbrenderer/spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "rspec"
require "json"
require "tmpdir"

ERB_RENDERER_ROOT = File.expand_path("..", File.dirname(__FILE__))

$LOAD_PATH.unshift(ERB_RENDERER_ROOT)
Loading