mvz-ruby-handlebars 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 37eaf9cc26d1c38afea06c4b57b024e15d38f1a1da9233176494bb39cd9e3331
4
+ data.tar.gz: 2c9adca712168ab377a64586db9e8b06610cb63f6cd6d888dfd66aab2452c28f
5
+ SHA512:
6
+ metadata.gz: e6286672304cddb4bc9aeac034dacd8ce7a01e0a4d4622780b0c2251cd50c85808855f3365ca0e4cd16fa9fefcccc3ae30d68d88fdae63270d8181f5ef1ced15
7
+ data.tar.gz: 5c09f90b840bfd76e650ac8c322f6bf13ec5dfe4a306a6e23a14ab864ecbdf0792aac09604f644ab9c2b0543d918c01c563ca81a9109a48770e6146d48b156ac
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Vincent
4
+ Copyright (c) 2018-2019 Matijs van Zuijlen
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,115 @@
1
+ ruby-handlebars
2
+ ===============
3
+
4
+ [![Build Status](https://travis-ci.org/mvz/ruby-handlebars.svg?branch=master)](https://travis-ci.org/mvz/ruby-handlebars)
5
+
6
+ Pure Ruby library for [Handlebars](http://handlebarsjs.com/) template system.
7
+ This should become an alternative for using the Handlebars javascript library via
8
+ [handlebars.rb](https://github.com/cowboyd/handlebars.rb).
9
+
10
+ Installing
11
+ ----------
12
+
13
+ Simply run:
14
+
15
+ ```shell
16
+ gem install mvz-ruby-handlebars
17
+ ```
18
+
19
+ No need for libv8, ruby-racer or any JS related tool.
20
+
21
+ Using
22
+ -----
23
+
24
+ A very simple case:
25
+
26
+ ```ruby
27
+
28
+ require 'ruby-handlebars'
29
+
30
+ hbs = Handlebars::Handlebars.new
31
+ hbs.compile("Hello {{name}}").call({name: 'world'})
32
+ # Gives: "Hello world", how original ...
33
+ ```
34
+
35
+ You can also use partials:
36
+
37
+ ```ruby
38
+ hbs.register_partial('full_name', "{{person.first_name}} {{person.last_name}}")
39
+ hbs.compile("Hello {{> full_name}}").call({person: {first_name: 'Pinkie', last_name: 'Pie'}})
40
+ # Gives: "Hello Pinkie Pie"
41
+ ```
42
+
43
+ You can also register inline helpers:
44
+
45
+ ```ruby
46
+ hbs.register_helper('strip') {|context, value| value.strip}
47
+ hbs.compile("Hello {{strip name}}").call({name: ' world '})
48
+ # Will give (again ....): "Hello world"
49
+ ```
50
+
51
+ or block helpers:
52
+
53
+ ```ruby
54
+ hbs.register_helper('comment') do |context, commenter, block|
55
+ block.fn(context).split("\n").map do |line|
56
+ "#{commenter} #{line}"
57
+ end.join("\n")
58
+ end
59
+
60
+ hbs.compile("{{#comment '//'}}My comment{{/comment}}").call
61
+ # Will give: "// My comment"
62
+ ```
63
+
64
+ Note that in any block helper you can use an ``else`` block:
65
+
66
+ ```ruby
67
+ hbs.register_helper('markdown') do |context, block, else_block|
68
+ html = md_to_html(block.fn(context))
69
+ html.nil? : else_block.fn(context) : html
70
+ end
71
+
72
+ template = [
73
+ "{{#markdown}}",
74
+ " {{ description }}",
75
+ "{{else}}",
76
+ " Description is not valid markdown, no preview available",
77
+ "{{/markdown}}"
78
+ ].join("\n")
79
+
80
+ hbs.compile(template).call({description: my_description})
81
+ # Output will depend on the validity of the 'my_description' variable
82
+ ```
83
+
84
+ Two default helpers are provided: ``each`` and ``if``. It is not yet possible to name the current item in an each loop and ``this`` must be used to reference it.
85
+
86
+ ```ruby
87
+ template = [
88
+ "{{#each items}}",
89
+ " {{{ this }}}",
90
+ "{{else}}",
91
+ " No items",
92
+ "{{/each}}",
93
+ "",
94
+ "{{#if my_condition}}",
95
+ " It's ok",
96
+ "{{else}}",
97
+ " or maybe not",
98
+ "{{/if}}",
99
+ ].join("\n")
100
+ ```
101
+
102
+ Limitations and roadmap
103
+ -----------------------
104
+
105
+ This gem does not reuse the real Handlebars code (the JS one) and not everything is handled yet (but it will be someday ;) ):
106
+
107
+ - there is no escaping, all strings are considered as safe (so ``{{{ my_var }}}`` and ``{{ my_var }}``) will output the same thing
108
+ - the parser is not fully tested yet, it may complain with spaces ...
109
+ - curly bracket are __not__ usable in the template content yet. one workaround is to create simple helpers to generate them
110
+ - parsing errors are, well, not helpful at all
111
+
112
+ Aknowledgements
113
+ ---------------
114
+
115
+ This is a fork of the [ruby-handlebars]("https://github.com/vincent-psarga/ruby-handlebars") gem, which sadly seems unmaintained.
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rake/clean'
4
+ require 'bundler/gem_tasks'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new('spec')
8
+
9
+ task default: :spec
@@ -0,0 +1,85 @@
1
+ require_relative 'ruby-handlebars/version'
2
+ require_relative 'ruby-handlebars/helper'
3
+ require_relative 'ruby-handlebars/context'
4
+ require_relative 'ruby-handlebars/parser'
5
+ require_relative 'ruby-handlebars/template'
6
+ require_relative 'ruby-handlebars/tree'
7
+
8
+ module Handlebars
9
+ class Handlebars
10
+ def initialize
11
+ @helpers = {}
12
+ @partials = {}
13
+
14
+ register_default_helpers
15
+ end
16
+
17
+ def compile(template)
18
+ Template.new(self, template_to_ast(template))
19
+ end
20
+
21
+ def register_helper(name, &fn)
22
+ @helpers[name.to_s] = Helper.new(self, fn)
23
+ end
24
+
25
+ def get_helper(name)
26
+ @helpers[name.to_s]
27
+ end
28
+
29
+ def register_partial(name, content)
30
+ @partials[name.to_s] = Template.new(self, template_to_ast(content))
31
+ end
32
+
33
+ def get_partial(name)
34
+ @partials[name.to_s]
35
+ end
36
+
37
+ private
38
+
39
+ PARSER = Parser.new
40
+ TRANSFORM = Transform.new
41
+
42
+ def template_to_ast(content)
43
+ TRANSFORM.apply(PARSER.parse(content))
44
+ end
45
+
46
+ def register_default_helpers
47
+ register_if_helper
48
+ register_each_helper
49
+ end
50
+
51
+ def register_if_helper
52
+ register_helper('if') do |context, condition, block, else_block|
53
+ condition = !condition.empty? if condition.respond_to?(:empty?)
54
+
55
+ if condition
56
+ block.fn(context)
57
+ elsif else_block
58
+ else_block.fn(context)
59
+ else
60
+ ""
61
+ end
62
+ end
63
+ end
64
+
65
+ def register_each_helper
66
+ register_helper('each') do |context, items, block, else_block|
67
+ current_this = context.get('this')
68
+
69
+ if (items.nil? || items.empty?)
70
+ if else_block
71
+ result = else_block.fn(context)
72
+ end
73
+ else
74
+ result = items.map do |item|
75
+ context.add_item(:this, item)
76
+ block.fn(context)
77
+ end.join('')
78
+ end
79
+
80
+ context.add_item(:this, current_this)
81
+ result
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,55 @@
1
+ module Handlebars
2
+ class Context
3
+ def initialize(hbs, data)
4
+ @hbs = hbs
5
+ @locals = {}
6
+ @data = data
7
+ end
8
+
9
+ def get(path)
10
+ items = path.split('.'.freeze)
11
+
12
+ if @locals.key? items.first.to_sym
13
+ current = @locals
14
+ else
15
+ current = @data
16
+ end
17
+ until items.empty?
18
+ current = get_attribute(current, items.shift)
19
+ end
20
+
21
+ current
22
+ end
23
+
24
+ def get_helper(name)
25
+ @hbs.get_helper(name)
26
+ end
27
+
28
+ def get_partial(name)
29
+ @hbs.get_partial(name)
30
+ end
31
+
32
+ def add_item(key, value)
33
+ @locals[key.to_sym] = value
34
+ end
35
+
36
+ private
37
+
38
+ def get_attribute(item, attribute)
39
+ sym_attr = attribute.to_sym
40
+ str_attr = attribute.to_s
41
+
42
+ if item.respond_to?(:[]) && item.respond_to?(:has_key?)
43
+ if item.has_key?(sym_attr)
44
+ return item[sym_attr]
45
+ elsif item.has_key?(str_attr)
46
+ return item[str_attr]
47
+ end
48
+ end
49
+
50
+ if item.respond_to?(sym_attr)
51
+ return item.send(sym_attr)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'tree'
2
+
3
+ module Handlebars
4
+ class Helper
5
+ def initialize(hbs, fn)
6
+ @hbs = hbs
7
+ @fn = fn
8
+ end
9
+
10
+ def apply(context, arguments = [], block = [])
11
+ arguments = [arguments] unless arguments.is_a? Array
12
+ args = [context] + arguments.map {|arg| arg.eval(context)} + split_block(block || [])
13
+
14
+ @fn.call(*args)
15
+ end
16
+
17
+ def split_block(block)
18
+ helper_block = Tree::Block.new([])
19
+ inverse_block = Tree::Block.new([])
20
+
21
+ receiver = helper_block
22
+ else_found = false
23
+
24
+ block.each do |item|
25
+ if item.is_a?(Tree::Helper) && item.is_else?
26
+ receiver = inverse_block
27
+ else_found = true
28
+ next
29
+ end
30
+
31
+ receiver.add_item(item)
32
+ end
33
+
34
+ if else_found
35
+ return [helper_block, inverse_block]
36
+ else
37
+ return [helper_block]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,70 @@
1
+ require 'parslet'
2
+
3
+ module Handlebars
4
+ class Parser < Parslet::Parser
5
+ rule(:space) { match('\s').repeat(1) }
6
+ rule(:space?) { space.maybe }
7
+ rule(:dot) { str('.') }
8
+ rule(:gt) { str('>')}
9
+ rule(:hash) { str('#')}
10
+ rule(:slash) { str('/')}
11
+ rule(:ocurly) { str('{')}
12
+ rule(:ccurly) { str('}')}
13
+
14
+ rule(:docurly) { ocurly >> ocurly }
15
+ rule(:dccurly) { ccurly >> ccurly }
16
+
17
+ rule(:identifier) { match['a-zA-Z0-9_\?'].repeat(1) }
18
+ rule(:path) { identifier >> (dot >> identifier).repeat }
19
+
20
+ rule(:nocurly) { match('[^{}]') }
21
+ rule(:eof) { any.absent? }
22
+ rule(:template_content) {
23
+ (
24
+ nocurly.repeat(1) | # A sequence of non-curlies
25
+ ocurly >> nocurly | # Opening curly that doesn't start a {{}}
26
+ ccurly | # Closing curly that is not inside a {{}}
27
+ ocurly >> eof # Opening curly that doesn't start a {{}} because it's the end
28
+ ).repeat(1).as(:template_content) }
29
+
30
+ rule(:sq_string) { match("'") >> match("[^']").repeat.maybe.as(:str_content) >> match("'") }
31
+ rule(:dq_string) { match('"') >> match('[^"]').repeat.maybe.as(:str_content) >> match('"') }
32
+ rule(:string) { sq_string | dq_string }
33
+
34
+ rule(:parameter) { (path | string).as(:parameter_name) }
35
+ rule(:parameters) { parameter >> (space >> parameter).repeat }
36
+
37
+ rule(:unsafe_helper) { docurly >> space? >> path.as(:helper_name) >> (space? >> parameters.as(:parameters)).maybe >> space? >> dccurly}
38
+ rule(:safe_helper) { ocurly >> helper >> ccurly }
39
+
40
+ rule(:helper) { unsafe_helper | safe_helper }
41
+
42
+ rule(:block_helper) {
43
+ docurly >>
44
+ hash >>
45
+ identifier.capture(:helper_name).as(:helper_name) >>
46
+ (space >> parameters.as(:parameters)).maybe >>
47
+ space? >>
48
+ dccurly >>
49
+ scope {
50
+ block
51
+ } >>
52
+ dynamic { |src, scope|
53
+ docurly >> slash >> str(scope.captures[:helper_name]) >> dccurly
54
+ }
55
+ }
56
+
57
+ rule(:partial) {
58
+ docurly >>
59
+ gt >>
60
+ space? >>
61
+ identifier.as(:partial_name) >>
62
+ space? >>
63
+ dccurly
64
+ }
65
+
66
+ rule(:block) { (template_content | helper | partial | block_helper ).repeat.as(:block_items) }
67
+
68
+ root :block
69
+ end
70
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'context'
2
+
3
+ module Handlebars
4
+ class Template
5
+ def initialize(hbs, ast)
6
+ @hbs = hbs
7
+ @ast = ast
8
+ end
9
+
10
+ def call(args = nil)
11
+ ctx = Context.new(@hbs, args)
12
+
13
+ @ast.eval(ctx)
14
+ end
15
+
16
+ def call_with_context(ctx)
17
+ @ast.eval(ctx)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,101 @@
1
+ require 'parslet'
2
+
3
+ module Handlebars
4
+ module Tree
5
+ class TreeItem < Struct
6
+ def eval(context)
7
+ _eval(context)
8
+ end
9
+ end
10
+
11
+ class TemplateContent < TreeItem.new(:content)
12
+ def _eval(context)
13
+ return content
14
+ end
15
+ end
16
+
17
+ class String < TreeItem.new(:content)
18
+ def _eval(context)
19
+ return content
20
+ end
21
+ end
22
+
23
+ class Parameter < TreeItem.new(:name)
24
+ def _eval(context)
25
+ if name.is_a?(Parslet::Slice)
26
+ context.get(name.to_s)
27
+ else
28
+ name._eval(context)
29
+ end
30
+ end
31
+ end
32
+
33
+ class Helper < TreeItem.new(:name, :parameters, :block)
34
+ def _eval(context)
35
+ if context.get_helper(name.to_s).nil?
36
+ context.get(name.to_s)
37
+ else
38
+ context.get_helper(name.to_s).apply(context, parameters, block)
39
+ end
40
+ end
41
+
42
+ def is_else?
43
+ name.to_s == 'else'
44
+ end
45
+ end
46
+
47
+ class Partial < TreeItem.new(:partial_name)
48
+ def _eval(context)
49
+ context.get_partial(partial_name.to_s).call_with_context(context)
50
+ end
51
+ end
52
+
53
+ class Block < TreeItem.new(:items)
54
+ def _eval(context)
55
+ items.map {|item| item._eval(context)}.join()
56
+ end
57
+ alias :fn :_eval
58
+
59
+ def add_item(i)
60
+ items << i
61
+ end
62
+ end
63
+ end
64
+
65
+ class Transform < Parslet::Transform
66
+ rule(template_content: simple(:content)) {Tree::TemplateContent.new(content)}
67
+ rule(str_content: simple(:content)) {Tree::String.new(content)}
68
+ rule(parameter_name: simple(:name)) {Tree::Parameter.new(name)}
69
+
70
+ rule(
71
+ helper_name: simple(:name)
72
+ ) {
73
+ Tree::Helper.new(name, [])
74
+ }
75
+
76
+ rule(
77
+ helper_name: simple(:name),
78
+ parameters: subtree(:parameters)
79
+ ) {
80
+ Tree::Helper.new(name, parameters)
81
+ }
82
+
83
+ rule(
84
+ helper_name: simple(:name),
85
+ block_items: subtree(:block_items)
86
+ ) {
87
+ Tree::Helper.new(name, [], block_items)
88
+ }
89
+
90
+ rule(
91
+ helper_name: simple(:name),
92
+ parameters: subtree(:parameters),
93
+ block_items: subtree(:block_items)
94
+ ) {
95
+ Tree::Helper.new(name, parameters, block_items)
96
+ }
97
+
98
+ rule(partial_name: simple(:partial_name)) {Tree::Partial.new(partial_name)}
99
+ rule(block_items: subtree(:block_items)) {Tree::Block.new(block_items)}
100
+ end
101
+ end