hotcell 0.0.1 → 0.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.
- 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
|