hotcell 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -1
- data/.rspec +1 -0
- data/.rvmrc +1 -1
- data/.travis.yml +7 -0
- data/Gemfile +4 -1
- data/README.md +361 -2
- data/Rakefile +28 -6
- data/ext/lexerc/extconf.rb +3 -0
- data/ext/lexerc/lexerc.c +618 -0
- data/ext/lexerc/lexerc.h +20 -0
- data/ext/lexerc/lexerc.rl +167 -0
- data/hotcell.gemspec +8 -7
- data/lib/hotcell/commands/case.rb +59 -0
- data/lib/hotcell/commands/cycle.rb +38 -0
- data/lib/hotcell/commands/for.rb +70 -0
- data/lib/hotcell/commands/if.rb +51 -0
- data/lib/hotcell/commands/include.rb +21 -0
- data/lib/hotcell/commands/scope.rb +13 -0
- data/lib/hotcell/commands/unless.rb +23 -0
- data/lib/hotcell/commands.rb +13 -0
- data/lib/hotcell/config.rb +33 -6
- data/lib/hotcell/context.rb +40 -7
- data/lib/hotcell/errors.rb +37 -28
- data/lib/hotcell/extensions.rb +4 -0
- data/lib/hotcell/lexer.rb +19 -635
- data/lib/hotcell/lexerr.rb +572 -0
- data/lib/hotcell/lexerr.rl +137 -0
- data/lib/hotcell/node/assigner.rb +1 -5
- data/lib/hotcell/node/block.rb +17 -40
- data/lib/hotcell/node/command.rb +29 -22
- data/lib/hotcell/node/hasher.rb +1 -1
- data/lib/hotcell/node/summoner.rb +2 -6
- data/lib/hotcell/node/tag.rb +10 -7
- data/lib/hotcell/node.rb +12 -1
- data/lib/hotcell/parser.rb +474 -408
- data/lib/hotcell/parser.y +175 -117
- data/lib/hotcell/resolver.rb +44 -0
- data/lib/hotcell/source.rb +35 -0
- data/lib/hotcell/template.rb +15 -6
- data/lib/hotcell/version.rb +1 -1
- data/lib/hotcell.rb +15 -10
- data/spec/data/templates/simple.hc +1 -0
- data/spec/lib/hotcell/commands/case_spec.rb +39 -0
- data/spec/lib/hotcell/commands/cycle_spec.rb +29 -0
- data/spec/lib/hotcell/commands/for_spec.rb +65 -0
- data/spec/lib/hotcell/commands/if_spec.rb +35 -0
- data/spec/lib/hotcell/commands/include_spec.rb +39 -0
- data/spec/lib/hotcell/commands/scope_spec.rb +16 -0
- data/spec/lib/hotcell/commands/unless_spec.rb +23 -0
- data/spec/lib/hotcell/config_spec.rb +35 -10
- data/spec/lib/hotcell/context_spec.rb +58 -18
- data/spec/lib/hotcell/lexer_spec.rb +37 -28
- data/spec/lib/hotcell/node/block_spec.rb +28 -56
- data/spec/lib/hotcell/node/command_spec.rb +7 -31
- data/spec/lib/hotcell/node/tag_spec.rb +16 -0
- data/spec/lib/hotcell/parser_spec.rb +152 -123
- data/spec/lib/hotcell/resolver_spec.rb +28 -0
- data/spec/lib/hotcell/source_spec.rb +41 -0
- data/spec/lib/hotcell/template_spec.rb +47 -4
- data/spec/lib/hotcell_spec.rb +2 -1
- data/spec/spec_helper.rb +6 -2
- metadata +54 -24
- data/lib/hotcell/.DS_Store +0 -0
- data/lib/hotcell/lexer.rl +0 -299
- data/misc/rage.rl +0 -1999
- data/misc/unicode2ragel.rb +0 -305
@@ -0,0 +1,167 @@
|
|
1
|
+
%%{
|
2
|
+
machine puffer_lexer;
|
3
|
+
|
4
|
+
plus = '+';
|
5
|
+
minus = '-';
|
6
|
+
multiply = '*';
|
7
|
+
power = '**';
|
8
|
+
divide = '/';
|
9
|
+
modulo = '%';
|
10
|
+
arithmetic = plus | minus | multiply | power | divide | modulo;
|
11
|
+
|
12
|
+
and = '&&';
|
13
|
+
or = '||';
|
14
|
+
not = '!';
|
15
|
+
equal = '==';
|
16
|
+
inequal = '!=';
|
17
|
+
gt = '>';
|
18
|
+
gte = '>=';
|
19
|
+
lt = '<';
|
20
|
+
lte = '<=';
|
21
|
+
logic = and | or | not | equal | inequal | gt | gte | lt | lte;
|
22
|
+
|
23
|
+
assign = '=';
|
24
|
+
comma = ',';
|
25
|
+
period = '.';
|
26
|
+
colon = ':';
|
27
|
+
question = '?';
|
28
|
+
semicolon = ';';
|
29
|
+
newline = '\n';
|
30
|
+
flow = assign | comma | period | colon | question | semicolon | newline;
|
31
|
+
|
32
|
+
array_open = '[';
|
33
|
+
array_close = ']';
|
34
|
+
hash_open = '{';
|
35
|
+
hash_close = '}';
|
36
|
+
bracket_open = '(';
|
37
|
+
bracket_close = ')';
|
38
|
+
structure = array_open | array_close | hash_open | hash_close | bracket_open | bracket_close;
|
39
|
+
|
40
|
+
|
41
|
+
escaped_symbol = '\\' any;
|
42
|
+
|
43
|
+
squote = "'";
|
44
|
+
snon_quote = [^\\'];
|
45
|
+
sstring = squote (snon_quote | escaped_symbol)* squote @lerr{ raise_unterminated_string; };
|
46
|
+
|
47
|
+
dquote = '"';
|
48
|
+
dnon_quote = [^\\"];
|
49
|
+
dstring = dquote (dnon_quote | escaped_symbol)* dquote @lerr{ raise_unterminated_string; };
|
50
|
+
|
51
|
+
rquote = '/';
|
52
|
+
rnon_quote = [^\\/];
|
53
|
+
regexp = rquote @{ regexp_ambiguity(fgoto expression;) }
|
54
|
+
(rnon_quote | escaped_symbol)* rquote alpha* @lerr{ raise_unterminated_regexp; };
|
55
|
+
|
56
|
+
|
57
|
+
numeric = '-'? digit* ('.' digit+)?;
|
58
|
+
identifer = (alpha | '_') (alnum | '_')* [?!]?;
|
59
|
+
operator = arithmetic | logic | flow | structure;
|
60
|
+
comment = '#' ([^\n}]+ | '}' [^}])*;
|
61
|
+
blank = [\t\v\f\r ];
|
62
|
+
|
63
|
+
tag_open = '{{' '!'?;
|
64
|
+
tag_close = '}}';
|
65
|
+
template = [^{]+ | '{';
|
66
|
+
|
67
|
+
template_comment_open = '{{#';
|
68
|
+
template_comment_close = '#}}';
|
69
|
+
template_comment_body = [^\#]+ | '#';
|
70
|
+
|
71
|
+
|
72
|
+
expression := |*
|
73
|
+
tag_close => { emit_tag; fret; };
|
74
|
+
operator => { emit_operator; };
|
75
|
+
numeric => { emit_numeric; };
|
76
|
+
identifer => { emit_identifer; };
|
77
|
+
sstring => { emit_sstring; };
|
78
|
+
dstring => { emit_dstring; };
|
79
|
+
regexp => { emit_regexp; };
|
80
|
+
comment => { emit_comment; };
|
81
|
+
blank;
|
82
|
+
*|;
|
83
|
+
|
84
|
+
template_comment := |*
|
85
|
+
template_comment_close => { emit_comment; fret; };
|
86
|
+
template_comment_body => { emit_comment; };
|
87
|
+
*|;
|
88
|
+
|
89
|
+
main := |*
|
90
|
+
tag_open => { emit_tag; fcall expression; };
|
91
|
+
template_comment_open => { emit_comment; fcall template_comment; };
|
92
|
+
template => { emit_template; };
|
93
|
+
*|;
|
94
|
+
}%%
|
95
|
+
|
96
|
+
#include <ruby.h>
|
97
|
+
#include <lexerc.h>
|
98
|
+
|
99
|
+
static VALUE mHotcell;
|
100
|
+
static VALUE cHotcellLexer;
|
101
|
+
|
102
|
+
%% write data;
|
103
|
+
|
104
|
+
static char *p;
|
105
|
+
static char *ts;
|
106
|
+
static char *te;
|
107
|
+
static char *data;
|
108
|
+
static VALUE encoding;
|
109
|
+
|
110
|
+
static VALUE tokenize(VALUE self) {
|
111
|
+
VALUE source = rb_iv_get(self, "@source");
|
112
|
+
encoding = rb_funcall(source, rb_intern("encoding"), 0);
|
113
|
+
VALUE string = rb_funcall(source, rb_intern("source"), 0);
|
114
|
+
rb_iv_set(self, "@token_array", rb_ary_new());
|
115
|
+
|
116
|
+
data = RSTRING_PTR(string);
|
117
|
+
unsigned long length = RSTRING_LEN(string);
|
118
|
+
|
119
|
+
int cs, top, act;
|
120
|
+
int *stack = malloc(sizeof(int) * 300);
|
121
|
+
|
122
|
+
p = data;
|
123
|
+
char *pe = data + length;
|
124
|
+
char *eof = pe;
|
125
|
+
|
126
|
+
%% write init;
|
127
|
+
%% write exec;
|
128
|
+
|
129
|
+
free(stack);
|
130
|
+
|
131
|
+
if (ts > 0 && ((ts - data) < (pe - data - 1))) {
|
132
|
+
raise_unexpected_symbol;
|
133
|
+
}
|
134
|
+
|
135
|
+
return rb_iv_get(self, "@token_array");
|
136
|
+
}
|
137
|
+
|
138
|
+
static VALUE current_position(VALUE self) {
|
139
|
+
return INT2FIX(ts - data);
|
140
|
+
}
|
141
|
+
|
142
|
+
static VALUE current_value(VALUE self) {
|
143
|
+
VALUE string = rb_str_new(ts, te - ts);
|
144
|
+
return rb_funcall(string, rb_intern("force_encoding"), 1, encoding);
|
145
|
+
}
|
146
|
+
|
147
|
+
static VALUE current_error(VALUE self) {
|
148
|
+
VALUE parsed = rb_str_new(ts, p - ts == 0 ? 1 : p - ts);
|
149
|
+
VALUE encoded = rb_funcall(parsed, rb_intern("force_encoding"), 1, encoding);
|
150
|
+
|
151
|
+
VALUE source = rb_iv_get(self, "@source");
|
152
|
+
VALUE info = rb_funcall(source, rb_intern("info"), 1, INT2FIX(ts - data));
|
153
|
+
VALUE line = rb_funcall(info, rb_intern("[]"), 1, ID2SYM(rb_intern("line")));
|
154
|
+
VALUE column = rb_funcall(info, rb_intern("[]"), 1, ID2SYM(rb_intern("column")));
|
155
|
+
|
156
|
+
return rb_ary_new3(3, encoded, line, column);
|
157
|
+
}
|
158
|
+
|
159
|
+
void Init_lexerc() {
|
160
|
+
mHotcell = rb_const_get(rb_cObject, rb_intern("Hotcell"));
|
161
|
+
cHotcellLexer = rb_const_get(mHotcell, rb_intern("Lexer"));
|
162
|
+
|
163
|
+
rb_define_method(cHotcellLexer, "tokenize", tokenize, 0);
|
164
|
+
rb_define_method(cHotcellLexer, "current_position", current_position, 0);
|
165
|
+
rb_define_method(cHotcellLexer, "current_value", current_value, 0);
|
166
|
+
rb_define_method(cHotcellLexer, "current_error", current_error, 0);
|
167
|
+
}
|
data/hotcell.gemspec
CHANGED
@@ -4,19 +4,20 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'hotcell/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name =
|
7
|
+
gem.name = 'hotcell'
|
8
8
|
gem.version = Hotcell::VERSION
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
9
|
+
gem.authors = ['pyromaniac']
|
10
|
+
gem.email = ['kinwizard@gmail.com']
|
11
11
|
gem.description = %q{Sandboxed ruby template processor}
|
12
12
|
gem.summary = %q{Sandboxed ruby template processor}
|
13
|
-
gem.homepage =
|
13
|
+
gem.homepage = ''
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.extensions = ['ext/lexerc/extconf.rb']
|
16
17
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
-
gem.require_paths = [
|
19
|
+
gem.require_paths = ['lib']
|
19
20
|
|
20
|
-
gem.add_runtime_dependency
|
21
|
-
gem.add_runtime_dependency
|
21
|
+
gem.add_runtime_dependency 'racc'
|
22
|
+
gem.add_runtime_dependency 'activesupport'
|
22
23
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class Case < Hotcell::Block
|
4
|
+
class When < Hotcell::Command
|
5
|
+
validate_arguments_count min: 1
|
6
|
+
end
|
7
|
+
|
8
|
+
subcommand else: Hotcell::Commands::If::Else, when: When
|
9
|
+
validate_arguments_count 1
|
10
|
+
|
11
|
+
def subcommand_error subcommand, *allowed_names
|
12
|
+
raise Hotcell::BlockError.new(
|
13
|
+
"Unexpected `#{subcommand.name}` for `#{name}` command",
|
14
|
+
*subcommand.position_info
|
15
|
+
) unless allowed_names.flatten.include?(subcommand.name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def expected_when_error other = nil
|
19
|
+
raise Hotcell::BlockError.new(
|
20
|
+
"Expected `when` for `#{name}` command",
|
21
|
+
*(other ? other.position_info : position_info)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate!
|
26
|
+
subcommand_error subcommands.first, 'when' if subcommands.count == 1
|
27
|
+
|
28
|
+
last = subcommands.length - 1
|
29
|
+
subcommands.each_with_index do |subcommand, index|
|
30
|
+
subcommand_error subcommand, (index == last ? ['when', 'else'] : ['when'])
|
31
|
+
end
|
32
|
+
|
33
|
+
super
|
34
|
+
|
35
|
+
expected_when_error subnodes.first unless subnodes.first.is_a?(Command) && subnodes.first.name == 'when'
|
36
|
+
end
|
37
|
+
|
38
|
+
def process context, value
|
39
|
+
conditions = []
|
40
|
+
subnodes.each do |subnode|
|
41
|
+
if subnode.is_a?(Hotcell::Command)
|
42
|
+
conditions.last[1] = '' if conditions.last && conditions.last[1] == nil
|
43
|
+
conditions << (subnode.name == 'when' ? [subnode] : [true])
|
44
|
+
else
|
45
|
+
conditions.last[1] = subnode
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
condition = conditions.detect do |condition|
|
50
|
+
condition[0].is_a?(Hotcell::Command) ?
|
51
|
+
condition[0].render_children(context).include?(value) : condition[0]
|
52
|
+
end
|
53
|
+
condition ? condition[1].render(context) : nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Hotcell.register_command case: Hotcell::Commands::Case
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class Cycle < Hotcell::Command
|
4
|
+
def process context, *arguments
|
5
|
+
targets, group = normalize_arguments arguments
|
6
|
+
|
7
|
+
context.shared[:cycle] ||= {}
|
8
|
+
index = context.shared[:cycle][group] || 0
|
9
|
+
|
10
|
+
result = targets[index]
|
11
|
+
index += 1
|
12
|
+
index = 0 if index >= targets.size
|
13
|
+
|
14
|
+
context.shared[:cycle][group] = index
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
def normalize_arguments arguments
|
19
|
+
if arguments.count == 1
|
20
|
+
if arguments.first.is_a? Hash
|
21
|
+
[Array.wrap(arguments.first.values.first), arguments.first.keys.first]
|
22
|
+
else
|
23
|
+
[Array.wrap(arguments.first), default_group]
|
24
|
+
end
|
25
|
+
else
|
26
|
+
options = arguments.extract_options!
|
27
|
+
[arguments, options['group'].to_s.presence || default_group]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_group
|
32
|
+
@default_group ||= object_id.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Hotcell.register_command cycle: Hotcell::Commands::Cycle
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class For < Hotcell::Block
|
4
|
+
validate_arguments_count 2
|
5
|
+
|
6
|
+
def validate!
|
7
|
+
super
|
8
|
+
|
9
|
+
raise Hotcell::SyntaxError.new(
|
10
|
+
"Expected IDENTIFER as first argument in `#{name}` command", *position_info
|
11
|
+
) unless children[0].is_a?(Hotcell::Summoner)
|
12
|
+
|
13
|
+
children[0] = children[0].name
|
14
|
+
end
|
15
|
+
|
16
|
+
def process context, variable, options
|
17
|
+
forloop = options['loop'] == true ? 'loop' : options['loop']
|
18
|
+
items = Array.wrap(options['in'])
|
19
|
+
length = items.size
|
20
|
+
|
21
|
+
items.map.with_index do |item, index|
|
22
|
+
scope = { variable => item }
|
23
|
+
scope.merge!(forloop => Forloop.new(items, index)) if forloop
|
24
|
+
|
25
|
+
context.scoped scope do
|
26
|
+
subnodes.first.try(:render, context)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Forloop < Hotcell::Manipulator
|
32
|
+
attr_reader :index
|
33
|
+
|
34
|
+
def initialize object, index
|
35
|
+
@object, @index = object, index
|
36
|
+
end
|
37
|
+
|
38
|
+
def prev
|
39
|
+
@next ||= @object[index - 1] if index - 1 >= 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def next
|
43
|
+
@next ||= @object[index + 1]
|
44
|
+
end
|
45
|
+
|
46
|
+
def size
|
47
|
+
@size ||= @object.size
|
48
|
+
end
|
49
|
+
alias_method :length, :size
|
50
|
+
alias_method :count, :size
|
51
|
+
|
52
|
+
def rindex
|
53
|
+
@rindex ||= size - index - 1
|
54
|
+
end
|
55
|
+
|
56
|
+
def first
|
57
|
+
@first ||= index == 0
|
58
|
+
end
|
59
|
+
alias_method :first?, :first
|
60
|
+
|
61
|
+
def last
|
62
|
+
@last ||= index == size - 1
|
63
|
+
end
|
64
|
+
alias_method :last?, :last
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Hotcell.register_command for: Hotcell::Commands::For
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class If < Hotcell::Block
|
4
|
+
class Else < Hotcell::Command
|
5
|
+
validate_arguments_count 0
|
6
|
+
end
|
7
|
+
|
8
|
+
class Elsif < Hotcell::Command
|
9
|
+
validate_arguments_count 1
|
10
|
+
end
|
11
|
+
|
12
|
+
subcommand else: Else, elsif: Elsif
|
13
|
+
validate_arguments_count 1
|
14
|
+
|
15
|
+
def subcommand_error subcommand, *allowed_names
|
16
|
+
raise Hotcell::BlockError.new(
|
17
|
+
"Unexpected `#{subcommand.name}` for `#{name}` command",
|
18
|
+
*subcommand.position_info
|
19
|
+
) unless allowed_names.flatten.include?(subcommand.name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate!
|
23
|
+
last = subcommands.length - 1
|
24
|
+
subcommands.each_with_index do |subcommand, index|
|
25
|
+
subcommand_error subcommand, (index == last ? ['elsif', 'else'] : ['elsif'])
|
26
|
+
end
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
def process context, condition
|
32
|
+
conditions = [[condition]]
|
33
|
+
subnodes.each do |subnode|
|
34
|
+
if subnode.is_a?(Hotcell::Command)
|
35
|
+
conditions.last[1] = '' if conditions.last[1] == nil
|
36
|
+
conditions << (subnode.name == 'elsif' ? [subnode] : [true])
|
37
|
+
else
|
38
|
+
conditions.last[1] = subnode
|
39
|
+
end
|
40
|
+
end
|
41
|
+
condition = conditions.detect do |condition|
|
42
|
+
condition[0].is_a?(Hotcell::Command) ?
|
43
|
+
condition[0].render_children(context).first : condition[0]
|
44
|
+
end
|
45
|
+
condition ? condition[1].render(context) : nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Hotcell.register_command if: Hotcell::Commands::If
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class Include < Hotcell::Command
|
4
|
+
def process context, *arguments
|
5
|
+
locals = arguments.extract_options!
|
6
|
+
path = arguments.first
|
7
|
+
|
8
|
+
context.scoped locals do
|
9
|
+
template(path, context).render(context)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def template path, context
|
14
|
+
resolver = context.shared[:resolver] || Hotcell.resolver
|
15
|
+
resolver.template(path, context)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Hotcell.register_command include: Hotcell::Commands::Include
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class Scope < Hotcell::Block
|
4
|
+
def process context, scope = {}
|
5
|
+
context.scoped scope do
|
6
|
+
subnodes.first.try(:render, context)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Hotcell.register_command scope: Hotcell::Commands::Scope
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
class Unless < Hotcell::Block
|
4
|
+
subcommand else: Hotcell::Commands::If::Else
|
5
|
+
validate_arguments_count 1
|
6
|
+
|
7
|
+
def validate!
|
8
|
+
raise Hotcell::BlockError.new(
|
9
|
+
"Unexpected `#{subcommands[1].name}` for `#{name}` command",
|
10
|
+
*subcommands[1].position_info
|
11
|
+
) if subcommands[1]
|
12
|
+
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def process context, condition
|
17
|
+
condition ? subnodes[2].try(:render, context) : subnodes[0].try(:render, context)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Hotcell.register_command unless: Hotcell::Commands::Unless
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Hotcell
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'hotcell/commands/if'
|
8
|
+
require 'hotcell/commands/unless'
|
9
|
+
require 'hotcell/commands/for'
|
10
|
+
require 'hotcell/commands/scope'
|
11
|
+
require 'hotcell/commands/cycle'
|
12
|
+
require 'hotcell/commands/case'
|
13
|
+
require 'hotcell/commands/include'
|
data/lib/hotcell/config.rb
CHANGED
@@ -4,22 +4,37 @@ module Hotcell
|
|
4
4
|
class Config
|
5
5
|
include Singleton
|
6
6
|
|
7
|
-
attr_reader :commands, :blocks, :
|
7
|
+
attr_reader :commands, :blocks, :helpers
|
8
|
+
attr_accessor :resolver
|
8
9
|
|
9
10
|
def initialize
|
10
11
|
@commands = {}
|
11
12
|
@blocks = {}
|
12
|
-
@
|
13
|
+
@helpers = []
|
14
|
+
@resolver = Hotcell::Resolver.new
|
13
15
|
end
|
14
16
|
|
15
|
-
|
17
|
+
# Adds command or block to the list of default commands or blocks returned
|
18
|
+
# by `Hotcell.commands` and `Hotcell.blocks` accessors respectively
|
19
|
+
#
|
20
|
+
# Usage:
|
21
|
+
#
|
22
|
+
# Hotcell.register_command :block, BlockClass
|
23
|
+
# Hotcell.register_command :command, CommandClass
|
24
|
+
# Hotcell.register_command block: BlockClass, command: CommandClass
|
25
|
+
#
|
26
|
+
def register_command name, klass = nil
|
27
|
+
if name.is_a? Hash
|
28
|
+
name.each do |(name, klass)|
|
29
|
+
register_command name, klass
|
30
|
+
end
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
16
34
|
name = name.to_s
|
17
35
|
if klass < ::Hotcell::Block
|
18
36
|
raise "Command `#{name}` already defined, you can not define block with the same name" if commands.key?(name)
|
19
37
|
blocks[name] = klass
|
20
|
-
klass.subcommands.each do |subcommand|
|
21
|
-
subcommands[subcommand] = klass
|
22
|
-
end
|
23
38
|
elsif klass < ::Hotcell::Command
|
24
39
|
raise "Block `#{name}` already defined, you can not define command with the same name" if blocks.key?(name)
|
25
40
|
commands[name] = klass
|
@@ -27,5 +42,17 @@ module Hotcell
|
|
27
42
|
raise "Cannot register command `#{name}` because handler is not a Hotcell::Command or Hotcell::Block"
|
28
43
|
end
|
29
44
|
end
|
45
|
+
|
46
|
+
# Adds helper to the list of default helpers, accessible by `Hotcell.helpers`
|
47
|
+
#
|
48
|
+
# Usage:
|
49
|
+
#
|
50
|
+
# Hotcell.register_helpers Helper
|
51
|
+
# Hotcell.register_helpers Helper1, Helper2
|
52
|
+
# Hotcell.register_helpers helpers_array
|
53
|
+
#
|
54
|
+
def register_helpers *helpers
|
55
|
+
@helpers |= helpers.flatten
|
56
|
+
end
|
30
57
|
end
|
31
58
|
end
|
data/lib/hotcell/context.rb
CHANGED
@@ -2,35 +2,68 @@ require 'hotcell/scope'
|
|
2
2
|
|
3
3
|
module Hotcell
|
4
4
|
class Context
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :options
|
6
6
|
delegate :[], :[]=, :key?, :scoped, to: :scope
|
7
7
|
|
8
8
|
DEFAULT_RESCUER = ->(e){ "#{e.class}: #{e.message}" }
|
9
9
|
|
10
10
|
def initialize options = {}
|
11
|
+
@options = normalize_options options
|
12
|
+
end
|
13
|
+
|
14
|
+
def normalize_options options
|
11
15
|
options = options.dup
|
12
16
|
|
13
17
|
scope = options.delete(:scope) || {}
|
14
18
|
scope.merge! (options.delete(:variables) || {}).stringify_keys
|
15
19
|
scope.merge! (options.delete(:environment) || {}).symbolize_keys
|
16
20
|
|
17
|
-
|
18
|
-
|
21
|
+
shared = options.delete(:shared).presence || {}
|
22
|
+
helpers = Array.wrap(options.delete(:helpers))
|
23
|
+
|
24
|
+
rescuer = options.delete(:rescuer) || DEFAULT_RESCUER
|
25
|
+
reraise = !!options.delete(:reraise)
|
26
|
+
|
27
|
+
scope.merge!(options.stringify_keys)
|
19
28
|
|
20
|
-
|
29
|
+
{ scope: scope, shared: shared, helpers: helpers, reraise: reraise, rescuer: rescuer }
|
30
|
+
end
|
31
|
+
|
32
|
+
def scope
|
33
|
+
@scope ||= Scope.new options[:scope]
|
34
|
+
end
|
35
|
+
|
36
|
+
def shared
|
37
|
+
@shared ||= options[:shared]
|
21
38
|
end
|
22
39
|
|
23
40
|
def safe *default
|
24
41
|
yield
|
25
42
|
rescue => e
|
26
|
-
rescue_result = rescuer.call(e)
|
43
|
+
rescue_result = options[:rescuer].call(e)
|
27
44
|
default.size > 0 ? default.first : rescue_result
|
28
45
|
ensure
|
29
|
-
raise e if e && reraise
|
46
|
+
raise e if e && options[:reraise]
|
30
47
|
end
|
31
48
|
|
32
49
|
def manipulator_invoke method, *arguments
|
33
|
-
|
50
|
+
if arguments.any?
|
51
|
+
helpers.manipulator_invoke(method, *arguments)
|
52
|
+
else
|
53
|
+
scope.key?(method) ? scope[method] : helpers.manipulator_invoke(method)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def helpers
|
60
|
+
@helpers ||= helpers_class.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def helpers_class
|
64
|
+
@helpers_class ||= Class.new(Hotcell::Manipulator).tap do |klass|
|
65
|
+
options[:helpers].each { |helper| klass.send(:include, helper) }
|
66
|
+
end
|
34
67
|
end
|
35
68
|
end
|
36
69
|
end
|