cucumber 4.0.0.rc.1 → 4.0.0.rc.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +97 -4
  3. data/CONTRIBUTING.md +1 -18
  4. data/README.md +4 -5
  5. data/lib/autotest/cucumber_mixin.rb +2 -10
  6. data/lib/cucumber.rb +1 -1
  7. data/lib/cucumber/cli/configuration.rb +1 -1
  8. data/lib/cucumber/cli/main.rb +1 -0
  9. data/lib/cucumber/cli/options.rb +18 -13
  10. data/lib/cucumber/cli/profile_loader.rb +23 -12
  11. data/lib/cucumber/configuration.rb +11 -2
  12. data/lib/cucumber/deprecate.rb +29 -5
  13. data/lib/cucumber/errors.rb +5 -2
  14. data/lib/cucumber/events.rb +12 -7
  15. data/lib/cucumber/events/envelope.rb +9 -0
  16. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  17. data/lib/cucumber/events/test_case_created.rb +13 -0
  18. data/lib/cucumber/events/test_case_ready.rb +12 -0
  19. data/lib/cucumber/events/test_step_created.rb +13 -0
  20. data/lib/cucumber/filters.rb +1 -0
  21. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  22. data/lib/cucumber/formatter/ast_lookup.rb +43 -38
  23. data/lib/cucumber/formatter/backtrace_filter.rb +4 -1
  24. data/lib/cucumber/formatter/console.rb +4 -9
  25. data/lib/cucumber/formatter/console_issues.rb +1 -1
  26. data/lib/cucumber/formatter/duration.rb +1 -1
  27. data/lib/cucumber/formatter/duration_extractor.rb +2 -0
  28. data/lib/cucumber/formatter/errors.rb +6 -0
  29. data/lib/cucumber/formatter/html.rb +24 -0
  30. data/lib/cucumber/formatter/http_io.rb +146 -0
  31. data/lib/cucumber/formatter/interceptor.rb +3 -21
  32. data/lib/cucumber/formatter/io.rb +14 -8
  33. data/lib/cucumber/formatter/json.rb +46 -36
  34. data/lib/cucumber/formatter/junit.rb +13 -11
  35. data/lib/cucumber/formatter/message.rb +22 -0
  36. data/lib/cucumber/formatter/message_builder.rb +243 -0
  37. data/lib/cucumber/formatter/pretty.rb +65 -60
  38. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  39. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  40. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  41. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  42. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  43. data/lib/cucumber/formatter/summary.rb +1 -1
  44. data/lib/cucumber/formatter/usage.rb +3 -3
  45. data/lib/cucumber/gherkin/data_table_parser.rb +12 -3
  46. data/lib/cucumber/gherkin/steps_parser.rb +13 -3
  47. data/lib/cucumber/glue/hook.rb +18 -2
  48. data/lib/cucumber/glue/proto_world.rb +30 -18
  49. data/lib/cucumber/glue/registry_and_more.rb +40 -3
  50. data/lib/cucumber/glue/snippet.rb +2 -2
  51. data/lib/cucumber/glue/step_definition.rb +28 -4
  52. data/lib/cucumber/hooks.rb +8 -8
  53. data/lib/cucumber/multiline_argument.rb +1 -1
  54. data/lib/cucumber/multiline_argument/data_table.rb +17 -13
  55. data/lib/cucumber/platform.rb +1 -1
  56. data/lib/cucumber/rake/task.rb +3 -0
  57. data/lib/cucumber/runtime.rb +29 -3
  58. data/lib/cucumber/runtime/after_hooks.rb +6 -2
  59. data/lib/cucumber/runtime/before_hooks.rb +6 -2
  60. data/lib/cucumber/runtime/for_programming_languages.rb +1 -0
  61. data/lib/cucumber/runtime/step_hooks.rb +3 -2
  62. data/lib/cucumber/runtime/support_code.rb +3 -3
  63. data/lib/cucumber/runtime/user_interface.rb +2 -10
  64. data/lib/cucumber/step_definitions.rb +2 -2
  65. data/lib/cucumber/version +1 -1
  66. metadata +227 -73
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gherkin/gherkin'
3
+ require 'gherkin'
4
4
  require 'gherkin/dialect'
5
5
 
6
6
  module Cucumber
@@ -12,9 +12,10 @@ module Cucumber
12
12
 
13
13
  def parse(text)
14
14
  gherkin_document = nil
15
- messages = ::Gherkin::Gherkin.from_source('dummy', feature_header + text, include_source: false, include_pickles: false)
15
+ messages = ::Gherkin.from_source('dummy', feature_header + text, gherkin_options)
16
+
16
17
  messages.each do |message|
17
- gherkin_document = message.gherkinDocument.to_hash unless message.gherkinDocument.nil?
18
+ gherkin_document = message.gherkin_document.to_hash unless message.gherkin_document.nil?
18
19
  end
19
20
 
20
21
  return if gherkin_document.nil?
@@ -23,6 +24,14 @@ module Cucumber
23
24
  end
24
25
  end
25
26
 
27
+ def gherkin_options
28
+ {
29
+ include_source: false,
30
+ include_gherkin_document: true,
31
+ include_pickles: false
32
+ }
33
+ end
34
+
26
35
  def feature_header
27
36
  dialect = ::Gherkin::Dialect.for('en')
28
37
  %(#{dialect.feature_keywords[0]}:
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gherkin/gherkin'
3
+ require 'gherkin'
4
4
  require 'gherkin/dialect'
5
5
 
6
6
  module Cucumber
@@ -14,14 +14,24 @@ module Cucumber
14
14
  def parse(text)
15
15
  dialect = ::Gherkin::Dialect.for(@language)
16
16
  gherkin_document = nil
17
- messages = ::Gherkin::Gherkin.from_source('dummy', feature_header(dialect) + text, default_dialect: @language, include_source: false, include_pickles: false)
17
+ messages = ::Gherkin.from_source('dummy', feature_header(dialect) + text, gherkin_options)
18
+
18
19
  messages.each do |message|
19
- gherkin_document = message.gherkinDocument.to_hash unless message.gherkinDocument.nil?
20
+ gherkin_document = message.gherkin_document.to_hash unless message.gherkin_document.nil?
20
21
  end
21
22
 
22
23
  @builder.steps(gherkin_document[:feature][:children][0][:scenario][:steps])
23
24
  end
24
25
 
26
+ def gherkin_options
27
+ {
28
+ default_dialect: @language,
29
+ include_source: false,
30
+ include_gherkin_document: true,
31
+ include_pickles: false
32
+ }
33
+ end
34
+
25
35
  def feature_header(dialect)
26
36
  %(#{dialect.feature_keywords[0]}:
27
37
  #{dialect.scenario_keywords[0]}:
@@ -6,9 +6,10 @@ module Cucumber
6
6
  module Glue
7
7
  # TODO: Kill pointless wrapper for Before, After and AfterStep hooks with fire
8
8
  class Hook
9
- attr_reader :tag_expressions, :location
9
+ attr_reader :id, :tag_expressions, :location
10
10
 
11
- def initialize(registry, tag_expressions, proc)
11
+ def initialize(id, registry, tag_expressions, proc)
12
+ @id = id
12
13
  @registry = registry
13
14
  @tag_expressions = sanitize_tag_expressions(tag_expressions)
14
15
  @proc = proc
@@ -27,6 +28,21 @@ module Cucumber
27
28
  )
28
29
  end
29
30
 
31
+ def to_envelope
32
+ Cucumber::Messages::Envelope.new(
33
+ hook: Cucumber::Messages::Hook.new(
34
+ id: id,
35
+ tag_expression: tag_expressions.join(' '),
36
+ source_reference: Cucumber::Messages::SourceReference.new(
37
+ uri: location.file,
38
+ location: Cucumber::Messages::Location.new(
39
+ line: location.lines.first
40
+ )
41
+ )
42
+ )
43
+ )
44
+ end
45
+
30
46
  private
31
47
 
32
48
  def sanitize_tag_expressions(tag_expressions)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cucumber/gherkin/formatter/ansi_escapes'
4
4
  require 'cucumber/core/test/data_table'
5
+ require 'cucumber/deprecate'
5
6
 
6
7
  module Cucumber
7
8
  module Glue
@@ -67,9 +68,8 @@ module Cucumber
67
68
  # %w{ CUC-101 Peeler 22 }
68
69
  # ])
69
70
  #
70
- def table(text_or_table, file = nil, line = 0)
71
- location = !file ? Core::Test::Location.of_caller : Core::Test::Location.new(file, line)
72
- MultilineArgument::DataTable.from(text_or_table, location)
71
+ def table(text_or_table)
72
+ MultilineArgument::DataTable.from(text_or_table)
73
73
  end
74
74
 
75
75
  # Print a message to the output.
@@ -80,7 +80,16 @@ module Cucumber
80
80
  #
81
81
  # If you'd prefer to see the message immediately, call {Kernel.puts} instead.
82
82
  def puts(*messages)
83
- super
83
+ Cucumber.deprecate(
84
+ 'Messages emitted with "puts" will no longer be caught by Cucumber ' \
85
+ 'and sent to the formatter. If you want message to be in the formatted output, ' \
86
+ "please use log(message) instead.\n" \
87
+ 'If you simply want it in the console, '\
88
+ 'keep using "puts" (or Kernel.puts to avoid this message)',
89
+ 'puts(message)',
90
+ '5.0.0'
91
+ )
92
+ messages.each { |message| log(message.to_s) }
84
93
  end
85
94
 
86
95
  # Pause the tests and ask the operator for input
@@ -89,7 +98,21 @@ module Cucumber
89
98
  end
90
99
 
91
100
  # Embed an image in the output
92
- def embed(file, mime_type, label = 'Screenshot')
101
+ def embed(file, mime_type, _label = 'Screenshot')
102
+ Cucumber.deprecate(
103
+ 'Please use attach(file, media_type) instead',
104
+ 'embed(file, mime_type, label)',
105
+ '5.0.0'
106
+ )
107
+ attach(file, mime_type)
108
+ end
109
+
110
+ def log(message)
111
+ raise Cucumber::LogTypeInvalid unless message.is_a?(String)
112
+ attach(message.dup, 'text/x.cucumber.log+plain')
113
+ end
114
+
115
+ def attach(file, media_type)
93
116
  super
94
117
  end
95
118
 
@@ -146,23 +169,12 @@ module Cucumber
146
169
  runtime.invoke_dynamic_steps(steps_text, language, location)
147
170
  end
148
171
 
149
- # rubocop:disable UnneededInterpolation
150
- define_method(:puts) do |*messages|
151
- # Even though they won't be output until later, converting the messages to
152
- # strings right away will protect them from modifications to their original
153
- # objects in the mean time
154
- messages.collect! { |message| "#{message}" }
155
-
156
- runtime.puts(*messages)
157
- end
158
- # rubocop:enable UnneededInterpolation
159
-
160
172
  define_method(:ask) do |question, timeout_seconds = 60|
161
173
  runtime.ask(question, timeout_seconds)
162
174
  end
163
175
 
164
- define_method(:embed) do |file, mime_type, label = 'Screenshot'|
165
- runtime.embed(file, mime_type, label)
176
+ define_method(:attach) do |file, media_type|
177
+ runtime.attach(file, media_type)
166
178
  end
167
179
 
168
180
  # Prints the list of modules that are included in the World
@@ -72,18 +72,33 @@ module Cucumber
72
72
  end
73
73
 
74
74
  def register_rb_hook(phase, tag_expressions, proc)
75
- add_hook(phase, Hook.new(self, tag_expressions, proc))
75
+ hook = add_hook(phase, Hook.new(@configuration.id_generator.new_id, self, tag_expressions, proc))
76
+ @configuration.notify :envelope, hook.to_envelope
77
+ hook
76
78
  end
77
79
 
78
80
  def define_parameter_type(parameter_type)
81
+ @configuration.notify :envelope, parameter_type_envelope(parameter_type)
82
+
79
83
  @parameter_type_registry.define_parameter_type(parameter_type)
80
84
  end
81
85
 
82
86
  def register_rb_step_definition(string_or_regexp, proc_or_sym, options)
83
- step_definition = StepDefinition.new(self, string_or_regexp, proc_or_sym, options)
87
+ step_definition = StepDefinition.new(@configuration.id_generator.new_id, self, string_or_regexp, proc_or_sym, options)
84
88
  @step_definitions << step_definition
85
89
  @configuration.notify :step_definition_registered, step_definition
90
+ @configuration.notify :envelope, step_definition.to_envelope
86
91
  step_definition
92
+ rescue Cucumber::CucumberExpressions::UndefinedParameterTypeError => e
93
+ # TODO: add a way to extract the parameter type directly from the error.
94
+ type_name = e.message.match(/^Undefined parameter type \{(.*)\}$/)[1]
95
+
96
+ @configuration.notify :envelope, Cucumber::Messages::Envelope.new(
97
+ undefined_parameter_type: Cucumber::Messages::UndefinedParameterType.new(
98
+ name: type_name,
99
+ expression: string_or_regexp
100
+ )
101
+ )
87
102
  end
88
103
 
89
104
  def build_rb_world_factory(world_modules, namespaced_world_modules, proc)
@@ -102,7 +117,14 @@ module Cucumber
102
117
 
103
118
  def load_code_file(code_file)
104
119
  return unless File.extname(code_file) == '.rb'
105
- load File.expand_path(code_file) # This will cause self.add_step_definition, self.add_hook, and self.define_parameter_type to be called from Glue::Dsl
120
+
121
+ # This will cause self.add_step_definition, self.add_hook, and self.define_parameter_type to be called from Glue::Dsl
122
+
123
+ if Cucumber.use_legacy_autoloader
124
+ load File.expand_path(code_file)
125
+ else
126
+ require File.expand_path(code_file)
127
+ end
106
128
  end
107
129
 
108
130
  def begin_scenario(test_case)
@@ -164,6 +186,21 @@ module Cucumber
164
186
 
165
187
  private
166
188
 
189
+ def parameter_type_envelope(parameter_type)
190
+ # TODO: should me moved to Cucumber::Expression::ParameterType#to_envelope ?
191
+ # Note: that would mean that cucumber-expression would depend on cucumber-messages
192
+
193
+ Cucumber::Messages::Envelope.new(
194
+ parameter_type: Cucumber::Messages::ParameterType.new(
195
+ id: @configuration.id_generator.new_id,
196
+ name: parameter_type.name,
197
+ regular_expressions: parameter_type.regexps.map(&:to_s),
198
+ prefer_for_regular_expression_match: parameter_type.prefer_for_regexp_match?,
199
+ use_for_snippets: parameter_type.use_for_snippets?
200
+ )
201
+ )
202
+ end
203
+
167
204
  def available_step_definition_hash
168
205
  @available_step_definition_hash ||= {}
169
206
  end
@@ -92,7 +92,7 @@ module Cucumber
92
92
  def to_s
93
93
  header = generated_expressions.each_with_index.map do |expr, i|
94
94
  prefix = i.zero? ? '' : '# '
95
- "#{prefix}#{code_keyword}(\"#{expr.source}\") do#{parameters(expr)}"
95
+ "#{prefix}#{code_keyword}('#{expr.source}') do#{parameters(expr)}"
96
96
  end.join("\n")
97
97
 
98
98
  body = String.new # rubocop:disable Style/EmptyLiteral
@@ -174,7 +174,7 @@ module Cucumber
174
174
 
175
175
  class DocString
176
176
  def append_block_parameter_to(array)
177
- array << 'string'
177
+ array << 'doc_string'
178
178
  end
179
179
 
180
180
  def append_comment_to(string); end
@@ -24,9 +24,9 @@ module Cucumber
24
24
  end
25
25
 
26
26
  class << self
27
- def new(registry, string_or_regexp, proc_or_sym, options)
27
+ def new(id, registry, string_or_regexp, proc_or_sym, options)
28
28
  raise MissingProc if proc_or_sym.nil?
29
- super registry, registry.create_expression(string_or_regexp), create_proc(proc_or_sym, options)
29
+ super id, registry, registry.create_expression(string_or_regexp), create_proc(proc_or_sym, options)
30
30
  end
31
31
 
32
32
  private
@@ -62,16 +62,40 @@ module Cucumber
62
62
  end
63
63
  end
64
64
 
65
- attr_reader :expression, :registry
65
+ attr_reader :id, :expression, :registry
66
66
 
67
- def initialize(registry, expression, proc)
67
+ def initialize(id, registry, expression, proc)
68
68
  raise 'No regexp' if expression.is_a?(Regexp)
69
+ @id = id
69
70
  @registry = registry
70
71
  @expression = expression
71
72
  @proc = proc
72
73
  # @registry.available_step_definition(regexp_source, location)
73
74
  end
74
75
 
76
+ def to_envelope
77
+ Cucumber::Messages::Envelope.new(
78
+ step_definition: Cucumber::Messages::StepDefinition.new(
79
+ id: id,
80
+ pattern: Cucumber::Messages::StepDefinition::StepDefinitionPattern.new(
81
+ source: expression.source.to_s,
82
+ type: expression_type
83
+ ),
84
+ source_reference: Cucumber::Messages::SourceReference.new(
85
+ uri: location.file,
86
+ location: Cucumber::Messages::Location.new(
87
+ line: location.lines.first
88
+ )
89
+ )
90
+ )
91
+ )
92
+ end
93
+
94
+ def expression_type
95
+ return Cucumber::Messages::StepDefinition::StepDefinitionPattern::StepDefinitionPatternType::CUCUMBER_EXPRESSION if expression.is_a?(CucumberExpressions::CucumberExpression)
96
+ Cucumber::Messages::StepDefinition::StepDefinitionPattern::StepDefinitionPatternType::REGULAR_EXPRESSION
97
+ end
98
+
75
99
  # @api private
76
100
  def to_hash
77
101
  type = expression.is_a?(CucumberExpressions::RegularExpression) ? 'regular expression' : 'cucumber expression'
@@ -9,17 +9,17 @@ module Cucumber
9
9
  # source for test steps
10
10
  module Hooks
11
11
  class << self
12
- def before_hook(location, &block)
13
- build_hook_step(location, block, BeforeHook, Core::Test::UnskippableAction)
12
+ def before_hook(id, location, &block)
13
+ build_hook_step(id, location, block, BeforeHook, Core::Test::UnskippableAction)
14
14
  end
15
15
 
16
- def after_hook(location, &block)
17
- build_hook_step(location, block, AfterHook, Core::Test::UnskippableAction)
16
+ def after_hook(id, location, &block)
17
+ build_hook_step(id, location, block, AfterHook, Core::Test::UnskippableAction)
18
18
  end
19
19
 
20
- def after_step_hook(test_step, location, &block)
20
+ def after_step_hook(id, test_step, location, &block)
21
21
  raise ArgumentError if test_step.hook?
22
- build_hook_step(location, block, AfterStepHook, Core::Test::Action)
22
+ build_hook_step(id, location, block, AfterStepHook, Core::Test::Action)
23
23
  end
24
24
 
25
25
  def around_hook(&block)
@@ -28,10 +28,10 @@ module Cucumber
28
28
 
29
29
  private
30
30
 
31
- def build_hook_step(location, block, hook_type, action_type)
31
+ def build_hook_step(id, location, block, hook_type, action_type)
32
32
  action = action_type.new(location, &block)
33
33
  hook = hook_type.new(action.location)
34
- Core::Test::HookStep.new(hook.text, location, action)
34
+ Core::Test::HookStep.new(id, hook.text, location, action)
35
35
  end
36
36
  end
37
37
 
@@ -15,7 +15,7 @@ module Cucumber
15
15
  location ||= Core::Test::Location.of_caller
16
16
  case argument
17
17
  when String
18
- builder.doc_string(Core::Test::DocString.new(argument, content_type, location))
18
+ builder.doc_string(Core::Test::DocString.new(argument, content_type))
19
19
  when Array
20
20
  location = location.on_line(argument.first.line..argument.last.line)
21
21
  builder.data_table(argument.map(&:cells), location)
@@ -36,12 +36,12 @@ module Cucumber
36
36
  end
37
37
 
38
38
  class << self
39
- def from(data, location = Core::Test::Location.of_caller)
39
+ def from(data)
40
40
  case data
41
41
  when Array
42
- from_array(data, location)
42
+ from_array(data)
43
43
  when String
44
- parse(data, location)
44
+ parse(data)
45
45
  else
46
46
  raise ArgumentError, 'expected data to be a String or an Array.'
47
47
  end
@@ -49,15 +49,15 @@ module Cucumber
49
49
 
50
50
  private
51
51
 
52
- def parse(text, location = Core::Test::Location.of_caller)
52
+ def parse(text)
53
53
  builder = Builder.new
54
54
  parser = Cucumber::Gherkin::DataTableParser.new(builder)
55
55
  parser.parse(text)
56
- from_array(builder.rows, location)
56
+ from_array(builder.rows)
57
57
  end
58
58
 
59
- def from_array(data, location = Core::Test::Location.of_caller)
60
- new Core::Test::DataTable.new(data, location)
59
+ def from_array(data)
60
+ new Core::Test::DataTable.new(data)
61
61
  end
62
62
  end
63
63
 
@@ -111,7 +111,7 @@ module Cucumber
111
111
  # registered with #map_column! and #map_headers!.
112
112
  #
113
113
  def dup
114
- self.class.new(Core::Test::DataTable.new(raw, location), @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
114
+ self.class.new(Core::Test::DataTable.new(raw), @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
115
115
  end
116
116
 
117
117
  # Returns a new, transposed table. Example:
@@ -126,7 +126,7 @@ module Cucumber
126
126
  # | 4 | 2 |
127
127
  #
128
128
  def transpose
129
- self.class.new(Core::Test::DataTable.new(raw.transpose, location), @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
129
+ self.class.new(Core::Test::DataTable.new(raw.transpose), @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
130
130
  end
131
131
 
132
132
  # Converts this table into an Array of Hash where the keys of each
@@ -269,7 +269,7 @@ module Cucumber
269
269
 
270
270
  # Returns a new Table where the headers are redefined. See #map_headers!
271
271
  def map_headers(mappings = {}, &block)
272
- self.class.new(Core::Test::DataTable.new(raw, location), @conversion_procs.dup, mappings, block)
272
+ self.class.new(Core::Test::DataTable.new(raw), @conversion_procs.dup, mappings, block)
273
273
  end
274
274
 
275
275
  # Change how #hashes converts column values. The +column_name+ argument identifies the column
@@ -294,7 +294,7 @@ module Cucumber
294
294
  def map_column(column_name, strict = true, &conversion_proc)
295
295
  conversion_procs = @conversion_procs.dup
296
296
  conversion_procs[column_name.to_s] = { strict: strict, proc: conversion_proc }
297
- self.class.new(Core::Test::DataTable.new(raw, location), conversion_procs, @header_mappings.dup, @header_conversion_proc)
297
+ self.class.new(Core::Test::DataTable.new(raw), conversion_procs, @header_mappings.dup, @header_conversion_proc)
298
298
  end
299
299
 
300
300
  # Compares +other_table+ to self. If +other_table+ contains columns
@@ -352,7 +352,11 @@ module Cucumber
352
352
  end
353
353
  end
354
354
 
355
- def to_hash(cells) #:nodoc:
355
+ def to_hash
356
+ cells_rows.map { |cells| cells.map(&:value) }
357
+ end
358
+
359
+ def cells_to_hash(cells) #:nodoc:
356
360
  hash = Hash.new do |hash_inner, key|
357
361
  hash_inner[key.to_s] if key.is_a?(Symbol)
358
362
  end
@@ -551,7 +555,7 @@ module Cucumber
551
555
  end
552
556
 
553
557
  def to_hash #:nodoc:
554
- @to_hash ||= @table.to_hash(self)
558
+ @to_hash ||= @table.cells_to_hash(self)
555
559
  end
556
560
 
557
561
  def value(n) #:nodoc: