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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -1
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -1
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -1
  7. data/README.md +361 -2
  8. data/Rakefile +28 -6
  9. data/ext/lexerc/extconf.rb +3 -0
  10. data/ext/lexerc/lexerc.c +618 -0
  11. data/ext/lexerc/lexerc.h +20 -0
  12. data/ext/lexerc/lexerc.rl +167 -0
  13. data/hotcell.gemspec +8 -7
  14. data/lib/hotcell/commands/case.rb +59 -0
  15. data/lib/hotcell/commands/cycle.rb +38 -0
  16. data/lib/hotcell/commands/for.rb +70 -0
  17. data/lib/hotcell/commands/if.rb +51 -0
  18. data/lib/hotcell/commands/include.rb +21 -0
  19. data/lib/hotcell/commands/scope.rb +13 -0
  20. data/lib/hotcell/commands/unless.rb +23 -0
  21. data/lib/hotcell/commands.rb +13 -0
  22. data/lib/hotcell/config.rb +33 -6
  23. data/lib/hotcell/context.rb +40 -7
  24. data/lib/hotcell/errors.rb +37 -28
  25. data/lib/hotcell/extensions.rb +4 -0
  26. data/lib/hotcell/lexer.rb +19 -635
  27. data/lib/hotcell/lexerr.rb +572 -0
  28. data/lib/hotcell/lexerr.rl +137 -0
  29. data/lib/hotcell/node/assigner.rb +1 -5
  30. data/lib/hotcell/node/block.rb +17 -40
  31. data/lib/hotcell/node/command.rb +29 -22
  32. data/lib/hotcell/node/hasher.rb +1 -1
  33. data/lib/hotcell/node/summoner.rb +2 -6
  34. data/lib/hotcell/node/tag.rb +10 -7
  35. data/lib/hotcell/node.rb +12 -1
  36. data/lib/hotcell/parser.rb +474 -408
  37. data/lib/hotcell/parser.y +175 -117
  38. data/lib/hotcell/resolver.rb +44 -0
  39. data/lib/hotcell/source.rb +35 -0
  40. data/lib/hotcell/template.rb +15 -6
  41. data/lib/hotcell/version.rb +1 -1
  42. data/lib/hotcell.rb +15 -10
  43. data/spec/data/templates/simple.hc +1 -0
  44. data/spec/lib/hotcell/commands/case_spec.rb +39 -0
  45. data/spec/lib/hotcell/commands/cycle_spec.rb +29 -0
  46. data/spec/lib/hotcell/commands/for_spec.rb +65 -0
  47. data/spec/lib/hotcell/commands/if_spec.rb +35 -0
  48. data/spec/lib/hotcell/commands/include_spec.rb +39 -0
  49. data/spec/lib/hotcell/commands/scope_spec.rb +16 -0
  50. data/spec/lib/hotcell/commands/unless_spec.rb +23 -0
  51. data/spec/lib/hotcell/config_spec.rb +35 -10
  52. data/spec/lib/hotcell/context_spec.rb +58 -18
  53. data/spec/lib/hotcell/lexer_spec.rb +37 -28
  54. data/spec/lib/hotcell/node/block_spec.rb +28 -56
  55. data/spec/lib/hotcell/node/command_spec.rb +7 -31
  56. data/spec/lib/hotcell/node/tag_spec.rb +16 -0
  57. data/spec/lib/hotcell/parser_spec.rb +152 -123
  58. data/spec/lib/hotcell/resolver_spec.rb +28 -0
  59. data/spec/lib/hotcell/source_spec.rb +41 -0
  60. data/spec/lib/hotcell/template_spec.rb +47 -4
  61. data/spec/lib/hotcell_spec.rb +2 -1
  62. data/spec/spec_helper.rb +6 -2
  63. metadata +54 -24
  64. data/lib/hotcell/.DS_Store +0 -0
  65. data/lib/hotcell/lexer.rl +0 -299
  66. data/misc/rage.rl +0 -1999
  67. 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 = "hotcell"
7
+ gem.name = 'hotcell'
8
8
  gem.version = Hotcell::VERSION
9
- gem.authors = ["pyromaniac"]
10
- gem.email = ["kinwizard@gmail.com"]
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 = ["lib"]
19
+ gem.require_paths = ['lib']
19
20
 
20
- gem.add_runtime_dependency "activesupport"
21
- gem.add_runtime_dependency "racc"
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'
@@ -4,22 +4,37 @@ module Hotcell
4
4
  class Config
5
5
  include Singleton
6
6
 
7
- attr_reader :commands, :blocks, :subcommands
7
+ attr_reader :commands, :blocks, :helpers
8
+ attr_accessor :resolver
8
9
 
9
10
  def initialize
10
11
  @commands = {}
11
12
  @blocks = {}
12
- @subcommands = {}
13
+ @helpers = []
14
+ @resolver = Hotcell::Resolver.new
13
15
  end
14
16
 
15
- def register_command name, klass
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
@@ -2,35 +2,68 @@ require 'hotcell/scope'
2
2
 
3
3
  module Hotcell
4
4
  class Context
5
- attr_reader :scope, :rescuer, :reraise
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
- @rescuer = options.delete(:rescuer) || DEFAULT_RESCUER
18
- @reraise = !!options.delete(:reraise)
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
- @scope = Scope.new scope.merge!(options.stringify_keys)
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
- scope[method]
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