├── .gitignore ├── .rubocop.yml ├── spec ├── spec_helper.rb └── erb2epp_spec.rb ├── bin └── erb2epp ├── Gemfile ├── Rakefile ├── .github └── workflows │ └── test.yml ├── erb2epp.gemspec ├── LICENSE └── lib └── erb2epp.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | inherit_gem: 3 | voxpupuli-rubocop: rubocop.yml 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'erb2epp' 4 | -------------------------------------------------------------------------------- /bin/erb2epp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'erb2epp' 5 | 6 | Erb2epp.new.run 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'rake', '~> 13.0' 8 | 9 | group :test do 10 | gem 'rspec', '~> 3.12' 11 | end 12 | 13 | group :rubocop do 14 | gem 'voxpupuli-rubocop', '~> 1.4.0' 15 | end 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | default_tasks = [] 4 | 5 | begin 6 | require 'voxpupuli/rubocop/rake' 7 | rescue LoadError 8 | # rubocop bundler group not enabled 9 | else 10 | default_tasks << :rubocop 11 | end 12 | 13 | begin 14 | require 'rspec/core/rake_task' 15 | rescue LoadError 16 | # test group not enabled 17 | else 18 | RSpec::Core::RakeTask.new 19 | default_tasks << :spec 20 | end 21 | 22 | task default: default_tasks if default_tasks.any? 23 | -------------------------------------------------------------------------------- /spec/erb2epp_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Erb2epp do 6 | describe '.rewrite_code' do 7 | subject(:rewritten) { described_class.new.rewrite_code(code) } 8 | 9 | context 'with empty string' do 10 | let(:code) { '' } 11 | 12 | it { is_expected.to eq('') } 13 | end 14 | 15 | context 'with an instance variable' do 16 | let(:code) { '@x' } 17 | 18 | it { is_expected.to eq('$x') } 19 | end 20 | 21 | context 'with simple if' do 22 | let(:code) { 'if @x' } 23 | 24 | it { is_expected.to eq('if $x {') } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | concurrency: 11 | group: ${{ github.ref_name }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | rubocop: 16 | name: Rubocop 17 | uses: theforeman/actions/.github/workflows/rubocop.yml@v0 18 | with: 19 | ruby: '3.2' 20 | 21 | test: 22 | name: Tests 23 | uses: theforeman/actions/.github/workflows/test-gem.yml@v0 24 | 25 | suite: 26 | name: Test suite 27 | runs-on: ubuntu-latest 28 | needs: 29 | - rubocop 30 | - test 31 | steps: 32 | - run: echo Suite complete 33 | -------------------------------------------------------------------------------- /erb2epp.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |s| 4 | s.name = 'erb2epp' 5 | s.version = '0.1.0' 6 | s.licenses = ['MIT'] 7 | s.summary = 'Convert Ruby ERB templates to Puppet EPP' 8 | s.description = <<~DESC 9 | This tool aims to convert the templates and rewrite the syntax where 10 | needed. It also provides a list of variables used, which makes it easy to 11 | add data types for additional validation and provide the context to the 12 | template. 13 | DESC 14 | s.authors = ['Ewoud Kohl van Wijngaarden'] 15 | s.email = 'ewoud+rubygems@kohlvanwijngaarden.nl' 16 | s.files = Dir['lib/**/*.rb'] + Dir['bin/*'] + ['LICENSE'] 17 | s.homepage = 'https://github.com/ekohl/erb2epp' 18 | s.metadata = { 'source_code_uri' => 'https://github.com/ekohl/erb2pp' } 19 | s.executables << 'erb2epp' 20 | 21 | s.required_ruby_version = '>= 2.7.0', '< 4' 22 | 23 | s.add_runtime_dependency 'temple', '~> 0.10.0' 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Ewoud Kohl van Wijngaarden 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/erb2epp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'temple' 4 | require 'ripper' 5 | 6 | # Rewrite code from Ruby ERB to Puppet EPP 7 | class Erb2epp 8 | def initialize 9 | @vars_found = [] 10 | end 11 | 12 | # Rewrite a piece of Ruby code 13 | # 14 | # The ruby code blocks in ERB are not full programs so a parser can't be 15 | # used. A lexer is used instead. 16 | def rewrite_code(code) 17 | tokens = Ripper.lex(code) 18 | res = [] 19 | ops = {} 20 | 21 | # First pass: rewrite 22 | tokens.each do |token| 23 | # [[lineno, column], type, token, state] 24 | _, type, value, _state = token 25 | r = value 26 | 27 | case type 28 | when :on_ivar 29 | r = value.gsub(/@([a-z][A-Za-z0-9_]*)/, '$\1') 30 | @vars_found.push r 31 | when :on_op 32 | case value 33 | when '||' 34 | r = 'or' 35 | when '&&' 36 | r = 'and' 37 | end 38 | when :on_kw 39 | case value 40 | when 'if' 41 | ops['if'] = true 42 | when 'then' 43 | r = '{' 44 | ops['if'] = false 45 | when 'do', '{' 46 | r = '{' 47 | when 'else' 48 | r = '} else {' 49 | when 'end' 50 | r = '}' 51 | end 52 | end 53 | 54 | res.push r 55 | end 56 | 57 | # Append `{` to the `if` code line 58 | res.push ' {' if ops['if'] == true 59 | 60 | # Assemble the tokens again 61 | res.join 62 | rescue StandardError 63 | warn code 64 | raise 65 | end 66 | 67 | def walk_erb(node) 68 | out = +'' 69 | case node[0] 70 | when :multi 71 | node[1..].each do |n| 72 | out << walk_erb(n) 73 | end 74 | when :static 75 | out << node[1] 76 | when :newline 77 | # Eat the newline 78 | when :code 79 | # Handle <% 80 | out << "<%#{rewrite_code(node[1])}%>" 81 | when :escape 82 | # Handle <%= 83 | # TODO: node[1] is a boolean, what to do with it? 84 | out << '<%=' 85 | out << walk_erb(node[2]) 86 | out << '%>' 87 | when :dynamic 88 | # Handle ruby code 89 | out << rewrite_code(node[1]) 90 | else 91 | warn "Unknown ERB node type #{node[0]}" 92 | exit(1) 93 | end 94 | 95 | out 96 | end 97 | 98 | def run(input = $stdin, output = $stdout) 99 | parser = Temple::ERB::Parser.new 100 | ast = parser.call(input.read) 101 | epp = walk_erb(ast) 102 | 103 | output.puts '<%- |' 104 | @vars_found.sort.uniq.each { |v| output.puts " #{v}," } 105 | output.puts '| -%>' 106 | output.puts epp 107 | end 108 | end 109 | --------------------------------------------------------------------------------