pax_global_header00006660000000000000000000000064145071745440014525gustar00rootroot0000000000000052 comment=ae5199cc5ae4d1987d78da0dfe9c2b39d29fd191 temple-0.10.3/000077500000000000000000000000001450717454400130745ustar00rootroot00000000000000temple-0.10.3/.github/000077500000000000000000000000001450717454400144345ustar00rootroot00000000000000temple-0.10.3/.github/workflows/000077500000000000000000000000001450717454400164715ustar00rootroot00000000000000temple-0.10.3/.github/workflows/test.yml000066400000000000000000000012201450717454400201660ustar00rootroot00000000000000name: test on: push: branches: - master pull_request: types: - opened - synchronize - reopened schedule: - cron: "00 15 * * *" jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - '2.5' - '2.6' - '2.7' - '3.0' - '3.1' - '3.2' - jruby - truffleruby-head steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake spec temple-0.10.3/.gitignore000066400000000000000000000000501450717454400150570ustar00rootroot00000000000000coverage .yardoc doc Gemfile.lock /pkg/ temple-0.10.3/.yardopts000066400000000000000000000000661450717454400147440ustar00rootroot00000000000000--title Temple --files EXPRESSIONS.md,CHANGES,LICENSE temple-0.10.3/CHANGES000066400000000000000000000165101450717454400140720ustar00rootroot000000000000000.10.3 * Remove test files from the gem package (#146) * Add DynamicMerger filter (#147) 0.10.2 * Fix Sinatra capture_generator problem (#145) 0.10.1 * Use specified :capture_generator for nested captures (#112, #144) * Compatibility with frozen string literals 0.10.0 * Regression: Revert changes to :capture_generator since 0.8.2 (#112, #113, #137) * Regression: Ensure that output buffer is not reused for capturing in Rails (#135) * Drop support for Rails 4.x 0.9.1 * Fix Slim's error in AttributeMerger due to 0.9.0's :capture_generator (#137) * Use specified :capture_generator for nested captures (#112) * Fix Temple::ERB::Engine's <%= to not escape and <%== to escape expressions 0.9.0 * Require Ruby 2.5+ (#131) * Change default :capture_generator to self (#113) * Improve compatibility with Rails 7.1 (#135) * Support Rails 6.1's annotate_rendered_view_with_filenames with Temple::Filters::Ambles (#134) * Fix a crash in StringSplitter filter (#138) * Fix a warning by Object#=~ since Ruby 2.6 (#129) * Fix deprecated Tilt template mime type (#108) * Stop using deprecated EscapeUtils from Temple::Utils (#136) 0.8.2 * Support TruffleRuby in Temple::Filters::StaticAnalyzer (#127) * Support TruffleRuby in Temple::Filters::StringSplitter (#127) 0.8.1 * Stop relying on deprecated method in Rails (#121) * Fix issue with --enable-frozen-string-literal * Escape html in markdown 0.8.0 * Add Temple::StaticAnalyzer to analyze Ruby expressions * Support newlines in Temple::Filters::StaticAnalyzer 0.7.8 * Fix a warning in StaticAnalyzer 0.7.7 * Add Temple::Filters::StaticAnalyzer, Temple::Filters::StringSplitter * Freeze string literals 0.7.6 * EngineDSL - add support for use(:Filter) { FilterClassName } 0.7.5 * HTML::Pretty Fix indentation issue (https://github.com/slim-template/slim-rails/issues/78) 0.7.4 * EngineDSL: allow to replace/remove with regexp * Fix deprecation warning (#83) 0.7.3 * Temple::ERB::Trimming - replace option trim_mode with trim and switch to erubis-like trimming 0.7.2 * Remove Filters::StaticFreezer, the generator does the freezing 0.7.1 * Rename *Hash to *Map * Add Filters::StaticFreezer 0.7.0 * Drop Ruby 1.8.7 support * EngineDSL: Remove option filter * HTML: Deprecate :html4, :html5 formats * HTML: Add format :xml * Rename DefaultOptions to ClassOptions * Deprecate default_options in favor of options * Add Utils.indent_dynamic 0.6.10 * Tilt template: Support :outvar and save/restore buffer to make the behaviour compatible with ERB 0.6.9 * HTML::Pretty: Fix wrong line numbers * Tilt template: Don't overwrite buffer always * Generator: add preamble and postamble which do nothing * Tilt template: don't overwrite streaming option * OptionHash: inherit valid keys * temple/html/safe: add poor man's html_safe? implementation (not required automatically) * Temple::Mixins::GrammarDSL - Add some missing match? methods * Temple::Utils.escape_html_safe - Add parameter safe 0.6.8 * HTML::Fast add svg doctype * Render standalone html 5 attributes 0.6.7 * HTML::Pretty - change some block level tags * Reduce memory allocations in immutable hash 0.6.6 * Use default encoding utf-8 * Escape also ' * Try to load escape_utils by default 0.6.5 * Added Filters::CodeMerger * Added Filters::Encoding * Added Filters::RemoveBOM * Added Generators::ERB 0.6.4 * Check for ActionView instead of Rails (#72) 0.6.3 * Fix HTML escaping for HTML::Pretty (Issue #69) 0.6.2 * [:html, :js, code] abstraction added 0.6.1 * HTML::Pretty improved 0.6.0 * HTML::AttributeMerger: rename option :attr_delimiter to :merge_attrs * HTML: rename option :attr_wrapper to :attr_quote 0.5.5 * HTML pretty: Do not remove empty lines, add newline after doctype 0.5.4 * HTML::AttributeMerger fixed, it didn't remove first empty attribute values * Add HTML::AttributeRemover back, :remove_empty_attrs must be an Array of Strings now of the attributes to be removed if empty * Simplify [:case] expression grammar * Ignore parameter :outvar by sinatra since sinatra assumes also that the buffer is a String, they should set :buffer and :generator explicitly if they need the access 0.5.3 * Only print an message if invalid options are passed to Temple filters or engines since many libraries seem to use Slim and Temple in an incorrect way 0.5.2 * Fix the :outvar problem really 0.5.1 * Support Sinatra :outvar option in Tilt template 0.5.0 * Added exception Temple::FilterError which should be thrown by filters * Added Temple::Parser as default base class for parsers * escape_html doesn't escape / anymore * HTML::AttributeSorter uses stable sorting now * HTML::AttributeRemover removed (Was too Slim specific) * Engine option :chain removed * Option validation implemented (Use define_options in your filters) * Deprecated options implemented (Use deprecated_options in your filters) * ThreadOptions added, Method #with_options 0.4.1 * Generators: produce optimized code * remove deprecated method EngineDSL#wildcard * Set tilt template default_mime_type to text/html * HTML: Support conditional comments [:html, :condcomment, ...] 0.4.0 * Split Temple::HTML::AttributeMerger in AttributeSorter, AttributeMerger and AttributeRemover * Fix issue #58 0.3.5 * Temple::HTML::Pretty improved * :sort_attrs option (default: true) added to HTML::AttributeMerger; if set to false, the attributes will appear in the insertion order * Temple::Mixins::EngineDSL api changed ("wildcard" is deprecated, use "use" instead) * Temple::Mixins::CompiledDispatcher supports arbitrary levels now * Don't use gsub! on incoming strings (#57) * Fix newlines in erb parser (#46) 0.3.4 * Bugfix release (0.3.3 was yanked) 0.3.3 * Support for rails 3.1 streaming * Add EngineDSL#wildcard * HTML::Fast/Pretty supports only :xhtml and :html formats from now on * HTML::AttributeMerger extracted from HTML::Fast 0.3.1, 0.3.2 * Don't modify strings destructively with gsub! in HTML::Pretty. This doesn't work with Rails safe buffers in version >= 3.0.8. 0.3.0 * Compiled expression dispatching * Method temple_dispatch is obsolete * EscapeHTML renamed to Escapable * Control flow filter added * HTML filter: Tag and attribute expressions changed * Expression grammar added * Expression validator added * Debugger filter removed (Validator is better replacement) 0.2.0 * Add mutable/immutable hashes for option inheritance * Rails template support added * Rename Filter#compile to Filter#call * Engine chain reconfiguration (append, prepend, replace, ...) * HTML filter: Don't output empty attributes * Escape expression changed [:escape, true/false, Expression] 0.1.8 * HTML filter: Support :format => :html (alias for :html5) 0.1.7 * HTML::Pretty indents dynamic content only if it doesn't contain preformatted tags 0.1.6 * Flexible chain building 0.1.5 * Default options for engines 0.1.4 * HTML::Pretty added * Tilt-based template class added * Escaping filter added * Filter base class added * Fix capturing (Issue #15) 0.1.3 * Close issue #10 * Refactoring 0.1.2 * Add HTML filter * Remove Escapable filter * Add method for checking if expression is empty 0.1.1 * Test added 0.1.0 * Initial release temple-0.10.3/EXPRESSIONS.md000066400000000000000000000164221450717454400152450ustar00rootroot00000000000000Temple expression documentation =============================== Temple uses S-expressions to represent the parsed template code. The S-expressions are passed from filter to filter until the generator. The generator transforms the S-expression to a ruby code string. See the {file:README.md README} for an introduction. In this document we documented all the expressions which are used by Temple. There is also a formal grammar which can validate expressions. The Core Abstraction -------------------- The core abstraction is what every template evetually should be compiled to. Currently it consists of six types: multi, static, dynamic, code, newline and capture. When compiling, there's two different strings we'll have to think about. First we have the generated code. This is what your engine (from Temple's point of view) spits out. If you construct this carefully enough, you can make exceptions report correct line numbers, which is very convenient. Then there's the result. This is what your engine (from the user's point of view) spits out. It's what happens if you evaluate the generated code. ### [:multi, *sexp] Multi is what glues everything together. It's simply a sexp which combines several others sexps: [:multi, [:static, "Hello "], [:dynamic, "@world"]] ### [:static, string] Static indicates that the given string should be appended to the result. Example: [:static, "Hello World"] is the same as: _buf << "Hello World" [:static, "Hello \n World"] is the same as _buf << "Hello\nWorld" ### [:dynamic, ruby] Dynamic indicates that the given Ruby code should be evaluated and then appended to the result. The Ruby code must be a complete expression in the sense that you can pass it to eval() and it would not raise SyntaxError. Example: [:dynamic, 'Math::PI * r**2'] ### [:code, ruby] Code indicates that the given Ruby code should be evaluated, and may change the control flow. Any \n causes a newline in the generated code. Example: [:code, 'area = Math::PI * r**2'] ### [:newline] Newline causes a newline in the generated code, but not in the result. ### [:capture, variable_name, sexp] Evaluates the Sexp using the rules above, but instead of appending to the result, it sets the content to the variable given. Example: [:multi, [:static, "Some content"], [:capture, "foo", [:static, "More content"]], [:dynamic, "foo.downcase"]] is the same as: _buf << "Some content" foo = "More content" _buf << foo.downcase Control flow abstraction ------------------------ Control flow abstractions can be used to write common ruby control flow constructs. These expressions are compiled to [:code, ruby] by Temple::Filters::ControlFlow ### [:if, condition, if-sexp, optional-else-sexp] Example: [:if, "1+1 == 2", [:static, "Yes"], [:static, "No"]] is the same as: if 1+1 == 2 _buf << "Yes" else _buf << "No" end ### [:block, ruby, sexp] Example: [:block, '10.times do', [:static, 'Hello']] is the same as: 10.times do _buf << 'Hello' end ### [:case, argument, [condition, sexp], [condition, sexp], ...] Example: [:case, 'value', ["1", "value is 1"], ["2", "value is 2"], [:else, "don't know"]] is the same as: case value when 1 _buf << "value is 1" when 2 _buf << "value is 2" else _buf << "don't know" end ### [:cond, [condition, sexp], [condition, sexp], ...] [:cond, ["a", "a is true"], ["b", "b is true"], [:else, "a and b are false"]] is the same as: case when a _buf << "a is true" when b _buf << "b is true" else _buf << "a and b are false" end Escape abstraction ------------------ The Escape abstraction is processed by Temple::Filters::Escapable. ### [:escape, bool, sexp] The boolean flag switches escaping on or off for the content sexp. Dynamic and static expressions are manipulated. Example: [:escape, true, [:multi, [:dynamic, "code"], [:static, "<"], [:escape, false, [:static, ">"]]]] is transformed to [:multi, [:dynamic, 'escape_html(code)'], [:static, '<'], [:static, '>']] HTML abstraction ---------------- The HTML abstraction is processed by the html filters (Temple::HTML::Fast and Temple::HTML::Pretty). ### [:html, :doctype, string] Example: [:html, :doctype, '5'] generates Supported doctypes:
NameGenerated doctype
1.1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
html, 5<!DOCTYPE html>
strict<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
frameset<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
mobile<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">
basic<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
transitional<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
### [:html, :comment, sexp] Example: [:html, :comment, [:static, 'comment']] generates: ### [:html, :condcomment, condition, sexp] Example: [:html, :condcomment, 'IE', [:static, 'comment']] generates: ### [:html, :tag, identifier, attributes, optional-sexp] HTML tag abstraction. Identifier can be a String or a Symbol. If the optional content Sexp is omitted the tag is closed (e.g. `
` ``). The tag is also closed if the content Sexp is empty (consists only of :multi and :newline expressions) and the tag is registered as auto-closing. Example: [:html, :tag, 'img', [:html, :attrs, [:html, :attr, 'src', 'image.png']]] [:html, :tag, 'p', [:multi], [:static, 'Content']] generates:

Content

### [:html, :attrs, attributes] List of html attributes [:html, :attr, identifier, sexp] ### [:html, :attr, identifier, sexp] HTML attribute abstraction. Identifier can be a String or a Symbol. ### [:html, :js, code] HTML javascript abstraction which wraps the js code in a HTML comment or CDATA depending on document format. Formal grammar -------------- Validate expressions with Temple::Grammar.match? and Temple::Grammar.validate! Temple::Grammar.match? [:multi, [:static, 'Valid Temple Expression']] Temple::Grammar.validate! [:multi, 'Invalid Temple Expression'] The formal grammar is given in a Ruby DSL similar to EBNF and should be easy to understand if you know EBNF. Repeated tokens are given by appending ?, * or + as in regular expressions. * ? means zero or one occurence * \* means zero or more occurences * \+ means one or more occurences temple-0.10.3/Gemfile000066400000000000000000000000471450717454400143700ustar00rootroot00000000000000source 'https://rubygems.org/' gemspec temple-0.10.3/LICENSE000066400000000000000000000020371450717454400141030ustar00rootroot00000000000000Copyright (c) 2010 Magnus Holm Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. temple-0.10.3/README.md000066400000000000000000000216751450717454400143660ustar00rootroot00000000000000Temple ====== [![test](https://github.com/judofyr/temple/actions/workflows/test.yml/badge.svg)](https://github.com/judofyr/temple/actions/workflows/test.yml) [![Code Climate](https://codeclimate.com/github/judofyr/temple.svg)](https://codeclimate.com/github/judofyr/temple) [![Gem Version](https://badge.fury.io/rb/temple.svg)](https://rubygems.org/gems/temple) [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/gems/temple/frames) Temple is an abstraction and a framework for compiling templates to pure Ruby. It's all about making it easier to experiment, implement and optimize template languages. If you're interested in implementing your own template language, or anything else related to the internals of a template engine: You've come to the right place. Have a look around, and if you're still wondering: Ask on the mailing list and we'll try to do our best. In fact, it doesn't have to be related to Temple at all. As long as it has something to do with template languages, we're interested: . Links ----- * Source: * Bugs: * List: * API documentation: * Latest Gem: * GitHub master: * Abstractions: Overview -------- Temple is built on a theory that every template consists of three elements: * Static text * Dynamic text (pieces of Ruby which are evaluated and sent to the client) * Codes (pieces of Ruby which are evaluated and *not* sent to the client, but might change the control flow). The goal of a template engine is to take the template and eventually compile it into *the core abstraction*: ```ruby [:multi, [:static, "Hello "], [:dynamic, "@user.name"], [:static, "!\n"], [:code, "if @user.birthday == Date.today"], [:static, "Happy birthday!"], [:code, "end"]] ``` Then you can apply some optimizations, feed it to Temple and it generates fast Ruby code for you: ```ruby _buf = [] _buf << ("Hello #{@user.name}!\n") if @user.birthday == Date.today _buf << "Happy birthday!" end _buf.join ``` S-expression ------------ In Temple, an Sexp is simply an array (or a subclass) where the first element is the *type* and the rest are the *arguments*. The type must be a symbol and it's recommended to only use strings, symbols, arrays and numbers as arguments. Temple uses Sexps to represent templates because it's a simple and straightforward data structure, which can easily be written by hand and manipulated by computers. Some examples: ```ruby [:static, "Hello World!"] [:multi, [:static, "Hello "], [:dynamic, "@world"]] [:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]] ``` *NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp` class. While you can use this class (since it's a subclass of Array), it's not what Temple mean by "Sexp". Abstractions ------------ The idea behind Temple is that abstractions are good, and it's better to have too many than too few. While you should always end up with the core abstraction, you shouldn't stress about it. Take one step at a time, and only do one thing at every step. So what's an abstraction? An abstraction is when you introduce a new types: ```ruby # Instead of: [:static, "Use the force"] # You use: [:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]] ``` ### Why are abstractions so important? First of all, it means that several template engines can share code. Instead of having two engines which goes all the way to generating HTML, you have two smaller engines which only compiles to the HTML abstraction together with something that compiles the HTML abstraction to the core abstraction. Often you also introduce abstractions because there's more than one way to do it. There's not a single way to generate HTML. Should it be indented? If so, with tabs or spaces? Or should it remove as much whitespace as possible? Single or double quotes in attributes? Escape all weird UTF-8 characters? With an abstraction you can easily introduce a completely new HTML compiler, and whatever is below doesn't have to care about it *at all*. They just continue to use the HTML abstraction. Maybe you even want to write your compiler in another language? Sexps are easily serialized and if you don't mind working across processes, it's not a problem at all. All abstractions used by Temple are documented in [EXPRESSIONS.md](EXPRESSIONS.md). Compilers --------- A *compiler* is simply an object which responds a method called #call which takes one argument and returns a value. It's illegal for a compiler to mutate the argument, and it should be possible to use the same instance several times (although not by several threads at the same time). While a compiler can be any object, you very often want to structure it as a class. Temple then assumes the initializer takes an optional option hash: ```ruby class MyCompiler def initialize(options = {}) @options = options end def call(exp) # do stuff end end ``` ### Parsers In Temple, a parser is also a compiler, because a compiler is just something that takes some input and produces some output. A parser is then something that takes a string and returns an Sexp. It's important to remember that the parser *should be dumb*. No optimization, no guesses. It should produce an Sexp that is as close to the source as possible. You should invent your own abstraction. Maybe you even want to separate the parsers into several parts and introduce several abstractions on the way? ### Filters A filter is a compiler which take an Sexp and returns an Sexp. It might turn convert it one step closer to the core-abstraction, it might create a new abstraction, or it might just optimize in the current abstraction. Ultimately, it's still just a compiler which takes an Sexp and returns an Sexp. For instance, Temple ships with {Temple::Filters::DynamicInliner} and {Temple::Filters::StaticMerger} which are general optimization filters which works on the core abstraction. An HTML compiler would be a filter, since it would take an Sexp in the HTML abstraction and compile it down to the core abstraction. ### Generators A generator is a compiler which takes an Sexp and returns a string which is valid Ruby code. Most of the time you would just use {Temple::Generators::ArrayBuffer} or any of the other generators in {Temple::Generators}, but nothing stops you from writing your own. In fact, one of the great things about Temple is that if you write a new generator which turns out to be a lot faster then the others, it's going to make *every single engine* based on Temple faster! So if you have any ideas, please share them - it's highly appreciated. Engines ------- When you have a chain of a parsers, some filters and a generator you can finally create your *engine*. Temple provides {Temple::Engine} which makes this very easy: ```ruby class MyEngine < Temple::Engine # First run MyParser use MyParser # Then a custom filter use MyFilter # Then some general optimizations filters filter :MultiFlattener filter :StaticMerger filter :DynamicInliner # Finally the generator generator :ArrayBuffer end engine = MyEngine.new(strict: "For MyParser") engine.call(something) ``` And then? --------- You've ran the template through the parser, some filters and in the end a generator. What happens next? Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and Rails. ```ruby require 'tilt' # Create template class MyTemplate and register your file extension MyTemplate = Temple::Templates::Tilt(MyEngine, register_as: 'ext') Tilt.new('example.ext').render # => Render a file MyTemplate.new { "String" }.render # => Render a string ``` Installation ------------ You need at least Ruby 1.9.3 to work with Temple. Temple is published as a Ruby Gem which can be installed as following: ```bash $ gem install temple ``` Engines using Temple -------------------- * [Slim](https://github.com/slim-template/slim) * [Hamlit](https://github.com/k0kubun/hamlit) * [Faml](https://github.com/eagletmt/faml) * [Sal](https://github.com/stonean/sal.rb) * [Temple-Mustache (Example implementation)](https://github.com/minad/temple-mustache) * Temple ERB example implementation (Temple::ERB::Template) * [WLang](https://github.com/blambeau/wlang) Acknowledgements ---------------- Thanks to [_why](http://en.wikipedia.org/wiki/Why_the_lucky_stiff) for creating an excellent template engine (Markaby) which is quite slow. That's how I started experimenting with template engines in the first place. I also owe [Ryan Davis](http://zenspider.com/) a lot for his excellent projects ParserTree, RubyParser, Ruby2Ruby and SexpProcessor. Temple is heavily inspired by how these tools work. temple-0.10.3/Rakefile000066400000000000000000000001611450717454400145370ustar00rootroot00000000000000require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) task default: :spec temple-0.10.3/lib/000077500000000000000000000000001450717454400136425ustar00rootroot00000000000000temple-0.10.3/lib/temple.rb000066400000000000000000000060641450717454400154630ustar00rootroot00000000000000# frozen_string_literal: true require 'temple/version' module Temple autoload :InvalidExpression, 'temple/exceptions' autoload :FilterError, 'temple/exceptions' autoload :Generator, 'temple/generator' autoload :Parser, 'temple/parser' autoload :Engine, 'temple/engine' autoload :Utils, 'temple/utils' autoload :Filter, 'temple/filter' autoload :Templates, 'temple/templates' autoload :Grammar, 'temple/grammar' autoload :ImmutableMap, 'temple/map' autoload :MutableMap, 'temple/map' autoload :OptionMap, 'temple/map' autoload :StaticAnalyzer, 'temple/static_analyzer' module Mixins autoload :Dispatcher, 'temple/mixins/dispatcher' autoload :CompiledDispatcher, 'temple/mixins/dispatcher' autoload :EngineDSL, 'temple/mixins/engine_dsl' autoload :GrammarDSL, 'temple/mixins/grammar_dsl' autoload :Options, 'temple/mixins/options' autoload :ClassOptions, 'temple/mixins/options' autoload :Template, 'temple/mixins/template' end module ERB autoload :Engine, 'temple/erb/engine' autoload :Parser, 'temple/erb/parser' autoload :Trimming, 'temple/erb/trimming' autoload :Template, 'temple/erb/template' end module Generators autoload :ERB, 'temple/generators/erb' autoload :Array, 'temple/generators/array' autoload :ArrayBuffer, 'temple/generators/array_buffer' autoload :StringBuffer, 'temple/generators/string_buffer' autoload :RailsOutputBuffer, 'temple/generators/rails_output_buffer' end module Filters autoload :Ambles, 'temple/filters/ambles' autoload :CodeMerger, 'temple/filters/code_merger' autoload :ControlFlow, 'temple/filters/control_flow' autoload :MultiFlattener, 'temple/filters/multi_flattener' autoload :StaticAnalyzer, 'temple/filters/static_analyzer' autoload :StaticMerger, 'temple/filters/static_merger' autoload :StringSplitter, 'temple/filters/string_splitter' autoload :DynamicInliner, 'temple/filters/dynamic_inliner' autoload :DynamicMerger, 'temple/filters/dynamic_merger' autoload :Escapable, 'temple/filters/escapable' autoload :Eraser, 'temple/filters/eraser' autoload :Validator, 'temple/filters/validator' autoload :Encoding, 'temple/filters/encoding' autoload :RemoveBOM, 'temple/filters/remove_bom' end module HTML autoload :Dispatcher, 'temple/html/dispatcher' autoload :Filter, 'temple/html/filter' autoload :Fast, 'temple/html/fast' autoload :Pretty, 'temple/html/pretty' autoload :AttributeMerger, 'temple/html/attribute_merger' autoload :AttributeRemover, 'temple/html/attribute_remover' autoload :AttributeSorter, 'temple/html/attribute_sorter' end end temple-0.10.3/lib/temple/000077500000000000000000000000001450717454400151305ustar00rootroot00000000000000temple-0.10.3/lib/temple/engine.rb000066400000000000000000000032001450717454400167150ustar00rootroot00000000000000# frozen_string_literal: true module Temple # An engine is simply a chain of compilers (that often includes a parser, # some filters and a generator). # # class MyEngine < Temple::Engine # # First run MyParser, passing the :strict option # use MyParser, :strict # # # Then a custom filter # use MyFilter # # # Then some general optimizations filters # filter :MultiFlattener # filter :StaticMerger # filter :DynamicInliner # # # Finally the generator # generator :ArrayBuffer, :buffer # end # # class SpecialEngine < MyEngine # append MyCodeOptimizer # before :ArrayBuffer, Temple::Filters::Validator # replace :ArrayBuffer, Temple::Generators::RailsOutputBuffer # end # # engine = MyEngine.new(strict: "For MyParser") # engine.call(something) # # @api public class Engine include Mixins::Options include Mixins::EngineDSL extend Mixins::EngineDSL define_options :file, :streaming, :buffer, :save_buffer attr_reader :chain def self.chain @chain ||= superclass.respond_to?(:chain) ? superclass.chain.dup : [] end def initialize(opts = {}) super @chain = self.class.chain.dup end def call(input) call_chain.inject(input) {|m, e| e.call(m) } end protected def chain_modified! @call_chain = nil end def call_chain @call_chain ||= @chain.map do |name, constructor| f = constructor.call(self) raise "Constructor #{name} must return callable object" if f && !f.respond_to?(:call) f end.compact end end end temple-0.10.3/lib/temple/erb/000077500000000000000000000000001450717454400157005ustar00rootroot00000000000000temple-0.10.3/lib/temple/erb/engine.rb000066400000000000000000000006221450717454400174720ustar00rootroot00000000000000# frozen_string_literal: true module Temple module ERB # Example ERB engine implementation # # @api public class Engine < Temple::Engine use Temple::ERB::Parser use Temple::ERB::Trimming filter :Escapable filter :StringSplitter filter :StaticAnalyzer filter :MultiFlattener filter :StaticMerger generator :ArrayBuffer end end end temple-0.10.3/lib/temple/erb/parser.rb000066400000000000000000000021551450717454400175240ustar00rootroot00000000000000# frozen_string_literal: true module Temple module ERB # Example ERB parser # # @api public class Parser < Temple::Parser ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m def call(input) result = [:multi] pos = 0 input.scan(ERB_PATTERN) do |token, indicator, code| text = input[pos...$~.begin(0)] pos = $~.end(0) if token case token when "\n" result << [:static, "#{text}\n"] << [:newline] when '<%%', '%%>' result << [:static, text] unless text.empty? token.slice!(1) result << [:static, token] end else result << [:static, text] unless text.empty? case indicator when '#' result << [:code, "\n" * code.count("\n")] when /=/ result << [:escape, indicator.size == 2, [:dynamic, code]] else result << [:code, code] end end end result << [:static, input[pos..-1]] end end end end temple-0.10.3/lib/temple/erb/template.rb000066400000000000000000000004111450717454400200340ustar00rootroot00000000000000# frozen_string_literal: true module Temple # ERB example implementation # # Example usage: # Temple::ERB::Template.new { "<%= 'Hello, world!' %>" }.render # module ERB # ERB Template class Template = Temple::Templates::Tilt(Engine) end end temple-0.10.3/lib/temple/erb/trimming.rb000066400000000000000000000012311450717454400200500ustar00rootroot00000000000000# frozen_string_literal: true module Temple module ERB # ERB trimming like in erubis # Deletes spaces around '<% %>' and leave spaces around '<%= %>'. # @api public class Trimming < Filter define_options trim: true def on_multi(*exps) exps = exps.each_with_index.map do |e,i| if e.first == :static && i > 0 && exps[i-1].first == :code [:static, e.last.lstrip] elsif e.first == :static && i < exps.size-1 && exps[i+1].first == :code [:static, e.last.rstrip] else e end end if options[:trim] [:multi, *exps] end end end end temple-0.10.3/lib/temple/exceptions.rb000066400000000000000000000004551450717454400176420ustar00rootroot00000000000000# frozen_string_literal: true module Temple # Exception raised if invalid temple expression is found # # @api public class InvalidExpression < RuntimeError end # Exception raised if something bad happens in a Temple filter # # @api public class FilterError < RuntimeError end end temple-0.10.3/lib/temple/filter.rb000066400000000000000000000002711450717454400167420ustar00rootroot00000000000000# frozen_string_literal: true module Temple # Temple base filter # @api public class Filter include Utils include Mixins::Dispatcher include Mixins::Options end end temple-0.10.3/lib/temple/filters/000077500000000000000000000000001450717454400166005ustar00rootroot00000000000000temple-0.10.3/lib/temple/filters/ambles.rb000066400000000000000000000007161450717454400203740ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters class Ambles < Filter define_options :preamble, :postamble def initialize(*) super @preamble = options[:preamble] @postamble = options[:postamble] end def call(ast) ret = [:multi] ret << [:static, @preamble] if @preamble ret << ast ret << [:static, @postamble] if @postamble ret end end end end temple-0.10.3/lib/temple/filters/code_merger.rb000066400000000000000000000012621450717454400214010ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # @api public class CodeMerger < Filter def on_multi(*exps) result = [:multi] code = nil exps.each do |exp| if exp.first == :code if code code << '; ' unless code =~ /\n\Z/ code << exp.last else code = exp.last.dup result << [:code, code] end elsif code && exp.first == :newline code << "\n" else result << compile(exp) code = nil end end result.size == 2 ? result[1] : result end end end end temple-0.10.3/lib/temple/filters/control_flow.rb000066400000000000000000000022601450717454400216340ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Control flow filter which processes [:if, condition, yes-exp, no-exp] # and [:block, code, content] expressions. # This is useful for ruby code generation with lots of conditionals. # # @api public class ControlFlow < Filter def on_if(condition, yes, no = nil) result = [:multi, [:code, "if #{condition}"], compile(yes)] while no && no.first == :if result << [:code, "elsif #{no[1]}"] << compile(no[2]) no = no[3] end result << [:code, 'else'] << compile(no) if no result << [:code, 'end'] result end def on_case(arg, *cases) result = [:multi, [:code, arg ? "case (#{arg})" : 'case']] cases.map do |c| condition, exp = c result << [:code, condition == :else ? 'else' : "when #{condition}"] << compile(exp) end result << [:code, 'end'] result end def on_cond(*cases) on_case(nil, *cases) end def on_block(code, exp) [:multi, [:code, code], compile(exp), [:code, 'end']] end end end end temple-0.10.3/lib/temple/filters/dynamic_inliner.rb000066400000000000000000000042771450717454400223030ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Inlines several static/dynamic into a single dynamic. # # @api public class DynamicInliner < Filter def on_multi(*exps) result = [:multi] curr = nil prev = [] state = :looking exps.each do |exp| type, arg = exp case type when :newline if state == :looking # We haven't found any static/dynamic, so let's just add it result << exp else # We've found something, so let's make sure the generated # dynamic contains a newline by escaping a newline and # starting a new string: # # "Hello "\ # "#{@world}" prev << exp curr[1] << "\"\\\n\"" end when :dynamic, :static case state when :looking # Found a single static/dynamic. We don't want to turn this # into a dynamic yet. Instead we store it, and if we find # another one, we add both then. state = :single prev = [exp] curr = [:dynamic, '"'.dup] when :single # Yes! We found another one. Add the current dynamic to the result. state = :several result << curr end curr[1] << (type == :static ? arg.inspect[1..-2] : "\#{#{arg}}") else if state != :looking # We need to add the closing quote. curr[1] << '"' # If we found a single exp last time, let's add it. result.concat(prev) if state == :single end # Compile the current exp result << compile(exp) # Now we're looking for more! state = :looking end end if state != :looking # We need to add the closing quote. curr[1] << '"' # If we found a single exp last time, let's add it. result.concat(prev) if state == :single end result.size == 2 ? result[1] : result end end end end temple-0.10.3/lib/temple/filters/dynamic_merger.rb000066400000000000000000000040571450717454400221200ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"'] class DynamicMerger < Filter def on_multi(*exps) exps = exps.dup result = [:multi] buffer = [] until exps.empty? type, arg = exps.first if type == :dynamic && arg.count("\n") == 0 buffer << exps.shift elsif type == :static && exps.size > (count = arg.count("\n")) && exps[1, count].all? { |e| e == [:newline] } (1 + count).times { buffer << exps.shift } elsif type == :newline && exps.size > (count = count_newline(exps)) && exps[count].first == :static && count == exps[count].last.count("\n") (count + 1).times { buffer << exps.shift } else result.concat(merge_dynamic(buffer)) buffer = [] result << compile(exps.shift) end end result.concat(merge_dynamic(buffer)) result.size == 2 ? result[1] : result end private def merge_dynamic(exps) # Merge exps only when they have both :static and :dynamic unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic } return exps end strlit_body = String.new exps.each do |type, arg| case type when :static strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n") when :dynamic strlit_body << "\#{#{arg}}" when :newline # newline is added by `gsub('\n', "\n")` else raise "unexpected type #{type.inspect} is given to #merge_dynamic" end end [[:dynamic, "%Q\0#{strlit_body}\0"]] end def count_newline(exps) count = 0 exps.each do |exp| if exp == [:newline] count += 1 else return count end end return count end end end end temple-0.10.3/lib/temple/filters/encoding.rb000066400000000000000000000012111450717454400207060ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Try to encode input string # # @api public class Encoding < Parser define_options encoding: 'utf-8' def call(s) if options[:encoding] && s.respond_to?(:encoding) old_enc = s.encoding s = s.dup if s.frozen? s.force_encoding(options[:encoding]) # Fall back to old encoding if new encoding is invalid unless s.valid_encoding? s.force_encoding(old_enc) s.force_encoding(::Encoding::BINARY) unless s.valid_encoding? end end s end end end end temple-0.10.3/lib/temple/filters/eraser.rb000066400000000000000000000010261450717454400204050ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Erase expressions with a certain type # # @api public class Eraser < Filter # [] is the empty type => keep all define_options :erase, keep: [[]] def compile(exp) exp.first == :multi || (do?(:keep, exp) && !do?(:erase, exp)) ? super(exp) : [:multi] end protected def do?(list, exp) options[list].to_a.map {|type| [*type] }.any? {|type| exp[0,type.size] == type } end end end end temple-0.10.3/lib/temple/filters/escapable.rb000066400000000000000000000023271450717454400210500ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Escape dynamic or static expressions. # This filter must be used after Temple::HTML::* and before the generators. # It can be enclosed with Temple::Filters::DynamicInliner filters to # reduce calls to Temple::Utils#escape_html. # # @api public class Escapable < Filter # Activate the usage of html_safe? if it is available (for Rails 3 for example) define_options :escape_code, :disable_escape, use_html_safe: ''.respond_to?(:html_safe?) def initialize(opts = {}) super @escape_code = options[:escape_code] || "::Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((%s))" @escaper = eval("proc {|v| #{@escape_code % 'v'} }") @escape = false end def on_escape(flag, exp) old = @escape @escape = flag && !options[:disable_escape] compile(exp) ensure @escape = old end def on_static(value) [:static, @escape ? @escaper[value] : value] end def on_dynamic(value) [:dynamic, @escape ? @escape_code % value : value] end end end end temple-0.10.3/lib/temple/filters/multi_flattener.rb000066400000000000000000000011011450717454400223140ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Flattens nested multi expressions # # @api public class MultiFlattener < Filter def on_multi(*exps) # If the multi contains a single element, just return the element return compile(exps.first) if exps.size == 1 result = [:multi] exps.each do |exp| exp = compile(exp) if exp.first == :multi result.concat(exp[1..-1]) else result << exp end end result end end end end temple-0.10.3/lib/temple/filters/remove_bom.rb000066400000000000000000000005161450717454400212610ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Remove BOM from input string # # @api public class RemoveBOM < Parser def call(s) return s if s.encoding.name !~ /^UTF-(8|16|32)(BE|LE)?/ s.gsub(Regexp.new("\\A\uFEFF".encode(s.encoding.name)), ''.freeze) end end end end temple-0.10.3/lib/temple/filters/static_analyzer.rb000066400000000000000000000013401450717454400223170ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Convert [:dynamic, code] to [:static, text] if code is static Ruby expression. class StaticAnalyzer < Filter def call(exp) # Optimize only when Ripper is available. if ::Temple::StaticAnalyzer.available? super else exp end end def on_dynamic(code) if ::Temple::StaticAnalyzer.static?(code) exp = [:static, eval(code).to_s] newlines = code.count("\n") if newlines == 0 exp else [:multi, exp, *newlines.times.map { [:newline] }] end else [:dynamic, code] end end end end end temple-0.10.3/lib/temple/filters/static_merger.rb000066400000000000000000000014661450717454400217640ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Merges several statics into a single static. Example: # # [:multi, # [:static, "Hello "], # [:static, "World!"]] # # Compiles to: # # [:static, "Hello World!"] # # @api public class StaticMerger < Filter def on_multi(*exps) result = [:multi] text = nil exps.each do |exp| if exp.first == :static if text text << exp.last else text = exp.last.dup result << [:static, text] end else result << compile(exp) text = nil unless exp.first == :newline end end result.size == 2 ? result[1] : result end end end end temple-0.10.3/lib/temple/filters/string_splitter.rb000066400000000000000000000075771450717454400224010ustar00rootroot00000000000000# frozen_string_literal: true begin require 'ripper' rescue LoadError end module Temple module Filters # Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']] class StringSplitter < Filter if defined?(Ripper) && Ripper.respond_to?(:lex) class << self # `code` param must be valid string literal def compile(code) [].tap do |exps| tokens = Ripper.lex(code.strip) tokens.pop while tokens.last && [:on_comment, :on_sp].include?(tokens.last[1]) if tokens.size < 2 raise(FilterError, "Expected token size >= 2 but got: #{tokens.size}") end compile_tokens!(exps, tokens) end end private def strip_quotes!(tokens) _, type, beg_str = tokens.shift if type != :on_tstring_beg raise(FilterError, "Expected :on_tstring_beg but got: #{type}") end _, type, end_str = tokens.pop if type != :on_tstring_end raise(FilterError, "Expected :on_tstring_end but got: #{type}") end [beg_str, end_str] end def compile_tokens!(exps, tokens) beg_str, end_str = strip_quotes!(tokens) until tokens.empty? _, type, str = tokens.shift case type when :on_tstring_content beg_str, end_str = escape_quotes(beg_str, end_str) exps << [:static, eval("#{beg_str}#{str}#{end_str}").to_s] when :on_embexpr_beg embedded = shift_balanced_embexpr(tokens) exps << [:dynamic, embedded] unless embedded.empty? end end end # Some quotes are split-unsafe. Replace such quotes with null characters. def escape_quotes(beg_str, end_str) case [beg_str[-1], end_str] when ['(', ')'], ['[', ']'], ['{', '}'] [beg_str.sub(/.\z/) { "\0" }, "\0"] else [beg_str, end_str] end end def shift_balanced_embexpr(tokens) String.new.tap do |embedded| embexpr_open = 1 until tokens.empty? _, type, str = tokens.shift case type when :on_embexpr_beg embexpr_open += 1 when :on_embexpr_end embexpr_open -= 1 break if embexpr_open == 0 end embedded << str end end end end def on_dynamic(code) return [:dynamic, code] unless string_literal?(code) return [:dynamic, code] if code.include?("\n") temple = [:multi] StringSplitter.compile(code).each do |type, content| case type when :static temple << [:static, content] when :dynamic temple << on_dynamic(content) end end temple end private def string_literal?(code) return false if SyntaxChecker.syntax_error?(code) type, instructions = Ripper.sexp(code) return false if type != :program return false if instructions.size > 1 type, _ = instructions.first type == :string_literal end class SyntaxChecker < Ripper class ParseError < StandardError; end def self.syntax_error?(code) self.new(code).parse false rescue ParseError true end private def on_parse_error(*) raise ParseError end end else # Do nothing if ripper is unavailable def call(ast) ast end end end end end temple-0.10.3/lib/temple/filters/validator.rb000066400000000000000000000004761450717454400211210ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Filters # Validates temple expression with given grammar # # @api public class Validator < Filter define_options grammar: Temple::Grammar def compile(exp) options[:grammar].validate!(exp) exp end end end end temple-0.10.3/lib/temple/generator.rb000066400000000000000000000037371450717454400174550ustar00rootroot00000000000000# frozen_string_literal: true module Temple # Abstract generator base class # Generators should inherit this class and # compile the Core Abstraction to ruby code. # # @api public class Generator include Utils include Mixins::CompiledDispatcher include Mixins::Options define_options :save_buffer, capture_generator: 'StringBuffer', buffer: '_buf', freeze_static: true def call(exp) [preamble, compile(exp), postamble].flatten.compact.join('; ') end def preamble [save_buffer, create_buffer] end def postamble [return_buffer, restore_buffer] end def save_buffer "begin; #{@original_buffer = unique_name} = #{buffer} if defined?(#{buffer})" if options[:save_buffer] end def restore_buffer "ensure; #{buffer} = #{@original_buffer}; end" if options[:save_buffer] end def create_buffer end def return_buffer 'nil' end def on(*exp) raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}" end def on_multi(*exp) exp.map {|e| compile(e) }.join('; '.freeze) end def on_newline "\n" end def on_capture(name, exp) capture_generator.new(capture_generator: options[:capture_generator], freeze_static: options[:freeze_static], buffer: name).call(exp) end def on_static(text) concat(options[:freeze_static] ? "#{text.inspect}.freeze" : text.inspect) end def on_dynamic(code) concat(code) end def on_code(code) code end protected def buffer options[:buffer] end def capture_generator @capture_generator ||= Class === options[:capture_generator] ? options[:capture_generator] : Generators.const_get(options[:capture_generator]) end def concat(str) "#{buffer} << (#{str})" end end end temple-0.10.3/lib/temple/generators/000077500000000000000000000000001450717454400173015ustar00rootroot00000000000000temple-0.10.3/lib/temple/generators/array.rb000066400000000000000000000005531450717454400207470ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Generators # Implements an array buffer. # # _buf = [] # _buf << "static" # _buf << dynamic # _buf # # @api public class Array < Generator def create_buffer "#{buffer} = []" end def return_buffer buffer end end end end temple-0.10.3/lib/temple/generators/array_buffer.rb000066400000000000000000000013611450717454400222760ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Generators # Just like Array, but calls #join on the array. # # _buf = [] # _buf << "static" # _buf << dynamic # _buf.join("") # # @api public class ArrayBuffer < Array def call(exp) case exp.first when :static [save_buffer, "#{buffer} = #{exp.last.inspect}", restore_buffer].compact.join('; ') when :dynamic [save_buffer, "#{buffer} = (#{exp.last}).to_s", restore_buffer].compact.join('; ') else super end end def return_buffer freeze = options[:freeze_static] ? '.freeze' : '' "#{buffer} = #{buffer}.join(\"\"#{freeze})" end end end end temple-0.10.3/lib/temple/generators/erb.rb000066400000000000000000000010271450717454400203760ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Generators # Implements an ERB generator. # # @api public class ERB < Generator def call(exp) compile(exp) end def on_multi(*exp) exp.map {|e| compile(e) }.join('') end def on_capture(name, exp) on_code(super) end def on_static(text) text end def on_dynamic(code) "<%= #{code} %>" end def on_code(code) "<% #{code} %>" end end end end temple-0.10.3/lib/temple/generators/rails_output_buffer.rb000066400000000000000000000017231450717454400237140ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Generators # Implements a rails output buffer. # # @output_buffer = ActiveSupport::SafeBuffer # @output_buffer.safe_concat "static" # @output_buffer.safe_concat dynamic.to_s # @output_buffer # # @api public class RailsOutputBuffer < StringBuffer define_options :streaming, # ignored buffer_class: 'ActionView::OutputBuffer', buffer: '@output_buffer', capture_generator: RailsOutputBuffer def call(exp) [preamble, compile(exp), postamble].flatten.compact.join('; '.freeze) end def create_buffer if buffer == '@output_buffer' "#{buffer} = output_buffer || #{options[:buffer_class]}.new" else "#{buffer} = #{options[:buffer_class]}.new" end end def concat(str) "#{buffer}.safe_concat((#{str}))" end end end end temple-0.10.3/lib/temple/generators/string_buffer.rb000066400000000000000000000007041450717454400224660ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Generators # Implements a string buffer. # # _buf = '' # _buf << "static" # _buf << dynamic.to_s # _buf # # @api public class StringBuffer < ArrayBuffer def create_buffer "#{buffer} = ''.dup" end def return_buffer buffer end def on_dynamic(code) concat("(#{code}).to_s") end end end end temple-0.10.3/lib/temple/grammar.rb000066400000000000000000000032331450717454400171040ustar00rootroot00000000000000# frozen_string_literal: true module Temple # Temple expression grammar which can be used to validate Temple expressions. # # Example: # Temple::Grammar.match? [:static, 'Valid Temple Expression'] # Temple::Grammar.validate! [:multi, 'Invalid Temple Expression'] # # See {file:EXPRESSIONS.md Expression documentation}. # # @api public module Grammar extend Mixins::GrammarDSL Expression << # Core abstraction [:multi, 'Expression*'] | [:static, String] | [:dynamic, String] | [:code, String] | [:capture, String, Expression] | [:newline] | # Control flow abstraction [:if, String, Expression, 'Expression?'] | [:block, String, Expression] | [:case, String, 'Case*'] | [:cond, 'Case*'] | # Escape abstraction [:escape, Bool, Expression] | # HTML abstraction [:html, :doctype, String] | [:html, :comment, Expression] | [:html, :condcomment, String, Expression]| [:html, :js, Expression] | [:html, :tag, HTMLIdentifier, Expression, 'Expression?'] | [:html, :attrs, 'HTMLAttr*'] | HTMLAttr EmptyExp << [:newline] | [:multi, 'EmptyExp*'] HTMLAttr << [:html, :attr, HTMLIdentifier, Expression] HTMLIdentifier << Symbol | String Case << [Condition, Expression] Condition << String | :else Bool << true | false end end temple-0.10.3/lib/temple/html/000077500000000000000000000000001450717454400160745ustar00rootroot00000000000000temple-0.10.3/lib/temple/html/attribute_merger.rb000066400000000000000000000025541450717454400217730ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # This filter merges html attributes (e.g. used for id and class) # @api public class AttributeMerger < Filter define_options merge_attrs: {'id' => '_', 'class' => ' '} def on_html_attrs(*attrs) values = {} attrs.each do |_, _, name, value| name = name.to_s if values[name] raise(FilterError, "Multiple #{name} attributes specified") unless options[:merge_attrs][name] values[name] << value else values[name] = [value] end end attrs = values.map do |name, value| if (delimiter = options[:merge_attrs][name]) && value.size > 1 exp = [:multi] if value.all? {|v| contains_nonempty_static?(v) } exp << value.first value[1..-1].each {|v| exp << [:static, delimiter] << v } else captures = unique_name exp << [:code, "#{captures} = []"] value.each_with_index {|v, i| exp << [:capture, "#{captures}[#{i}]", v] } exp << [:dynamic, "#{captures}.reject(&:empty?).join(#{delimiter.inspect})"] end else exp = value.first end [:html, :attr, name, exp] end [:html, :attrs, *attrs] end end end end temple-0.10.3/lib/temple/html/attribute_remover.rb000066400000000000000000000020151450717454400221610ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # This filter removes empty attributes # @api public class AttributeRemover < Filter define_options remove_empty_attrs: %w(id class) def initialize(opts = {}) super raise ArgumentError, "Option :remove_empty_attrs must be an Array of Strings" unless Array === options[:remove_empty_attrs] && options[:remove_empty_attrs].all? {|a| String === a } end def on_html_attrs(*attrs) [:multi, *attrs.map {|attr| compile(attr) }] end def on_html_attr(name, value) return super unless options[:remove_empty_attrs].include?(name.to_s) if empty_exp?(value) value elsif contains_nonempty_static?(value) [:html, :attr, name, value] else tmp = unique_name [:multi, [:capture, tmp, compile(value)], [:if, "!#{tmp}.empty?", [:html, :attr, name, [:dynamic, tmp]]]] end end end end end temple-0.10.3/lib/temple/html/attribute_sorter.rb000066400000000000000000000011751450717454400220260ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # This filter sorts html attributes. # @api public class AttributeSorter < Filter define_options sort_attrs: true def call(exp) options[:sort_attrs] ? super : exp end def on_html_attrs(*attrs) n = 0 # Use n to make sort stable. This is important because the merger could be executed afterwards. [:html, :attrs, *attrs.sort_by do |attr| raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr [attr[2].to_s, n += 1] end] end end end end temple-0.10.3/lib/temple/html/dispatcher.rb000066400000000000000000000014011450717454400205430ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # @api private module Dispatcher def on_html_attrs(*attrs) [:html, :attrs, *attrs.map {|a| compile(a) }] end def on_html_attr(name, content) [:html, :attr, name, compile(content)] end def on_html_comment(content) [:html, :comment, compile(content)] end def on_html_condcomment(condition, content) [:html, :condcomment, condition, compile(content)] end def on_html_js(content) [:html, :js, compile(content)] end def on_html_tag(name, attrs, content = nil) result = [:html, :tag, name, compile(attrs)] content ? (result << compile(content)) : result end end end end temple-0.10.3/lib/temple/html/fast.rb000066400000000000000000000117451450717454400173660ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # @api public class Fast < Filter DOCTYPES = { xml: { '1.1' => '', '5' => '', 'html' => '', 'strict' => '', 'frameset' => '', 'mobile' => '', 'basic' => '', 'transitional' => '', 'svg' => '' }, html: { '5' => '', 'html' => '', 'strict' => '', 'frameset' => '', 'transitional' => '' } } DOCTYPES[:xhtml] = DOCTYPES[:xml] DOCTYPES.freeze # See http://www.w3.org/html/wg/drafts/html/master/single-page.html#void-elements HTML_VOID_ELEMENTS = %w[area base br col embed hr img input keygen link menuitem meta param source track wbr] define_options format: :xhtml, attr_quote: '"', autoclose: HTML_VOID_ELEMENTS, js_wrapper: nil def initialize(opts = {}) super @format = options[:format] unless [:xhtml, :html, :xml].include?(@format) if @format == :html4 || @format == :html5 warn "Format #{@format.inspect} is deprecated, use :html" @format = :html else raise ArgumentError, "Invalid format #{@format.inspect}" end end wrapper = options[:js_wrapper] wrapper = @format == :xml || @format == :xhtml ? :cdata : :comment if wrapper == :guess @js_wrapper = case wrapper when :comment [ "" ] when :cdata [ "\n//\n" ] when :both [ "" ] when nil when Array wrapper else raise ArgumentError, "Invalid JavaScript wrapper #{wrapper.inspect}" end end def on_html_doctype(type) type = type.to_s.downcase if type =~ /^xml(\s+(.+))?$/ raise(FilterError, 'Invalid xml directive in html mode') if @format == :html w = options[:attr_quote] str = "" else str = DOCTYPES[@format][type] || raise(FilterError, "Invalid doctype #{type}") end [:static, str] end def on_html_comment(content) [:multi, [:static, '']] end def on_html_condcomment(condition, content) on_html_comment [:multi, [:static, "[#{condition}]>"], content, [:static, ''] result << compile(content) if content result << [:static, ""] if !closed result end def on_html_attrs(*attrs) [:multi, *attrs.map {|attr| compile(attr) }] end def on_html_attr(name, value) if @format == :html && empty_exp?(value) [:static, " #{name}"] else [:multi, [:static, " #{name}=#{options[:attr_quote]}"], compile(value), [:static, options[:attr_quote]]] end end def on_html_js(content) if @js_wrapper [:multi, [:static, @js_wrapper.first], compile(content), [:static, @js_wrapper.last]] else compile(content) end end end end end temple-0.10.3/lib/temple/html/filter.rb000066400000000000000000000007221450717454400177070ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # @api public class Filter < Temple::Filter include Dispatcher def contains_nonempty_static?(exp) case exp.first when :multi exp[1..-1].any? {|e| contains_nonempty_static?(e) } when :escape contains_nonempty_static?(exp.last) when :static !exp.last.empty? else false end end end end end temple-0.10.3/lib/temple/html/pretty.rb000066400000000000000000000064541450717454400177610ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML # @api public class Pretty < Fast define_options indent: ' ', pretty: true, indent_tags: %w(article aside audio base body datalist dd div dl dt fieldset figure footer form head h1 h2 h3 h4 h5 h6 header hgroup hr html li link meta nav ol option p rp rt ruby section script style table tbody td tfoot th thead tr ul video doctype).freeze, pre_tags: %w(code pre textarea).freeze def initialize(opts = {}) super @indent_next = nil @indent = 0 @pretty = options[:pretty] @pre_tags = @format != :xml && Regexp.union(options[:pre_tags].map {|t| "<#{t}" }) end def call(exp) @pretty ? [:multi, preamble, compile(exp)] : super end def on_static(content) return [:static, content] unless @pretty unless @pre_tags && @pre_tags =~ content content = content.sub(/\A\s*\n?/, "\n".freeze) if @indent_next content = content.gsub("\n".freeze, indent) end @indent_next = false [:static, content] end def on_dynamic(code) return [:dynamic, code] unless @pretty indent_next, @indent_next = @indent_next, false [:dynamic, "::Temple::Utils.indent_dynamic((#{code}), #{indent_next.inspect}, #{indent.inspect}#{@pre_tags ? ', ' + @pre_tags_name : ''})"] end def on_html_doctype(type) return super unless @pretty [:multi, [:static, tag_indent('doctype')], super] end def on_html_comment(content) return super unless @pretty result = [:multi, [:static, tag_indent('comment')], super] @indent_next = false result end def on_html_tag(name, attrs, content = nil) return super unless @pretty name = name.to_s closed = !content || (empty_exp?(content) && options[:autoclose].include?(name)) @pretty = false result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)] result << [:static, (closed && @format != :html ? ' /' : '') + '>'] @pretty = !@pre_tags || !options[:pre_tags].include?(name) if content @indent += 1 result << compile(content) @indent -= 1 end unless closed indent = tag_indent(name) result << [:static, "#{content && !empty_exp?(content) ? indent : ''}"] end @pretty = true result end protected def preamble return [:multi] unless @pre_tags @pre_tags_name = unique_name [:code, "#{@pre_tags_name} = /#{@pre_tags.source}/"] end def indent "\n" + (options[:indent] || '') * @indent end # Return indentation before tag def tag_indent(name) if @format == :xml flag = @indent_next != nil @indent_next = true else flag = @indent_next != nil && (@indent_next || options[:indent_tags].include?(name)) @indent_next = options[:indent_tags].include?(name) end flag ? indent : '' end end end end temple-0.10.3/lib/temple/html/safe.rb000066400000000000000000000005621450717454400173420ustar00rootroot00000000000000# frozen_string_literal: true module Temple module HTML class SafeString < String def html_safe?; true end def html_safe; self end def to_s; self end end end end class Object def html_safe?; false end end class Numeric def html_safe?; true end end class String def html_safe Temple::HTML::SafeString.new(self) end end temple-0.10.3/lib/temple/map.rb000066400000000000000000000041771450717454400162430ustar00rootroot00000000000000# frozen_string_literal: true module Temple # Immutable map class which supports map merging # @api public class ImmutableMap include Enumerable def initialize(*map) @map = map.compact end def include?(key) @map.any? {|h| h.include?(key) } end def [](key) @map.each {|h| return h[key] if h.include?(key) } nil end def each keys.each {|k| yield(k, self[k]) } end def keys @map.inject([]) {|keys, h| keys.concat(h.keys) }.uniq end def values keys.map {|k| self[k] } end def to_hash result = {} each {|k, v| result[k] = v } result end end # Mutable map class which supports map merging # @api public class MutableMap < ImmutableMap def initialize(*map) super({}, *map) end def []=(key, value) @map.first[key] = value end def update(map) @map.first.update(map) end end class OptionMap < MutableMap def initialize(*map, &block) super(*map) @handler = block @valid = {} @deprecated = {} end def []=(key, value) validate_key!(key) super end def update(map) validate_map!(map) super end def valid_keys (keys + @valid.keys + @map.map {|h| h.valid_keys if h.respond_to?(:valid_keys) }.compact.flatten).uniq end def add_valid_keys(*keys) keys.flatten.each { |key| @valid[key] = true } end def add_deprecated_keys(*keys) keys.flatten.each { |key| @valid[key] = @deprecated[key] = true } end def validate_map!(map) map.to_hash.keys.each {|key| validate_key!(key) } end def validate_key!(key) @handler.call(self, key, :deprecated) if deprecated_key?(key) @handler.call(self, key, :invalid) unless valid_key?(key) end def deprecated_key?(key) @deprecated.include?(key) || @map.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) } end def valid_key?(key) include?(key) || @valid.include?(key) || @map.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) } end end end temple-0.10.3/lib/temple/mixins/000077500000000000000000000000001450717454400164375ustar00rootroot00000000000000temple-0.10.3/lib/temple/mixins/dispatcher.rb000066400000000000000000000111001450717454400211030ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Mixins # @api private module CoreDispatcher def on_multi(*exps) multi = [:multi] exps.each {|exp| multi << compile(exp) } multi end def on_capture(name, exp) [:capture, name, compile(exp)] end end # @api private module EscapeDispatcher def on_escape(flag, exp) [:escape, flag, compile(exp)] end end # @api private module ControlFlowDispatcher def on_if(condition, *cases) [:if, condition, *cases.compact.map {|e| compile(e) }] end def on_case(arg, *cases) [:case, arg, *cases.map {|condition, exp| [condition, compile(exp)] }] end def on_block(code, content) [:block, code, compile(content)] end def on_cond(*cases) [:cond, *cases.map {|condition, exp| [condition, compile(exp)] }] end end # @api private module CompiledDispatcher def call(exp) compile(exp) end def compile(exp) dispatcher(exp) end private def dispatcher(exp) replace_dispatcher(exp) end def replace_dispatcher(exp) tree = DispatchNode.new dispatched_methods.each do |method| method.split('_'.freeze)[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method end self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def dispatcher(exp) return replace_dispatcher(exp) if self.class != #{self.class} #{tree.compile.gsub("\n", "\n ")} end RUBY dispatcher(exp) end def dispatched_methods re = /^on(_[a-zA-Z0-9]+)*$/ self.methods.map(&:to_s).select(&re.method(:=~)) end # @api private class DispatchNode < Hash attr_accessor :method def initialize super { |hsh,key| hsh[key] = DispatchNode.new } @method = nil end def compile(level = 0, call_parent = nil) call_method = method ? (level == 0 ? "#{method}(*exp)" : "#{method}(*exp[#{level}..-1])") : call_parent if empty? raise 'Invalid dispatcher node' unless method call_method else code = String.new code << "case(exp[#{level}])\n" each do |key, child| code << "when #{key.inspect}\n " << child.compile(level + 1, call_method).gsub("\n".freeze, "\n ".freeze) << "\n".freeze end code << "else\n " << (call_method || 'exp') << "\nend" end end end end # @api public # # Implements a compatible call-method # based on the including classe's methods. # # It uses every method starting with # "on" and uses the rest of the method # name as prefix of the expression it # will receive. So, if a dispatcher # has a method named "on_x", this method # will be called with arg0,..,argN # whenever an expression like [:x, arg0,..,argN ] # is encountered. # # This works with longer prefixes, too. # For example a method named "on_y_z" # will be called whenever an expression # like [:y, :z, .. ] is found. Furthermore, # if additionally a method named "on_y" # is present, it will be called when an # expression starts with :y but then does # not contain with :z. This way a # dispatcher can implement namespaces. # # @note # Processing does not reach into unknown # expression types by default. # # @example # class MyAwesomeDispatch # include Temple::Mixins::Dispatcher # def on_awesome(thing) # keep awesome things # return [:awesome, thing] # end # def on_boring(thing) # make boring things awesome # return [:awesome, thing+" with bacon"] # end # def on(type,*args) # unknown stuff is boring too # return [:awesome, 'just bacon'] # end # end # filter = MyAwesomeDispatch.new # # Boring things are converted: # filter.call([:boring, 'egg']) #=> [:awesome, 'egg with bacon'] # # Unknown things too: # filter.call([:foo]) #=> [:awesome, 'just bacon'] # # Known but not boring things won't be touched: # filter.call([:awesome, 'chuck norris']) #=>[:awesome, 'chuck norris'] # module Dispatcher include CompiledDispatcher include CoreDispatcher include EscapeDispatcher include ControlFlowDispatcher end end end temple-0.10.3/lib/temple/mixins/engine_dsl.rb000066400000000000000000000115211450717454400210730ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Mixins # @api private module EngineDSL def chain_modified! end def append(*args, &block) chain << chain_element(args, block) chain_modified! end def prepend(*args, &block) chain.unshift(chain_element(args, block)) chain_modified! end def remove(name) name = chain_name(name) raise "#{name} not found" unless chain.reject! {|i| name === i.first } chain_modified! end alias use append def before(name, *args, &block) name = chain_name(name) e = chain_element(args, block) chain.map! {|f| name === f.first ? [e, f] : [f] }.flatten!(1) raise "#{name} not found" unless chain.include?(e) chain_modified! end def after(name, *args, &block) name = chain_name(name) e = chain_element(args, block) chain.map! {|f| name === f.first ? [f, e] : [f] }.flatten!(1) raise "#{name} not found" unless chain.include?(e) chain_modified! end def replace(name, *args, &block) name = chain_name(name) e = chain_element(args, block) chain.map! {|f| name === f.first ? e : f } raise "#{name} not found" unless chain.include?(e) chain_modified! end # Shortcuts to access namespaces { filter: Temple::Filters, generator: Temple::Generators, html: Temple::HTML }.each do |method, mod| define_method(method) do |name, *options| use(name, mod.const_get(name), *options) end end private def chain_name(name) case name when Class name.name.to_sym when Symbol, String name.to_sym when Regexp name else raise(ArgumentError, 'Name argument must be Class, Symbol, String or Regexp') end end def chain_class_constructor(filter, local_options) define_options(filter.options.valid_keys) if respond_to?(:define_options) && filter.respond_to?(:options) proc do |engine| opts = {}.update(engine.options) opts.delete_if {|k,v| !filter.options.valid_key?(k) } if filter.respond_to?(:options) opts.update(local_options) if local_options filter.new(opts) end end def chain_proc_constructor(name, filter) raise(ArgumentError, 'Proc or blocks must have arity 0 or 1') if filter.arity > 1 method_name = "FILTER #{name}" c = Class === self ? self : singleton_class filter = c.class_eval { define_method(method_name, &filter); instance_method(method_name) } proc do |engine| if filter.arity == 1 # the proc takes one argument, e.g. use(:Filter) {|exp| exp } filter.bind(engine) else f = filter.bind(engine).call if f.respond_to? :call # the proc returns a callable object, e.g. use(:Filter) { Filter.new } f else raise(ArgumentError, 'Proc or blocks must return a Callable or a Class') unless f.respond_to? :new # the proc returns a class, e.g. use(:Filter) { Filter } f.new(f.respond_to?(:options) ? engine.options.to_hash.select {|k,v| f.options.valid_key?(k) } : engine.options) end end end end def chain_element(args, block) name = args.shift if Class === name filter = name name = filter.name.to_sym else raise(ArgumentError, 'Name argument must be Class or Symbol') unless Symbol === name end if block raise(ArgumentError, 'Class and block argument are not allowed at the same time') if filter filter = block end filter ||= args.shift case filter when Proc # Proc or block argument # The proc is converted to a method of the engine class. # The proc can then access the option hash of the engine. raise(ArgumentError, 'Too many arguments') unless args.empty? [name, chain_proc_constructor(name, filter)] when Class # Class argument (e.g Filter class) # The options are passed to the classes constructor. raise(ArgumentError, 'Too many arguments') if args.size > 1 [name, chain_class_constructor(filter, args.first)] else # Other callable argument (e.g. Object of class which implements #call or Method) # The callable has no access to the option hash of the engine. raise(ArgumentError, 'Too many arguments') unless args.empty? raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call) [name, proc { filter }] end end end end end temple-0.10.3/lib/temple/mixins/grammar_dsl.rb000066400000000000000000000101441450717454400212540ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Mixins # @api private module GrammarDSL class Rule def initialize(grammar) @grammar = grammar end def match?(exp) match(exp, []) end alias === match? alias =~ match? def |(rule) Or.new(@grammar, self, rule) end def copy_to(grammar) copy = dup.instance_eval { @grammar = grammar; self } copy.after_copy(self) if copy.respond_to?(:after_copy) copy end end class Or < Rule def initialize(grammar, *children) super(grammar) @children = children.map {|rule| @grammar.Rule(rule) } end def <<(rule) @children << @grammar.Rule(rule) self end alias | << def match(exp, unmatched) tmp = [] @children.any? {|rule| rule.match(exp, tmp) } || (unmatched.concat(tmp) && false) end def after_copy(source) @children = @children.map {|child| child.copy_to(@grammar) } end end class Root < Or def initialize(grammar, name) super(grammar) @name = name.to_sym end def match(exp, unmatched) success = super unmatched << [@name, exp] unless success success end def validate!(exp) unmatched = [] unless match(exp, unmatched) require 'pp' entry = unmatched.first unmatched.reverse_each do |u| entry = u if u.flatten.size < entry.flatten.size end raise(InvalidExpression, PP.pp(entry.last, "#{@grammar}::#{entry.first} did not match\n".dup)) end end def copy_to(grammar) grammar.const_defined?(@name) ? grammar.const_get(@name) : super end def after_copy(source) @grammar.const_set(@name, self) super end end class Element < Or def initialize(grammar, rule) super(grammar) @rule = grammar.Rule(rule) end def match(exp, unmatched) return false unless Array === exp && !exp.empty? head, *tail = exp @rule.match(head, unmatched) && super(tail, unmatched) end def after_copy(source) @children = @children.map do |child| child == source ? self : child.copy_to(@grammar) end @rule = @rule.copy_to(@grammar) end end class Value < Rule def initialize(grammar, value) super(grammar) @value = value end def match(exp, unmatched) @value === exp end end def extended(mod) mod.extend GrammarDSL constants.each do |name| const_get(name).copy_to(mod) if Rule === const_get(name) end end def match?(exp) const_get(:Expression).match?(exp) end alias === match? alias =~ match? def validate!(exp) const_get(:Expression).validate!(exp) end def Value(value) Value.new(self, value) end def Rule(rule) case rule when Rule rule when Symbol, Class, true, false, nil Value(rule) when Array start = Or.new(self) curr = [start] rule.each do |elem| case elem when /^(.*)(\*|\?|\+)$/ elem = Element.new(self, const_get($1)) curr.each {|c| c << elem } elem << elem if $2 != '?' curr = $2 == '+' ? [elem] : (curr << elem) else elem = Element.new(self, elem) curr.each {|c| c << elem } curr = [elem] end end elem = Value([]) curr.each {|c| c << elem } start else raise ArgumentError, "Invalid grammar rule '#{rule.inspect}'" end end def const_missing(name) const_set(name, Root.new(self, name)) end end end end temple-0.10.3/lib/temple/mixins/options.rb000066400000000000000000000044471450717454400204700ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Mixins # @api public module ClassOptions def set_default_options(opts) warn 'set_default_options has been deprecated, use set_options' set_options(opts) end def default_options warn 'default_options has been deprecated, use options' options end def set_options(opts) options.update(opts) end def options @options ||= OptionMap.new(superclass.respond_to?(:options) ? superclass.options : nil) do |hash, key, what| warn "#{self}: Option #{key.inspect} is #{what}" unless @option_validator_disabled end end def define_options(*opts) if opts.last.respond_to?(:to_hash) hash = opts.pop.to_hash options.add_valid_keys(hash.keys) options.update(hash) end options.add_valid_keys(opts) end def define_deprecated_options(*opts) if opts.last.respond_to?(:to_hash) hash = opts.pop.to_hash options.add_deprecated_keys(hash.keys) options.update(hash) end options.add_deprecated_keys(opts) end def disable_option_validator! @option_validator_disabled = true end end module ThreadOptions def with_options(options) old_options = thread_options Thread.current[thread_options_key] = ImmutableMap.new(options, thread_options) yield ensure Thread.current[thread_options_key] = old_options end def thread_options Thread.current[thread_options_key] end protected def thread_options_key @thread_options_key ||= "#{self.name}-thread-options".to_sym end end # @api public module Options def self.included(base) base.class_eval do extend ClassOptions extend ThreadOptions end end attr_reader :options def initialize(opts = {}) self.class.options.validate_map!(opts) self.class.options.validate_map!(self.class.thread_options) if self.class.thread_options @options = ImmutableMap.new({}.update(self.class.options).update(self.class.thread_options || {}).update(opts)) end end end end temple-0.10.3/lib/temple/mixins/template.rb000066400000000000000000000013271450717454400206020ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Mixins # @api private module Template include ClassOptions def compile(code, options) engine = options.delete(:engine) raise 'No engine configured' unless engine engine.new(options).call(code) end def register_as(*names) raise NotImplementedError end def create(engine, options) register_as = options.delete(:register_as) template = Class.new(self) template.disable_option_validator! template.options[:engine] = engine template.options.update(options) template.register_as(*register_as) if register_as template end end end end temple-0.10.3/lib/temple/parser.rb000066400000000000000000000002321450717454400167460ustar00rootroot00000000000000# frozen_string_literal: true module Temple # Temple base parser # @api public class Parser include Utils include Mixins::Options end end temple-0.10.3/lib/temple/static_analyzer.rb000066400000000000000000000032571450717454400206600ustar00rootroot00000000000000# frozen_string_literal: true begin require 'ripper' rescue LoadError end module Temple module StaticAnalyzer STATIC_TOKENS = [ :on_tstring_beg, :on_tstring_end, :on_tstring_content, :on_embexpr_beg, :on_embexpr_end, :on_lbracket, :on_rbracket, :on_qwords_beg, :on_words_sep, :on_qwords_sep, :on_lparen, :on_rparen, :on_lbrace, :on_rbrace, :on_label, :on_int, :on_float, :on_imaginary, :on_comma, :on_sp, :on_ignored_nl, ].freeze DYNAMIC_TOKENS = [ :on_ident, :on_period, ].freeze STATIC_KEYWORDS = [ 'true', 'false', 'nil', ].freeze STATIC_OPERATORS = [ '=>', ].freeze class << self def available? defined?(Ripper) && Ripper.respond_to?(:lex) end def static?(code) return false if code.nil? || code.strip.empty? return false if syntax_error?(code) Ripper.lex(code).each do |_, token, str| case token when *STATIC_TOKENS # noop when :on_kw return false unless STATIC_KEYWORDS.include?(str) when :on_op return false unless STATIC_OPERATORS.include?(str) when *DYNAMIC_TOKENS return false else return false end end true end def syntax_error?(code) SyntaxChecker.new(code).parse false rescue SyntaxChecker::ParseError true end end if defined?(Ripper) class SyntaxChecker < Ripper class ParseError < StandardError; end private def on_parse_error(*) raise ParseError end end end end end temple-0.10.3/lib/temple/templates.rb000066400000000000000000000004431450717454400174540ustar00rootroot00000000000000# frozen_string_literal: true module Temple # @api public module Templates autoload :Tilt, 'temple/templates/tilt' autoload :Rails, 'temple/templates/rails' def self.method_missing(name, engine, options = {}) const_get(name).create(engine, options) end end end temple-0.10.3/lib/temple/templates/000077500000000000000000000000001450717454400171265ustar00rootroot00000000000000temple-0.10.3/lib/temple/templates/rails.rb000066400000000000000000000021251450717454400205650ustar00rootroot00000000000000# frozen_string_literal: true module Temple module Templates class Rails extend Mixins::Template def call(template, source = nil) opts = {}.update(self.class.options).update(file: template.identifier) if ActionView::Base.try(:annotate_rendered_view_with_filenames) && template.format == :html opts[:preamble] = "\n" opts[:postamble] = "\n" end self.class.compile((source || template.source), opts) end def supports_streaming? self.class.options[:streaming] end def self.register_as(*names) raise 'Rails is not loaded - Temple::Templates::Rails cannot be used' unless defined?(::ActionView) if ::ActiveSupport::VERSION::MAJOR < 5 raise "Temple supports only Rails 5 and greater, your Rails version is #{::ActiveSupport::VERSION::STRING}" end names.each do |name| ::ActionView::Template.register_template_handler name.to_sym, new end end end end end temple-0.10.3/lib/temple/templates/tilt.rb000066400000000000000000000017741450717454400204400ustar00rootroot00000000000000# frozen_string_literal: true require 'tilt' module Temple module Templates class Tilt < ::Tilt::Template extend Mixins::Template define_options mime_type: 'text/html' # Prepare Temple template # # Called immediately after template data is loaded. # # @return [void] def prepare opts = {}.update(self.class.options).update(options).update(file: eval_file) metadata[:mime_type] = opts.delete(:mime_type) if opts.include?(:outvar) opts[:buffer] = opts.delete(:outvar) opts[:save_buffer] = true end @src = self.class.compile(data, opts) end # A string containing the (Ruby) source code for the template. # # @param [Hash] locals Local variables # @return [String] Compiled template ruby code def precompiled_template(locals = {}) @src end def self.register_as(*names) ::Tilt.register(self, *names.map(&:to_s)) end end end end temple-0.10.3/lib/temple/utils.rb000066400000000000000000000044571450717454400166270ustar00rootroot00000000000000# frozen_string_literal: true begin require 'cgi/escape' rescue LoadError end module Temple # @api public module Utils extend self # Returns an escaped copy of `html`. # Strings which are declared as html_safe are not escaped. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html_safe(html) s = html.to_s s.html_safe? || html.html_safe? ? s : escape_html(s) end if defined?(CGI.escapeHTML) # Returns an escaped copy of `html`. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html(html) CGI.escapeHTML(html.to_s) end else # Used by escape_html # @api private ESCAPE_HTML = { '&' => '&', '"' => '"', '\'' => ''', '<' => '<', '>' => '>' }.freeze ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) # Returns an escaped copy of `html`. # # @param html [String] The string to escape # @return [String] The escaped string def escape_html(html) html.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML) end end # Generate unique variable name # # @param prefix [String] Variable name prefix # @return [String] Variable name def unique_name(prefix = nil) @unique_name ||= 0 prefix ||= (@unique_prefix ||= self.class.name.gsub('::'.freeze, '_'.freeze).downcase) "_#{prefix}#{@unique_name += 1}" end # Check if expression is empty # # @param exp [Array] Temple expression # @return true if expression is empty def empty_exp?(exp) case exp[0] when :multi exp[1..-1].all? {|e| empty_exp?(e) } when :newline true else false end end def indent_dynamic(text, indent_next, indent, pre_tags = nil) text = text.to_s safe = text.respond_to?(:html_safe?) && text.html_safe? return text if pre_tags && text =~ pre_tags level = text.scan(/^\s*/).map(&:size).min text = text.gsub(/(?!\A)^\s{#{level}}/, '') if level > 0 text = text.sub(/\A\s*\n?/, "\n".freeze) if indent_next text = text.gsub("\n".freeze, indent) safe ? text.html_safe : text end end end temple-0.10.3/lib/temple/version.rb000066400000000000000000000001051450717454400171360ustar00rootroot00000000000000# frozen_string_literal: true module Temple VERSION = '0.10.3' end temple-0.10.3/spec/000077500000000000000000000000001450717454400140265ustar00rootroot00000000000000temple-0.10.3/spec/engine_spec.rb000066400000000000000000000125011450717454400166310ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'spec_helper' class Callable1 def call(exp) exp end end class Callable2 def call(exp) exp end end class MySpecialFilter def initialize(opts = {}) end def call(exp) exp end end class TestEngine < Temple::Engine use(:Parser) do |input| [:static, input] end use :MyFilter1, proc {|exp| exp } use :MyFilter2, proc {|exp| exp } use Temple::HTML::Pretty, pretty: true filter :MultiFlattener generator :ArrayBuffer use(:BeforeBeforeLast) { MySpecialFilter } use :BeforeLast, Callable1.new use(:Last) { Callable2.new } end describe Temple::Engine do it 'should build chain' do expect(TestEngine.chain.size).to eq(9) expect(TestEngine.chain[0].first).to eq(:Parser) expect(TestEngine.chain[0].size).to eq(2) expect(TestEngine.chain[0].last).to be_a(Proc) expect(TestEngine.chain[1].first).to eq(:MyFilter1) expect(TestEngine.chain[1].size).to eq(2) expect(TestEngine.chain[1].last).to be_a(Proc) expect(TestEngine.chain[2].first).to eq(:MyFilter2) expect(TestEngine.chain[2].size).to eq(2) expect(TestEngine.chain[2].last).to be_a(Proc) expect(TestEngine.chain[3].first).to eq(:'Temple::HTML::Pretty') expect(TestEngine.chain[3].size).to eq(2) expect(TestEngine.chain[3].last).to be_a(Proc) expect(TestEngine.chain[4].first).to eq(:MultiFlattener) expect(TestEngine.chain[4].size).to eq(2) expect(TestEngine.chain[4].last).to be_a(Proc) expect(TestEngine.chain[5].first).to eq(:ArrayBuffer) expect(TestEngine.chain[5].size).to eq(2) expect(TestEngine.chain[5].last).to be_a(Proc) expect(TestEngine.chain[6].first).to eq(:BeforeBeforeLast) expect(TestEngine.chain[6].size).to eq(2) expect(TestEngine.chain[6].last).to be_a(Proc) expect(TestEngine.chain[7].first).to eq(:BeforeLast) expect(TestEngine.chain[7].size).to eq(2) expect(TestEngine.chain[7].last).to be_a(Proc) expect(TestEngine.chain[8].first).to eq(:Last) expect(TestEngine.chain[8].size).to eq(2) expect(TestEngine.chain[8].last).to be_a(Proc) end it 'should instantiate chain' do call_chain = TestEngine.new.send(:call_chain) expect(call_chain[0]).to be_a(Method) expect(call_chain[1]).to be_a(Method) expect(call_chain[2]).to be_a(Method) expect(call_chain[3]).to be_a(Temple::HTML::Pretty) expect(call_chain[4]).to be_a(Temple::Filters::MultiFlattener) expect(call_chain[5]).to be_a(Temple::Generators::ArrayBuffer) expect(call_chain[6]).to be_a(MySpecialFilter) expect(call_chain[7]).to be_a(Callable1) expect(call_chain[8]).to be_a(Callable2) end it 'should have #append' do engine = TestEngine.new call_chain = engine.send(:call_chain) expect(call_chain.size).to eq(9) engine.append :MyFilter3 do |exp| exp end expect(TestEngine.chain.size).to eq(9) expect(engine.chain.size).to eq(10) expect(engine.chain[9].first).to eq(:MyFilter3) expect(engine.chain[9].size).to eq(2) expect(engine.chain[9].last).to be_a(Proc) call_chain = engine.send(:call_chain) expect(call_chain.size).to eq(10) expect(call_chain[9]).to be_a(Method) end it 'should have #prepend' do engine = TestEngine.new call_chain = engine.send(:call_chain) expect(call_chain.size).to eq(9) engine.prepend :MyFilter0 do |exp| exp end expect(TestEngine.chain.size).to eq(9) expect(engine.chain.size).to eq(10) expect(engine.chain[0].first).to eq(:MyFilter0) expect(engine.chain[0].size).to eq(2) expect(engine.chain[0].last).to be_a(Proc) expect(engine.chain[1].first).to eq(:Parser) call_chain = engine.send(:call_chain) expect(call_chain.size).to eq(10) expect(call_chain[0]).to be_a(Method) end it 'should have #after' do engine = TestEngine.new engine.after :Parser, :MyFilter0 do |exp| exp end expect(TestEngine.chain.size).to eq(9) expect(engine.chain.size).to eq(10) expect(engine.chain[0].first).to eq(:Parser) expect(engine.chain[1].first).to eq(:MyFilter0) expect(engine.chain[2].first).to eq(:MyFilter1) end it 'should have #before' do engine = TestEngine.new engine.before :MyFilter1, :MyFilter0 do |exp| exp end expect(TestEngine.chain.size).to eq(9) expect(engine.chain.size).to eq(10) expect(engine.chain[0].first).to eq(:Parser) expect(engine.chain[1].first).to eq(:MyFilter0) expect(engine.chain[2].first).to eq(:MyFilter1) end it 'should have #remove' do engine = TestEngine.new engine.remove :MyFilter1 expect(TestEngine.chain.size).to eq(9) expect(engine.chain.size).to eq(8) expect(engine.chain[0].first).to eq(:Parser) expect(engine.chain[1].first).to eq(:MyFilter2) engine = TestEngine.new engine.remove /Last/ expect(engine.chain.size).to eq(6) end it 'should have #replace' do engine = TestEngine.new engine.replace :Parser, :MyParser do |exp| exp end expect(engine.chain.size).to eq(9) expect(engine.chain[0].first).to eq(:MyParser) end it 'should work with inheritance' do inherited_engine = Class.new(TestEngine) expect(inherited_engine.chain.size).to eq(9) inherited_engine.append :MyFilter3 do |exp| exp end expect(inherited_engine.chain.size).to eq(10) expect(TestEngine.chain.size).to eq(9) end end temple-0.10.3/spec/erb_spec.rb000066400000000000000000000017721450717454400161440ustar00rootroot00000000000000require 'spec_helper' require 'tilt/erubi' describe Temple::ERB::Engine do it 'should compile erb' do src = %q{ %% hi = hello <% 3.times do |n| %> * <%= n %> <% end %> } expect(erb(src)).to eq(erubi(src)) end it 'should recognize comments' do src = %q{ hello <%# comment -- ignored -- useful in testing %> world} expect(erb(src)).to eq(erubi(src)) end it 'should recognize <%% and %%>' do src = %q{ <%% <% if true %> %%> <% end %> } expect(erb(src)).to eq("\n<%\n %>\n") end it 'should escape automatically' do src = '<%== "<" %>' ans = '<' expect(erb(src)).to eq(ans) end it 'should support = to disable automatic escape' do src = '<%= "<" %>' ans = '<' expect(erb(src)).to eq(ans) end it 'should support trim mode' do src = %q{ %% hi = hello <% 3.times do |n| %> * <%= n %> <% end %> } expect(erb(src, trim: true)).to eq(erubi(src, trim: true)) expect(erb(src, trim: false)).to eq(erubi(src, trim: false)) end end temple-0.10.3/spec/filter_spec.rb000066400000000000000000000014371450717454400166570ustar00rootroot00000000000000require 'spec_helper' class SimpleFilter < Temple::Filter define_options :key def on_test(arg) [:on_test, arg] end end describe Temple::Filter do it 'should support options' do expect(Temple::Filter).to respond_to(:default_options) expect(Temple::Filter).to respond_to(:set_default_options) expect(Temple::Filter).to respond_to(:define_options) expect(Temple::Filter.new.options).to be_a(Temple::ImmutableMap) expect(SimpleFilter.new(key: 3).options[:key]).to eq(3) end it 'should implement call' do expect(Temple::Filter.new.call([:exp])).to eq([:exp]) end it 'should process expressions' do filter = SimpleFilter.new expect(filter.call([:unhandled])).to eq([:unhandled]) expect(filter.call([:test, 42])).to eq([:on_test, 42]) end end temple-0.10.3/spec/filters/000077500000000000000000000000001450717454400154765ustar00rootroot00000000000000temple-0.10.3/spec/filters/code_merger_spec.rb000066400000000000000000000014341450717454400213120ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::CodeMerger do before do @filter = Temple::Filters::CodeMerger.new end it 'should merge serveral codes' do expect(@filter.call([:multi, [:code, "a"], [:code, "b"], [:code, "c"] ])).to eq [:code, "a; b; c"] end it 'should merge serveral codes around static' do expect(@filter.call([:multi, [:code, "a"], [:code, "b"], [:static, "123"], [:code, "a"], [:code, "b"] ])).to eq [:multi, [:code, "a; b"], [:static, "123"], [:code, "a; b"] ] end it 'should merge serveral codes with newlines' do expect(@filter.call([:multi, [:code, "a"], [:code, "b"], [:newline], [:code, "c"] ])).to eq [:code, "a; b\nc"] end end temple-0.10.3/spec/filters/control_flow_spec.rb000066400000000000000000000036701450717454400215520ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::ControlFlow do before do @filter = Temple::Filters::ControlFlow.new end it 'should process blocks' do expect(@filter.call([:block, 'loop do', [:static, 'Hello'] ])).to eq [:multi, [:code, 'loop do'], [:static, 'Hello'], [:code, 'end']] end it 'should process if' do expect(@filter.call([:if, 'condition', [:static, 'Hello'] ])).to eq [:multi, [:code, 'if condition'], [:static, 'Hello'], [:code, 'end'] ] end it 'should process if with else' do expect(@filter.call([:if, 'condition', [:static, 'True'], [:static, 'False'] ])).to eq [:multi, [:code, 'if condition'], [:static, 'True'], [:code, 'else'], [:static, 'False'], [:code, 'end'] ] end it 'should create elsif' do expect(@filter.call([:if, 'condition1', [:static, '1'], [:if, 'condition2', [:static, '2'], [:static, '3']] ])).to eq [:multi, [:code, 'if condition1'], [:static, '1'], [:code, 'elsif condition2'], [:static, '2'], [:code, 'else'], [:static, '3'], [:code, 'end'] ] end it 'should process cond' do expect(@filter.call([:cond, ['cond1', [:exp1]], ['cond2', [:exp2]], [:else, [:exp3]], ])).to eq [:multi, [:code, 'case'], [:code, 'when cond1'], [:exp1], [:code, 'when cond2'], [:exp2], [:code, 'else'], [:exp3], [:code, 'end'] ] end it 'should process case' do expect(@filter.call([:case, 'var', ['Array', [:exp1]], ['String', [:exp2]], [:else, [:exp3]], ])).to eq [:multi, [:code, 'case (var)'], [:code, 'when Array'], [:exp1], [:code, 'when String'], [:exp2], [:code, 'else'], [:exp3], [:code, 'end'] ] end end temple-0.10.3/spec/filters/dynamic_inliner_spec.rb000066400000000000000000000047521450717454400222110ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::DynamicInliner do before do @filter = Temple::Filters::DynamicInliner.new end it 'should compile several statics into dynamic' do expect(@filter.call([:multi, [:static, "Hello "], [:static, "World\n "], [:static, "Have a nice day"] ])).to eq [:dynamic, '"Hello World\n Have a nice day"'] end it 'should compile several dynamics into dynamic' do expect(@filter.call([:multi, [:dynamic, "@hello"], [:dynamic, "@world"], [:dynamic, "@yeah"] ])).to eq [:dynamic, '"#{@hello}#{@world}#{@yeah}"'] end it 'should compile static and dynamic into dynamic' do expect(@filter.call([:multi, [:static, "Hello"], [:dynamic, "@world"], [:dynamic, "@yeah"], [:static, "Nice"] ])).to eq [:dynamic, '"Hello#{@world}#{@yeah}Nice"'] end it 'should merge statics and dynamics around a code' do expect(@filter.call([:multi, [:static, "Hello "], [:dynamic, "@world"], [:code, "Oh yeah"], [:dynamic, "@yeah"], [:static, "Once more"] ])).to eq [:multi, [:dynamic, '"Hello #{@world}"'], [:code, "Oh yeah"], [:dynamic, '"#{@yeah}Once more"'] ] end it 'should keep codes intact' do expect(@filter.call([:multi, [:code, 'foo']])).to eq([:code, 'foo']) end it 'should keep single statics intact' do expect(@filter.call([:multi, [:static, 'foo']])).to eq([:static, 'foo']) end it 'should keep single dynamic intact' do expect(@filter.call([:multi, [:dynamic, 'foo']])).to eq([:dynamic, 'foo']) end it 'should inline inside multi' do expect(@filter.call([:multi, [:static, "Hello "], [:dynamic, "@world"], [:multi, [:static, "Hello "], [:dynamic, "@world"]], [:static, "Hello "], [:dynamic, "@world"] ])).to eq [:multi, [:dynamic, '"Hello #{@world}"'], [:dynamic, '"Hello #{@world}"'], [:dynamic, '"Hello #{@world}"'] ] end it 'should merge across newlines' do exp = expect(@filter.call([:multi, [:static, "Hello \n"], [:newline], [:dynamic, "@world"], [:newline] ])).to eq [:dynamic, ['"Hello \n"', '"#{@world}"', '""'].join("\\\n")] end it 'should compile static followed by newline' do expect(@filter.call([:multi, [:static, "Hello \n"], [:newline], [:code, "world"] ])).to eq [:multi, [:static, "Hello \n"], [:newline], [:code, "world"] ] end end temple-0.10.3/spec/filters/dynamic_merger_spec.rb000066400000000000000000000043731450717454400220310ustar00rootroot00000000000000describe Temple::Filters::DynamicMerger do describe '#call' do def assert_compile(expected, input) actual = Temple::Filters::DynamicMerger.new.compile(input) expect(expected).to eq(actual) end def assert_noop(input) actual = Temple::Filters::DynamicMerger.new.compile(input) expect(input).to eq(actual) end def strlit(body) "%Q\0#{body}\0" end specify { assert_compile([:static, 'foo'], [:multi, [:static, 'foo']]) } specify { assert_compile([:dynamic, 'foo'], [:multi, [:dynamic, 'foo']]) } specify { assert_noop([:multi, [:static, 'foo'], [:newline]]) } specify { assert_noop([:multi, [:dynamic, 'foo'], [:newline]]) } specify { assert_noop([:multi, [:static, "foo\n"], [:newline]]) } specify { assert_noop([:multi, [:static, 'foo'], [:dynamic, "foo\n"], [:newline]]) } specify { assert_noop([:multi, [:static, "foo\n"], [:dynamic, 'foo'], [:newline]]) } specify do assert_compile([:dynamic, strlit("\#{foo}foo\n")], [:multi, [:dynamic, 'foo'], [:static, "foo\n"], [:newline]]) end specify do assert_compile([:multi, [:dynamic, strlit("\#{foo}foo\n\n")], [:newline], [:code, 'foo'], ], [:multi, [:dynamic, 'foo'], [:static, "foo\n\n"], [:newline], [:newline], [:newline], [:code, 'foo'], ]) end specify do assert_compile([:multi, [:dynamic, strlit("\#{foo}foo\n")], [:code, 'bar'], [:dynamic, strlit("\#{foo}foo\n")], ], [:multi, [:dynamic, 'foo'], [:static, "foo\n"], [:newline], [:code, 'bar'], [:dynamic, 'foo'], [:static, "foo\n"], [:newline], ]) end specify do assert_compile([:multi, [:newline], [:dynamic, strlit("foo\n\#{foo}")]], [:multi, [:newline], [:newline], [:static, "foo\n"], [:dynamic, 'foo']]) end specify { assert_compile([:static, "\n"], [:multi, [:static, "\n"]]) } specify { assert_compile([:newline], [:multi, [:newline]]) } end end temple-0.10.3/spec/filters/eraser_spec.rb000066400000000000000000000020001450717454400203060ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::Eraser do it 'should respect keep' do eraser = Temple::Filters::Eraser.new(keep: [:a]) expect(eraser.call([:multi, [:a], [:b], [:c] ])).to eq [:multi, [:a], [:multi], [:multi] ] end it 'should respect erase' do eraser = Temple::Filters::Eraser.new(erase: [:a]) expect(eraser.call([:multi, [:a], [:b], [:c] ])).to eq [:multi, [:multi], [:b], [:c] ] end it 'should choose erase over keep' do eraser = Temple::Filters::Eraser.new(keep: [:a, :b], erase: [:a]) expect(eraser.call([:multi, [:a], [:b], [:c] ])).to eq [:multi, [:multi], [:b], [:multi] ] end it 'should erase nested types' do eraser = Temple::Filters::Eraser.new(erase: [[:a, :b]]) expect(eraser.call([:multi, [:a, :a], [:a, :b], [:b] ])).to eq [:multi, [:a, :a], [:multi], [:b] ] end end temple-0.10.3/spec/filters/escapable_spec.rb000066400000000000000000000025211450717454400207540ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::Escapable do before do @filter = Temple::Filters::Escapable.new end it 'should handle escape expressions' do expect(@filter.call([:escape, true, [:multi, [:static, "a < b"], [:dynamic, "ruby_method"]] ])).to eq [:multi, [:static, "a < b"], [:dynamic, "::Temple::Utils.escape_html((ruby_method))"], ] end it 'should keep codes intact' do exp = [:multi, [:code, 'foo']] expect(@filter.call(exp)).to eq(exp) end it 'should keep statics intact' do exp = [:multi, [:static, '<']] expect(@filter.call(exp)).to eq(exp) end it 'should keep dynamic intact' do exp = [:multi, [:dynamic, 'foo']] expect(@filter.call(exp)).to eq(exp) end it 'should have use_html_safe option' do with_html_safe do filter = Temple::Filters::Escapable.new(use_html_safe: true) expect(filter.call([:escape, true, [:static, Temple::HTML::SafeString.new("a < b")] ])).to eq [:static, "a < b"] end end it 'should support censoring' do filter = Temple::Filters::Escapable.new(escape_code: '(%s).gsub("Temple sucks", "Temple rocks")') expect(filter.call([:escape, true, [:static, "~~ Temple sucks ~~"] ])).to eq [:static, "~~ Temple rocks ~~"] end end temple-0.10.3/spec/filters/multi_flattener_spec.rb000066400000000000000000000013101450717454400222260ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::MultiFlattener do before do @filter = Temple::Filters::MultiFlattener.new end it 'should flatten nested multi expressions' do expect(@filter.call([:multi, [:static, "a"], [:multi, [:dynamic, "aa"], [:multi, [:static, "aaa"], [:static, "aab"], ], [:dynamic, "ab"], ], [:static, "b"], ])).to eq [:multi, [:static, "a"], [:dynamic, "aa"], [:static, "aaa"], [:static, "aab"], [:dynamic, "ab"], [:static, "b"], ] end it 'should return first element' do expect(@filter.call([:multi, [:code, 'foo']])).to eq([:code, 'foo']) end end temple-0.10.3/spec/filters/static_analyzer_spec.rb000066400000000000000000000020551450717454400222330ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::StaticAnalyzer do before do @filter = Temple::Filters::StaticAnalyzer.new @generator = Temple::Generator.new end if Temple::StaticAnalyzer.available? it 'should convert :dynamic to :static if code is static' do expect(@filter.call([:dynamic, '"#{"hello"}#{100}"'])).to eq([:static, 'hello100']) end it 'should not convert :dynamic if code is dynamic' do exp = [:dynamic, '"#{hello}#{100}"'] expect(@filter.call(exp)).to eq(exp) end it 'should not change number of newlines in generated code' do exp = [:dynamic, "[100,\n200,\n]"] expect(@filter.call(exp)).to eq([:multi, [:static, '[100, 200]'], [:newline], [:newline]]) expect(@generator.call(@filter.call(exp)).count("\n")).to eq(@generator.call(exp).count("\n")) end else it 'should do nothing' do [ [:dynamic, '"#{"hello"}#{100}"'], [:dynamic, '"#{hello}#{100}"'], ].each do |exp| expect(@filter.call(exp)).to eq(exp) end end end end temple-0.10.3/spec/filters/static_merger_spec.rb000066400000000000000000000017431450717454400216720ustar00rootroot00000000000000require 'spec_helper' describe Temple::Filters::StaticMerger do before do @filter = Temple::Filters::StaticMerger.new end it 'should merge serveral statics' do expect(@filter.call([:multi, [:static, "Hello "], [:static, "World, "], [:static, "Good night"] ])).to eq [:static, "Hello World, Good night"] end it 'should merge serveral statics around code' do expect(@filter.call([:multi, [:static, "Hello "], [:static, "World!"], [:code, "123"], [:static, "Good night, "], [:static, "everybody"] ])).to eq [:multi, [:static, "Hello World!"], [:code, "123"], [:static, "Good night, everybody"] ] end it 'should merge serveral statics across newlines' do expect(@filter.call([:multi, [:static, "Hello "], [:static, "World, "], [:newline], [:static, "Good night"] ])).to eq [:multi, [:static, "Hello World, Good night"], [:newline] ] end end temple-0.10.3/spec/filters/string_splitter_spec.rb000066400000000000000000000042561450717454400223000ustar00rootroot00000000000000require 'spec_helper' begin require 'ripper' rescue LoadError end if defined?(Ripper) describe Temple::Filters::StringSplitter do before do @filter = Temple::Filters::StringSplitter.new end { %q|''| => [:multi], %q|""| => [:multi], %q|"hello"| => [:multi, [:static, 'hello']], %q|"hello #{}world"| => [:multi, [:static, 'hello '], [:static, 'world']], %q|"#{hello}"| => [:multi, [:dynamic, 'hello']], %q|"nya#{123}"| => [:multi, [:static, 'nya'], [:dynamic, '123']], %q|"#{()}()"| => [:multi, [:dynamic, '()'], [:static, '()']], %q|" #{ " #{ '#{}' } " }"| => [:multi, [:static, ' '], [:multi, [:static, ' '], [:multi, [:static, '#{}']], [:static, ' ']]], %q|%Q[a#{b}c#{d}e]| => [:multi, [:static, 'a'], [:dynamic, 'b'], [:static, 'c'], [:dynamic, 'd'], [:static, 'e']], %q|%q[a#{b}c#{d}e]| => [:multi, [:static, 'a#{b}c#{d}e']], %q|"\#{}#{123}"| => [:multi, [:static, '#{}'], [:dynamic, '123']], %q|"#{ '}' }"| => [:multi, [:multi, [:static, '}']]], %q| "a" # hello | => [:multi, [:static, 'a']], %q|"\""| => [:multi, [:static, '"']], %q|"\\\\\\""| => [:multi, [:static, '\\"']], %q|'\"'| => [:multi, [:static, '\"']], %q|'\\\"'| => [:multi, [:static, '\\"']], %q|"static#{dynamic}"| => [:multi, [:static, 'static'], [:dynamic, 'dynamic']], }.each do |code, expected| it "should split #{code}" do actual = @filter.call([:dynamic, code]) expect(actual).to eq(expected) end end describe '.compile' do it 'should raise CompileError for non-string literals' do expect { Temple::Filters::StringSplitter.compile('1') }.to raise_error(Temple::FilterError) end it 'should compile strings quoted with parenthesis' do tokens = Temple::Filters::StringSplitter.compile('%Q(href("#{1 + 1}");)') expect(tokens).to eq([[:static, "href(\""], [:dynamic, "1 + 1"], [:static, "\");"]]) end end end end temple-0.10.3/spec/generator_spec.rb000066400000000000000000000153071450717454400173610ustar00rootroot00000000000000require 'spec_helper' class SimpleGenerator < Temple::Generator def preamble "#{buffer} = BUFFER" end def postamble buffer end def on_static(s) concat "S:#{s}" end def on_dynamic(s) concat "D:#{s}" end def on_code(s) "C:#{s}" end end class SimpleCaptureGenerator < SimpleGenerator def preamble "#{buffer} = CAPTURE_BUFFER" end end describe Temple::Generator do it 'should compile simple expressions' do gen = SimpleGenerator.new expect(gen.call([:static, 'test'])).to eq('_buf = BUFFER; _buf << (S:test); _buf') expect(gen.call([:dynamic, 'test'])).to eq('_buf = BUFFER; _buf << (D:test); _buf') expect(gen.call([:code, 'test'])).to eq('_buf = BUFFER; C:test; _buf') end it 'should compile multi expression' do gen = SimpleGenerator.new(buffer: "VAR") expect(gen.call([:multi, [:static, "static"], [:dynamic, "dynamic"], [:code, "code"] ])).to eq('VAR = BUFFER; VAR << (S:static); VAR << (D:dynamic); C:code; VAR') end it 'should compile capture' do gen = SimpleGenerator.new(buffer: "VAR", capture_generator: SimpleGenerator) expect(gen.call([:capture, "foo", [:static, "test"] ])).to eq('VAR = BUFFER; foo = BUFFER; foo << (S:test); foo; VAR') end it 'should compile capture with multi' do gen = SimpleGenerator.new(buffer: "VAR", capture_generator: SimpleGenerator) expect(gen.call([:multi, [:static, "before"], [:capture, "foo", [:multi, [:static, "static"], [:dynamic, "dynamic"], [:code, "code"]]], [:static, "after"] ])).to eq('VAR = BUFFER; VAR << (S:before); foo = BUFFER; foo << (S:static); ' + 'foo << (D:dynamic); C:code; foo; VAR << (S:after); VAR') end it 'should compile nested captures with the same capture_generator' do gen = SimpleGenerator.new(buffer: "VAR", capture_generator: SimpleCaptureGenerator) expect(gen.call( [:capture, "foo", [:multi, [:static, "a"], [:capture, "bar", [:multi, [:static, "b"], [:capture, "baz", [:multi, [:static, "c"], ]] ]] ]] )).to eq "VAR = BUFFER; foo = CAPTURE_BUFFER; foo << (S:a); bar = CAPTURE_BUFFER; bar << (S:b); baz = CAPTURE_BUFFER; baz << (S:c); baz; bar; foo; VAR" end it 'should compile newlines' do gen = SimpleGenerator.new(buffer: "VAR") expect(gen.call([:multi, [:static, "static"], [:newline], [:dynamic, "dynamic"], [:newline], [:code, "code"] ])).to eq("VAR = BUFFER; VAR << (S:static); \n; " + "VAR << (D:dynamic); \n; C:code; VAR") end end describe Temple::Generators::Array do it 'should compile simple expressions' do gen = Temple::Generators::Array.new(freeze_static: false) expect(gen.call([:static, 'test'])).to eq('_buf = []; _buf << ("test"); _buf') expect(gen.call([:dynamic, 'test'])).to eq('_buf = []; _buf << (test); _buf') expect(gen.call([:code, 'test'])).to eq('_buf = []; test; _buf') expect(gen.call([:multi, [:static, 'a'], [:static, 'b']])).to eq('_buf = []; _buf << ("a"); _buf << ("b"); _buf') expect(gen.call([:multi, [:static, 'a'], [:dynamic, 'b']])).to eq('_buf = []; _buf << ("a"); _buf << (b); _buf') end it 'should freeze static' do gen = Temple::Generators::Array.new(freeze_static: true) expect(gen.call([:static, 'test'])).to eq('_buf = []; _buf << ("test".freeze); _buf') end end describe Temple::Generators::ArrayBuffer do it 'should compile simple expressions' do gen = Temple::Generators::ArrayBuffer.new(freeze_static: false) expect(gen.call([:static, 'test'])).to eq('_buf = "test"') expect(gen.call([:dynamic, 'test'])).to eq('_buf = (test).to_s') expect(gen.call([:code, 'test'])).to eq('_buf = []; test; _buf = _buf.join("")') expect(gen.call([:multi, [:static, 'a'], [:static, 'b']])).to eq('_buf = []; _buf << ("a"); _buf << ("b"); _buf = _buf.join("")') expect(gen.call([:multi, [:static, 'a'], [:dynamic, 'b']])).to eq('_buf = []; _buf << ("a"); _buf << (b); _buf = _buf.join("")') end it 'should freeze static' do gen = Temple::Generators::ArrayBuffer.new(freeze_static: true) expect(gen.call([:static, 'test'])).to eq('_buf = "test"') expect(gen.call([:multi, [:dynamic, '1'], [:static, 'test']])).to eq('_buf = []; _buf << (1); _buf << ("test".freeze); _buf = _buf.join("".freeze)') end end describe Temple::Generators::StringBuffer do it 'should compile simple expressions' do gen = Temple::Generators::StringBuffer.new(freeze_static: false) expect(gen.call([:static, 'test'])).to eq('_buf = "test"') expect(gen.call([:dynamic, 'test'])).to eq('_buf = (test).to_s') expect(gen.call([:code, 'test'])).to eq('_buf = \'\'.dup; test; _buf') expect(gen.call([:multi, [:static, 'a'], [:static, 'b']])).to eq('_buf = \'\'.dup; _buf << ("a"); _buf << ("b"); _buf') expect(gen.call([:multi, [:static, 'a'], [:dynamic, 'b']])).to eq('_buf = \'\'.dup; _buf << ("a"); _buf << ((b).to_s); _buf') end it 'should freeze static' do gen = Temple::Generators::StringBuffer.new(freeze_static: true) expect(gen.call([:static, 'test'])).to eq('_buf = "test"') expect(gen.call([:multi, [:dynamic, '1'], [:static, 'test']])).to eq('_buf = \'\'.dup; _buf << ((1).to_s); _buf << ("test".freeze); _buf') end end describe Temple::Generators::ERB do it 'should compile simple expressions' do gen = Temple::Generators::ERB.new expect(gen.call([:static, 'test'])).to eq('test') expect(gen.call([:dynamic, 'test'])).to eq('<%= test %>') expect(gen.call([:code, 'test'])).to eq('<% test %>') expect(gen.call([:multi, [:static, 'a'], [:static, 'b']])).to eq('ab') expect(gen.call([:multi, [:static, 'a'], [:dynamic, 'b']])).to eq('a<%= b %>') end end describe Temple::Generators::RailsOutputBuffer do it 'should compile simple expressions' do gen = Temple::Generators::RailsOutputBuffer.new(freeze_static: false) expect(gen.call([:static, 'test'])).to eq('@output_buffer = output_buffer || ActionView::OutputBuffer.new; ' + '@output_buffer.safe_concat(("test")); @output_buffer') expect(gen.call([:dynamic, 'test'])).to eq('@output_buffer = output_buffer || ActionView::OutputBuffer.new; ' + '@output_buffer.safe_concat(((test).to_s)); @output_buffer') expect(gen.call([:code, 'test'])).to eq('@output_buffer = output_buffer || ActionView::OutputBuffer.new; ' + 'test; @output_buffer') end it 'should freeze static' do gen = Temple::Generators::RailsOutputBuffer.new(freeze_static: true) expect(gen.call([:static, 'test'])).to eq('@output_buffer = output_buffer || ActionView::OutputBuffer.new; @output_buffer.safe_concat(("test".freeze)); @output_buffer') end end temple-0.10.3/spec/grammar_spec.rb000066400000000000000000000045041450717454400170160ustar00rootroot00000000000000require 'spec_helper' describe Temple::Grammar do it 'should match core expressions' do expect(Temple::Grammar).to be_match([:multi]) expect(Temple::Grammar).to be_match([:multi, [:multi]]) expect(Temple::Grammar).to be_match([:static, 'Text']) expect(Temple::Grammar).to be_match([:dynamic, 'Text']) expect(Temple::Grammar).to be_match([:code, 'Text']) expect(Temple::Grammar).to be_match([:capture, 'Text', [:multi]]) expect(Temple::Grammar).to be_match([:newline]) end it 'should not match invalid core expressions' do expect(Temple::Grammar).not_to be_match([:multi, 'String']) expect(Temple::Grammar).not_to be_match([:static]) expect(Temple::Grammar).not_to be_match([:dynamic, 1]) expect(Temple::Grammar).not_to be_match([:code, :sym]) expect(Temple::Grammar).not_to be_match([:capture, [:multi]]) expect(Temple::Grammar).not_to be_match([:newline, [:multi]]) end it 'should match control flow expressions' do expect(Temple::Grammar).to be_match([:if, 'Condition', [:multi]]) expect(Temple::Grammar).to be_match([:if, 'Condition', [:multi], [:multi]]) expect(Temple::Grammar).to be_match([:block, 'Loop', [:multi]]) expect(Temple::Grammar).to be_match([:case, 'Arg', ['Cond1', [:multi]], ['Cond1', [:multi]], [:else, [:multi]]]) expect(Temple::Grammar).not_to be_match([:case, 'Arg', [:sym, [:multi]]]) expect(Temple::Grammar).to be_match([:cond, ['Cond1', [:multi]], ['Cond2', [:multi]], [:else, [:multi]]]) expect(Temple::Grammar).not_to be_match([:cond, [:sym, [:multi]]]) end it 'should match escape expression' do expect(Temple::Grammar).to be_match([:escape, true, [:multi]]) expect(Temple::Grammar).to be_match([:escape, false, [:multi]]) end it 'should match html expressions' do expect(Temple::Grammar).to be_match([:html, :doctype, 'Doctype']) expect(Temple::Grammar).to be_match([:html, :comment, [:multi]]) expect(Temple::Grammar).to be_match([:html, :tag, 'Tag', [:multi]]) expect(Temple::Grammar).to be_match([:html, :tag, 'Tag', [:multi], [:multi]]) expect(Temple::Grammar).to be_match([:html, :tag, 'Tag', [:multi], [:static, 'Text']]) expect(Temple::Grammar).to be_match([:html, :tag, 'Tag', [:html, :attrs, [:html, :attr, 'id', [:static, 'val']]], [:static, 'Text']]) end end temple-0.10.3/spec/html/000077500000000000000000000000001450717454400147725ustar00rootroot00000000000000temple-0.10.3/spec/html/attribute_merger_spec.rb000066400000000000000000000057621450717454400217070ustar00rootroot00000000000000require 'spec_helper' describe Temple::HTML::AttributeMerger do before do @merger = Temple::HTML::AttributeMerger.new end it 'should pass static attributes through' do expect(@merger.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static, 'b']]], [:content] ])).to eq [:html, :tag, "div", [:html, :attrs, [:html, :attr, "class", [:static, "b"]]], [:content]] end it 'should preserve the order of html attributes' do expect(@merger.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ])).to eq [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]]] # Use case: expect(@merger.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]] ])).to eq [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]]] end it 'should merge ids' do expect(@merger.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'id', [:dynamic, 'a']], [:html, :attr, 'id', [:dynamic, 'b']]], [:content] ])).to eq [:html, :tag, "div", [:html, :attrs, [:html, :attr, "id", [:multi, [:code, "_temple_html_attributemerger1 = []"], [:capture, "_temple_html_attributemerger1[0]", [:dynamic, "a"]], [:capture, "_temple_html_attributemerger1[1]", [:dynamic, "b"]], [:dynamic, "_temple_html_attributemerger1.reject(&:empty?).join(\"_\")"]]]], [:content]] end it 'should merge classes' do expect(@merger.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static, 'a']], [:html, :attr, 'class', [:dynamic, 'b']]], [:content] ])).to eq [:html, :tag, "div", [:html, :attrs, [:html, :attr, "class", [:multi, [:code, "_temple_html_attributemerger1 = []"], [:capture, "_temple_html_attributemerger1[0]", [:static, "a"]], [:capture, "_temple_html_attributemerger1[1]", [:dynamic, "b"]], [:dynamic, "_temple_html_attributemerger1.reject(&:empty?).join(\" \")"]]]], [:content]] end end temple-0.10.3/spec/html/attribute_remover_spec.rb000066400000000000000000000027421450717454400221000ustar00rootroot00000000000000require 'spec_helper' describe Temple::HTML::AttributeRemover do before do @remover = Temple::HTML::AttributeRemover.new end it 'should pass static attributes through' do expect(@remover.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:static, 'b']]], [:content] ])).to eq [:html, :tag, "div", [:multi, [:html, :attr, "class", [:static, "b"]]], [:content]] end it 'should check for empty dynamic attribute if it is included in :remove_empty_attrs' do expect(@remover.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'class', [:dynamic, 'b']]], [:content] ])).to eq [:html, :tag, "div", [:multi, [:multi, [:capture, "_temple_html_attributeremover1", [:dynamic, "b"]], [:if, "!_temple_html_attributeremover1.empty?", [:html, :attr, "class", [:dynamic, "_temple_html_attributeremover1"]]]]], [:content]] end it 'should not check for empty dynamic attribute if it is not included in :remove_empty_attrs' do expect(@remover.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'name', [:dynamic, 'b']]], [:content] ])).to eq [:html, :tag, "div", [:multi, [:html, :attr, "name", [:dynamic, "b"]]], [:content]] end end temple-0.10.3/spec/html/attribute_sorter_spec.rb000066400000000000000000000037411450717454400217370ustar00rootroot00000000000000require 'spec_helper' describe Temple::HTML::AttributeSorter do before do @ordered = Temple::HTML::AttributeSorter.new @unordered = Temple::HTML::AttributeSorter.new sort_attrs: false end it 'should sort html attributes by name by default, when :sort_attrs is true' do expect(@ordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ])).to eq [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']], [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']]]] end it 'should preserve the order of html attributes when :sort_attrs is false' do expect(@unordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]] ])).to eq [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'c', [:static, '1']], [:html, :attr, 'd', [:static, '2']], [:html, :attr, 'a', [:static, '3']], [:html, :attr, 'b', [:static, '4']]]] # Use case: expect(@unordered.call([:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]] ])).to eq [:html, :tag, 'meta', [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']], [:html, :attr, 'content', [:static, '']]]] end end temple-0.10.3/spec/html/fast_spec.rb000066400000000000000000000075261450717454400173000ustar00rootroot00000000000000require 'spec_helper' describe Temple::HTML::Fast do before do @html = Temple::HTML::Fast.new end it 'should compile html doctype' do expect(@html.call([:multi, [:html, :doctype, '5']])).to eq([:multi, [:static, '']]) expect(@html.call([:multi, [:html, :doctype, 'html']])).to eq([:multi, [:static, '']]) expect(@html.call([:multi, [:html, :doctype, '1.1']])).to eq [:multi, [:static, '']] end it 'should compile xml encoding' do expect(@html.call([:html, :doctype, 'xml latin1'])).to eq([:static, ""]) end it 'should compile html comment' do expect(@html.call([:html, :comment, [:static, 'test']])).to eq([:multi, [:static, ""]]) end it 'should compile js wrapped in comments' do expect(Temple::HTML::Fast.new(js_wrapper: nil).call([:html, :js, [:static, 'test']])).to eq([:static, "test"]) expect(Temple::HTML::Fast.new(js_wrapper: :comment).call([:html, :js, [:static, 'test']])).to eq([:multi, [:static, ""]]) expect(Temple::HTML::Fast.new(js_wrapper: :cdata).call([:html, :js, [:static, 'test']])).to eq([:multi, [:static, "\n//\n"]]) expect(Temple::HTML::Fast.new(js_wrapper: :both).call([:html, :js, [:static, 'test']])).to eq([:multi, [:static, ""]]) end it 'should guess default js comment' do expect(Temple::HTML::Fast.new(js_wrapper: :guess, format: :xhtml).call([:html, :js, [:static, 'test']])).to eq([:multi, [:static, "\n//\n"]]) expect(Temple::HTML::Fast.new(js_wrapper: :guess, format: :html).call([:html, :js, [:static, 'test']])).to eq([:multi, [:static, ""]]) end it 'should compile autoclosed html tag' do expect(@html.call([:html, :tag, 'img', [:attrs], [:multi, [:newline]] ])).to eq [:multi, [:static, ""], [:multi, [:newline]]] end it 'should compile explicitly closed html tag' do expect(@html.call([:html, :tag, 'closed', [:attrs] ])).to eq [:multi, [:static, ""]] end it 'should compile html with content' do expect(@html.call([:html, :tag, 'div', [:attrs], [:content] ])).to eq [:multi, [:static, ""], [:content], [:static, ""]] end it 'should compile html with attrs' do expect(@html.call([:html, :tag, 'div', [:html, :attrs, [:html, :attr, 'id', [:static, 'test']], [:html, :attr, 'class', [:dynamic, 'block']]], [:content] ])).to eq [:multi, [:static, ""], [:content], [:static, ""]] end it 'should keep codes intact' do exp = [:multi, [:code, 'foo']] expect(@html.call(exp)).to eq(exp) end it 'should keep statics intact' do exp = [:multi, [:static, '<']] expect(@html.call(exp)).to eq(exp) end it 'should keep dynamic intact' do exp = [:multi, [:dynamic, 'foo']] expect(@html.call(exp)).to eq(exp) end end temple-0.10.3/spec/html/pretty_spec.rb000066400000000000000000000037621450717454400176700ustar00rootroot00000000000000require 'spec_helper' describe Temple::HTML::Pretty do before do @html = Temple::HTML::Pretty.new end it 'should indent nested tags' do expect(@html.call([:html, :tag, 'div', [:multi], [:html, :tag, 'p', [:multi], [:multi, [:static, 'text'], [:dynamic, 'code']]] ])).to eq [:multi, [:code, "_temple_html_pretty1 = /"], [:multi, [:static, "\n "], [:multi, [:static, "\n text"], [:dynamic, "::Temple::Utils.indent_dynamic((code), false, \"\\n \", _temple_html_pretty1)"]], [:static, "\n

"]], [:static, "\n"]]] end it 'should not indent preformatted tags' do expect(@html.call([:html, :tag, 'pre', [:multi], [:html, :tag, 'p', [:multi], [:static, 'text']] ])).to eq [:multi, [:code, "_temple_html_pretty1 = /"], [:multi, [:static, ""], [:static, "text"], [:static, "

"]], [:static, ""]]] end it 'should not escape html_safe strings' do with_html_safe do expect(@html.call( [:dynamic, '"text<".html_safe'] )).to eq [:multi, [:code, "_temple_html_pretty1 = / 3 }]", "[\n1,\n]"].each do |exp| expect(Temple::StaticAnalyzer.static?(exp)).to eq(true) end end it 'should return false if given Ruby expression is dynamic' do ['1 + 2', 'variable', 'method_call(a)', 'CONSTANT'].each do |exp| expect(Temple::StaticAnalyzer.static?(exp)).to eq(false) end end end describe '.syntax_error?' do it 'should return false if given Ruby expression is valid' do ['Foo.bar.baz { |c| c.d! }', '{ foo: bar }'].each do |exp| expect(Temple::StaticAnalyzer.syntax_error?(exp)).to eq(false) end end it 'should return true if given Ruby expression is invalid' do ['Foo.bar.baz { |c| c.d! ', ' foo: bar '].each do |exp| expect(Temple::StaticAnalyzer.syntax_error?(exp)).to eq(true) end end end end end temple-0.10.3/spec/utils_spec.rb000066400000000000000000000022631450717454400165300ustar00rootroot00000000000000require 'spec_helper' class UniqueTest include Temple::Utils end describe Temple::Utils do it 'has empty_exp?' do expect(Temple::Utils.empty_exp?([:multi])).to eq(true) expect(Temple::Utils.empty_exp?([:multi, [:multi]])).to eq(true) expect(Temple::Utils.empty_exp?([:multi, [:multi, [:newline]], [:newline]])).to eq(true) expect(Temple::Utils.empty_exp?([:multi])).to eq(true) expect(Temple::Utils.empty_exp?([:multi, [:multi, [:static, 'text']]])).to eq(false) expect(Temple::Utils.empty_exp?([:multi, [:newline], [:multi, [:dynamic, 'text']]])).to eq(false) end it 'has unique_name' do u = UniqueTest.new expect(u.unique_name).to eq('_uniquetest1') expect(u.unique_name).to eq('_uniquetest2') expect(UniqueTest.new.unique_name).to eq('_uniquetest1') end it 'has escape_html' do expect(Temple::Utils.escape_html('<')).to eq('<') end it 'should escape unsafe html strings' do with_html_safe do expect(Temple::Utils.escape_html_safe('<')).to eq('<') end end it 'should not escape safe html strings' do with_html_safe do expect(Temple::Utils.escape_html_safe('<'.html_safe)).to eq('<') end end end temple-0.10.3/temple.gemspec000066400000000000000000000017161450717454400157340ustar00rootroot00000000000000require File.dirname(__FILE__) + '/lib/temple/version' require 'date' Gem::Specification.new do |s| s.name = 'temple' s.version = Temple::VERSION s.date = Date.today.to_s s.authors = ['Magnus Holm', 'Daniel Mendler'] s.email = ['judofyr@gmail.com', 'mail@daniel-mendler.de'] s.homepage = 'https://github.com/judofyr/temple' s.summary = 'Template compilation framework in Ruby' s.require_paths = %w(lib) s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec)/}) } s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.license = 'MIT' s.required_ruby_version = '>= 2.5.0' # Tilt is only development dependency because most parts of Temple # can be used without it. s.add_development_dependency('tilt') s.add_development_dependency('rspec') s.add_development_dependency('rake') s.add_development_dependency('erubi') end