hotcell 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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