wlang 2.0.1 → 2.1.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.
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'