express_templates 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +53 -0
- data/Rakefile +32 -0
- data/lib/express_templates/component.rb +92 -0
- data/lib/express_templates/components/html_tag.rb +36 -0
- data/lib/express_templates/components/wrapper.rb +51 -0
- data/lib/express_templates/components/yielder.rb +21 -0
- data/lib/express_templates/components.rb +8 -0
- data/lib/express_templates/expander.rb +116 -0
- data/lib/express_templates/html5_emitter.rb +45 -0
- data/lib/express_templates/renderer.rb +16 -0
- data/lib/express_templates/template/handler.rb +15 -0
- data/lib/express_templates/version.rb +3 -0
- data/lib/express_templates.rb +12 -0
- data/lib/tasks/gara_tasks.rake +4 -0
- data/test/component_test.rb +77 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/controllers/hello_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/hello/show.html.et +5 -0
- data/test/dummy/app/views/layouts/application.html.et +15 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +82 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/assets.rb +8 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/log/development.log +6478 -0
- data/test/dummy/log/test.log +20859 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/controllers/hello_controller_test.rb +14 -0
- data/test/dummy/test/helpers/hello_helper_test.rb +4 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/219e4ef52e71a2dd3142d36271626492 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/29331726baea36717bf28565c0be1881 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/4584795e7f513db14ffca10dab44759e +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/72a227156d935c44e2aad8718b3952fb +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cfea650552ed221f83ea2c8fc3198c53 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/e27275f4020fd1fc40de79a1e5db4c96 +0 -0
- data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/expander_stack_test.rb +41 -0
- data/test/expander_test.rb +56 -0
- data/test/fixtures/a_big_page.html +595 -0
- data/test/gara_test.rb +19 -0
- data/test/handler_test.rb +122 -0
- data/test/performance_test.rb +93 -0
- data/test/test_helper.rb +17 -0
- data/test/wrapper_test.rb +23 -0
- metadata +271 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f322ad6cb1fb8bf620577f100a9a70274c6172d1
|
4
|
+
data.tar.gz: ea903155b79430c8e5ec5f557ea80ee7461d9fdd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b71ed00ff0f5a2424a94b84da6c4104b988942b9fa37d7e36ec7c892118ccc33be46893c862be0e5ffec0b2ded2c5ed844d713e7650b3f5d0c82f0f78d0e726
|
7
|
+
data.tar.gz: d026690d15f79913cd90f112f437990449af06c795e718cf512a2d1a34d037ffa43e8e8161b20f9a7db4ebe9cf4aee9ea34421f628449d5f51b2584f98460446
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 ÆLOGICA | Build it better here.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# ExpressTemplates
|
2
|
+
|
3
|
+
Provides a macro-ish DSL for use in constructing html templates in plain Ruby as an alternative to Erb or HAML.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
Add this to your gemfile:
|
8
|
+
|
9
|
+
gem 'express_templates'
|
10
|
+
|
11
|
+
Rename your application.html.erb to application.html.et.
|
12
|
+
|
13
|
+
Change your template to look like this.
|
14
|
+
|
15
|
+
html(lang: "en") {
|
16
|
+
head {
|
17
|
+
meta charset: 'utf-8'
|
18
|
+
meta name: 'viewport', content: "width=device-width, initial-scale=1.0"
|
19
|
+
title {
|
20
|
+
content_for(:title)
|
21
|
+
}
|
22
|
+
stylesheet_link_tag "application", media: 'all', 'data-turbolinks-track' => true
|
23
|
+
csrf_meta_tags
|
24
|
+
}
|
25
|
+
body {
|
26
|
+
yield
|
27
|
+
javascript_include_tag "application"
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
Everything should work as you would expect.
|
32
|
+
|
33
|
+
Set your editor syntax for .et files to Ruby.
|
34
|
+
|
35
|
+
### How It Works
|
36
|
+
|
37
|
+
ExpressTemplates works via a good-enough stand in for a true macro system in Ruby which would make this type of thing considerably easier.
|
38
|
+
|
39
|
+
Basically, we use these "macros" and Ruby's block structure to build up a tree of components corresponding to the HTML structure of a document fragment. Each HTML5 tag is a component available in the form of a macro. Unrecognized identifiers are wrapped for later evaluation, presumably in a ViewContext.
|
40
|
+
|
41
|
+
yield and local variables which we may expect to be available in a view context are also wrapped for evaluation later.
|
42
|
+
|
43
|
+
## Background
|
44
|
+
|
45
|
+
Sufficent motivation for this gem can be explained thusly: The bondage of HAML is unnecessary. The clutter of Erb is unsightly.
|
46
|
+
|
47
|
+
I generall prefer "one syntax per file" for reasons of cognative load and maintainability.
|
48
|
+
|
49
|
+
Ultimately my objective with ExpressTemplates is to get away from writing HTML directly and to use this as a substrate for building pages out of higher-level, reusable components which include not only DOM elements but also behaviors.
|
50
|
+
|
51
|
+
ExpressTemplates are part of the AppExpress platform at [appexpress.io](http://appexpress.io).
|
52
|
+
|
53
|
+
This project rocks and uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ExpressTemplates'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
25
|
+
t.libs << 'lib'
|
26
|
+
t.libs << 'test'
|
27
|
+
t.pattern = 'test/**/*_test.rb'
|
28
|
+
t.verbose = false
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
class Component
|
3
|
+
|
4
|
+
attr_accessor :children
|
5
|
+
|
6
|
+
INDENT = ' '
|
7
|
+
|
8
|
+
def initialize(*children_or_options)
|
9
|
+
@children = []
|
10
|
+
@options = {}.with_indifferent_access
|
11
|
+
_process(*children_or_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.macro_name
|
15
|
+
@macro_name ||= to_s.split('::').last.underscore
|
16
|
+
end
|
17
|
+
|
18
|
+
def macro_name ; self.class.macro_name end
|
19
|
+
|
20
|
+
def html_options
|
21
|
+
@options.each_pair.map do |name, value|
|
22
|
+
%Q(#{name}=\\"#{value}\\")
|
23
|
+
end.join(" ")
|
24
|
+
end
|
25
|
+
|
26
|
+
def start_tag
|
27
|
+
"<#{macro_name}#{html_options.empty? ? '' : ' '+html_options}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def close_tag
|
31
|
+
"</#{macro_name}>"
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_css_class(css_class)
|
35
|
+
@options['class'] ||= ''
|
36
|
+
@options['class'] = (@options['class'].split + [css_class]).join(" ")
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(name, *args)
|
40
|
+
add_css_class(name)
|
41
|
+
_process(*args) unless args.empty?
|
42
|
+
return self
|
43
|
+
end
|
44
|
+
|
45
|
+
def compile
|
46
|
+
ruby_fragments = @children.map do |child|
|
47
|
+
if child.respond_to?(:compile)
|
48
|
+
child.compile
|
49
|
+
else
|
50
|
+
%Q("#{child}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
ruby_fragments.unshift %Q("#{start_tag}")
|
54
|
+
ruby_fragments.push %Q("#{close_tag}")
|
55
|
+
ruby_fragments.reject {|frag| frag.empty? }.join("+")
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_template(depth = 0)
|
59
|
+
template_fragments = @children.map do |child|
|
60
|
+
if child.kind_of?(ExpressTemplates::Component)
|
61
|
+
child.to_template(depth+1)
|
62
|
+
else
|
63
|
+
child
|
64
|
+
end
|
65
|
+
end
|
66
|
+
indent = INDENT*(depth+1)
|
67
|
+
macro_name + _blockify(template_fragments.join("\n#{indent}"), depth)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def _indent(code)
|
72
|
+
code.split("\n").map {|line| INDENT + line }.join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def _blockify(code, depth)
|
76
|
+
indent = INDENT*depth
|
77
|
+
code.empty? ? code : " {\n#{_indent(code)}\n}\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
def _process(*children_or_options)
|
81
|
+
children_or_options.each do |child_or_option|
|
82
|
+
if child_or_option.kind_of?(Hash)
|
83
|
+
@options.merge!(child_or_option)
|
84
|
+
else
|
85
|
+
@children << child_or_option
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
class HtmlTag < ExpressTemplates::Component
|
4
|
+
TAGS = [ :a, :abbr, :address, :area, :article, :aside, :audio,
|
5
|
+
:b, :base, :bdi, :bdo, :blockquote, :body, :br, :button,
|
6
|
+
:canvas, :caption, :cite, :code, :col, :colgroup,
|
7
|
+
:data, :datalist, :dd, :del, :details, :dfn, :div, :dl, :dt,
|
8
|
+
:em, :embed,
|
9
|
+
:fieldset, :figcaption, :figure, :footer, :form,
|
10
|
+
:h1, :h2, :h3, :h4, :h5, :h6, :head, :header, :hr, :html,
|
11
|
+
:i, :iframe, :img, :input, :ins,
|
12
|
+
:kbd, :keygen,
|
13
|
+
:label, :legend, :li, :link,
|
14
|
+
:main, :map, :mark, :math, :menu, :menuitem, :meta, :meter,
|
15
|
+
:nav, :noscript,
|
16
|
+
:object, :ol, :optgroup, :option, :output,
|
17
|
+
:p, :param, :pre, :progress,
|
18
|
+
:q,
|
19
|
+
:rp, :rt, :ruby,
|
20
|
+
:s, :samp, :script, :section, :select, :small, :source,
|
21
|
+
:span, :strong, :style, :sub, :sup, :summary, :svg,
|
22
|
+
:table, :tbody, :td, :textarea, :tfoot, :th, :thead, :time, :title,
|
23
|
+
:tr, :track,
|
24
|
+
:u, :ul,
|
25
|
+
:var, :video,
|
26
|
+
:wbr]
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
HtmlTag::TAGS.each do |tag|
|
31
|
+
klass = tag.to_s.titleize
|
32
|
+
ExpressTemplates::Components.module_eval "class #{klass} < HtmlTag ; end"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
# wrap locals and helpers for evaluation during render
|
4
|
+
class Wrapper
|
5
|
+
|
6
|
+
attr_accessor :name, :args
|
7
|
+
|
8
|
+
def initialize(name, *args)
|
9
|
+
@name = name
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def compile
|
14
|
+
# insure nils do not blow up view
|
15
|
+
%Q("\#\{#{_compile}\}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_template
|
19
|
+
_compile
|
20
|
+
end
|
21
|
+
|
22
|
+
def children
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def _compile
|
28
|
+
if @args.empty?
|
29
|
+
return name
|
30
|
+
else
|
31
|
+
args_string = args.slice(0..-2).map(&:inspect).join(', ')
|
32
|
+
last_arg = ''
|
33
|
+
if args.last.is_a?(Hash) # expand a hash
|
34
|
+
unless args.last.empty?
|
35
|
+
# This approach has limitations - will only work on structures of
|
36
|
+
# immediate values
|
37
|
+
last_arg = args.last.inspect.match(/^\{(.*)\}$/)[1]
|
38
|
+
last_arg.gsub!(/:(\w+)=>/, '\1: ') # use ruby 2 hash syntax
|
39
|
+
else
|
40
|
+
last_arg = "{}" # empty hash
|
41
|
+
end
|
42
|
+
else
|
43
|
+
last_arg = args.last.inspect
|
44
|
+
end
|
45
|
+
args_string << (args_string.empty? ? last_arg : ", #{last_arg}")
|
46
|
+
return "#{name}(#{args_string})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Components
|
3
|
+
class Yielder
|
4
|
+
def initialize(*args)
|
5
|
+
@arg = args.first
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile
|
9
|
+
if @arg
|
10
|
+
"yield(#{@arg})"
|
11
|
+
else
|
12
|
+
"yield"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_template
|
17
|
+
compile
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'express_templates/components'
|
2
|
+
module ExpressTemplates
|
3
|
+
class Expander
|
4
|
+
|
5
|
+
cattr_accessor :module_search_space
|
6
|
+
|
7
|
+
attr_accessor :stack
|
8
|
+
|
9
|
+
def self.expand(template, source)
|
10
|
+
expanded = new(template).expand(source)
|
11
|
+
compiled = expanded.map(&:compile)
|
12
|
+
return compiled.join("+")
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(template)
|
16
|
+
@template = template
|
17
|
+
@stack = Stack.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def expand(source=nil, &block)
|
21
|
+
if source
|
22
|
+
modified = source.gsub(/(\W)(yield)(\.*)?/, '\1 (stack << ExpressTemplates::Components::Yielder.new(\3))')
|
23
|
+
modified.gsub!(/(\W)(@\w+)(\W)?/, '\1 (stack << ExpressTemplates::Components::Wrapper.new("\2") )\3')
|
24
|
+
instance_eval(modified, @template.inspect)
|
25
|
+
stack.current
|
26
|
+
else
|
27
|
+
instance_eval &block
|
28
|
+
stack.current
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# define a "macro" method for a component
|
33
|
+
# these methods accept args which are passed to the
|
34
|
+
# initializer for the component
|
35
|
+
# blocks supplied are evaluated and any returned objects are
|
36
|
+
# added as children to the component
|
37
|
+
def self.register_macros_for(*components)
|
38
|
+
components.each do |component|
|
39
|
+
define_method(component.macro_name.to_sym) do |*args, &block|
|
40
|
+
stack << if block
|
41
|
+
begin
|
42
|
+
stack.descend!
|
43
|
+
block.call
|
44
|
+
# anything stored on stack.current or on stack.next is added as a child
|
45
|
+
# this is a bit problematic in the case where we would have
|
46
|
+
# blocks and helpers or locals mixed
|
47
|
+
component.new(*(args.push(*(stack.current))))
|
48
|
+
ensure
|
49
|
+
stack.ascend!
|
50
|
+
end
|
51
|
+
else
|
52
|
+
component.new(*(args))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
@module_search_space = [ExpressTemplates::Components]
|
60
|
+
|
61
|
+
@module_search_space.each do |mod|
|
62
|
+
register_macros_for(*
|
63
|
+
mod.constants.map { |const| [mod.to_s, const.to_s].join("::").constantize }.
|
64
|
+
select { |klass| klass.ancestors.include? (ExpressTemplates::Component) }
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(name, *args)
|
69
|
+
stack.current << ExpressTemplates::Components::Wrapper.new(name.to_s, *args)
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
class Stack
|
74
|
+
def initialize
|
75
|
+
@stack = [[]]
|
76
|
+
@frame = 0
|
77
|
+
end
|
78
|
+
|
79
|
+
def all
|
80
|
+
@stack
|
81
|
+
end
|
82
|
+
|
83
|
+
def dump
|
84
|
+
puts "Current frame: #{@frame}"
|
85
|
+
puts all.map(&:inspect).join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def <<(child)
|
89
|
+
current << child
|
90
|
+
child
|
91
|
+
end
|
92
|
+
|
93
|
+
def current
|
94
|
+
@stack[@frame]
|
95
|
+
end
|
96
|
+
|
97
|
+
def next
|
98
|
+
@stack[@frame+1] ||= []
|
99
|
+
end
|
100
|
+
|
101
|
+
def descend!
|
102
|
+
@frame += 1
|
103
|
+
@stack[@frame] ||= []
|
104
|
+
@stack[@frame].clear
|
105
|
+
@frame
|
106
|
+
end
|
107
|
+
|
108
|
+
def ascend!
|
109
|
+
raise "Cannot ascend" if @frame <= 0
|
110
|
+
current.clear ;
|
111
|
+
self.next.clear
|
112
|
+
@frame -= 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
|
3
|
+
HTML5_TAGS = [ :a, :abbr, :address, :area, :article, :aside, :audio,
|
4
|
+
:b, :base, :bdi, :bdo, :blockquote, :body, :br, :button,
|
5
|
+
:canvas, :caption, :cite, :code, :col, :colgroup,
|
6
|
+
:data, :datalist, :dd, :del, :details, :dfn, :div, :dl, :dt,
|
7
|
+
:em, :embed,
|
8
|
+
:fieldset, :figcaption, :figure, :footer, :form,
|
9
|
+
:h1, :h2, :h3, :h4, :h5, :h6, :head, :header, :hr, :html,
|
10
|
+
:i, :iframe, :img, :input, :ins,
|
11
|
+
:kbd, :keygen,
|
12
|
+
:label, :legend, :li, :link,
|
13
|
+
:main, :map, :mark, :math, :menu, :menuitem, :meta, :meter,
|
14
|
+
:nav, :noscript,
|
15
|
+
:object, :ol, :optgroup, :option, :output,
|
16
|
+
:p, :param, :pre, :progress,
|
17
|
+
:q,
|
18
|
+
:rp, :rt, :ruby,
|
19
|
+
:s, :samp, :script, :section, :select, :small, :source,
|
20
|
+
:span, :strong, :style, :sub, :sup, :summary, :svg,
|
21
|
+
:table, :tbody, :td, :textarea, :tfoot, :th, :thead, :time, :title,
|
22
|
+
:tr, :track,
|
23
|
+
:u, :ul,
|
24
|
+
:var, :video,
|
25
|
+
:wbr]
|
26
|
+
|
27
|
+
module Html5Emitter
|
28
|
+
|
29
|
+
HTML5_TAGS.each do |tag|
|
30
|
+
define_method(tag) do |*args, &block|
|
31
|
+
if block
|
32
|
+
content_tag(tag, capture(&block), *args)
|
33
|
+
else
|
34
|
+
# adjust args because content_tag expects options in the
|
35
|
+
# second position when have nil content
|
36
|
+
if args.first.is_a?(Hash)
|
37
|
+
content_tag(tag, *(args.unshift(nil)))
|
38
|
+
else
|
39
|
+
content_tag(tag, *args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Renderer
|
3
|
+
# render accepts source or block, expands to macros
|
4
|
+
# compiles the resulting macros
|
5
|
+
# and then evaluates the resulting string of ruby in the context provided
|
6
|
+
def render context=nil, template_source=nil, &block
|
7
|
+
expander = ExpressTemplates::Expander.new(nil)
|
8
|
+
expanded_template = if block
|
9
|
+
(expander.expand(&block).map(&:compile).join(';'))
|
10
|
+
else
|
11
|
+
expander.expand(template_source).map(&:compile).join(";")
|
12
|
+
end
|
13
|
+
context.instance_eval expanded_template
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
module Template
|
3
|
+
class Handler
|
4
|
+
def self.call(template)
|
5
|
+
new.call(template)
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(template)
|
9
|
+
# returns a string to be eval'd
|
10
|
+
ExpressTemplates::Expander.expand(template, template.source)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ExpressTemplates
|
2
|
+
require 'express_templates/template/handler'
|
3
|
+
require 'express_templates/html5_emitter'
|
4
|
+
require 'express_templates/renderer'
|
5
|
+
require 'express_templates/expander'
|
6
|
+
require 'express_templates/components'
|
7
|
+
extend Renderer
|
8
|
+
if defined?(Rails)
|
9
|
+
::ActionView::Template.register_template_handler :et, ExpressTemplates::Template::Handler
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ComponentTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
class Bare < ExpressTemplates::Component ; end
|
6
|
+
class Sub < ExpressTemplates::Component ; end
|
7
|
+
|
8
|
+
def bare_component(*args)
|
9
|
+
Bare.new(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def sub_component(*args)
|
13
|
+
Sub.new(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
test "#macro_name returns the name of the class" do
|
18
|
+
assert_equal 'bare', bare_component.macro_name
|
19
|
+
end
|
20
|
+
|
21
|
+
test "#compile returns a string" do
|
22
|
+
assert_kind_of String, bare_component.compile
|
23
|
+
end
|
24
|
+
|
25
|
+
test "has no children" do
|
26
|
+
assert_empty bare_component.children
|
27
|
+
end
|
28
|
+
|
29
|
+
def bare_with_2_children
|
30
|
+
component = bare_component "child1", "child2"
|
31
|
+
end
|
32
|
+
|
33
|
+
test "can be created with children" do
|
34
|
+
assert_equal 2, bare_with_2_children.children.size
|
35
|
+
assert_equal "child2", bare_with_2_children.children.last
|
36
|
+
end
|
37
|
+
|
38
|
+
test "#compile on bare_with_2_children yields '\"<bare>\"+\"child1\"+\"child2\"+\"</bare>\"'" do
|
39
|
+
assert_equal '"<bare>"+"child1"+"child2"+"</bare>"', bare_with_2_children.compile
|
40
|
+
end
|
41
|
+
|
42
|
+
test "#start_tag is my macro_name as an xml start tag" do
|
43
|
+
assert_equal "<#{bare_component.macro_name}>", bare_component.start_tag
|
44
|
+
end
|
45
|
+
|
46
|
+
test "#close_tag is my macro_name as an xml close tag" do
|
47
|
+
assert_equal "</#{bare_component.macro_name}>", bare_component.close_tag
|
48
|
+
end
|
49
|
+
|
50
|
+
def component_with_subcomponent
|
51
|
+
bare_component sub_component
|
52
|
+
end
|
53
|
+
|
54
|
+
test "#compile on component_with_subcomponent returns a string which when eval'd looks like '<bare><sub></sub></bare>'" do
|
55
|
+
assert_equal '<bare><sub></sub></bare>', eval(component_with_subcomponent.compile)
|
56
|
+
end
|
57
|
+
|
58
|
+
test "#to_template on bare_component returns 'bare'" do
|
59
|
+
assert_equal 'bare', bare_component.to_template
|
60
|
+
end
|
61
|
+
|
62
|
+
test "#to_template on component_with_subcomponent returns 'bare {\n sub\n}\n'" do
|
63
|
+
assert_equal "bare {\n sub\n}\n", component_with_subcomponent.to_template
|
64
|
+
end
|
65
|
+
|
66
|
+
test "#to_template on nested components indents properly'" do
|
67
|
+
expected = %Q(bare {
|
68
|
+
sub {
|
69
|
+
sub
|
70
|
+
}
|
71
|
+
}
|
72
|
+
)
|
73
|
+
assert_equal expected, Bare.new(Sub.new(Sub.new)).to_template
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|