├── lib ├── erbse │ ├── version.rb │ └── parser.rb └── erbse.rb ├── test ├── test_helper.rb └── erbse_test.rb ├── benchmark ├── templates │ ├── _footer.html │ ├── bench_erb.rhtml │ ├── bench_erubis.rhtml │ ├── bench_eruby.rhtml │ └── _header.html ├── Makefile ├── bench_context.yaml └── bench.rb ├── Gemfile ├── Rakefile ├── erbse.gemspec ├── MIT-LICENSE ├── CHANGES.md └── README.md /lib/erbse/version.rb: -------------------------------------------------------------------------------- 1 | module Erbse 2 | VERSION = "0.1.4" 3 | end 4 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "erbse" 2 | require "minitest/autorun" 3 | -------------------------------------------------------------------------------- /benchmark/templates/_footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem "minitest-line" 6 | -------------------------------------------------------------------------------- /benchmark/Makefile: -------------------------------------------------------------------------------- 1 | 2 | bench: 3 | ruby bench.rb -n 10000 4 | 5 | clean: 6 | rm -rf bench_*.rhtml* 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rake/testtask' 5 | 6 | desc 'Default: run unit tests.' 7 | task :default => :test 8 | 9 | Rake::TestTask.new(:test) do |test| 10 | test.libs << 'test' 11 | test.pattern = 'test/*_test.rb' 12 | test.verbose = true 13 | end 14 | -------------------------------------------------------------------------------- /benchmark/templates/bench_erb.rhtml: -------------------------------------------------------------------------------- 1 | 2 | <% 3 | n = 0 4 | for item in list 5 | n += 1 6 | %> 7 | 8 | <%= n %> 9 | 10 | <%= item['symbol'] %> 11 | 12 | 13 | <%= item['name'] %> 14 | 15 | 16 | <%= item['price'] %> 17 | 18 | <% if item['change'] < 0.0 %> 19 | <%= item['change'] %> 20 | <%= item['ratio'] %> 21 | <% else %> 22 | <%= item['change'] %> 23 | <%= item['ratio'] %> 24 | <% end %> 25 | 26 | <% 27 | end 28 | %> 29 | 30 | -------------------------------------------------------------------------------- /benchmark/templates/bench_erubis.rhtml: -------------------------------------------------------------------------------- 1 | 2 | <% 3 | n = 0 4 | for item in list 5 | n += 1 6 | %> 7 | 8 | <%= n %> 9 | 10 | <%= item['symbol'] %> 11 | 12 | 13 | <%= item['name'] %> 14 | 15 | 16 | <%= item['price'] %> 17 | 18 | <% if item['change'] < 0.0 %> 19 | <%= item['change'] %> 20 | <%= item['ratio'] %> 21 | <% else %> 22 | <%= item['change'] %> 23 | <%= item['ratio'] %> 24 | <% end %> 25 | 26 | <% 27 | end 28 | %> 29 | 30 | -------------------------------------------------------------------------------- /benchmark/templates/bench_eruby.rhtml: -------------------------------------------------------------------------------- 1 | 2 | <% 3 | n = 0 4 | for item in list 5 | n += 1 6 | %> 7 | 8 | <%= n %> 9 | 10 | <%= item['symbol'] %> 11 | 12 | 13 | <%= item['name'] %> 14 | 15 | 16 | <%= item['price'] %> 17 | 18 | <% if item['change'] < 0.0 %> 19 | <%= item['change'] %> 20 | <%= item['ratio'] %> 21 | <% else %> 22 | <%= item['change'] %> 23 | <%= item['ratio'] %> 24 | <% end %> 25 | 26 | <% 27 | end 28 | %> 29 | 30 | -------------------------------------------------------------------------------- /erbse.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib/', __FILE__) 2 | $:.unshift lib unless $:.include?(lib) 3 | 4 | require 'erbse/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "erbse" 8 | spec.version = Erbse::VERSION 9 | spec.platform = Gem::Platform::RUBY 10 | spec.authors = ["Nick Sutterer"] 11 | spec.email = ["apotonick@gmail.com"] 12 | spec.homepage = "https://github.com/apotonick/erbse" 13 | spec.summary = %q{Updated Erubis.} 14 | spec.description = %q{An updated Erubis with block support. Block inheritance soon to come.} 15 | spec.license = 'MIT' 16 | 17 | spec.files = `git ls-files`.split("\n") 18 | spec.test_files = `git ls-files -- {test}/*`.split("\n") 19 | spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_dependency "temple" 23 | 24 | spec.add_development_dependency "rake" 25 | spec.add_development_dependency "minitest" 26 | end 27 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | copyright(c) 2006-2011 kuwata-lab.com all rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/erbse.rb: -------------------------------------------------------------------------------- 1 | require "temple" 2 | require "erbse/parser" 3 | 4 | module Erbse 5 | class BlockFilter < Temple::Filter 6 | # Highly inspired by https://github.com/slim-template/slim/blob/master/lib/slim/controls.rb#on_slim_output 7 | def on_erb_block(code, content_ast) 8 | # this is for <%= do %> 9 | outter_i = unique_name 10 | inner_i = unique_name 11 | 12 | # this still needs the Temple::Filters::ControlFlow run-through. 13 | [:multi, 14 | [:block, "#{outter_i} = #{code}", 15 | [:capture, inner_i, compile(content_ast)] 16 | ], 17 | [:dynamic, outter_i] # return the outter buffer. # DISCUSS: why do we need that, again? 18 | ] 19 | end 20 | 21 | # assign all code in the block to new local output buffer without outputting it. 22 | # handles <%@ do %> 23 | def on_capture_block(code, content_ast) 24 | [:multi, 25 | [:block, code, # var = capture do 26 | [:capture, unique_name, compile(content_ast)] 27 | ] 28 | ] 29 | end 30 | end 31 | 32 | class Engine < Temple::Engine 33 | use Parser 34 | use BlockFilter 35 | 36 | # filter :MultiFlattener 37 | # filter :StaticMerger 38 | # filter :DynamicInliner 39 | filter :ControlFlow 40 | 41 | generator :ArrayBuffer 42 | end 43 | # DISCUSS: can we add more optimizers? 44 | end 45 | 46 | -------------------------------------------------------------------------------- /benchmark/templates/_header.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Stock Prices 7 | 8 | 9 | 10 | 11 | 12 | 13 | 40 | 41 | 42 | 43 | 44 | 45 |

Stock Prices

46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.1.4 2 | 3 | * Newlines are now properly reflected in the compiled code. 4 | 5 | # 0.1.3 6 | 7 | * Do not trim whitespace between ERB tags. 8 | 9 | # 0.1.2 10 | 11 | * Postfix conditionals are now parsed properly: code such as `<% puts if true %>` now works, thanks to @aiomaster's work. 12 | * `<%@ code %>` now requires an explicit whitespace after the `@` for backward-compatibility. 13 | 14 | # 0.1.1 15 | 16 | * Introduce the `<%@ %>` tag. This is a built-in capture mechanism. It will assign all block content to a local variable but *not* output it. 17 | * Make comments be recognized before `end`, which fixes a syntax error with `<%# end %>`. 18 | * Don't recognize ERB tags with a string containing "do" as a block. 19 | 20 | # 0.1.0 21 | 22 | * Internally, we're parsing the ERB template into a SEXP structure and let [Temple](https://github.com/judofyr/temple) compile it to Ruby. Many thanks to the Temple team! 😘 23 | * Yielding ERB blocks will simply return the content, no output buffering with instance variables will happen. 24 | This allows to pass ERB blocks around and yield them in other objects without having it output twice as in 0.0.2. 25 | * No instance variables are used anymore, output buffering always happens via locals the way [Slim](https://github.com/slim-template/slim) does it. This might result in a minimal speed decrease but cleans up the code and architecture immensely. 26 | * Removed `Erbse::Template`, it was completely unnecessary code. 27 | 28 | # 0.0.2 29 | 30 | * First release. No escaping is happening and I'm not sure how capture works, yet. But: it's great! 31 | -------------------------------------------------------------------------------- /lib/erbse/parser.rb: -------------------------------------------------------------------------------- 1 | module Erbse 2 | class Parser 3 | # ERB_EXPR = /(\n)|<%(=|\#)?(.*?)%>(\n)*/m # this is the desired pattern. 4 | ERB_EXPR = /(\n)|<=|<%(=+|-|\#|@\s|%)?(.*?)[-=]?%>/m # this is for backward-compatibility. 5 | # BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ 6 | BLOCK_EXPR = /\sdo\s*\z|\sdo\s+\|[^|]*\|\s*\z/ 7 | BLOCK_EXEC = /\A\s*(if|unless)\b|#{BLOCK_EXPR}/ 8 | 9 | # Parsing patterns 10 | # 11 | # Blocks will be recognized when written: 12 | # <% ... do %> or <% ... do |...| %> 13 | 14 | def initialize(*) 15 | end 16 | 17 | def call(str) 18 | pos = 0 19 | buffers = [] 20 | result = [:multi] 21 | buffers << result 22 | match = nil 23 | 24 | str.scan(ERB_EXPR) do |newline, indicator, code| 25 | match = Regexp.last_match 26 | len = match.begin(0) - pos 27 | 28 | text = str[pos, len] 29 | pos = match.end(0) 30 | ch = indicator ? indicator[0] : nil 31 | 32 | if newline 33 | buffers.last << [:static, "#{text}\n"] << [:newline] 34 | next 35 | end 36 | 37 | if text and !text.empty? # text 38 | buffers.last << [:static, text] 39 | end 40 | 41 | if ch == ?= # <%= %> 42 | if code =~ BLOCK_EXPR 43 | buffers.last << [:erb, :block, code, block = [:multi]] # picked up by our own BlockFilter. 44 | buffers << block 45 | else 46 | buffers.last << [:dynamic, code] 47 | end 48 | elsif ch =~ /#/ # DISCUSS: doesn't catch <% # this %> 49 | newlines = code.count("\n") 50 | buffers.last.concat [[:newline]] * newlines if newlines > 0 51 | elsif code =~ /\bend\b/ # <% end %> 52 | buffers.pop 53 | elsif ch == ?@ 54 | buffers.last << [:capture, :block, code, block = [:multi]] # picked up by our own BlockFilter. # TODO: merge with %= ? 55 | buffers << block 56 | else # <% %> 57 | if code =~ BLOCK_EXEC 58 | buffers.last << [:block, code, block = [:multi]] # picked up by Temple's ControlFlow filter. 59 | buffers << block 60 | else 61 | buffers.last << [:code, code] 62 | end 63 | end 64 | end 65 | 66 | # add text after last/none ERB tag. 67 | buffers.last << [:static, str[pos..str.length]] if pos < str.length 68 | 69 | buffers.last 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Erbse 2 | 3 | _An updated version of Erubis._ 4 | 5 | Erbse compiles an ERB string to a string of Ruby. 6 | 7 | ## API 8 | 9 | The API is one public method. 10 | 11 | ```ruby 12 | Erbse::Engine.new.call("<% ... %>") #=> string of compiled ruby. 13 | ``` 14 | 15 | The returned string can then be `eval`uated in a certain context. 16 | 17 | ## Output Buffers 18 | 19 | Erbse does not use instance variables as output buffer, only local variables. 20 | 21 | | Tag | Behavior | 22 | | --- | --- | 23 | | `<% %>` | Executes the code but does not output anything. | 24 | | `<% .. do %>` | Executes the code but does not output anything. In the block, output is written to the current buffer. | 25 | | `<%= %>` | Executes the code, outputs to current buffer. | 26 | | `<%= .. do %>` | Executes the code and appends returned value to the current buffer. In the block, output is written to a new buffer that is returned when `yield`ing. | 27 | | `<%@ .. do %>` | Executes the code but does not output anything. In the block, output is written to a new buffer that is returned when `yield`ing. | 28 | 29 | 30 | ## Block Yielding 31 | 32 | Erbse supports blocks à la Rails. 33 | 34 | You may pass any mix of text/ERB via blocks to Ruby methods. 35 | 36 | ```erb 37 | <%= form do %> 38 | Please fill out all fields! 39 | <%= input :email %> 40 |
#symbolnamepricechangeratio