erb2rux 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/Gemfile +9 -0
 - data/LICENSE +21 -0
 - data/README.md +45 -0
 - data/Rakefile +14 -0
 - data/bin/erb2rux +77 -0
 - data/erb2rux.gemspec +22 -0
 - data/lib/erb2rux/component_render.rb +49 -0
 - data/lib/erb2rux/transformer.rb +405 -0
 - data/lib/erb2rux/version.rb +3 -0
 - data/lib/erb2rux.rb +4 -0
 - data/spec/spec_helper.rb +16 -0
 - data/spec/transformer_spec.rb +257 -0
 - metadata +97 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 4a3f63bab9bef707b611464f3d952d7f505e22d0593976105cfc803eff905685
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: c45fa2a5e37eedac6c0d095fe3441e840e8e2b3c640076cbd74440226e7d5db5
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 06e32ead01821495acab5335a36a407ab9bc36e32a331b9775e35e92c49b0dd8b9f3ea3438f9ad24219e7a2fb4565f9e93629794414f2ad02a950732a04c7de5
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 87372c28d66eb2e8c971d18537999359b14731293e8d9a3dfec57a95ce0d050eaa8acfbdd7bd07cb51e3b60eddb13a664d947ad77e3cf0f28c11f1b04328cc39
         
     | 
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            The MIT License (MIT)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Copyright (c) 2021 Cameron Dutro
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         
     | 
| 
      
 6 
     | 
    
         
            +
            of this software and associated documentation files (the "Software"), to deal
         
     | 
| 
      
 7 
     | 
    
         
            +
            in the Software without restriction, including without limitation the rights
         
     | 
| 
      
 8 
     | 
    
         
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         
     | 
| 
      
 9 
     | 
    
         
            +
            copies of the Software, and to permit persons to whom the Software is
         
     | 
| 
      
 10 
     | 
    
         
            +
            furnished to do so, subject to the following conditions:
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be included in all
         
     | 
| 
      
 13 
     | 
    
         
            +
            copies or substantial portions of the Software.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         
     | 
| 
      
 16 
     | 
    
         
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         
     | 
| 
      
 17 
     | 
    
         
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         
     | 
| 
      
 18 
     | 
    
         
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         
     | 
| 
      
 19 
     | 
    
         
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         
     | 
| 
      
 20 
     | 
    
         
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         
     | 
| 
      
 21 
     | 
    
         
            +
            SOFTWARE.
         
     | 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## erb2rux
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            erb2rux is an ERB to [Rux](https://github.com/camertron/rux) converter. It's used to translate Rails view files, usually written in ERB (embedded Ruby) syntax, into Rux syntax. Rux allows you to write HTML in your Ruby code, much like JSX allows you to write HTML in your JavaScript. It's great for rendering [view components](https://viewcomponent.org/).
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## Installation
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            Simply run `gem install erb2rux`.
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            The project ships with a single executable called `erb2rux`. It takes any number of files as arguments, or a single "-" character to read from [standard input](https://en.wikipedia.org/wiki/Standard_streams). In the case of standard input, `erb2rux` will print the resulting Rux code to standard output (i.e. your terminal screen). Otherwise, the list of files will be transpiled and written to the same location as the original file, with either the default extension (.html.ruxt) or one you specify.
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            Here's an example showing how to transpile a single file:
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 18 
     | 
    
         
            +
            erb2rux app/views/products/index.html.erb
         
     | 
| 
      
 19 
     | 
    
         
            +
            ```
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            This will create app/views/products/index.html.ruxt containing Rux code equivalent to the given ERB file.
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            To use a different extension, pass the -x option:
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 26 
     | 
    
         
            +
            erb2rux -x .html.rux app/views/products/index.html.erb
         
     | 
| 
      
 27 
     | 
    
         
            +
            ```
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            Finally, here's the equivalent command using standard in/out:
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 32 
     | 
    
         
            +
            cat app/views/products/index.html.erb | erb2rux -
         
     | 
| 
      
 33 
     | 
    
         
            +
            ```
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            ## Running Tests
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            `bundle exec rspec` should do the trick.
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            ## License
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            Licensed under the MIT license. See LICENSE for details.
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            ## Authors
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            * Cameron C. Dutro: http://github.com/camertron
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'bundler'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rspec/core/rake_task'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rubygems/package_task'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require 'erb2rux'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Bundler::GemHelper.install_tasks
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            task default: :spec
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            desc 'Run specs'
         
     | 
| 
      
 12 
     | 
    
         
            +
            RSpec::Core::RakeTask.new do |t|
         
     | 
| 
      
 13 
     | 
    
         
            +
              t.pattern = './spec/**/*_spec.rb'
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
    
        data/bin/erb2rux
    ADDED
    
    | 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #! /usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            $:.push(File.expand_path('./lib'))
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'erb2rux'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            class Erb2RuxCLI
         
     | 
| 
      
 9 
     | 
    
         
            +
              def self.parse(argv)
         
     | 
| 
      
 10 
     | 
    
         
            +
                if argv.empty?
         
     | 
| 
      
 11 
     | 
    
         
            +
                  puts 'Please pass a list of files to transpile, or - to read from STDIN'
         
     | 
| 
      
 12 
     | 
    
         
            +
                  exit 1
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                options = {
         
     | 
| 
      
 16 
     | 
    
         
            +
                  extension: '.html.ruxt'
         
     | 
| 
      
 17 
     | 
    
         
            +
                }
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                parser = OptionParser.new do |opts|
         
     | 
| 
      
 20 
     | 
    
         
            +
                  opts.banner = "Usage: erb2rux [options] paths"
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  oneline(<<~DESC).tap do |desc|
         
     | 
| 
      
 23 
     | 
    
         
            +
                    The file extension to use for output files. Ignored if reading from STDIN
         
     | 
| 
      
 24 
     | 
    
         
            +
                    (default: #{options[:extension]}).
         
     | 
| 
      
 25 
     | 
    
         
            +
                  DESC
         
     | 
| 
      
 26 
     | 
    
         
            +
                    opts.on('-xEXT', '--extension=EXT', desc) do |ext|
         
     | 
| 
      
 27 
     | 
    
         
            +
                      options[:extension] = ext
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  opts.on('-h', '--help', 'Prints this help info') do
         
     | 
| 
      
 32 
     | 
    
         
            +
                    puts opts
         
     | 
| 
      
 33 
     | 
    
         
            +
                    exit
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                parser.parse!(argv)
         
     | 
| 
      
 38 
     | 
    
         
            +
                new({ **options, files: argv })
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              def self.oneline(str)
         
     | 
| 
      
 42 
     | 
    
         
            +
                str.split("\n").join(' ')
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
              def initialize(options)
         
     | 
| 
      
 46 
     | 
    
         
            +
                @options = options
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              def stdin?
         
     | 
| 
      
 50 
     | 
    
         
            +
                @options[:files].first == '-'
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              def each_file(&block)
         
     | 
| 
      
 54 
     | 
    
         
            +
                @options[:files].each do |in_file|
         
     | 
| 
      
 55 
     | 
    
         
            +
                  ext_idx = in_file.index('.')
         
     | 
| 
      
 56 
     | 
    
         
            +
                  out_file = "#{in_file[0...ext_idx]}#{extension}"
         
     | 
| 
      
 57 
     | 
    
         
            +
                  yield in_file, out_file
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              def extension
         
     | 
| 
      
 62 
     | 
    
         
            +
                @options[:extension]
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
            end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            cli = Erb2RuxCLI.parse(ARGV)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            if cli.stdin?
         
     | 
| 
      
 69 
     | 
    
         
            +
              puts Erb2Rux::Transformer.transform(STDIN.read)
         
     | 
| 
      
 70 
     | 
    
         
            +
              exit 0
         
     | 
| 
      
 71 
     | 
    
         
            +
            end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            cli.each_file do |in_file, out_file|
         
     | 
| 
      
 74 
     | 
    
         
            +
              result = Erb2Rux::Transformer.transform(File.read(in_file))
         
     | 
| 
      
 75 
     | 
    
         
            +
              File.write(out_file, result)
         
     | 
| 
      
 76 
     | 
    
         
            +
              puts "Wrote #{out_file}"
         
     | 
| 
      
 77 
     | 
    
         
            +
            end
         
     | 
    
        data/erb2rux.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $:.unshift File.join(File.dirname(__FILE__), 'lib')
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'erb2rux/version'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 5 
     | 
    
         
            +
              s.name     = 'erb2rux'
         
     | 
| 
      
 6 
     | 
    
         
            +
              s.version  = ::Erb2Rux::VERSION
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.authors  = ['Cameron Dutro']
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.email    = ['camertron@gmail.com']
         
     | 
| 
      
 9 
     | 
    
         
            +
              s.homepage = 'http://github.com/camertron/erb2rux'
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.description = s.summary = 'Automatically convert .html.erb files into .rux files.'
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.platform = Gem::Platform::RUBY
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              s.add_dependency 'actionview', '~> 6.1'
         
     | 
| 
      
 14 
     | 
    
         
            +
              s.add_dependency 'parser', '~> 3.0'
         
     | 
| 
      
 15 
     | 
    
         
            +
              s.add_dependency 'unparser', '~> 0.6'
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              s.require_path = 'lib'
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              s.executables << 'erb2rux'
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'erb2rux.gemspec']
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Erb2Rux
         
     | 
| 
      
 2 
     | 
    
         
            +
              class ComponentRender
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :component_name, :component_kwargs
         
     | 
| 
      
 4 
     | 
    
         
            +
                attr_reader :send_range, :block_end_range, :block_body_range, :block_arg
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(component_name:, component_kwargs:, send_range:, block_end_range:, block_body_range:, block_arg:)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @component_name = component_name
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @component_kwargs = component_kwargs
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @send_range = send_range
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @block_end_range = block_end_range
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @block_body_range = block_body_range
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @block_arg = block_arg
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def close_tag
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @close_tag ||= "</#{component_name}>"
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def open_tag
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @open_tag ||= ''.tap do |result|
         
     | 
| 
      
 21 
     | 
    
         
            +
                    result << "<#{component_name}"
         
     | 
| 
      
 22 
     | 
    
         
            +
                    result << ' ' unless kwargs.empty?
         
     | 
| 
      
 23 
     | 
    
         
            +
                    result << kwargs
         
     | 
| 
      
 24 
     | 
    
         
            +
                    result << '>'
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def self_closing_tag
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @self_closing_tag ||= ''.tap do |result|
         
     | 
| 
      
 30 
     | 
    
         
            +
                    result << "<#{component_name}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                    result << ' ' unless kwargs.empty?
         
     | 
| 
      
 32 
     | 
    
         
            +
                    result << kwargs
         
     | 
| 
      
 33 
     | 
    
         
            +
                    result << ' />'
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                private
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def kwargs
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @kwargs = begin
         
     | 
| 
      
 41 
     | 
    
         
            +
                    kwargs = component_kwargs.map do |key, value|
         
     | 
| 
      
 42 
     | 
    
         
            +
                      "#{key}={#{value}}"
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    kwargs << "as={\"#{block_arg}\"}" if block_arg
         
     | 
| 
      
 45 
     | 
    
         
            +
                    kwargs.join(' ')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,405 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'action_view'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'parser'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'unparser'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Erb2Rux
         
     | 
| 
      
 6 
     | 
    
         
            +
              NodeMeta = Struct.new(:node, :stype, :replacement, :in_code)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              class Transformer
         
     | 
| 
      
 9 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def transform(source)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # Remove any extra spaces between the ERB tag and the content, eg.
         
     | 
| 
      
 12 
     | 
    
         
            +
                    # "<%= foo %>" becomes "<%=foo%>". ActionView's Erubi parser
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # treats this extra whitespace as part of the output, which results
         
     | 
| 
      
 14 
     | 
    
         
            +
                    # in funky indentation issues.
         
     | 
| 
      
 15 
     | 
    
         
            +
                    source.gsub!(/(<%=?) */, '\1')
         
     | 
| 
      
 16 
     | 
    
         
            +
                    source.gsub!(/ *(%>)/, '\1')
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    # This is how Rails translates ERB to Ruby code, so it's probably the
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # way we should do it too. It takes blocks into account, which, TIL, is
         
     | 
| 
      
 20 
     | 
    
         
            +
                    # something regular ERB doesn't do. The resulting ruby code works by
         
     | 
| 
      
 21 
     | 
    
         
            +
                    # appending to an @output_buffer instance variable. Different methods
         
     | 
| 
      
 22 
     | 
    
         
            +
                    # are used for chunks of code vs HTML strings, which allows the
         
     | 
| 
      
 23 
     | 
    
         
            +
                    # transformer to distinguish between them. See #send_type_for for
         
     | 
| 
      
 24 
     | 
    
         
            +
                    # details.
         
     | 
| 
      
 25 
     | 
    
         
            +
                    ruby_code = ActionView::Template::Handlers::ERB::Erubi.new(source).src
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    # ActionView adds this final line at the end of all compiled templates,
         
     | 
| 
      
 28 
     | 
    
         
            +
                    # so we need to remmove it.
         
     | 
| 
      
 29 
     | 
    
         
            +
                    ruby_code = ruby_code.chomp('@output_buffer.to_s')
         
     | 
| 
      
 30 
     | 
    
         
            +
                    ast = ::Parser::CurrentRuby.parse(ruby_code)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    rewrite(ast)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  private
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  # Determines whether or not the given node is itself a node (i.e. an
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # instance of Parser::AST::Node) that identifies a visible chunk of
         
     | 
| 
      
 38 
     | 
    
         
            +
                  # code in the source buffer. (In addition to being Node instances,
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # children can be strings, symbols, or nil, and sometimes only hold
         
     | 
| 
      
 40 
     | 
    
         
            +
                  # metadata, i.e. don't reference a visible portion of the source).
         
     | 
| 
      
 41 
     | 
    
         
            +
                  def is_node?(obj)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    obj.respond_to?(:children) && obj.location.expression
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def rewrite(node)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    return unless is_node?(node)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    # The "send type" refers to the way this node is being appended to the
         
     | 
| 
      
 49 
     | 
    
         
            +
                    # output buffer. <%= %> tags result in a call to append=, which has a
         
     | 
| 
      
 50 
     | 
    
         
            +
                    # send type of :code. <% %> tags result in no append calls and have a
         
     | 
| 
      
 51 
     | 
    
         
            +
                    # send type of nil. Finally, regular 'ol strings (i.e. HTML) result in
         
     | 
| 
      
 52 
     | 
    
         
            +
                    # a call to safe_append= and have a send type of :string.
         
     | 
| 
      
 53 
     | 
    
         
            +
                    case send_type_for(node)
         
     | 
| 
      
 54 
     | 
    
         
            +
                      when :code
         
     | 
| 
      
 55 
     | 
    
         
            +
                        rewrite_code(node)
         
     | 
| 
      
 56 
     | 
    
         
            +
                      when :string
         
     | 
| 
      
 57 
     | 
    
         
            +
                        rewrite_string(node)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      else
         
     | 
| 
      
 59 
     | 
    
         
            +
                        # Code in <% %> tags should be left as-is. Instead of rewriting it
         
     | 
| 
      
 60 
     | 
    
         
            +
                        # we recurse and process all the children.
         
     | 
| 
      
 61 
     | 
    
         
            +
                        rewrite_children(node)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def rewrite_code(node)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    # Any node that is passed to this method will be a s(:send) node that
         
     | 
| 
      
 67 
     | 
    
         
            +
                    # identifies a chunk of Ruby code that should be surrounded by Rux
         
     | 
| 
      
 68 
     | 
    
         
            +
                    # braces. The arg below is the argument to the @output_buffer.append=
         
     | 
| 
      
 69 
     | 
    
         
            +
                    # call.
         
     | 
| 
      
 70 
     | 
    
         
            +
                    _, _, arg, * = *node
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    # Does this node render a view component?
         
     | 
| 
      
 73 
     | 
    
         
            +
                    if (component_render = identify_component_render(arg))
         
     | 
| 
      
 74 
     | 
    
         
            +
                      _, _, block_body, = *arg
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                      return arg.location.expression.source.dup.tap do |code|
         
     | 
| 
      
 77 
     | 
    
         
            +
                        # If the render call has a block...
         
     | 
| 
      
 78 
     | 
    
         
            +
                        if block_body
         
     | 
| 
      
 79 
     | 
    
         
            +
                          # ...replace the end statement with the closing tag, recursively
         
     | 
| 
      
 80 
     | 
    
         
            +
                          # rewrite the block's body, and replace the render call with the
         
     | 
| 
      
 81 
     | 
    
         
            +
                          # opening tag. Do these things in reverse order so all the ranges
         
     | 
| 
      
 82 
     | 
    
         
            +
                          # are valid.
         
     | 
| 
      
 83 
     | 
    
         
            +
                          code[component_render.block_end_range] = component_render.close_tag
         
     | 
| 
      
 84 
     | 
    
         
            +
                          leading_ws, body, trailing_ws = ws_split(rewrite(block_body))
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                          code[component_render.block_body_range] = if body.empty?
         
     | 
| 
      
 87 
     | 
    
         
            +
                            "#{leading_ws}#{trailing_ws}"
         
     | 
| 
      
 88 
     | 
    
         
            +
                          else
         
     | 
| 
      
 89 
     | 
    
         
            +
                            "#{leading_ws}{#{body}}#{trailing_ws}"
         
     | 
| 
      
 90 
     | 
    
         
            +
                          end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                          code[component_render.send_range] = component_render.open_tag
         
     | 
| 
      
 93 
     | 
    
         
            +
                        else
         
     | 
| 
      
 94 
     | 
    
         
            +
                          # ...otherwise only replace the render call and self-close the tag.
         
     | 
| 
      
 95 
     | 
    
         
            +
                          code[component_render.send_range] = component_render.self_closing_tag
         
     | 
| 
      
 96 
     | 
    
         
            +
                        end
         
     | 
| 
      
 97 
     | 
    
         
            +
                      end
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    # ActionView wraps code in parens (eg. @output_buffer.append= ( foo ))
         
     | 
| 
      
 101 
     | 
    
         
            +
                    # which results in an extra s(:begin) node wrapped around the code node.
         
     | 
| 
      
 102 
     | 
    
         
            +
                    # Strip it off.
         
     | 
| 
      
 103 
     | 
    
         
            +
                    arg_body = arg.type == :begin ? arg.children[0] : arg
         
     | 
| 
      
 104 
     | 
    
         
            +
                    rewrite(arg_body)
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  def rewrite_string(node)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    # Any node that is passed to this method will be a s(:send) node that
         
     | 
| 
      
 109 
     | 
    
         
            +
                    # identifies a chunk of HTML. The arg below is the argument to the
         
     | 
| 
      
 110 
     | 
    
         
            +
                    # @output_buffer.safe_append= call.
         
     | 
| 
      
 111 
     | 
    
         
            +
                    _, _, arg, * = *node
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                    # Strip off quotes.
         
     | 
| 
      
 114 
     | 
    
         
            +
                    str = arg.children[0].location.expression.source[1..-2]
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                    # Rux leaves HTML as-is, i.e. doesn't quote it.
         
     | 
| 
      
 117 
     | 
    
         
            +
                    unless tag_start?(str)
         
     | 
| 
      
 118 
     | 
    
         
            +
                      # ActionView appends every literal string it finds in the ERB source,
         
     | 
| 
      
 119 
     | 
    
         
            +
                      # which includes newlines and other incidental whitespace that we
         
     | 
| 
      
 120 
     | 
    
         
            +
                      # programmers frequently use to indent our code and otherwise make it
         
     | 
| 
      
 121 
     | 
    
         
            +
                      # look readable to other humans. This extra whitespace isn't part of
         
     | 
| 
      
 122 
     | 
    
         
            +
                      # the string itself, but because there's nothing in ERB to indicate
         
     | 
| 
      
 123 
     | 
    
         
            +
                      # where whitespace should stop and the string should start,
         
     | 
| 
      
 124 
     | 
    
         
            +
                      # ActionView just sort of smushes it all together. The whitespace is
         
     | 
| 
      
 125 
     | 
    
         
            +
                      # important for the aforementioned formatting reasons, so it
         
     | 
| 
      
 126 
     | 
    
         
            +
                      # shouldn't just be thrown away. Instead, the following lines extract
         
     | 
| 
      
 127 
     | 
    
         
            +
                      # the important string part along with the leading and trailing
         
     | 
| 
      
 128 
     | 
    
         
            +
                      # whitespace, then quote the string and stick all the parts back
         
     | 
| 
      
 129 
     | 
    
         
            +
                      # together. Seems to work ok.
         
     | 
| 
      
 130 
     | 
    
         
            +
                      leading_ws, str, trailing_ws = ws_split(str)
         
     | 
| 
      
 131 
     | 
    
         
            +
                      str = "#{leading_ws}#{rb_quote(str)}#{trailing_ws}"
         
     | 
| 
      
 132 
     | 
    
         
            +
                    end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                    str
         
     | 
| 
      
 135 
     | 
    
         
            +
                  end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                  def tag_start?(str)
         
     | 
| 
      
 138 
     | 
    
         
            +
                    str.strip.start_with?('<')
         
     | 
| 
      
 139 
     | 
    
         
            +
                  end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                  def ws_split(str)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    leading_ws = str.match(/\A(\s*)/)
         
     | 
| 
      
 143 
     | 
    
         
            +
                    # Pass the second arg here to avoid considering the same whitespace
         
     | 
| 
      
 144 
     | 
    
         
            +
                    # as both leading _and_ trailing, as in the case where the string is
         
     | 
| 
      
 145 
     | 
    
         
            +
                    # entirely whitespace, etc.
         
     | 
| 
      
 146 
     | 
    
         
            +
                    trailing_ws = str.match(/(\s*)\z/, leading_ws.end(0))
         
     | 
| 
      
 147 
     | 
    
         
            +
                    middle = str[leading_ws.end(0)...(trailing_ws.begin(0))]
         
     | 
| 
      
 148 
     | 
    
         
            +
                    [leading_ws.captures[0], middle, trailing_ws.captures[0]]
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                  # Because we have to perform replacements in reverse order to avoid
         
     | 
| 
      
 152 
     | 
    
         
            +
                  # invalidating the ranges that come later in the source, it's impossible
         
     | 
| 
      
 153 
     | 
    
         
            +
                  # to know whether a particular node is already wrapped in Rux curly
         
     | 
| 
      
 154 
     | 
    
         
            +
                  # braces or not. In other words, whether or not the current node exists
         
     | 
| 
      
 155 
     | 
    
         
            +
                  # inside a code block is entirely determined by the nodes that come
         
     | 
| 
      
 156 
     | 
    
         
            +
                  # before it, which cannot be considered when iterating in reverse order.
         
     | 
| 
      
 157 
     | 
    
         
            +
                  # To mitigate this problem, we first iterate in a forward manner and
         
     | 
| 
      
 158 
     | 
    
         
            +
                  # accrue metadata for each node. The NodeMeta struct holds a reference
         
     | 
| 
      
 159 
     | 
    
         
            +
                  # to the node, meaning that the replacement algorithm can simply iterate
         
     | 
| 
      
 160 
     | 
    
         
            +
                  # backwards through a list of them.
         
     | 
| 
      
 161 
     | 
    
         
            +
                  def calc_node_meta(nodes)
         
     | 
| 
      
 162 
     | 
    
         
            +
                    # Start out assuming we're in code. This is also what the Rux parser
         
     | 
| 
      
 163 
     | 
    
         
            +
                    # does.
         
     | 
| 
      
 164 
     | 
    
         
            +
                    in_code = true
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                    nodes.each_with_object([]) do |child_node, memo|
         
     | 
| 
      
 167 
     | 
    
         
            +
                      next unless is_node?(child_node)
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                      stype = send_type_for(child_node)
         
     | 
| 
      
 170 
     | 
    
         
            +
                      replacement = rewrite(child_node)
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                      memo << NodeMeta.new(child_node, stype, replacement, in_code)
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                      case stype
         
     | 
| 
      
 175 
     | 
    
         
            +
                        when :string
         
     | 
| 
      
 176 
     | 
    
         
            +
                          if tag_start?(replacement)
         
     | 
| 
      
 177 
     | 
    
         
            +
                            # If we're inside an HTML tag, that must mean we're not in a
         
     | 
| 
      
 178 
     | 
    
         
            +
                            # code block anymore.
         
     | 
| 
      
 179 
     | 
    
         
            +
                            in_code = false
         
     | 
| 
      
 180 
     | 
    
         
            +
                          end
         
     | 
| 
      
 181 
     | 
    
         
            +
                        when :code
         
     | 
| 
      
 182 
     | 
    
         
            +
                          in_code = true
         
     | 
| 
      
 183 
     | 
    
         
            +
                      end
         
     | 
| 
      
 184 
     | 
    
         
            +
                    end
         
     | 
| 
      
 185 
     | 
    
         
            +
                  end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                  def rewrite_children(node)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    node_loc = node.location.expression
         
     | 
| 
      
 189 
     | 
    
         
            +
                    child_meta = calc_node_meta(node.children)
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                    node_loc.source.dup.tap do |result|
         
     | 
| 
      
 192 
     | 
    
         
            +
                      # The replacement algorithm needs to be able to consider the previous
         
     | 
| 
      
 193 
     | 
    
         
            +
                      # node's metadata, so we use a sliding window of 2.
         
     | 
| 
      
 194 
     | 
    
         
            +
                      reverse_each_cons(2, child_meta) do |prev, cur|
         
     | 
| 
      
 195 
     | 
    
         
            +
                        next unless cur
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                        child_loc = cur.node.location.expression
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                        # A code node inside HTML who's replacement isn't HTML. Needs to be
         
     | 
| 
      
 200 
     | 
    
         
            +
                        # wrapped in Rux curlies.
         
     | 
| 
      
 201 
     | 
    
         
            +
                        if (cur.stype == :code || cur.stype == nil) && !cur.in_code && !tag_start?(cur.replacement)
         
     | 
| 
      
 202 
     | 
    
         
            +
                          cur.replacement = "{#{cur.replacement}}"
         
     | 
| 
      
 203 
     | 
    
         
            +
                        end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                        # ERB nodes that occur right next to each other should be concatenated.
         
     | 
| 
      
 206 
     | 
    
         
            +
                        # Eg: foo <%= bar %> should result in "foo" + bar.
         
     | 
| 
      
 207 
     | 
    
         
            +
                        # This check makes sure that:
         
     | 
| 
      
 208 
     | 
    
         
            +
                        # 1. Both the previous and current nodes are ERB code (i.e. are
         
     | 
| 
      
 209 
     | 
    
         
            +
                        #    :code or :string).
         
     | 
| 
      
 210 
     | 
    
         
            +
                        # 2. Both the previous and current nodes are in a code block. If
         
     | 
| 
      
 211 
     | 
    
         
            +
                        #    they're not, it doesn't make much sense to concatenate them
         
     | 
| 
      
 212 
     | 
    
         
            +
                        #    using Ruby's `+' operator.
         
     | 
| 
      
 213 
     | 
    
         
            +
                        # 3. Both the previous and current nodes aren't 100% whitespace,
         
     | 
| 
      
 214 
     | 
    
         
            +
                        #    which would indicate they're for formatting purposes and don't
         
     | 
| 
      
 215 
     | 
    
         
            +
                        #    contain actual code.
         
     | 
| 
      
 216 
     | 
    
         
            +
                        should_concat =
         
     | 
| 
      
 217 
     | 
    
         
            +
                          prev &&
         
     | 
| 
      
 218 
     | 
    
         
            +
                          cur.stype &&
         
     | 
| 
      
 219 
     | 
    
         
            +
                          prev.stype &&
         
     | 
| 
      
 220 
     | 
    
         
            +
                          cur.in_code &&
         
     | 
| 
      
 221 
     | 
    
         
            +
                          prev.in_code &&
         
     | 
| 
      
 222 
     | 
    
         
            +
                          !cur.replacement.strip.empty? &&
         
     | 
| 
      
 223 
     | 
    
         
            +
                          !prev.replacement.strip.empty?
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                        if should_concat
         
     | 
| 
      
 226 
     | 
    
         
            +
                          cur.replacement = " + #{cur.replacement}"
         
     | 
| 
      
 227 
     | 
    
         
            +
                        end
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
      
 229 
     | 
    
         
            +
                        begin_pos = child_loc.begin_pos - node_loc.begin_pos
         
     | 
| 
      
 230 
     | 
    
         
            +
                        end_pos = child_loc.end_pos - node_loc.begin_pos
         
     | 
| 
      
 231 
     | 
    
         
            +
                        # Trim off those pesky trailing semicolons ActionView adds.
         
     | 
| 
      
 232 
     | 
    
         
            +
                        end_pos += 1 if result[end_pos] == ';'
         
     | 
| 
      
 233 
     | 
    
         
            +
                        result[begin_pos...end_pos] = cur.replacement
         
     | 
| 
      
 234 
     | 
    
         
            +
                      end
         
     | 
| 
      
 235 
     | 
    
         
            +
                    end
         
     | 
| 
      
 236 
     | 
    
         
            +
                  end
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
                  # Iterates backwards over the `items` enumerable and yields a sliding
         
     | 
| 
      
 239 
     | 
    
         
            +
                  # window of `size` elements.
         
     | 
| 
      
 240 
     | 
    
         
            +
                  #
         
     | 
| 
      
 241 
     | 
    
         
            +
                  # For example, reverse_each_cons(3, %w(a b c d)) yields the following:
         
     | 
| 
      
 242 
     | 
    
         
            +
                  #
         
     | 
| 
      
 243 
     | 
    
         
            +
                  # ["d", nil, nil]
         
     | 
| 
      
 244 
     | 
    
         
            +
                  # ["c", "d", nil]
         
     | 
| 
      
 245 
     | 
    
         
            +
                  # ["b", "c", "d"]
         
     | 
| 
      
 246 
     | 
    
         
            +
                  # ["a", "b", "c"]
         
     | 
| 
      
 247 
     | 
    
         
            +
                  # [nil, "a", "b"]
         
     | 
| 
      
 248 
     | 
    
         
            +
                  # [nil, nil, "a"]
         
     | 
| 
      
 249 
     | 
    
         
            +
                  def reverse_each_cons(size, items)
         
     | 
| 
      
 250 
     | 
    
         
            +
                    slots = Array.new(size)
         
     | 
| 
      
 251 
     | 
    
         
            +
                    enum = items.reverse_each
         
     | 
| 
      
 252 
     | 
    
         
            +
                    stops = nil
         
     | 
| 
      
 253 
     | 
    
         
            +
             
     | 
| 
      
 254 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 255 
     | 
    
         
            +
                      item = begin
         
     | 
| 
      
 256 
     | 
    
         
            +
                        stops += 1 if stops
         
     | 
| 
      
 257 
     | 
    
         
            +
                        stops ? nil : enum.next
         
     | 
| 
      
 258 
     | 
    
         
            +
                      rescue StopIteration
         
     | 
| 
      
 259 
     | 
    
         
            +
                        stops = 1
         
     | 
| 
      
 260 
     | 
    
         
            +
                        nil
         
     | 
| 
      
 261 
     | 
    
         
            +
                      end
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                      slots.unshift(item)
         
     | 
| 
      
 264 
     | 
    
         
            +
                      slots.pop
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
                      yield slots
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                      # It's not useful to anybody to yield an array of all nils.
         
     | 
| 
      
 269 
     | 
    
         
            +
                      break if stops == size - 1
         
     | 
| 
      
 270 
     | 
    
         
            +
                    end
         
     | 
| 
      
 271 
     | 
    
         
            +
                  end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
                  def identify_component_render(node)
         
     | 
| 
      
 274 
     | 
    
         
            +
                    send_node, *block_args_node = *node
         
     | 
| 
      
 275 
     | 
    
         
            +
                    block_arg_nodes = block_args_node[0]&.children || []
         
     | 
| 
      
 276 
     | 
    
         
            +
                    # Doesn't make sense for a component render to have more than one
         
     | 
| 
      
 277 
     | 
    
         
            +
                    # block argument.
         
     | 
| 
      
 278 
     | 
    
         
            +
                    return if block_arg_nodes.size > 1
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                    block_arg_node, = *block_arg_nodes
         
     | 
| 
      
 281 
     | 
    
         
            +
                    return if block_arg_node && block_arg_node.type != :arg
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
                    block_arg_name, = *block_arg_node
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                    receiver_node, method_name, *render_args = *send_node
         
     | 
| 
      
 286 
     | 
    
         
            +
                    # There must be no receiver (i.e. no dot), the method name must be
         
     | 
| 
      
 287 
     | 
    
         
            +
                    # "render," and it must take exactly one argument.
         
     | 
| 
      
 288 
     | 
    
         
            +
                    return if receiver_node || method_name != :render
         
     | 
| 
      
 289 
     | 
    
         
            +
                    return if !render_args || render_args.size != 1
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
      
 291 
     | 
    
         
            +
                    render_arg, = *render_args
         
     | 
| 
      
 292 
     | 
    
         
            +
                    # The argument passed to render must be a s(:send) node, which
         
     | 
| 
      
 293 
     | 
    
         
            +
                    # indicates the presence of .new being called on a component class.
         
     | 
| 
      
 294 
     | 
    
         
            +
                    # This seems fine for now, but might need to change if we find there
         
     | 
| 
      
 295 
     | 
    
         
            +
                    # are other common ways to pass component instances to render.
         
     | 
| 
      
 296 
     | 
    
         
            +
                    return if render_arg.type != :send
         
     | 
| 
      
 297 
     | 
    
         
            +
             
     | 
| 
      
 298 
     | 
    
         
            +
                    component_name_node, _, *component_args = *render_arg
         
     | 
| 
      
 299 
     | 
    
         
            +
                    # The Parser gem treats keyword args as a single arg, and since Rux
         
     | 
| 
      
 300 
     | 
    
         
            +
                    # doesn't allow positional arguments, it's best to leave non-conforming
         
     | 
| 
      
 301 
     | 
    
         
            +
                    # renders alone and bail out here.
         
     | 
| 
      
 302 
     | 
    
         
            +
                    return if component_args.size > 1
         
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
      
 304 
     | 
    
         
            +
                    component_kwargs, = *component_args
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
                    kwargs = if component_kwargs
         
     | 
| 
      
 307 
     | 
    
         
            +
                      # Whatever this first argument is, it'd better be a hash. This is how
         
     | 
| 
      
 308 
     | 
    
         
            +
                      # the Parser gem parses keyword arguments, even though kwargs are no
         
     | 
| 
      
 309 
     | 
    
         
            +
                      # longer treated as hashes in modern Rubies.
         
     | 
| 
      
 310 
     | 
    
         
            +
                      return unless component_kwargs.type == :hash
         
     | 
| 
      
 311 
     | 
    
         
            +
             
     | 
| 
      
 312 
     | 
    
         
            +
                      # Build up an array of 2-element [key, value] arrays
         
     | 
| 
      
 313 
     | 
    
         
            +
                      component_kwargs.children.map do |component_kwarg|
         
     | 
| 
      
 314 
     | 
    
         
            +
                        key, value = *component_kwarg
         
     | 
| 
      
 315 
     | 
    
         
            +
                        return unless [:sym, :str].include?(key.type)
         
     | 
| 
      
 316 
     | 
    
         
            +
             
     | 
| 
      
 317 
     | 
    
         
            +
                        [key.children[0], Unparser.unparse(value)]
         
     | 
| 
      
 318 
     | 
    
         
            +
                      end
         
     | 
| 
      
 319 
     | 
    
         
            +
                    else
         
     | 
| 
      
 320 
     | 
    
         
            +
                      # It's perfectly ok for components not to accept any args
         
     | 
| 
      
 321 
     | 
    
         
            +
                      []
         
     | 
| 
      
 322 
     | 
    
         
            +
                    end
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
      
 324 
     | 
    
         
            +
                    source = node.location.expression.source
         
     | 
| 
      
 325 
     | 
    
         
            +
             
     | 
| 
      
 326 
     | 
    
         
            +
                    # This is the base position all other positions should start from.
         
     | 
| 
      
 327 
     | 
    
         
            +
                    # The Parser gem's position information is all absolute, i.e.
         
     | 
| 
      
 328 
     | 
    
         
            +
                    # measured from the beginning of the original source buffer. We want to
         
     | 
| 
      
 329 
     | 
    
         
            +
                    # consider only this one node, meaning all positions must be adjusted
         
     | 
| 
      
 330 
     | 
    
         
            +
                    # so they are relative to it.
         
     | 
| 
      
 331 
     | 
    
         
            +
                    start = node.location.expression.begin_pos
         
     | 
| 
      
 332 
     | 
    
         
            +
                    send_stop = if block_arg_node
         
     | 
| 
      
 333 
     | 
    
         
            +
                      # Annoyingly, the Parser gem doesn't include the trailing block
         
     | 
| 
      
 334 
     | 
    
         
            +
                      # terminator pipe in location.end. We have to find it manually with
         
     | 
| 
      
 335 
     | 
    
         
            +
                      # this #index call instead. What a pain.
         
     | 
| 
      
 336 
     | 
    
         
            +
                      block_arg_start = block_arg_node.location.expression.begin_pos - start
         
     | 
| 
      
 337 
     | 
    
         
            +
                      source.index('|', block_arg_start) + 1
         
     | 
| 
      
 338 
     | 
    
         
            +
                    else
         
     | 
| 
      
 339 
     | 
    
         
            +
                      # If we get to this point and there is no block passed to render,
         
     | 
| 
      
 340 
     | 
    
         
            +
                      # that means we're looking at the surrounding block Erubi adds
         
     | 
| 
      
 341 
     | 
    
         
            +
                      # around Ruby code (effectively surrounding it with parens). In such
         
     | 
| 
      
 342 
     | 
    
         
            +
                      # a case, the "begin" location points to the opening left paren. If
         
     | 
| 
      
 343 
     | 
    
         
            +
                      # instead there _is_ a block passed to render, the "begin" location
         
     | 
| 
      
 344 
     | 
    
         
            +
                      # points to the "do" statement. Truly confusing, but here we are.
         
     | 
| 
      
 345 
     | 
    
         
            +
                      if node.location.begin.source == 'do'
         
     | 
| 
      
 346 
     | 
    
         
            +
                        node.location.begin.end_pos - start
         
     | 
| 
      
 347 
     | 
    
         
            +
                      else
         
     | 
| 
      
 348 
     | 
    
         
            +
                        node.location.expression.end_pos - start
         
     | 
| 
      
 349 
     | 
    
         
            +
                      end
         
     | 
| 
      
 350 
     | 
    
         
            +
                    end
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
                    block_end_range = nil
         
     | 
| 
      
 353 
     | 
    
         
            +
                    block_body_range = nil
         
     | 
| 
      
 354 
     | 
    
         
            +
             
     | 
| 
      
 355 
     | 
    
         
            +
                    if node.type == :block
         
     | 
| 
      
 356 
     | 
    
         
            +
                      # Use index here to find the first non-whitespace character after the
         
     | 
| 
      
 357 
     | 
    
         
            +
                      # render call, as well as the first non-whitespace character before
         
     | 
| 
      
 358 
     | 
    
         
            +
                      # the end of the "end" statement.
         
     | 
| 
      
 359 
     | 
    
         
            +
                      block_body_start = source.index(/\S/, send_stop)
         
     | 
| 
      
 360 
     | 
    
         
            +
                      block_body_end = source.rindex(/\S/, node.location.end.begin_pos - start - 1) + 1
         
     | 
| 
      
 361 
     | 
    
         
            +
             
     | 
| 
      
 362 
     | 
    
         
            +
                      block_end = node.location.end
         
     | 
| 
      
 363 
     | 
    
         
            +
                      block_end_range = (block_end.begin_pos - start)...(block_end.end_pos - start)
         
     | 
| 
      
 364 
     | 
    
         
            +
                      block_body_range = block_body_start...block_body_end
         
     | 
| 
      
 365 
     | 
    
         
            +
                    end
         
     | 
| 
      
 366 
     | 
    
         
            +
             
     | 
| 
      
 367 
     | 
    
         
            +
                    return ComponentRender.new(
         
     | 
| 
      
 368 
     | 
    
         
            +
                      component_name: Unparser.unparse(component_name_node),
         
     | 
| 
      
 369 
     | 
    
         
            +
                      component_kwargs: kwargs,
         
     | 
| 
      
 370 
     | 
    
         
            +
                      send_range: 0...send_stop,
         
     | 
| 
      
 371 
     | 
    
         
            +
                      block_end_range: block_end_range,
         
     | 
| 
      
 372 
     | 
    
         
            +
                      block_body_range: block_body_range,
         
     | 
| 
      
 373 
     | 
    
         
            +
                      block_arg: block_arg_name
         
     | 
| 
      
 374 
     | 
    
         
            +
                    )
         
     | 
| 
      
 375 
     | 
    
         
            +
                  end
         
     | 
| 
      
 376 
     | 
    
         
            +
             
     | 
| 
      
 377 
     | 
    
         
            +
                  # Escapes double quotes, then double quotes the result.
         
     | 
| 
      
 378 
     | 
    
         
            +
                  def rb_quote(str)
         
     | 
| 
      
 379 
     | 
    
         
            +
                    return '' if !str || str.empty?
         
     | 
| 
      
 380 
     | 
    
         
            +
                    "\"#{str.gsub("\"", "\\\"")}\""
         
     | 
| 
      
 381 
     | 
    
         
            +
                  end
         
     | 
| 
      
 382 
     | 
    
         
            +
             
     | 
| 
      
 383 
     | 
    
         
            +
                  def send_type_for(node)
         
     | 
| 
      
 384 
     | 
    
         
            +
                    return unless is_node?(node)
         
     | 
| 
      
 385 
     | 
    
         
            +
             
     | 
| 
      
 386 
     | 
    
         
            +
                    receiver_node, method_name, = *node
         
     | 
| 
      
 387 
     | 
    
         
            +
                    return unless is_node?(receiver_node)
         
     | 
| 
      
 388 
     | 
    
         
            +
             
     | 
| 
      
 389 
     | 
    
         
            +
                    # Does this node indicate a method called on the @output_buffer
         
     | 
| 
      
 390 
     | 
    
         
            +
                    # instance variable?
         
     | 
| 
      
 391 
     | 
    
         
            +
                    is_buffer_append = receiver_node.type == :ivar &&
         
     | 
| 
      
 392 
     | 
    
         
            +
                      receiver_node.children[0] == :@output_buffer
         
     | 
| 
      
 393 
     | 
    
         
            +
             
     | 
| 
      
 394 
     | 
    
         
            +
                    return unless is_buffer_append
         
     | 
| 
      
 395 
     | 
    
         
            +
             
     | 
| 
      
 396 
     | 
    
         
            +
                    case method_name
         
     | 
| 
      
 397 
     | 
    
         
            +
                      when :safe_append=
         
     | 
| 
      
 398 
     | 
    
         
            +
                        :string
         
     | 
| 
      
 399 
     | 
    
         
            +
                      when :append=
         
     | 
| 
      
 400 
     | 
    
         
            +
                        :code
         
     | 
| 
      
 401 
     | 
    
         
            +
                    end
         
     | 
| 
      
 402 
     | 
    
         
            +
                  end
         
     | 
| 
      
 403 
     | 
    
         
            +
                end
         
     | 
| 
      
 404 
     | 
    
         
            +
              end
         
     | 
| 
      
 405 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/erb2rux.rb
    ADDED
    
    
    
        data/spec/spec_helper.rb
    ADDED
    
    | 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $:.push(__dir__)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rspec'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'erb2rux'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'pry-byebug'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            Dir.chdir(__dir__) do
         
     | 
| 
      
 8 
     | 
    
         
            +
              Dir['support/*.rb'].each { |f| require f }
         
     | 
| 
      
 9 
     | 
    
         
            +
            end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module SpecHelpers
         
     | 
| 
      
 12 
     | 
    
         
            +
            end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            RSpec.configure do |config|
         
     | 
| 
      
 15 
     | 
    
         
            +
              config.include SpecHelpers
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,257 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Erb2Rux::Transformer do
         
     | 
| 
      
 4 
     | 
    
         
            +
              def transform(str)
         
     | 
| 
      
 5 
     | 
    
         
            +
                described_class.transform(str)
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              it 'handles a literal string' do
         
     | 
| 
      
 9 
     | 
    
         
            +
                expect(transform('foo')).to eq('"foo"')
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              it 'handles a single variable' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                expect(transform('<%= foo %>')).to eq('foo')
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              it 'concatenates strings and code' do
         
     | 
| 
      
 17 
     | 
    
         
            +
                expect(transform('foo <%= bar %>')).to eq('"foo"  + bar')
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              it 'handles a simple if statement' do
         
     | 
| 
      
 21 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 22 
     | 
    
         
            +
                  <% if foo %>
         
     | 
| 
      
 23 
     | 
    
         
            +
                    bar
         
     | 
| 
      
 24 
     | 
    
         
            +
                  <% else %>
         
     | 
| 
      
 25 
     | 
    
         
            +
                    <%= baz %>
         
     | 
| 
      
 26 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 27 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if foo
         
     | 
| 
      
 31 
     | 
    
         
            +
                    "bar"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  else
         
     | 
| 
      
 33 
     | 
    
         
            +
                    baz
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              it 'wraps code in curlies' do
         
     | 
| 
      
 39 
     | 
    
         
            +
                expect(transform('<a><%= foo %></a>')).to eq('<a>{foo}</a>')
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              it 'wraps control structures in curlies' do
         
     | 
| 
      
 43 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 44 
     | 
    
         
            +
                  <a>
         
     | 
| 
      
 45 
     | 
    
         
            +
                    <% if foo %>
         
     | 
| 
      
 46 
     | 
    
         
            +
                      bar
         
     | 
| 
      
 47 
     | 
    
         
            +
                    <% else %>
         
     | 
| 
      
 48 
     | 
    
         
            +
                      <%= baz %>
         
     | 
| 
      
 49 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 50 
     | 
    
         
            +
                  </a>
         
     | 
| 
      
 51 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  <a>
         
     | 
| 
      
 55 
     | 
    
         
            +
                    {if foo
         
     | 
| 
      
 56 
     | 
    
         
            +
                      "bar"
         
     | 
| 
      
 57 
     | 
    
         
            +
                    else
         
     | 
| 
      
 58 
     | 
    
         
            +
                      baz
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end}
         
     | 
| 
      
 60 
     | 
    
         
            +
                  </a>
         
     | 
| 
      
 61 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
              it 'handles component renders' do
         
     | 
| 
      
 65 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 66 
     | 
    
         
            +
                  <%= render(FooComponent.new) %>
         
     | 
| 
      
 67 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                expect(result).to eq('<FooComponent />')
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              it 'handles component renders with arguments' do
         
     | 
| 
      
 73 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 74 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) %>
         
     | 
| 
      
 75 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                expect(result).to eq('<FooComponent bar={"baz"} />')
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              it 'handles component renders with empty blocks' do
         
     | 
| 
      
 81 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 82 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do %>
         
     | 
| 
      
 83 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 84 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  <FooComponent bar={"baz"}>
         
     | 
| 
      
 88 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 89 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              it 'handles component renders with blocks that contain strings' do
         
     | 
| 
      
 93 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 94 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do %>
         
     | 
| 
      
 95 
     | 
    
         
            +
                    foobar
         
     | 
| 
      
 96 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 97 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  <FooComponent bar={"baz"}>
         
     | 
| 
      
 101 
     | 
    
         
            +
                    {"foobar"}
         
     | 
| 
      
 102 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 103 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
              it 'handles component renders with blocks that contain code' do
         
     | 
| 
      
 107 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 108 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do %>
         
     | 
| 
      
 109 
     | 
    
         
            +
                    <%= foobar %>
         
     | 
| 
      
 110 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 111 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  <FooComponent bar={"baz"}>
         
     | 
| 
      
 115 
     | 
    
         
            +
                    {foobar}
         
     | 
| 
      
 116 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 117 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
              it 'handles component renders with blocks that contain strings and code' do
         
     | 
| 
      
 121 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 122 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do %>
         
     | 
| 
      
 123 
     | 
    
         
            +
                    <%= foobar %> foobaz
         
     | 
| 
      
 124 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 125 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  <FooComponent bar={"baz"}>
         
     | 
| 
      
 129 
     | 
    
         
            +
                    {foobar +  "foobaz"}
         
     | 
| 
      
 130 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 131 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 132 
     | 
    
         
            +
              end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
              it 'handles component renders with blocks that have a block arg' do
         
     | 
| 
      
 135 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 136 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do |component| %>
         
     | 
| 
      
 137 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 138 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  <FooComponent bar={"baz"} as={"component"}>
         
     | 
| 
      
 142 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 143 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 144 
     | 
    
         
            +
              end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
              it 'handles component renders with blocks that have a block arg and code' do
         
     | 
| 
      
 147 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 148 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do |component| %>
         
     | 
| 
      
 149 
     | 
    
         
            +
                    <% component.sidebar do %>
         
     | 
| 
      
 150 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 151 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 152 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 155 
     | 
    
         
            +
                  <FooComponent bar={"baz"} as={"component"}>
         
     | 
| 
      
 156 
     | 
    
         
            +
                    {component.sidebar do
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end}
         
     | 
| 
      
 158 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 159 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 160 
     | 
    
         
            +
              end
         
     | 
| 
      
 161 
     | 
    
         
            +
             
     | 
| 
      
 162 
     | 
    
         
            +
              it 'handles component renders with blocks that have a block arg and multiple expressions' do
         
     | 
| 
      
 163 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 164 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do |component| %>
         
     | 
| 
      
 165 
     | 
    
         
            +
                    <% component.sidebar do %>
         
     | 
| 
      
 166 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 167 
     | 
    
         
            +
                    <% component.main do %>
         
     | 
| 
      
 168 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 169 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 170 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 173 
     | 
    
         
            +
                  <FooComponent bar={"baz"} as={"component"}>
         
     | 
| 
      
 174 
     | 
    
         
            +
                    {component.sidebar do
         
     | 
| 
      
 175 
     | 
    
         
            +
                    end
         
     | 
| 
      
 176 
     | 
    
         
            +
                    component.main do
         
     | 
| 
      
 177 
     | 
    
         
            +
                    end}
         
     | 
| 
      
 178 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 179 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 180 
     | 
    
         
            +
              end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
              it 'handles nesting other components inside blocks' do
         
     | 
| 
      
 183 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 184 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do |component| %>
         
     | 
| 
      
 185 
     | 
    
         
            +
                    <% component.sidebar do %>
         
     | 
| 
      
 186 
     | 
    
         
            +
                      <%= render(SidebarComponent.new) %>
         
     | 
| 
      
 187 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 188 
     | 
    
         
            +
                    <% component.main do %>
         
     | 
| 
      
 189 
     | 
    
         
            +
                      <%= render(MainComponent.new) %>
         
     | 
| 
      
 190 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 191 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 192 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 195 
     | 
    
         
            +
                  <FooComponent bar={"baz"} as={"component"}>
         
     | 
| 
      
 196 
     | 
    
         
            +
                    {component.sidebar do
         
     | 
| 
      
 197 
     | 
    
         
            +
                      <SidebarComponent />
         
     | 
| 
      
 198 
     | 
    
         
            +
                    end
         
     | 
| 
      
 199 
     | 
    
         
            +
                    component.main do
         
     | 
| 
      
 200 
     | 
    
         
            +
                      <MainComponent />
         
     | 
| 
      
 201 
     | 
    
         
            +
                    end}
         
     | 
| 
      
 202 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 203 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 204 
     | 
    
         
            +
              end
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
              it 'handles nesting other components with arguments inside blocks' do
         
     | 
| 
      
 207 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 208 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do |component| %>
         
     | 
| 
      
 209 
     | 
    
         
            +
                    <% component.sidebar do %>
         
     | 
| 
      
 210 
     | 
    
         
            +
                      <%= render(SidebarComponent.new(bar: 'baz')) %>
         
     | 
| 
      
 211 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 212 
     | 
    
         
            +
                    <% component.main do %>
         
     | 
| 
      
 213 
     | 
    
         
            +
                      <%= render(MainComponent.new(bar: 'baz')) %>
         
     | 
| 
      
 214 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 215 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 216 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 219 
     | 
    
         
            +
                  <FooComponent bar={"baz"} as={"component"}>
         
     | 
| 
      
 220 
     | 
    
         
            +
                    {component.sidebar do
         
     | 
| 
      
 221 
     | 
    
         
            +
                      <SidebarComponent bar={"baz"} />
         
     | 
| 
      
 222 
     | 
    
         
            +
                    end
         
     | 
| 
      
 223 
     | 
    
         
            +
                    component.main do
         
     | 
| 
      
 224 
     | 
    
         
            +
                      <MainComponent bar={"baz"} />
         
     | 
| 
      
 225 
     | 
    
         
            +
                    end}
         
     | 
| 
      
 226 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 227 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 228 
     | 
    
         
            +
              end
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
              it 'handles nesting other components with blocks' do
         
     | 
| 
      
 231 
     | 
    
         
            +
                result = transform(<<~ERB).strip
         
     | 
| 
      
 232 
     | 
    
         
            +
                  <%= render(FooComponent.new(bar: 'baz')) do |component| %>
         
     | 
| 
      
 233 
     | 
    
         
            +
                    <% component.sidebar do %>
         
     | 
| 
      
 234 
     | 
    
         
            +
                      <%= render(SidebarComponent.new(bar: 'baz')) do %>
         
     | 
| 
      
 235 
     | 
    
         
            +
                      <% end %>
         
     | 
| 
      
 236 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 237 
     | 
    
         
            +
                    <% component.main do %>
         
     | 
| 
      
 238 
     | 
    
         
            +
                      <%= render(MainComponent.new(bar: 'baz')) do %>
         
     | 
| 
      
 239 
     | 
    
         
            +
                      <% end %>
         
     | 
| 
      
 240 
     | 
    
         
            +
                    <% end %>
         
     | 
| 
      
 241 
     | 
    
         
            +
                  <% end %>
         
     | 
| 
      
 242 
     | 
    
         
            +
                ERB
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                expect(result).to eq(<<~RUX.strip)
         
     | 
| 
      
 245 
     | 
    
         
            +
                  <FooComponent bar={"baz"} as={"component"}>
         
     | 
| 
      
 246 
     | 
    
         
            +
                    {component.sidebar do
         
     | 
| 
      
 247 
     | 
    
         
            +
                      <SidebarComponent bar={"baz"}>
         
     | 
| 
      
 248 
     | 
    
         
            +
                      </SidebarComponent>
         
     | 
| 
      
 249 
     | 
    
         
            +
                    end
         
     | 
| 
      
 250 
     | 
    
         
            +
                    component.main do
         
     | 
| 
      
 251 
     | 
    
         
            +
                      <MainComponent bar={"baz"}>
         
     | 
| 
      
 252 
     | 
    
         
            +
                      </MainComponent>
         
     | 
| 
      
 253 
     | 
    
         
            +
                    end}
         
     | 
| 
      
 254 
     | 
    
         
            +
                  </FooComponent>
         
     | 
| 
      
 255 
     | 
    
         
            +
                RUX
         
     | 
| 
      
 256 
     | 
    
         
            +
              end
         
     | 
| 
      
 257 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,97 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: erb2rux
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.1.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Cameron Dutro
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire:
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2021-09-12 00:00:00.000000000 Z
         
     | 
| 
      
 12 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: actionview
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '6.1'
         
     | 
| 
      
 20 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 21 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 22 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 23 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 24 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 26 
     | 
    
         
            +
                    version: '6.1'
         
     | 
| 
      
 27 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 28 
     | 
    
         
            +
              name: parser
         
     | 
| 
      
 29 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 30 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 31 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 33 
     | 
    
         
            +
                    version: '3.0'
         
     | 
| 
      
 34 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 35 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 36 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 37 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 40 
     | 
    
         
            +
                    version: '3.0'
         
     | 
| 
      
 41 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 42 
     | 
    
         
            +
              name: unparser
         
     | 
| 
      
 43 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 44 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 45 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 46 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 47 
     | 
    
         
            +
                    version: '0.6'
         
     | 
| 
      
 48 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 49 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 50 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 51 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 52 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 53 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 54 
     | 
    
         
            +
                    version: '0.6'
         
     | 
| 
      
 55 
     | 
    
         
            +
            description: Automatically convert .html.erb files into .rux files.
         
     | 
| 
      
 56 
     | 
    
         
            +
            email:
         
     | 
| 
      
 57 
     | 
    
         
            +
            - camertron@gmail.com
         
     | 
| 
      
 58 
     | 
    
         
            +
            executables:
         
     | 
| 
      
 59 
     | 
    
         
            +
            - erb2rux
         
     | 
| 
      
 60 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 61 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 62 
     | 
    
         
            +
            files:
         
     | 
| 
      
 63 
     | 
    
         
            +
            - Gemfile
         
     | 
| 
      
 64 
     | 
    
         
            +
            - LICENSE
         
     | 
| 
      
 65 
     | 
    
         
            +
            - README.md
         
     | 
| 
      
 66 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 67 
     | 
    
         
            +
            - bin/erb2rux
         
     | 
| 
      
 68 
     | 
    
         
            +
            - erb2rux.gemspec
         
     | 
| 
      
 69 
     | 
    
         
            +
            - lib/erb2rux.rb
         
     | 
| 
      
 70 
     | 
    
         
            +
            - lib/erb2rux/component_render.rb
         
     | 
| 
      
 71 
     | 
    
         
            +
            - lib/erb2rux/transformer.rb
         
     | 
| 
      
 72 
     | 
    
         
            +
            - lib/erb2rux/version.rb
         
     | 
| 
      
 73 
     | 
    
         
            +
            - spec/spec_helper.rb
         
     | 
| 
      
 74 
     | 
    
         
            +
            - spec/transformer_spec.rb
         
     | 
| 
      
 75 
     | 
    
         
            +
            homepage: http://github.com/camertron/erb2rux
         
     | 
| 
      
 76 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 77 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 78 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
      
 79 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 80 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 81 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 82 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 83 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 84 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 85 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 86 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 87 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 88 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 89 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 90 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 91 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 92 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 93 
     | 
    
         
            +
            rubygems_version: 3.2.22
         
     | 
| 
      
 94 
     | 
    
         
            +
            signing_key:
         
     | 
| 
      
 95 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 96 
     | 
    
         
            +
            summary: Automatically convert .html.erb files into .rux files.
         
     | 
| 
      
 97 
     | 
    
         
            +
            test_files: []
         
     |