express_templates 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/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
|
+
*/
|