pax_global_header 0000666 0000000 0000000 00000000064 14507174544 0014525 g ustar 00root root 0000000 0000000 52 comment=ae5199cc5ae4d1987d78da0dfe9c2b39d29fd191
temple-0.10.3/ 0000775 0000000 0000000 00000000000 14507174544 0013074 5 ustar 00root root 0000000 0000000 temple-0.10.3/.github/ 0000775 0000000 0000000 00000000000 14507174544 0014434 5 ustar 00root root 0000000 0000000 temple-0.10.3/.github/workflows/ 0000775 0000000 0000000 00000000000 14507174544 0016471 5 ustar 00root root 0000000 0000000 temple-0.10.3/.github/workflows/test.yml 0000664 0000000 0000000 00000001220 14507174544 0020166 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000000050 14507174544 0015057 0 ustar 00root root 0000000 0000000 coverage
.yardoc
doc
Gemfile.lock
/pkg/
temple-0.10.3/.yardopts 0000664 0000000 0000000 00000000066 14507174544 0014744 0 ustar 00root root 0000000 0000000 --title Temple
--files EXPRESSIONS.md,CHANGES,LICENSE
temple-0.10.3/CHANGES 0000664 0000000 0000000 00000016510 14507174544 0014072 0 ustar 00root root 0000000 0000000 0.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.md 0000664 0000000 0000000 00000016422 14507174544 0015245 0 ustar 00root root 0000000 0000000 Temple 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:
Name | Generated 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/Gemfile 0000664 0000000 0000000 00000000047 14507174544 0014370 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org/'
gemspec
temple-0.10.3/LICENSE 0000664 0000000 0000000 00000002037 14507174544 0014103 0 ustar 00root root 0000000 0000000 Copyright (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.md 0000664 0000000 0000000 00000021675 14507174544 0014366 0 ustar 00root root 0000000 0000000 Temple
======
[](https://github.com/judofyr/temple/actions/workflows/test.yml) [](https://codeclimate.com/github/judofyr/temple) [](https://rubygems.org/gems/temple) [](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/Rakefile 0000664 0000000 0000000 00000000161 14507174544 0014537 0 ustar 00root root 0000000 0000000 require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
temple-0.10.3/lib/ 0000775 0000000 0000000 00000000000 14507174544 0013642 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple.rb 0000664 0000000 0000000 00000006064 14507174544 0015463 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0015130 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/engine.rb 0000664 0000000 0000000 00000003200 14507174544 0016715 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0015700 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/erb/engine.rb 0000664 0000000 0000000 00000000622 14507174544 0017472 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000002155 14507174544 0017524 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000411 14507174544 0020034 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001231 14507174544 0020050 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000455 14507174544 0017642 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000271 14507174544 0016742 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0016600 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/filters/ambles.rb 0000664 0000000 0000000 00000000716 14507174544 0020374 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001262 14507174544 0021401 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000002260 14507174544 0021634 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000004277 14507174544 0022303 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000004057 14507174544 0022120 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001211 14507174544 0020706 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001026 14507174544 0020405 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000002327 14507174544 0021050 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001101 14507174544 0022314 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000516 14507174544 0021261 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001340 14507174544 0022317 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001466 14507174544 0021764 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000007577 14507174544 0022401 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000476 14507174544 0021121 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000003737 14507174544 0017455 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0017301 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/generators/array.rb 0000664 0000000 0000000 00000000553 14507174544 0020747 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001361 14507174544 0022276 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001027 14507174544 0020376 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001723 14507174544 0023714 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000704 14507174544 0022466 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000003233 14507174544 0017104 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0016074 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/html/attribute_merger.rb 0000664 0000000 0000000 00000002554 14507174544 0021773 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000002015 14507174544 0022161 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001175 14507174544 0022026 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001401 14507174544 0020543 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000011745 14507174544 0017366 0 ustar 00root root 0000000 0000000 # 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, "#{name}>"] 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.rb 0000664 0000000 0000000 00000000722 14507174544 0017707 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000006454 14507174544 0017761 0 ustar 00root root 0000000 0000000 # 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 : ''}#{name}>"]
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.rb 0000664 0000000 0000000 00000000562 14507174544 0017342 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000004177 14507174544 0016243 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0016437 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/mixins/dispatcher.rb 0000664 0000000 0000000 00000011100 14507174544 0021103 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000011521 14507174544 0021073 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000010144 14507174544 0021254 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000004447 14507174544 0020470 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001327 14507174544 0020602 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000232 14507174544 0016746 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000003257 14507174544 0020660 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000443 14507174544 0017454 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14507174544 0017126 5 ustar 00root root 0000000 0000000 temple-0.10.3/lib/temple/templates/rails.rb 0000664 0000000 0000000 00000002125 14507174544 0020565 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000001774 14507174544 0020440 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000004457 14507174544 0016627 0 ustar 00root root 0000000 0000000 # 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.rb 0000664 0000000 0000000 00000000105 14507174544 0017136 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module Temple
VERSION = '0.10.3'
end
temple-0.10.3/spec/ 0000775 0000000 0000000 00000000000 14507174544 0014026 5 ustar 00root root 0000000 0000000 temple-0.10.3/spec/engine_spec.rb 0000664 0000000 0000000 00000012501 14507174544 0016631 0 ustar 00root root 0000000 0000000 # -*- 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.rb 0000664 0000000 0000000 00000001772 14507174544 0016144 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000001437 14507174544 0016657 0 ustar 00root root 0000000 0000000 require '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/ 0000775 0000000 0000000 00000000000 14507174544 0015476 5 ustar 00root root 0000000 0000000 temple-0.10.3/spec/filters/code_merger_spec.rb 0000664 0000000 0000000 00000001434 14507174544 0021312 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000003670 14507174544 0021552 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000004752 14507174544 0022211 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000004373 14507174544 0022031 0 ustar 00root root 0000000 0000000 describe 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.rb 0000664 0000000 0000000 00000002000 14507174544 0020306 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000002521 14507174544 0020754 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000001310 14507174544 0022226 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000002055 14507174544 0022233 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000001743 14507174544 0021672 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000004256 14507174544 0022300 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000015307 14507174544 0017361 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000004504 14507174544 0017016 0 ustar 00root root 0000000 0000000 require '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/ 0000775 0000000 0000000 00000000000 14507174544 0014772 5 ustar 00root root 0000000 0000000 temple-0.10.3/spec/html/attribute_merger_spec.rb 0000664 0000000 0000000 00000005762 14507174544 0021707 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000002742 14507174544 0022100 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000003741 14507174544 0021737 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000007526 14507174544 0017300 0 ustar 00root root 0000000 0000000 require '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.rb 0000664 0000000 0000000 00000003762 14507174544 0017670 0 ustar 00root root 0000000 0000000 require '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 = /