wlang 2.0.0.beta → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -8
- data/Gemfile.lock +4 -8
- data/Manifest.txt +0 -2
- data/README.md +36 -7
- data/lib/wlang.rb +3 -0
- data/lib/wlang/command.rb +3 -11
- data/lib/wlang/compiler.rb +11 -20
- data/lib/wlang/compiler/autospacing.rb +4 -2
- data/lib/wlang/compiler/dialect_enforcer.rb +1 -1
- data/lib/wlang/compiler/to_ruby_code.rb +1 -1
- data/lib/wlang/dialect.rb +163 -23
- data/lib/wlang/html.rb +9 -8
- data/lib/wlang/loader.rb +2 -4
- data/lib/wlang/scope.rb +17 -8
- data/lib/wlang/scope/proc_scope.rb +18 -0
- data/lib/wlang/source.rb +56 -0
- data/lib/wlang/source/front_matter.rb +51 -0
- data/lib/wlang/template.rb +55 -17
- data/lib/wlang/tilt.rb +3 -0
- data/lib/wlang/tilt/wlang_template.rb +43 -0
- data/lib/wlang/version.rb +1 -2
- data/spec/fixtures/templates/{hello.tpl → hello.wlang} +0 -0
- data/spec/fixtures/templates/hello_with_data.wlang +4 -0
- data/spec/fixtures/templates/hello_with_explicit_locals.wlang +6 -0
- data/spec/fixtures/templates/hello_with_partials.wlang +7 -0
- data/spec/integration/examples/{1-basics.txt → 1-html-intro/1-basics.md} +1 -1
- data/spec/integration/examples/{2-imperative.txt → 1-html-intro/2-imperative.md} +1 -1
- data/spec/integration/examples/{3-partials.txt → 1-html-intro/3-partials.md} +0 -0
- data/spec/integration/examples/{4-recursion.txt → 1-html-intro/4-recursion.md} +1 -2
- data/spec/integration/examples/1-html-intro/5-polymorphism.md +17 -0
- data/spec/integration/html/test_caret.rb +15 -2
- data/spec/integration/html/test_question.rb +12 -2
- data/spec/integration/html/test_sharp.rb +5 -1
- data/spec/integration/html/test_star.rb +1 -3
- data/spec/integration/test_examples.rb +2 -1
- data/spec/integration/test_readme.rb +14 -2
- data/spec/integration/tilt/test_wlang_template.rb +13 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/unit/compiler/test_dialect_enforcer.rb +1 -1
- data/spec/unit/compiler/test_to_ruby_proc.rb +15 -0
- data/spec/unit/dialect/test_compile.rb +0 -10
- data/spec/unit/dialect/test_evaluate.rb +36 -28
- data/spec/unit/dialect/test_new.rb +61 -0
- data/spec/unit/dialect/test_tag.rb +36 -0
- data/spec/unit/dialect/test_tag_dispatching_name.rb +22 -0
- data/spec/unit/dialect/test_with_scope.rb +8 -11
- data/spec/unit/scope/test_chain.rb +29 -0
- data/spec/unit/scope/test_coerce.rb +13 -2
- data/spec/unit/scope/test_proc_scope.rb +18 -0
- data/spec/unit/source/front_matter/test_locals.rb +39 -0
- data/spec/unit/source/front_matter/test_template_content.rb +22 -0
- data/spec/unit/source/test_locals.rb +12 -0
- data/spec/unit/source/test_path.rb +27 -0
- data/spec/unit/source/test_template_content.rb +37 -0
- data/spec/unit/source/test_with_front_matter.rb +19 -0
- data/spec/unit/template/test_call.rb +16 -0
- data/spec/unit/template/test_new.rb +20 -0
- data/spec/unit/template/test_path.rb +28 -0
- data/spec/unit/template/test_render.rb +44 -0
- data/spec/unit/template/test_to_ast.rb +12 -0
- data/spec/unit/template/test_to_ruby_code.rb +13 -0
- data/spec/unit/template/test_to_ruby_proc.rb +12 -0
- data/spec/unit/template/test_yaml_front_matter.rb +22 -0
- data/spec/unit/tilt/test_wlang_template.rb +65 -0
- data/wlang.gemspec +3 -5
- data/wlang.noespec +12 -17
- metadata +117 -85
- data/lib/wlang/dialect/dispatching.rb +0 -51
- data/lib/wlang/dialect/evaluation.rb +0 -30
- data/lib/wlang/dialect/tags.rb +0 -50
- data/lib/wlang/mustang.rb +0 -90
- data/spec/integration/test_mustang.rb +0 -120
- data/spec/unit/dialect/test_dispatching.rb +0 -19
- data/spec/unit/dialect/test_tags.rb +0 -32
data/lib/wlang/html.rb
CHANGED
@@ -5,8 +5,8 @@ module WLang
|
|
5
5
|
|
6
6
|
module Helpers
|
7
7
|
|
8
|
-
def value_of(fn)
|
9
|
-
evaluate(render(fn).to_s.strip)
|
8
|
+
def value_of(fn, *defaults)
|
9
|
+
evaluate(render(fn).to_s.strip, *defaults)
|
10
10
|
end
|
11
11
|
private :value_of
|
12
12
|
|
@@ -56,13 +56,14 @@ module WLang
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def question(buf, fn_if, fn_then, fn_else)
|
59
|
-
val
|
60
|
-
|
59
|
+
val = value_of(fn_if)
|
60
|
+
val = val.call if Proc===val
|
61
|
+
block = val ? fn_then : fn_else
|
62
|
+
render(block, nil, buf) if block
|
61
63
|
end
|
62
64
|
|
63
65
|
def caret(buf, fn_if, fn_then, fn_else)
|
64
|
-
|
65
|
-
render(val, nil, buf) if val
|
66
|
+
question(buf, fn_if, fn_else, fn_then)
|
66
67
|
end
|
67
68
|
|
68
69
|
def star(buf, coll_fn, elm_fn, between_fn)
|
@@ -75,12 +76,12 @@ module WLang
|
|
75
76
|
|
76
77
|
def greater(buf, fn)
|
77
78
|
val = value_of(fn)
|
78
|
-
val = Html.compile(val)
|
79
|
+
val = Html.compile(val) if String === val
|
79
80
|
render(val, nil, buf)
|
80
81
|
end
|
81
82
|
|
82
83
|
def sharp(buf, who_fn, then_fn)
|
83
|
-
val = value_of(who_fn)
|
84
|
+
val = value_of(who_fn, nil)
|
84
85
|
if val and not(val.respond_to?(:empty?) and val.empty?)
|
85
86
|
render(then_fn, val, buf)
|
86
87
|
end
|
data/lib/wlang/loader.rb
CHANGED
data/lib/wlang/scope.rb
CHANGED
@@ -12,18 +12,26 @@ module WLang
|
|
12
12
|
@root ||= RootScope.new
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.coerce(arg, parent =
|
15
|
+
def self.coerce(arg, parent = nil)
|
16
|
+
return arg if Scope===arg && parent.nil?
|
17
|
+
parent ||= root
|
16
18
|
clazz = case arg
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
when Binding
|
20
|
+
BindingScope
|
21
|
+
when Scope
|
22
|
+
ProxyScope
|
23
|
+
when Proc
|
24
|
+
ProcScope
|
25
|
+
else
|
26
|
+
ObjectScope
|
27
|
+
end
|
24
28
|
clazz.new(arg, parent)
|
25
29
|
end
|
26
30
|
|
31
|
+
def self.chain(scopes)
|
32
|
+
scopes.compact.inject(nil){|parent,child| Scope.coerce(child, parent)} || root
|
33
|
+
end
|
34
|
+
|
27
35
|
def push(x)
|
28
36
|
Scope.coerce(x, self)
|
29
37
|
end
|
@@ -55,3 +63,4 @@ require 'wlang/scope/root_scope'
|
|
55
63
|
require 'wlang/scope/proxy_scope'
|
56
64
|
require 'wlang/scope/object_scope'
|
57
65
|
require 'wlang/scope/binding_scope'
|
66
|
+
require 'wlang/scope/proc_scope'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module WLang
|
2
|
+
class Scope
|
3
|
+
class ProcScope < Scope
|
4
|
+
|
5
|
+
def fetch(key, &blk)
|
6
|
+
Scope.coerce(subject.call).fetch(key) do
|
7
|
+
parent.fetch(key, &blk)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
subject.inspect
|
13
|
+
end
|
14
|
+
alias :to_s :inspect
|
15
|
+
|
16
|
+
end # class ProcScope
|
17
|
+
end # class Scope
|
18
|
+
end # module WLang
|
data/lib/wlang/source.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module WLang
|
2
|
+
class Source
|
3
|
+
|
4
|
+
attr_reader :subject
|
5
|
+
attr_reader :template
|
6
|
+
|
7
|
+
def initialize(subject, template = nil)
|
8
|
+
@subject = subject
|
9
|
+
@template = template
|
10
|
+
@locals = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def path
|
14
|
+
find_on_subject(:path, :to_path)
|
15
|
+
end
|
16
|
+
alias :to_path :path
|
17
|
+
|
18
|
+
def locals
|
19
|
+
@locals
|
20
|
+
end
|
21
|
+
|
22
|
+
def raw_content
|
23
|
+
find_on_subject(:read, :to_str){
|
24
|
+
raise ArgumentError, "Invalid template source `#{subject}`"
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def template_content
|
29
|
+
raw_content
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_str
|
33
|
+
template_content
|
34
|
+
end
|
35
|
+
alias :to_s :to_str
|
36
|
+
|
37
|
+
def with_front_matter(enabled = true)
|
38
|
+
enabled ? FrontMatter.new(self, template) : self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def find_on_subject(*methods)
|
44
|
+
s = subject
|
45
|
+
if meth = methods.find{|m| s.respond_to?(m) }
|
46
|
+
subject.send(meth)
|
47
|
+
elsif block_given?
|
48
|
+
yield
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end # class Source
|
55
|
+
end # module WLang
|
56
|
+
require 'wlang/source/front_matter'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module WLang
|
2
|
+
class Source
|
3
|
+
class FrontMatter < Source
|
4
|
+
|
5
|
+
def initialize(*args, &block)
|
6
|
+
super
|
7
|
+
compile
|
8
|
+
end
|
9
|
+
|
10
|
+
def locals
|
11
|
+
@locals
|
12
|
+
end
|
13
|
+
|
14
|
+
def template_content
|
15
|
+
@template_content
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def compiler
|
21
|
+
template.compiler
|
22
|
+
end
|
23
|
+
|
24
|
+
def compile
|
25
|
+
raw = raw_content
|
26
|
+
if raw =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
27
|
+
@locals, @template_content = {}, $'
|
28
|
+
|
29
|
+
require 'yaml'
|
30
|
+
yaml = YAML::load($1)
|
31
|
+
|
32
|
+
# append explicit locals
|
33
|
+
@locals.merge!(yaml.delete("locals") || {})
|
34
|
+
|
35
|
+
# compile explicit partials
|
36
|
+
partials = yaml.delete("partials") || {}
|
37
|
+
partials.each_pair do |k,tpl|
|
38
|
+
@locals[k] = compiler.to_ruby_proc(tpl)
|
39
|
+
end
|
40
|
+
|
41
|
+
# append remaining data
|
42
|
+
@locals.merge!(yaml) unless yaml.empty?
|
43
|
+
else
|
44
|
+
@locals = {}
|
45
|
+
@template_content = raw
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/wlang/template.rb
CHANGED
@@ -1,25 +1,63 @@
|
|
1
1
|
module WLang
|
2
2
|
class Template
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
4
|
+
# Compilation options for the template, as initially passed to `new`
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
# The dialect class to use
|
8
|
+
attr_reader :dialect
|
9
|
+
|
10
|
+
# The underlying template compiler
|
11
|
+
attr_reader :compiler
|
12
|
+
|
13
|
+
# Creates a template instance
|
14
|
+
def initialize(source, options = {})
|
15
|
+
@options = options
|
16
|
+
@dialect = @options.delete(:dialect) || WLang::Html
|
17
|
+
@dialect_instance = @dialect.new(options, self)
|
18
|
+
@compiler = Compiler.new(dialect_instance)
|
19
|
+
@source = Source.new(source, self).with_front_matter(yaml_front_matter?)
|
20
|
+
@compiled = to_ruby_proc
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
@options[:path] || @source.path
|
25
|
+
end
|
26
|
+
|
27
|
+
def locals
|
28
|
+
@source.locals
|
29
|
+
end
|
30
|
+
|
31
|
+
def template_content
|
32
|
+
@source.template_content
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_ruby_proc
|
36
|
+
compiler.to_ruby_proc(template_content)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_ruby_code
|
40
|
+
compiler.to_ruby_code(template_content)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_ast
|
44
|
+
compiler.to_ast(template_content)
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(locs = {}, buffer = '')
|
48
|
+
scope = WLang::Scope.chain([locals, locs])
|
49
|
+
dialect_instance.dup.render compiled, scope, buffer
|
21
50
|
end
|
22
51
|
alias :render :call
|
23
52
|
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :source, :compiled, :dialect_instance
|
56
|
+
|
57
|
+
def yaml_front_matter?
|
58
|
+
opt = options[:yaml_front_matter]
|
59
|
+
opt.nil? or opt
|
60
|
+
end
|
61
|
+
|
24
62
|
end # class Template
|
25
63
|
end # module WLang
|
data/lib/wlang/tilt.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Tilt
|
2
|
+
class WLangTemplate < ::Tilt::Template
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def engine_initialized?
|
7
|
+
defined? ::WLang
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_options(options)
|
11
|
+
Class.new(WLangTemplate).tap{|c| c.default_options = options }
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_options=(options)
|
15
|
+
@default_options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_options
|
19
|
+
(superclass.default_options rescue {}).merge(@default_options || {})
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize_engine
|
25
|
+
require_template_library('wlang')
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def prepare
|
31
|
+
opts = self.class.default_options.merge(self.options)
|
32
|
+
opts.merge!(:path => file) if file
|
33
|
+
@engine = WLang::Template.new(data, opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
def evaluate(scope, locals, &block)
|
37
|
+
locals[:yield] = block if block
|
38
|
+
@engine.render WLang::Scope.chain([scope, locals])
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
register WLangTemplate, 'wlang'
|
43
|
+
end
|
data/lib/wlang/version.rb
CHANGED
File without changes
|
@@ -60,6 +60,6 @@ The last useful tag allows you to simply disable wlang rendering in specific tem
|
|
60
60
|
|
61
61
|
For instance, the following template renders the block text unchanged, even if it contains wlang tags:
|
62
62
|
|
63
|
-
Hello ${struct.name}, how are you?
|
63
|
+
Hello ${struct.name}, how are you?
|
64
64
|
|
65
65
|
This is especially useful for writing blog posts explaining wlang, using wlang itself. Therefore, it is a feature mostly needed by me ;-)
|
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Now that we know how to use partials, one might ask how to render a menu in a recursive way. For this, we only need another tag for explicitely manipulating the scope.
|
2
2
|
|
3
|
-
* sharp (#) renders its second block, in the scope of the value evaluated in first block
|
3
|
+
* sharp (#) renders its second block, in the scope of the value evaluated in first block, provided the later is not nil, undefined or empty
|
4
4
|
|
5
5
|
For the example above, our menu rendering is initiated as follows:
|
6
6
|
<ul>
|
@@ -13,4 +13,3 @@ For the example above, our menu rendering is initiated as follows:
|
|
13
13
|
</ul>
|
14
14
|
</ul>
|
15
15
|
|
16
|
-
Note that it is important to keep empty menus in the source data to avoid infinite recursions due the parent scope that already binds a value under `menu`.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Sometimes, you want to apply polymorphism to your templates; this it is really easy to do in wlang thanks to its high-order constructions.
|
2
|
+
|
3
|
+
For example, assume that you iterate some data and want render a partial in a polymorphism way, that is, selecting the partial according to some type, here the shape of the current iterated component. This is really simple:
|
4
|
+
|
5
|
+
<ul>
|
6
|
+
<li> Hello [] of width 10cm </li>
|
7
|
+
<li> Hello () of diameter 25cm </li>
|
8
|
+
</ul>
|
9
|
+
|
10
|
+
Which gives the following result:
|
11
|
+
|
12
|
+
<ul>
|
13
|
+
<li> Hello [] of width 10cm </li>
|
14
|
+
<li> Hello () of diameter 25cm <li>
|
15
|
+
</ul>
|
16
|
+
|
17
|
+
Great, isn't?
|
@@ -3,11 +3,18 @@ require 'wlang/html'
|
|
3
3
|
module WLang
|
4
4
|
describe Html, "^{...}" do
|
5
5
|
|
6
|
+
debug = Module.new{ def inspect; "lambda{ #{call.inspect} }"; end }
|
7
|
+
|
6
8
|
def render(tpl, scope = {})
|
7
9
|
Html.render(tpl, scope, "")
|
8
10
|
end
|
9
11
|
|
10
|
-
[
|
12
|
+
[
|
13
|
+
false,
|
14
|
+
nil,
|
15
|
+
lambda{ nil }.extend(debug),
|
16
|
+
lambda{ false }.extend(debug)
|
17
|
+
].each do |test|
|
11
18
|
context "when #{test.inspect}" do
|
12
19
|
it 'renders the then block' do
|
13
20
|
render("^{test}{hello}", binding).should eq("hello")
|
@@ -18,7 +25,13 @@ module WLang
|
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
|
-
[
|
28
|
+
[
|
29
|
+
true,
|
30
|
+
"Something",
|
31
|
+
[],
|
32
|
+
lambda{ true }.extend(debug),
|
33
|
+
lambda{ "something" }.extend(debug)
|
34
|
+
].each do |test|
|
22
35
|
context "when #{test.inspect}" do
|
23
36
|
it 'do not render the then block' do
|
24
37
|
render("^{test}{hello}", binding).should eq("")
|