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.
Files changed (74) hide show
  1. data/Gemfile +2 -8
  2. data/Gemfile.lock +4 -8
  3. data/Manifest.txt +0 -2
  4. data/README.md +36 -7
  5. data/lib/wlang.rb +3 -0
  6. data/lib/wlang/command.rb +3 -11
  7. data/lib/wlang/compiler.rb +11 -20
  8. data/lib/wlang/compiler/autospacing.rb +4 -2
  9. data/lib/wlang/compiler/dialect_enforcer.rb +1 -1
  10. data/lib/wlang/compiler/to_ruby_code.rb +1 -1
  11. data/lib/wlang/dialect.rb +163 -23
  12. data/lib/wlang/html.rb +9 -8
  13. data/lib/wlang/loader.rb +2 -4
  14. data/lib/wlang/scope.rb +17 -8
  15. data/lib/wlang/scope/proc_scope.rb +18 -0
  16. data/lib/wlang/source.rb +56 -0
  17. data/lib/wlang/source/front_matter.rb +51 -0
  18. data/lib/wlang/template.rb +55 -17
  19. data/lib/wlang/tilt.rb +3 -0
  20. data/lib/wlang/tilt/wlang_template.rb +43 -0
  21. data/lib/wlang/version.rb +1 -2
  22. data/spec/fixtures/templates/{hello.tpl → hello.wlang} +0 -0
  23. data/spec/fixtures/templates/hello_with_data.wlang +4 -0
  24. data/spec/fixtures/templates/hello_with_explicit_locals.wlang +6 -0
  25. data/spec/fixtures/templates/hello_with_partials.wlang +7 -0
  26. data/spec/integration/examples/{1-basics.txt → 1-html-intro/1-basics.md} +1 -1
  27. data/spec/integration/examples/{2-imperative.txt → 1-html-intro/2-imperative.md} +1 -1
  28. data/spec/integration/examples/{3-partials.txt → 1-html-intro/3-partials.md} +0 -0
  29. data/spec/integration/examples/{4-recursion.txt → 1-html-intro/4-recursion.md} +1 -2
  30. data/spec/integration/examples/1-html-intro/5-polymorphism.md +17 -0
  31. data/spec/integration/html/test_caret.rb +15 -2
  32. data/spec/integration/html/test_question.rb +12 -2
  33. data/spec/integration/html/test_sharp.rb +5 -1
  34. data/spec/integration/html/test_star.rb +1 -3
  35. data/spec/integration/test_examples.rb +2 -1
  36. data/spec/integration/test_readme.rb +14 -2
  37. data/spec/integration/tilt/test_wlang_template.rb +13 -0
  38. data/spec/spec_helper.rb +3 -3
  39. data/spec/unit/compiler/test_dialect_enforcer.rb +1 -1
  40. data/spec/unit/compiler/test_to_ruby_proc.rb +15 -0
  41. data/spec/unit/dialect/test_compile.rb +0 -10
  42. data/spec/unit/dialect/test_evaluate.rb +36 -28
  43. data/spec/unit/dialect/test_new.rb +61 -0
  44. data/spec/unit/dialect/test_tag.rb +36 -0
  45. data/spec/unit/dialect/test_tag_dispatching_name.rb +22 -0
  46. data/spec/unit/dialect/test_with_scope.rb +8 -11
  47. data/spec/unit/scope/test_chain.rb +29 -0
  48. data/spec/unit/scope/test_coerce.rb +13 -2
  49. data/spec/unit/scope/test_proc_scope.rb +18 -0
  50. data/spec/unit/source/front_matter/test_locals.rb +39 -0
  51. data/spec/unit/source/front_matter/test_template_content.rb +22 -0
  52. data/spec/unit/source/test_locals.rb +12 -0
  53. data/spec/unit/source/test_path.rb +27 -0
  54. data/spec/unit/source/test_template_content.rb +37 -0
  55. data/spec/unit/source/test_with_front_matter.rb +19 -0
  56. data/spec/unit/template/test_call.rb +16 -0
  57. data/spec/unit/template/test_new.rb +20 -0
  58. data/spec/unit/template/test_path.rb +28 -0
  59. data/spec/unit/template/test_render.rb +44 -0
  60. data/spec/unit/template/test_to_ast.rb +12 -0
  61. data/spec/unit/template/test_to_ruby_code.rb +13 -0
  62. data/spec/unit/template/test_to_ruby_proc.rb +12 -0
  63. data/spec/unit/template/test_yaml_front_matter.rb +22 -0
  64. data/spec/unit/tilt/test_wlang_template.rb +65 -0
  65. data/wlang.gemspec +3 -5
  66. data/wlang.noespec +12 -17
  67. metadata +117 -85
  68. data/lib/wlang/dialect/dispatching.rb +0 -51
  69. data/lib/wlang/dialect/evaluation.rb +0 -30
  70. data/lib/wlang/dialect/tags.rb +0 -50
  71. data/lib/wlang/mustang.rb +0 -90
  72. data/spec/integration/test_mustang.rb +0 -120
  73. data/spec/unit/dialect/test_dispatching.rb +0 -19
  74. data/spec/unit/dialect/test_tags.rb +0 -32
@@ -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 = value_of(fn_if) ? fn_then : fn_else
60
- render(val, nil, buf) if val
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
- val = value_of(fn_if) ? fn_else : fn_then
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) unless Template === 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
@@ -1,6 +1,4 @@
1
1
  require "citrus"
2
2
  require "temple"
3
- if RUBY_VERSION < "1.9"
4
- require 'backports'
5
- require "backports/basic_object" unless defined?(BasicObject)
6
- end
3
+ require "quickl"
4
+ require "epath"
@@ -12,18 +12,26 @@ module WLang
12
12
  @root ||= RootScope.new
13
13
  end
14
14
 
15
- def self.coerce(arg, parent = root)
15
+ def self.coerce(arg, parent = nil)
16
+ return arg if Scope===arg && parent.nil?
17
+ parent ||= root
16
18
  clazz = case arg
17
- when Binding
18
- BindingScope
19
- when Scope
20
- ProxyScope
21
- else
22
- ObjectScope
23
- end
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
@@ -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
@@ -1,25 +1,63 @@
1
1
  module WLang
2
2
  class Template
3
3
 
4
- attr_reader :dialect, :inner_proc
5
-
6
- def initialize(dialect, inner_proc)
7
- @dialect = dialect
8
- @inner_proc = inner_proc
9
- end
10
-
11
- def call(scope = {}, buffer = '')
12
- case i = inner_proc
13
- when String
14
- buffer << i
15
- else
16
- @dialect.dup.tap do |d|
17
- d.send(:render, i, scope, buffer)
18
- end
19
- buffer
20
- end
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
@@ -0,0 +1,3 @@
1
+ require 'wlang' unless defined?(WLang)
2
+ require 'tilt' unless defined?(Tilt)
3
+ require 'wlang/tilt/wlang_template'
@@ -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
@@ -4,10 +4,9 @@ module WLang
4
4
  MAJOR = 2
5
5
  MINOR = 0
6
6
  TINY = 0
7
- BETA = "beta"
8
7
 
9
8
  def self.to_s
10
- [ MAJOR, MINOR, TINY, BETA ].join('.')
9
+ [ MAJOR, MINOR, TINY ].join('.')
11
10
  end
12
11
 
13
12
  end
@@ -0,0 +1,4 @@
1
+ ---
2
+ who: world
3
+ ---
4
+ Hello ${who}!
@@ -0,0 +1,6 @@
1
+ ---
2
+ who: world
3
+ locals:
4
+ who_else: wlang
5
+ ---
6
+ Hello ${who} and ${who_else}!
@@ -0,0 +1,7 @@
1
+ ---
2
+ locals:
3
+ who_else: wlang
4
+ partials:
5
+ who: world and ${who_else}
6
+ ---
7
+ Hello >{who}!
@@ -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 ;-)
@@ -46,6 +46,6 @@ For instance,
46
46
 
47
47
  Renders an HTML list with the authors names. A separator can be specified through the third optional block:
48
48
 
49
- Bernard,Louis
49
+ Bernard, Louis
50
50
 
51
51
  Will render 'Bernard, Louis'.
@@ -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
- [false, nil].each do |test|
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
- [true, "Something", []].each do |test|
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("")