wlang 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/CHANGELOG.md +27 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +16 -0
  4. data/README.md +32 -55
  5. data/lib/wlang.rb +8 -0
  6. data/lib/wlang/compiler/grammar.citrus +19 -16
  7. data/lib/wlang/dialect.rb +18 -14
  8. data/lib/wlang/html.rb +7 -11
  9. data/lib/wlang/scope.rb +68 -28
  10. data/lib/wlang/scope/binding_scope.rb +3 -3
  11. data/lib/wlang/scope/null_scope.rb +34 -0
  12. data/lib/wlang/scope/object_scope.rb +18 -7
  13. data/lib/wlang/scope/proc_scope.rb +4 -4
  14. data/lib/wlang/scope/sinatra_scope.rb +44 -0
  15. data/lib/wlang/template.rb +10 -2
  16. data/lib/wlang/tilt/wlang_template.rb +1 -1
  17. data/lib/wlang/version.rb +2 -2
  18. data/spec/assumptions/test_core.rb +8 -0
  19. data/spec/fixtures/templates/hello_from_sinatra.wlang +1 -0
  20. data/spec/integration/html/test_greater.rb +12 -2
  21. data/spec/integration/sinatra/test_partials.rb +35 -0
  22. data/spec/integration/test_examples.rb +2 -2
  23. data/spec/spec_helper.rb +7 -0
  24. data/spec/unit/compiler/test_grammar.rb +1 -1
  25. data/spec/unit/compiler/test_parser.rb +17 -0
  26. data/spec/unit/dialect/test_context.rb +28 -0
  27. data/spec/unit/dialect/test_evaluate.rb +10 -0
  28. data/spec/unit/dialect/test_render.rb +4 -0
  29. data/spec/unit/scope/sinatra_scope/test_fetch.rb +28 -0
  30. data/spec/unit/scope/test_binding_scope.rb +1 -1
  31. data/spec/unit/scope/test_chain.rb +5 -5
  32. data/spec/unit/scope/test_coerce.rb +7 -3
  33. data/spec/unit/scope/test_null_scope.rb +35 -0
  34. data/spec/unit/scope/test_object_scope.rb +1 -1
  35. data/spec/unit/scope/test_proc_scope.rb +1 -1
  36. data/spec/unit/scope/test_push.rb +70 -0
  37. data/spec/unit/template/test_call_args_conventions.rb +101 -0
  38. data/spec/unit/test_assumptions.rb +12 -0
  39. data/spec/unit/test_scope.rb +26 -16
  40. data/wlang.gemspec +2 -0
  41. data/wlang.noespec +3 -1
  42. metadata +143 -33
  43. data/lib/wlang/scope/proxy_scope.rb +0 -18
  44. data/lib/wlang/scope/root_scope.rb +0 -24
  45. data/spec/unit/scope/test_proxy_scope.rb +0 -22
  46. data/spec/unit/scope/test_root_scope.rb +0 -22
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ # 2.1.0 / 2012-11-28
2
+
3
+ ## Enhancements
4
+
5
+ * The scoping mechanism has been clarified and enhanced (mostly private APIs).
6
+ In particular,
7
+ * Template#render and Dialect#render now accepts multiple scoping objects and chain them
8
+ as a unique scope. The latter is branched with template locals, which are always the
9
+ most-specific and therefore have highest priority.
10
+ * RootScope as been renamed to NullScope, Scope.root to Scope.null accordingly
11
+ * ProxyScope has been removed to keep scopes linear chains.
12
+
13
+ * Added Dialect#context, which allows knowing the subject of the less specific scope, that
14
+ is the first argument of Dialect#render and Template#render. In Sinatra/Tilt situation,
15
+ this simply correspond to the `scope`, typically the Sinatra app.
16
+
17
+ * Dialect#evaluate (through Scope#evaluate) now accepts an optional block for specifying
18
+ a computed default value instead of failing.
19
+
20
+ * WLang::Html partial tag >{...} now recognizes a Proc and simply renders the result of
21
+ calling it. This allows to use >{yield} in layouts instead of the less idomatic +{yield}.
22
+
23
+ ## Bug fixes
24
+
25
+ * Fixed a bug when parsing "hello { ${wlang} }" constructs (typically javascript or java)
26
+ (wlang inner constructions was not properly parsed)
27
+
1
28
  # 2.0.1 / 2012-06-12
2
29
 
3
30
  * Fix support for 1.8.7 and jruby (undefined method `ord' for String)
data/Gemfile CHANGED
@@ -16,4 +16,6 @@ group :development do
16
16
  gem "rspec", "~> 2.10.0"
17
17
  gem "yard", "~> 0.8.1"
18
18
  gem "bluecloth", "~> 2.2.0"
19
+ gem "sinatra", :git => "git://github.com/sinatra/sinatra" #">= 1.4"
20
+ gem "rack-test", "~> 0.6.1"
19
21
  end
data/Gemfile.lock CHANGED
@@ -1,3 +1,12 @@
1
+ GIT
2
+ remote: git://github.com/sinatra/sinatra
3
+ revision: 8752085a05c33edd13d3ec8d3187d1406456d404
4
+ specs:
5
+ sinatra (1.4.0)
6
+ rack (~> 1.3, >= 1.3.6)
7
+ rack-protection (~> 1.2)
8
+ tilt (~> 1.3, >= 1.3.3)
9
+
1
10
  GEM
2
11
  remote: http://rubygems.org/
3
12
  specs:
@@ -8,6 +17,11 @@ GEM
8
17
  diff-lcs (1.1.3)
9
18
  epath (0.2.0)
10
19
  quickl (0.4.3)
20
+ rack (1.4.1)
21
+ rack-protection (1.2.0)
22
+ rack
23
+ rack-test (0.6.1)
24
+ rack (>= 1.0)
11
25
  rake (0.9.2.2)
12
26
  rspec (2.10.0)
13
27
  rspec-core (~> 2.10.0)
@@ -32,8 +46,10 @@ DEPENDENCIES
32
46
  citrus (~> 2.4.1)
33
47
  epath (>= 0.2)
34
48
  quickl (~> 0.4.3)
49
+ rack-test (~> 0.6.1)
35
50
  rake (~> 0.9.2)
36
51
  rspec (~> 2.10.0)
52
+ sinatra!
37
53
  temple (~> 0.4.0)
38
54
  tilt (~> 1.3)
39
55
  yard (~> 0.8.1)
data/README.md CHANGED
@@ -23,7 +23,7 @@ WLang is a powerful code generation and templating engine, implemented on top of
23
23
  WLang 2.0 also has a few remaining issues.
24
24
 
25
25
  * It does not support rubinius so far, due to an incompatibility with the Citrus parser generator.
26
- * It also have some issues with spacing; not a big issue for HTML rendering but might prevent certain generation tasks.
26
+ * It has some issues with spacing; not a big issue for HTML rendering but might prevent certain generation tasks.
27
27
 
28
28
  ## Tunable templating engine
29
29
 
@@ -55,75 +55,52 @@ Highlighter.render('Hello ${who}!'), who: 'you & the world'
55
55
 
56
56
  WLang already provides a few useful dialects, such as WLang::Html (inspired by Mustache but a bit more powerful in my opinion). If they don't match your needs, it is up to you to define you own dialect for making your generation task easy. Have a look at the implementation of WLang's ones, it's pretty simple to get started!
57
57
 
58
- ## Abstract semantics
58
+ # Tilt integration
59
59
 
60
- WLang has a powerful semantics in terms of concatenation of strings and high-order functions (i.e. functions that take other functions as parameters). Let take the following template as an example:
60
+ WLang has built-in support for [Tilt](https://github.com/rtomayko/tilt) facade to templating engines. In order to use that API:
61
61
 
62
- ```
63
- Hello ${who} !
64
- ```
62
+ ```ruby
63
+ require 'tilt' # needed in your bundle, not a wlang dependency
64
+ require 'wlang' # loads Tilt support provided Tilt has already been required
65
65
 
66
- The functional semantics of this template is as follows:
66
+ template = Tilt.new("path/to/a/template.wlang") # suppose 'Hello ${who}!'
67
+ template.render(:who => "world")
68
+ # => Hello world!
67
69
 
68
- ```clojure
69
- (fn (concat "Hello", ($ (fn "who")), " !"))
70
+ template = Tilt.new("path/to/a/template.wlang", :dialect => Highlighter)
71
+ template.render(:who => "world")
72
+ # => Hello WORLD!
70
73
  ```
71
74
 
72
- That is, the compilation of this template yields a function that concatenates the
73
- string `"Hello"` with the result of the higher-order function `($ )` (that itself takes another function as a parameter, corresponding to the sub-template in its brackets delimited blocks) and then the string `" !"`. Providing a concrete semantics to those high-order functions yields so called WLang _dialects_, as we've seen before.
74
-
75
- Having a well-defined semantics allows wlang to properly compile your user-defined dialect and its instantiation engine so as to preserve decent performances. The WLang architecture is a typical compiler chain. This means that, provided some additional coding, you could even define your own language/syntax and reuse the compilation mechanism, provided that you preserve the semantics above.
75
+ Please note that you should require tilt first, then wlang. Otherwise, you'll have to require `wlang/tilt` explicitely.
76
76
 
77
- ## Higher-order constructs
77
+ # Sinatra integration
78
78
 
79
- A feature that distinguishes WLang from most templating engines is the fact that higher-level constructions are permitted. In addition to tag functions that accept multiple arguments, thus multiple blocks in the source text, those blocks may be complex templates themselves.
79
+ WLang comes bundled with built-in support for [Sinatra](https://github.com/sinatra/sinatra). As usual in Sinatra, you can simply invoke wlang as follows:
80
80
 
81
- For instance, the following behavior is perfectly implementable:
82
-
83
- ```ruby
84
- HighLevel.render 'Hello *{ ${collection} }{ ${self} }{ and } !',
85
- collection: 'whos', whos: [ "you", "wlang", "world" ]
86
- # => "Hello you and wlang and world"
87
- ```
88
-
89
- An implementation of `HighLevel` might be as follows:
90
-
91
- ```ruby
92
- class HighLevel < WLang::Dialect
93
-
94
- def join(buf, expr, main, between)
95
- evaluate(expr).each_with_index do |val,i|
96
- buf << render(between, val) unless i==0
97
- buf << render(main, val).strip
81
+ get '/' do
82
+ wlang :index, :locals => { ... }
98
83
  end
99
- end
100
84
 
101
- def print(buf, fn)
102
- buf << evaluate(fn).to_s
103
- end
85
+ As wlang encourages logic-less templates, you should always use locals. However, there is specific support for layouts and partials, as the following example demonstrates:
104
86
 
105
- tag '*', :join
106
- tag '$', :print
107
- end
108
- ```
87
+ get '/' do
88
+ wlang :index, :locals => {:who => "world"}
89
+ end
109
90
 
110
- Use at your own risk, though, as it might lead to dialects that are difficult to understand and/or use and present serious injections risks! Otherwise, higher-order constructions provides you with very powerful tools.
91
+ __END__
111
92
 
112
- # Tilt integration
93
+ @@layout
94
+ <html>
95
+ >{yield}
96
+ </html>
113
97
 
114
- WLang 2.0 has built-in support for [Tilt](https://github.com/rtomayko/tilt) facade to templating engines. In order to use that API:
98
+ @@index
99
+ Hello from a partial: >{partial}
115
100
 
116
- ```ruby
117
- require 'tilt' # needed in your bundle, not a wlang dependency
118
- require 'wlang' # loads Tilt support provided Tilt has already been required
119
-
120
- template = Tilt.new("path/to/a/template.wlang") # suppose 'Hello ${who}!'
121
- template.render(:who => "world")
122
- # => Hello world!
101
+ @@partial
102
+ yeah, a partial saying hello to '${who}'!
123
103
 
124
- template = Tilt.new(hello_path.to_s, :dialect => Highlighter)
125
- template.render(:who => "world")
126
- # => Hello WORLD!
127
- ```
104
+ Returned body will be (ignoring carriage returns):
128
105
 
129
- Please note that you should require tilt first, then wlang. Otherwise, you'll have to require `wlang/tilt` explicitely.
106
+ <html>Hello from a partial: yeah, a partial saying hello to 'world'!</html>
data/lib/wlang.rb CHANGED
@@ -27,6 +27,14 @@ module WLang
27
27
  end
28
28
  module_function :dialect
29
29
 
30
+ SinatraApp = proc{|arg|
31
+ defined?(Sinatra::Base) && Sinatra::Base===arg
32
+ }
33
+
34
+ TiltTemplate = proc{|arg|
35
+ defined?(Tilt::Template) && Tilt::Template===arg
36
+ }
37
+
30
38
  end # module WLang
31
39
  require 'wlang/compiler'
32
40
  require 'wlang/source'
@@ -1,11 +1,11 @@
1
1
  grammar WLang::Grammar
2
-
2
+
3
3
  rule template
4
4
  (strconcat !.){
5
5
  [:template, [:fn, strconcat.value]]
6
6
  }
7
7
  end
8
-
8
+
9
9
  rule strconcat
10
10
  (non_static | static)* {
11
11
  if matches.size == 1
@@ -15,53 +15,56 @@ grammar WLang::Grammar
15
15
  end
16
16
  }
17
17
  end
18
-
18
+
19
19
  rule static
20
- (!stop_char .)+ {
21
- [:static, to_s]
20
+ (!stop_char .)+ {
21
+ [:static, to_s]
22
22
  }
23
23
  end
24
-
24
+
25
25
  rule non_static
26
26
  block | wlang
27
27
  end
28
-
28
+
29
29
  rule block
30
30
  (fn_start strconcat fn_stop){
31
- [:static, to_s]
31
+ [:strconcat,
32
+ [:static, captures[:fn_start].first.to_s],
33
+ captures[:strconcat].first.value,
34
+ [:static, captures[:fn_stop].first.to_s]]
32
35
  }
33
36
  end
34
-
37
+
35
38
  rule wlang
36
39
  (symbols functions){
37
40
  [:wlang, symbols.to_s] + functions.value
38
41
  }
39
42
  end
40
-
43
+
41
44
  rule functions
42
45
  function+ { matches.map{|fn| [:fn, fn.value]} }
43
46
  end
44
-
47
+
45
48
  rule function
46
49
  (fn_start strconcat fn_stop){
47
50
  strconcat.value
48
51
  }
49
52
  end
50
-
53
+
51
54
  rule stop_char
52
55
  fn_start | fn_stop | (symbols fn_start)
53
56
  end
54
-
57
+
55
58
  rule symbols
56
59
  /[!\^%"\$&'\*\+\?@~\#,\-\.\/:;=<>\|_]+/
57
60
  end
58
-
61
+
59
62
  rule fn_start
60
63
  '{'
61
64
  end
62
-
65
+
63
66
  rule fn_stop
64
67
  '}'
65
68
  end
66
-
69
+
67
70
  end
data/lib/wlang/dialect.rb CHANGED
@@ -14,8 +14,8 @@ module WLang
14
14
  Template.new source, :dialect => self
15
15
  end
16
16
 
17
- def render(source, scope = {}, buffer = "")
18
- compile(source).call(scope, buffer)
17
+ def render(source, *args)
18
+ compile(source).call(*args)
19
19
  end
20
20
 
21
21
  # tag installation and dispatching
@@ -139,14 +139,12 @@ module WLang
139
139
  def render(fn, scope = nil, buffer = "")
140
140
  if scope.nil?
141
141
  case fn
142
- when String
143
- buffer << fn
144
- when Proc
145
- fn.call(self, buffer)
146
- when Template
147
- fn.call(@scope, buffer)
148
- else
149
- raise ArgumentError, "Unable to render `#{fn}`"
142
+ when String then buffer << fn
143
+ when Proc then fn.call(self, buffer)
144
+ when Template then fn.call(@scope, buffer)
145
+ when TiltTemplate then buffer << fn.render(@scope)
146
+ else
147
+ raise ArgumentError, "Unable to render `#{fn}`"
150
148
  end
151
149
  buffer
152
150
  else
@@ -156,9 +154,14 @@ module WLang
156
154
 
157
155
  # evaluation
158
156
 
157
+ # Returns the execution context, defined as the subject of the root scope.
158
+ def context
159
+ scope.root.subject
160
+ end
161
+
159
162
  # Returns the current rendering scope.
160
163
  def scope
161
- @scope ||= Scope.root
164
+ @scope || Scope.null
162
165
  end
163
166
 
164
167
  # Yields the block with a scope branched with a sub-scope `x`.
@@ -177,17 +180,18 @@ module WLang
177
180
  # Evaluation is delegated to the scope (@see Scope#evaluate) and the result returned
178
181
  # by this method.
179
182
  #
180
- def evaluate(expr, *default)
183
+ def evaluate(expr, *default, &bl)
181
184
  case expr
182
185
  when Symbol, String
183
186
  catch(:fail) do
184
- return scope.evaluate(expr, *default)
187
+ return scope.evaluate(expr, self, *default, &bl)
185
188
  end
186
189
  raise NameError, "Unable to find `#{expr}`"
187
190
  else
188
- evaluate(render(expr), *default)
191
+ evaluate(render(expr), *default, &bl)
189
192
  end
190
193
  end
194
+ alias :value_of :evaluate
191
195
 
192
196
  end # class Dialect
193
197
  end # module WLang
data/lib/wlang/html.rb CHANGED
@@ -5,11 +5,6 @@ module WLang
5
5
 
6
6
  module Helpers
7
7
 
8
- def value_of(fn, *defaults)
9
- evaluate(render(fn).to_s.strip, *defaults)
10
- end
11
- private :value_of
12
-
13
8
  def to_html(val)
14
9
  val = val.to_html if val.respond_to?(:to_html)
15
10
  val = to_html(val.call) if val.is_a?(Proc)
@@ -28,12 +23,12 @@ module WLang
28
23
  module HighOrderFunctions
29
24
 
30
25
  def bang(buf, fn)
31
- val = value_of(fn).to_s
26
+ val = evaluate(fn).to_s
32
27
  render(val, nil, buf)
33
28
  end
34
29
 
35
30
  def plus(buf, fn)
36
- val = value_of(fn)
31
+ val = evaluate(fn)
37
32
  val = to_html(val) unless val.is_a?(Template)
38
33
  render(val, nil, buf)
39
34
  end
@@ -56,7 +51,7 @@ module WLang
56
51
  end
57
52
 
58
53
  def question(buf, fn_if, fn_then, fn_else)
59
- val = value_of(fn_if)
54
+ val = evaluate(fn_if)
60
55
  val = val.call if Proc===val
61
56
  block = val ? fn_then : fn_else
62
57
  render(block, nil, buf) if block
@@ -67,7 +62,7 @@ module WLang
67
62
  end
68
63
 
69
64
  def star(buf, coll_fn, elm_fn, between_fn)
70
- collection = value_of(coll_fn)
65
+ collection = evaluate(coll_fn)
71
66
  collection.each_with_index do |elm,i|
72
67
  render(between_fn, elm, buf) if between_fn and (i > 0)
73
68
  render(elm_fn, elm, buf)
@@ -75,13 +70,14 @@ module WLang
75
70
  end
76
71
 
77
72
  def greater(buf, fn)
78
- val = value_of(fn)
73
+ val = evaluate(fn)
79
74
  val = Html.compile(val) if String === val
75
+ val = val.call if Proc === val and val.arity<=0
80
76
  render(val, nil, buf)
81
77
  end
82
78
 
83
79
  def sharp(buf, who_fn, then_fn)
84
- val = value_of(who_fn, nil)
80
+ val = evaluate(who_fn, nil)
85
81
  if val and not(val.respond_to?(:empty?) and val.empty?)
86
82
  render(then_fn, val, buf)
87
83
  end
data/lib/wlang/scope.rb CHANGED
@@ -4,36 +4,37 @@ module WLang
4
4
  attr_reader :subject
5
5
  attr_reader :parent
6
6
 
7
- def initialize(subject, parent)
8
- @subject, @parent = subject, parent
7
+ def initialize(subject)
8
+ @subject = subject
9
+ @parent = nil
9
10
  end
10
11
 
11
- def self.root
12
- @root ||= RootScope.new
12
+ def self.null
13
+ @null ||= NullScope.new
13
14
  end
14
15
 
15
- def self.coerce(arg, parent = nil)
16
- return arg if Scope===arg && parent.nil?
17
- parent ||= root
18
- clazz = case arg
19
- when Binding
20
- BindingScope
21
- when Scope
22
- ProxyScope
23
- when Proc
24
- ProcScope
25
- else
26
- ObjectScope
27
- end
28
- clazz.new(arg, parent)
16
+ def self.coerce(arg)
17
+ case arg
18
+ when Hash then ObjectScope.new(arg)
19
+ when Scope then arg
20
+ when SinatraApp then SinatraScope.new(arg)
21
+ when Binding then BindingScope.new(arg)
22
+ when Proc then ProcScope.new(arg)
23
+ else
24
+ ObjectScope.new(arg)
25
+ end
29
26
  end
30
27
 
31
28
  def self.chain(scopes)
32
- scopes.compact.inject(nil){|parent,child| Scope.coerce(child, parent)} || root
29
+ scopes.compact.inject(Scope.null){|p,c| p.push(c)}
30
+ end
31
+
32
+ def root
33
+ parent ? parent.root : self
33
34
  end
34
35
 
35
36
  def push(x)
36
- Scope.coerce(x, self)
37
+ append(Scope.coerce(x))
37
38
  end
38
39
 
39
40
  def pop
@@ -44,23 +45,62 @@ module WLang
44
45
  yield(self.push(x))
45
46
  end
46
47
 
47
- def evaluate(expr, *default)
48
- unfound = lambda{ default.empty? ? throw(:fail) : default.first }
49
- expr = expr.to_s.strip
50
- if expr.to_s.index('.').nil?
51
- fetch(expr.to_sym, &unfound)
48
+ def evaluate(expr, dialect, *default, &bl)
49
+ expr = expr.to_s.strip
50
+ unfound = lambda do
51
+ if default.empty?
52
+ bl ? bl.call(expr) : throw(:fail)
53
+ else
54
+ default.first
55
+ end
56
+ end
57
+ if expr.index('.').nil?
58
+ fetch(expr.to_sym, dialect, unfound)
52
59
  else
53
60
  keys = expr.split('.').map(&:to_sym)
54
61
  keys.inject(self){|scope,key|
55
- Scope.coerce(scope.fetch(key, &unfound))
62
+ found = scope.fetch(key, dialect, unfound)
63
+ Scope.coerce(found)
56
64
  }.subject
57
65
  end
58
66
  end
59
67
 
68
+ def subjects
69
+ arr = []
70
+ visit(:top_down){|s| arr << s.subject}
71
+ arr
72
+ end
73
+
74
+ protected
75
+
76
+ def visit(mode = :top_down, &visitor)
77
+ visitor.call(self) unless mode == :top_down
78
+ parent.visit(mode, &visitor) if parent
79
+ visitor.call(self) if mode == :top_down
80
+ end
81
+
82
+ def append(x)
83
+ x.prepend(self)
84
+ end
85
+
86
+ def prepend(x)
87
+ newp = parent ? parent.prepend(x) : x
88
+ dup.with_parent!(newp)
89
+ end
90
+
91
+ def with_parent!(p)
92
+ @parent = p
93
+ self
94
+ end
95
+
96
+ def safe_parent
97
+ parent || Scope.null
98
+ end
99
+
60
100
  end # class Scope
61
101
  end # module WLang
62
- require 'wlang/scope/root_scope'
63
- require 'wlang/scope/proxy_scope'
102
+ require 'wlang/scope/null_scope'
64
103
  require 'wlang/scope/object_scope'
65
104
  require 'wlang/scope/binding_scope'
66
105
  require 'wlang/scope/proc_scope'
106
+ require 'wlang/scope/sinatra_scope'