gherkin 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitattributes +1 -0
  2. data/History.txt +18 -0
  3. data/README.rdoc +4 -1
  4. data/Rakefile +4 -2
  5. data/VERSION.yml +1 -1
  6. data/bin/gherkin +1 -1
  7. data/dotnet/.gitignore +13 -0
  8. data/features/feature_parser.feature +22 -2
  9. data/features/native_lexer.feature +1 -1
  10. data/features/parser_with_native_lexer.feature +1 -1
  11. data/features/step_definitions/gherkin_steps.rb +2 -6
  12. data/features/step_definitions/pretty_printer_steps.rb +2 -3
  13. data/features/steps_parser.feature +1 -1
  14. data/gherkin.gemspec +53 -24
  15. data/java/Gherkin.iml +2 -4
  16. data/java/build.xml +3 -0
  17. data/java/src/gherkin/FixJava.java +6 -3
  18. data/java/src/gherkin/I18nLexer.java +48 -0
  19. data/java/src/gherkin/Listener.java +3 -1
  20. data/java/src/gherkin/Main.java +17 -0
  21. data/java/src/gherkin/Parser.java +9 -3
  22. data/java/src/gherkin/formatter/Argument.java +39 -0
  23. data/java/src/gherkin/formatter/ArgumentFormat.java +17 -0
  24. data/java/src/gherkin/formatter/Colors.java +7 -0
  25. data/java/src/gherkin/formatter/Formatter.java +15 -0
  26. data/java/src/gherkin/formatter/PrettyFormatter.java +219 -0
  27. data/java/src/gherkin/parser/StateMachineReader.java +8 -3
  28. data/java/test/gherkin/formatter/ArgumentTest.java +17 -0
  29. data/lib/gherkin/csharp_lexer.rb +15 -0
  30. data/lib/gherkin/format/argument.rb +35 -0
  31. data/lib/gherkin/format/monochrome_format.rb +9 -0
  32. data/lib/gherkin/i18n.rb +22 -0
  33. data/lib/gherkin/i18n.yml +34 -20
  34. data/lib/gherkin/i18n_lexer.rb +57 -13
  35. data/lib/gherkin/lexer.rb +9 -18
  36. data/lib/gherkin/parser.rb +3 -3
  37. data/lib/gherkin/parser/meta.txt +5 -4
  38. data/lib/gherkin/parser/root.txt +11 -9
  39. data/lib/gherkin/parser/steps.txt +4 -3
  40. data/lib/gherkin/rb_parser.rb +13 -5
  41. data/lib/gherkin/tools/colors.rb +119 -0
  42. data/lib/gherkin/tools/files.rb +6 -1
  43. data/lib/gherkin/tools/pretty_listener.rb +115 -23
  44. data/ragel/lexer.c.rl.erb +67 -51
  45. data/ragel/lexer.csharp.rl.erb +240 -0
  46. data/ragel/lexer.java.rl.erb +27 -18
  47. data/ragel/lexer.rb.rl.erb +17 -17
  48. data/ragel/lexer_common.rl.erb +8 -8
  49. data/spec/gherkin/c_lexer_spec.rb +4 -4
  50. data/spec/gherkin/csharp_lexer_spec.rb +20 -0
  51. data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
  52. data/spec/gherkin/fixtures/complex.feature +2 -0
  53. data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
  54. data/spec/gherkin/fixtures/i18n_fr.feature +1 -0
  55. data/spec/gherkin/fixtures/i18n_no.feature +1 -0
  56. data/spec/gherkin/fixtures/i18n_zh-CN.feature +1 -0
  57. data/spec/gherkin/format/argument_spec.rb +28 -0
  58. data/spec/gherkin/i18n_lexer_spec.rb +4 -4
  59. data/spec/gherkin/i18n_spec.rb +31 -23
  60. data/spec/gherkin/java_lexer_spec.rb +4 -3
  61. data/spec/gherkin/parser_spec.rb +5 -0
  62. data/spec/gherkin/rb_lexer_spec.rb +4 -2
  63. data/spec/gherkin/sexp_recorder.rb +1 -1
  64. data/spec/gherkin/shared/lexer_spec.rb +169 -60
  65. data/spec/gherkin/shared/py_string_spec.rb +6 -0
  66. data/spec/gherkin/shared/row_spec.rb +107 -0
  67. data/spec/gherkin/shared/tags_spec.rb +1 -1
  68. data/spec/gherkin/tools/colors_spec.rb +19 -0
  69. data/spec/gherkin/tools/pretty_listener_spec.rb +147 -0
  70. data/spec/spec_helper.rb +31 -7
  71. data/tasks/compile.rake +81 -7
  72. data/tasks/ragel_task.rb +6 -4
  73. data/tasks/rspec.rake +2 -2
  74. metadata +104 -41
  75. data/lib/gherkin/java_lexer.rb +0 -10
  76. data/spec/gherkin/shared/table_spec.rb +0 -97
@@ -77,7 +77,7 @@ module Gherkin
77
77
 
78
78
  def expected
79
79
  allowed = @transition_map[@state].find_all { |_, action| action != "E" }
80
- allowed.collect { |state| state[0] }.sort
80
+ allowed.collect { |state| state[0] }.sort - ['eof']
81
81
  end
82
82
 
83
83
  private
@@ -100,18 +100,26 @@ module Gherkin
100
100
 
101
101
  def transition_table(name)
102
102
  state_machine_reader = StateMachineReader.new
103
- lexer = Gherkin::Lexer['en'].new(state_machine_reader)
103
+ lexer = Gherkin::I18nLexer.lexer_class('en', false).new(state_machine_reader)
104
104
  lexer.scan(File.read(File.dirname(__FILE__) + "/parser/#{name}.txt"))
105
105
  state_machine_reader.rows
106
106
  end
107
107
 
108
108
  class StateMachineReader
109
109
  attr_reader :rows
110
- def table(rows, line_number)
111
- @rows = rows
110
+
111
+ def initialize
112
+ @rows = []
113
+ end
114
+
115
+ def row(row, line_number)
116
+ @rows << row
117
+ end
118
+
119
+ def eof
112
120
  end
113
121
  end
114
122
 
115
123
  end
116
124
  end
117
- end
125
+ end
@@ -0,0 +1,119 @@
1
+ require 'term/ansicolor'
2
+
3
+ module Gherkin
4
+ module Tools
5
+ # Defines aliases for coloured output. You don't invoke any methods from this
6
+ # module directly, but you can change the output colours by defining
7
+ # a <tt>GHERKIN_COLORS</tt> variable in your shell, very much like how you can
8
+ # tweak the familiar POSIX command <tt>ls</tt> with
9
+ # <a href="http://mipsisrisc.com/rambling/2008/06/27/lscolorsls_colors-now-with-linux-support/">$LSCOLORS/$LS_COLORS</a>
10
+ #
11
+ # The colours that you can change are:
12
+ #
13
+ # * <tt>undefined</tt> - defaults to <tt>yellow</tt>
14
+ # * <tt>pending</tt> - defaults to <tt>yellow</tt>
15
+ # * <tt>pending_param</tt> - defaults to <tt>yellow,bold</tt>
16
+ # * <tt>failed</tt> - defaults to <tt>red</tt>
17
+ # * <tt>failed_param</tt> - defaults to <tt>red,bold</tt>
18
+ # * <tt>passed</tt> - defaults to <tt>green</tt>
19
+ # * <tt>passed_param</tt> - defaults to <tt>green,bold</tt>
20
+ # * <tt>outline</tt> - defaults to <tt>cyan</tt>
21
+ # * <tt>outline_param</tt> - defaults to <tt>cyan,bold</tt>
22
+ # * <tt>skipped</tt> - defaults to <tt>cyan</tt>
23
+ # * <tt>skipped_param</tt> - defaults to <tt>cyan,bold</tt>
24
+ # * <tt>comment</tt> - defaults to <tt>grey</tt>
25
+ # * <tt>tag</tt> - defaults to <tt>cyan</tt>
26
+ #
27
+ # For instance, if your shell has a black background and a green font (like the
28
+ # "Homebrew" settings for OS X' Terminal.app), you may want to override passed
29
+ # steps to be white instead of green. Examples:
30
+ #
31
+ # export GHERKIN_COLORS="passed=white"
32
+ # export GHERKIN_COLORS="passed=white,bold:passed_param=white,bold,underline"
33
+ #
34
+ # (If you're on Windows, use SET instead of export).
35
+ # To see what colours and effects are available, just run this in your shell:
36
+ #
37
+ # ruby -e "require 'rubygems'; require 'term/ansicolor'; puts Term::ANSIColor.attributes"
38
+ #
39
+ # Although not listed, you can also use <tt>grey</tt>
40
+ module Colors
41
+ include Term::ANSIColor
42
+
43
+ ALIASES = Hash.new do |h,k|
44
+ if k.to_s =~ /(.*)_param/
45
+ h[$1] + ',bold'
46
+ end
47
+ end.merge({
48
+ 'undefined' => 'yellow',
49
+ 'pending' => 'yellow',
50
+ 'failed' => 'red',
51
+ 'passed' => 'green',
52
+ 'outline' => 'cyan',
53
+ 'skipped' => 'cyan',
54
+ 'comments' => 'grey',
55
+ 'tag' => 'cyan'
56
+ })
57
+
58
+ if ENV['GHERKIN_COLORS'] # Example: export GHERKIN_COLORS="passed=red:failed=yellow"
59
+ ENV['GHERKIN_COLORS'].split(':').each do |pair|
60
+ a = pair.split('=')
61
+ ALIASES[a[0]] = a[1]
62
+ end
63
+ end
64
+
65
+ ALIASES.each do |method, color|
66
+ unless method =~ /.*_param/
67
+ code = <<-EOF
68
+ def #{method}(string=nil, monochrome=false, &proc)
69
+ return string if monochrome
70
+ #{ALIASES[method].split(",").join("(") + "(string, &proc" + ")" * ALIASES[method].split(",").length}
71
+ end
72
+ # This resets the colour to the non-param colour
73
+ def #{method}_param(string=nil, monochrome=false, &proc)
74
+ return string if monochrome
75
+ #{ALIASES[method+'_param'].split(",").join("(") + "(string, &proc" + ")" * ALIASES[method+'_param'].split(",").length} + #{ALIASES[method].split(",").join(' + ')}
76
+ end
77
+ EOF
78
+ eval(code)
79
+ end
80
+ end
81
+
82
+ def self.define_grey #:nodoc:
83
+ begin
84
+ gem 'genki-ruby-terminfo'
85
+ require 'terminfo'
86
+ case TermInfo.default_object.tigetnum("colors")
87
+ when 0
88
+ raise "Your terminal doesn't support colours"
89
+ when 1
90
+ ::Term::ANSIColor.coloring = false
91
+ alias grey white
92
+ when 2..8
93
+ alias grey white
94
+ else
95
+ define_real_grey
96
+ end
97
+ rescue Exception => e
98
+ if e.class.name == 'TermInfo::TermInfoError'
99
+ STDERR.puts "*** WARNING ***"
100
+ STDERR.puts "You have the genki-ruby-terminfo gem installed, but you haven't set your TERM variable."
101
+ STDERR.puts "Try setting it to TERM=xterm-256color to get grey colour in output"
102
+ STDERR.puts "\n"
103
+ alias grey white
104
+ else
105
+ define_real_grey
106
+ end
107
+ end
108
+ end
109
+
110
+ def self.define_real_grey #:nodoc:
111
+ def grey(m) #:nodoc:
112
+ "\e[90m#{m}\e[0m"
113
+ end
114
+ end
115
+
116
+ define_grey
117
+ end
118
+ end
119
+ end
@@ -23,7 +23,12 @@ module Gherkin
23
23
  def scan(file, listener)
24
24
  parser = Parser.new(listener, true)
25
25
  lexer = I18nLexer.new(parser)
26
- lexer.scan(IO.read(file))
26
+ begin
27
+ lexer.scan(IO.read(file))
28
+ rescue => e
29
+ e.message << " (#{file})"
30
+ raise e
31
+ end
27
32
  end
28
33
  end
29
34
  end
@@ -1,10 +1,32 @@
1
1
  # encoding: utf-8
2
+
3
+ require 'gherkin/tools/colors'
4
+ require 'gherkin/format/monochrome_format'
5
+
2
6
  module Gherkin
3
7
  module Tools
8
+ # TODO: Rename to Gherkin::Pretty::PrettyReporter - that's what this class *does*
9
+ # (The fact that it conforms to the Gherkin Listener interface is secondary)
4
10
  class PrettyListener
5
- def initialize(io)
11
+ include Gherkin::Tools::Colors
12
+
13
+ class << self
14
+ def new(io, monochrome=false)
15
+ if defined?(JRUBY_VERSION)
16
+ require 'gherkin.jar'
17
+ Java::GherkinFormatter::PrettyFormatter.new(io, monochrome)
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(io, monochrome=false)
6
25
  @io = io
26
+ @monochrome = monochrome
27
+ @format = @monochrome ? Format::MonochromeFormat.new : Format::AnsiColorFormat.new
7
28
  @tags = nil
29
+ @comments = nil
8
30
  end
9
31
 
10
32
  def tag(name, line)
@@ -13,45 +35,48 @@ module Gherkin
13
35
  end
14
36
 
15
37
  def comment(content, line)
16
- @io.puts content
38
+ @comments ||= []
39
+ @comments << content
17
40
  end
18
41
 
19
42
  def feature(keyword, name, line)
20
- tags = @tags ? @tags.join(' ') + "\n" : ''
21
- @tags = nil
22
- @io.puts "#{tags}#{keyword}: #{indent(name, ' ')}"
43
+ @io.puts "#{grab_comments!('')}#{grab_tags!('')}#{keyword}: #{indent(name, ' ')}"
23
44
  end
24
45
 
25
46
  def background(keyword, name, line)
26
- @io.puts "\n #{keyword}: #{indent(name, ' ')}"
47
+ @io.puts "\n#{grab_comments!(' ')} #{keyword}: #{indent(name, ' ')}"
27
48
  end
28
49
 
29
- def scenario(keyword, name, line)
30
- tags = @tags ? ' ' + @tags.join(' ') + "\n" : ''
31
- @tags = nil
32
- @io.puts "\n#{tags} #{keyword}: #{indent(name, ' ')}"
50
+ def scenario(keyword, name, line, location=nil)
51
+ flush_table
52
+ @io.puts "\n#{grab_comments!(' ')}#{grab_tags!(' ')} #{keyword}: #{indent(name, ' ')}#{indented_scenario_location!(keyword, name, location)}"
33
53
  end
34
54
 
35
55
  def scenario_outline(keyword, name, line)
36
- tags = @tags ? ' ' + @tags.join(' ') + "\n" : ''
37
- @tags = nil
38
- @io.puts "\n#{tags} #{keyword}: #{indent(name, ' ')}"
56
+ flush_table
57
+ @io.puts "\n#{grab_comments!(' ')}#{grab_tags!(' ')} #{keyword}: #{indent(name, ' ')}"
39
58
  end
40
59
 
41
60
  def examples(keyword, name, line)
42
- @io.puts "\n #{keyword}: #{indent(name, ' ')}"
61
+ flush_table
62
+ @io.puts "\n#{grab_comments!(' ')}#{grab_tags!(' ')} #{keyword}: #{indent(name, ' ')}"
43
63
  end
44
64
 
45
- def step(keyword, name, line)
46
- @io.puts " #{keyword} #{indent(name, ' ')}"
65
+ def step(keyword, name, line, status=nil, arguments=nil, location=nil)
66
+ flush_table
67
+ status_param = "#{status}_param" if status
68
+ name = Gherkin::Format::Argument.format(name, @format, (arguments || []))
69
+ #{|arg| status_param ? self.__send__(status_param, arg, @monochrome) : arg} if arguments
70
+
71
+ step = "#{keyword}#{indent(name, ' ')}"
72
+ step = self.__send__(status, step, @monochrome) if status
73
+
74
+ @io.puts("#{grab_comments!(' ')} #{step}#{indented_step_location!(location)}")
47
75
  end
48
76
 
49
- def table(rows, line)
50
- rows = rows.to_a.map {|row| row.to_a} if defined?(JRUBY_VERSION) # Convert ArrayList
51
- max_lengths = rows.transpose.map { |col| col.map { |cell| cell.unpack("U*").length }.max }.flatten
52
- rows.each do |table_line|
53
- @io.puts ' | ' + table_line.zip(max_lengths).map { |cell, max_length| cell + ' ' * (max_length-cell.unpack("U*").length) }.join(' | ') + ' |'
54
- end
77
+ def row(row, line)
78
+ @rows ||= []
79
+ @rows << row
55
80
  end
56
81
 
57
82
  def py_string(string, line)
@@ -62,7 +87,48 @@ module Gherkin
62
87
  raise "SYNTAX ERROR"
63
88
  end
64
89
 
65
- private
90
+ def eof
91
+ flush_table
92
+ end
93
+
94
+ # This method can be invoked before a #scenario, to ensure location arguments are aligned
95
+ def steps(steps)
96
+ @step_lengths = steps.map {|keyword, name| (keyword+name).unpack("U*").length}
97
+ @max_step_length = @step_lengths.max
98
+ @step_index = -1
99
+ end
100
+
101
+ def exception(exception)
102
+ exception_text = "#{exception.message} (#{exception.class})\n#{(exception.backtrace || []).join("\n")}".gsub(/^/, ' ')
103
+ @io.puts(failed(exception_text, @monochrome))
104
+ end
105
+
106
+ def flush_table(exception=nil, statuses=nil)
107
+ return if @rows.nil?
108
+ cell_lengths = @rows.map { |col| col.map { |cell| cell.unpack("U*").length }}
109
+ max_lengths = cell_lengths.transpose.map { |col_lengths| col_lengths.max }.flatten
110
+
111
+ @rows.each_with_index do |row, i|
112
+ j = -1
113
+ @io.puts ' | ' + row.zip(max_lengths).map { |cell, max_length|
114
+ j += 1
115
+ color(cell, statuses, j) + ' ' * (max_length - cell_lengths[i][j])
116
+ }.join(' | ') + ' |'
117
+ exception(exception) if exception
118
+ end
119
+ @rows = nil
120
+ end
121
+
122
+ private
123
+
124
+ def color(cell, statuses, col)
125
+ if statuses
126
+ self.__send__(statuses[col], cell, @monochrome) + (@monochrome ? '' : reset)
127
+ else
128
+ cell
129
+ end
130
+ end
131
+
66
132
  if(RUBY_VERSION =~ /^1\.9/)
67
133
  START = /#{"^".encode('UTF-8')}/
68
134
  NL = Regexp.new("\n".encode('UTF-8'))
@@ -79,6 +145,32 @@ module Gherkin
79
145
  s
80
146
  end.join("\n")
81
147
  end
148
+
149
+ def grab_tags!(indent)
150
+ tags = @tags ? indent + @tags.join(' ') + "\n" : ''
151
+ @tags = nil
152
+ tags
153
+ end
154
+
155
+ def grab_comments!(indent)
156
+ comments = @comments ? indent + @comments.join("\n#{indent}") + "\n" : ''
157
+ @comments = nil
158
+ comments
159
+ end
160
+
161
+ def indented_scenario_location!(keyword, name, location)
162
+ return "" if location.nil?
163
+ l = (keyword+name).unpack("U*").length
164
+ @max_step_length = [@max_step_length, l].max
165
+ indent = @max_step_length - l
166
+ ' ' * indent + ' ' + comments("# #{location}", @monochrome)
167
+ end
168
+
169
+ def indented_step_location!(location)
170
+ return "" if location.nil?
171
+ indent = @max_step_length - @step_lengths[@step_index+=1]
172
+ ' ' * indent + ' ' + comments("# #{location}", @monochrome)
173
+ end
82
174
  end
83
175
  end
84
176
  end
@@ -14,12 +14,17 @@
14
14
  #ifdef HAVE_RUBY_ENCODING_H
15
15
  #include <ruby/encoding.h>
16
16
  #define ENCODED_STR_NEW(ptr, len) \
17
- rb_enc_str_new(ptr, len, rb_utf8_encoding());
17
+ rb_enc_str_new(ptr, len, rb_utf8_encoding())
18
18
  #else
19
19
  #define ENCODED_STR_NEW(ptr, len) \
20
- rb_str_new(ptr, len);
20
+ rb_str_new(ptr, len)
21
21
  #endif
22
22
 
23
+ #define LF_FLAG 0
24
+ #define CRLF_FLAG 1
25
+ #define LF "\n"
26
+ #define CRLF "\r\n"
27
+
23
28
  #ifndef RSTRING_PTR
24
29
  #define RSTRING_PTR(s) (RSTRING(s)->ptr)
25
30
  #endif
@@ -39,23 +44,22 @@ typedef struct lexer_state {
39
44
  int line_number;
40
45
  int current_line;
41
46
  int start_col;
47
+ int eol;
42
48
  size_t mark;
43
49
  size_t keyword_start;
44
50
  size_t keyword_end;
45
51
  size_t next_keyword_start;
46
52
  size_t content_start;
47
53
  size_t content_end;
48
- size_t field_len;
49
54
  size_t query_start;
50
55
  size_t last_newline;
51
56
  size_t final_newline;
52
57
  } lexer_state;
53
58
 
54
59
  static VALUE mGherkin;
55
- static VALUE mLexer;
56
60
  static VALUE mCLexer;
57
61
  static VALUE cI18nLexer;
58
- static VALUE rb_eGherkinLexerError;
62
+ static VALUE rb_eGherkinLexingError;
59
63
 
60
64
  #define LEN(AT, P) (P - data - lexer->AT)
61
65
  #define MARK(M, P) (lexer->M = (P) - data)
@@ -65,16 +69,16 @@ static VALUE rb_eGherkinLexerError;
65
69
  store_kw_con(listener, # EVENT, \
66
70
  PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end - 1)), \
67
71
  PTR_TO(content_start), LEN(content_start, PTR_TO(content_end)), \
68
- lexer->current_line); \
72
+ lexer->current_line, lexer->eol); \
69
73
  if (lexer->content_end != 0) { \
70
74
  p = PTR_TO(content_end - 1); \
71
75
  } \
72
- lexer->content_end = 0;
76
+ lexer->content_end = 0
73
77
 
74
78
  #define STORE_ATTR(ATTR) \
75
79
  store_attr(listener, # ATTR, \
76
80
  PTR_TO(content_start), LEN(content_start, p), \
77
- lexer->line_number);
81
+ lexer->line_number)
78
82
 
79
83
  %%{
80
84
  machine lexer;
@@ -102,39 +106,39 @@ static VALUE rb_eGherkinLexerError;
102
106
  }
103
107
 
104
108
  action store_feature_content {
105
- STORE_KW_END_CON(feature)
109
+ STORE_KW_END_CON(feature);
106
110
  }
107
111
 
108
112
  action store_background_content {
109
- STORE_KW_END_CON(background)
113
+ STORE_KW_END_CON(background);
110
114
  }
111
115
 
112
116
  action store_scenario_content {
113
- STORE_KW_END_CON(scenario)
117
+ STORE_KW_END_CON(scenario);
114
118
  }
115
119
 
116
120
  action store_scenario_outline_content {
117
- STORE_KW_END_CON(scenario_outline)
121
+ STORE_KW_END_CON(scenario_outline);
118
122
  }
119
123
 
120
124
  action store_examples_content {
121
- STORE_KW_END_CON(examples)
125
+ STORE_KW_END_CON(examples);
122
126
  }
123
127
 
124
128
  action store_step_content {
125
129
  store_kw_con(listener, "step",
126
130
  PTR_TO(keyword_start), LEN(keyword_start, PTR_TO(keyword_end)),
127
131
  PTR_TO(content_start), LEN(content_start, p),
128
- lexer->current_line);
132
+ lexer->current_line, lexer->eol);
129
133
  }
130
134
 
131
135
  action store_comment_content {
132
- STORE_ATTR(comment)
136
+ STORE_ATTR(comment);
133
137
  lexer->mark = 0;
134
138
  }
135
139
 
136
140
  action store_tag_content {
137
- STORE_ATTR(tag)
141
+ STORE_ATTR(tag);
138
142
  lexer->mark = 0;
139
143
  }
140
144
 
@@ -164,11 +168,10 @@ static VALUE rb_eGherkinLexerError;
164
168
  MARK(content_end, p);
165
169
  }
166
170
 
167
- action start_table {
171
+ action start_row {
168
172
  p = p - 1;
169
173
  lexer->current_line = lexer->line_number;
170
- rb_ary_clear(rows);
171
- rb_ary_clear(current_row);
174
+ current_row = rb_ary_new();
172
175
  }
173
176
 
174
177
  action begin_cell_content {
@@ -176,29 +179,20 @@ static VALUE rb_eGherkinLexerError;
176
179
  }
177
180
 
178
181
  action store_cell_content {
179
- VALUE con = Qnil;
180
- con = ENCODED_STR_NEW(PTR_TO(content_start), LEN(content_start, p));
182
+ VALUE con = ENCODED_STR_NEW(PTR_TO(content_start), LEN(content_start, p));
181
183
  rb_funcall(con, rb_intern("strip!"), 0);
182
184
 
183
185
  rb_ary_push(current_row, con);
184
186
  }
185
187
 
186
- action start_row {
187
- current_row = rb_ary_new();
188
- }
189
-
190
188
  action store_row {
191
- rb_ary_push(rows, current_row);
192
- }
193
-
194
- action store_table {
195
- rb_funcall(listener, rb_intern("table"), 2, rows, INT2FIX(lexer->current_line));
189
+ rb_funcall(listener, rb_intern("row"), 2, current_row, INT2FIX(lexer->current_line));
196
190
  }
197
191
 
198
192
  action end_feature {
199
193
  if (cs < lexer_first_final) {
200
194
  if (raise_lexer_error != NULL) {
201
- int count = 0;
195
+ size_t count = 0;
202
196
  int newstr_count = 0;
203
197
  size_t len;
204
198
  const char *buff;
@@ -229,8 +223,10 @@ static VALUE rb_eGherkinLexerError;
229
223
 
230
224
  int line = lexer->line_number;
231
225
  lexer_init(lexer); // Re-initialize so we can scan again with the same lexer
232
- raise_lexer_error(listener, newstr, line);
226
+ raise_lexer_error(newstr, line);
233
227
  }
228
+ } else {
229
+ rb_funcall(listener, rb_intern("eof"), 0);
234
230
  }
235
231
  }
236
232
 
@@ -251,28 +247,28 @@ strip_i(VALUE str, VALUE ary)
251
247
  }
252
248
 
253
249
  static VALUE
254
- multiline_strip(VALUE text)
250
+ multiline_strip(VALUE text, int eol)
255
251
  {
256
252
  VALUE map = rb_ary_new();
257
253
  VALUE split = rb_str_split(text, "\n");
258
254
 
259
255
  rb_iterate(rb_each, split, strip_i, map);
260
256
 
261
- return rb_ary_join(split, rb_str_new2("\n"));
257
+ return rb_ary_join(split, rb_str_new2( \
258
+ eol == CRLF_FLAG ? CRLF : LF ));
262
259
  }
263
260
 
264
261
  static void
265
262
  store_kw_con(VALUE listener, const char * event_name,
266
263
  const char * keyword_at, size_t keyword_length,
267
264
  const char * at, size_t length,
268
- int current_line)
265
+ int current_line, int eol)
269
266
  {
270
267
  VALUE con = Qnil, kw = Qnil;
271
268
  kw = ENCODED_STR_NEW(keyword_at, keyword_length);
272
269
  con = ENCODED_STR_NEW(at, length);
273
- con = multiline_strip(con);
270
+ con = multiline_strip(con, eol);
274
271
  rb_funcall(con, rb_intern("strip!"), 0);
275
- rb_funcall(kw, rb_intern("strip!"), 0);
276
272
  rb_funcall(listener, rb_intern(event_name), 3, kw, con, INT2FIX(current_line));
277
273
  }
278
274
 
@@ -296,14 +292,30 @@ store_pystring_content(VALUE listener,
296
292
  char pat[32];
297
293
  snprintf(pat, 32, "^ {0,%d}", start_col);
298
294
  VALUE re = rb_reg_regcomp(rb_str_new2(pat));
295
+ VALUE re2 = rb_reg_regcomp(rb_str_new2("\r\\Z"));
299
296
  rb_funcall(con, rb_intern("gsub!"), 2, re, rb_str_new2(""));
297
+ rb_funcall(con, rb_intern("sub!"), 2, re2, rb_str_new2(""));
300
298
  rb_funcall(listener, rb_intern("py_string"), 2, con, INT2FIX(current_line));
301
299
  }
302
300
 
303
301
  static void
304
- raise_lexer_error(VALUE listener, const char * at, int line)
302
+ raise_lexer_error(const char * at, int line)
305
303
  {
306
- rb_raise(rb_eGherkinLexerError, "Lexing error on line %d: '%s'.", line, at);
304
+ rb_raise(rb_eGherkinLexingError, "Lexing error on line %d: '%s'.", line, at);
305
+ }
306
+
307
+ static int
308
+ count_char(char char_to_count, char *str) {
309
+
310
+ int count = 0;
311
+ int i = 0;
312
+ while(str[i] != '\0') {
313
+ if(str[i] == char_to_count) {
314
+ count++;
315
+ }
316
+ i++;
317
+ }
318
+ return count;
307
319
  }
308
320
 
309
321
  static void lexer_init(lexer_state *lexer) {
@@ -311,7 +323,6 @@ static void lexer_init(lexer_state *lexer) {
311
323
  lexer->content_end = 0;
312
324
  lexer->content_len = 0;
313
325
  lexer->mark = 0;
314
- lexer->field_len = 0;
315
326
  lexer->keyword_start = 0;
316
327
  lexer->keyword_end = 0;
317
328
  lexer->next_keyword_start = 0;
@@ -319,6 +330,7 @@ static void lexer_init(lexer_state *lexer) {
319
330
  lexer->last_newline = 0;
320
331
  lexer->final_newline = 0;
321
332
  lexer->start_col = 0;
333
+ lexer->eol = LF_FLAG;
322
334
  }
323
335
 
324
336
  static VALUE CLexer_alloc(VALUE klass)
@@ -348,41 +360,45 @@ static VALUE CLexer_scan(VALUE self, VALUE input)
348
360
  lexer_state *lexer = NULL;
349
361
  DATA_GET(self, lexer_state, lexer);
350
362
 
363
+
351
364
  VALUE input_copy = rb_str_dup(input);
365
+
352
366
  rb_str_append(input_copy, rb_str_new2("\n%_FEATURE_END_%"));
353
367
  char *data = RSTRING_PTR(input_copy);
354
- long len = RSTRING_LEN(input_copy);
368
+ size_t len = RSTRING_LEN(input_copy);
369
+
370
+ if (count_char('\r', data) > (count_char('\n', data) / 2)) {
371
+ lexer->eol = CRLF_FLAG;
372
+ }
355
373
 
356
374
  if (len == 0) {
357
- rb_raise(rb_eGherkinLexerError, "No content to lex.");
375
+ rb_raise(rb_eGherkinLexingError, "No content to lex.");
358
376
  } else {
377
+
359
378
  const char *p, *pe, *eof;
360
379
  int cs = 0;
361
380
 
362
381
  VALUE listener = rb_iv_get(self, "@listener");
363
- VALUE rows = rb_ary_new();
364
- VALUE current_row = rb_ary_new();
365
-
382
+ VALUE current_row = Qnil;
383
+
366
384
  p = data;
367
385
  pe = data + len;
368
386
  eof = pe;
369
387
 
370
388
  assert(*pe == '\0' && "pointer does not end on NULL");
371
- assert(pe - p == len && "pointers aren't same distance");
372
389
 
373
390
  %% write init;
374
391
  %% write exec;
375
-
392
+
376
393
  assert(p <= pe && "data overflow after parsing execute");
377
394
  assert(lexer->content_start <= len && "content starts after data end");
378
395
  assert(lexer->mark < len && "mark is after data end");
379
- assert(lexer->field_len <= len && "field has length longer than the whole data");
380
396
 
381
397
  // Reset lexer by re-initializing the whole thing
382
398
  lexer_init(lexer);
383
399
 
384
400
  if (cs == lexer_error) {
385
- rb_raise(rb_eGherkinLexerError, "Invalid format, lexing fails.");
401
+ rb_raise(rb_eGherkinLexingError, "Invalid format, lexing fails.");
386
402
  } else {
387
403
  return Qtrue;
388
404
  }
@@ -392,8 +408,7 @@ static VALUE CLexer_scan(VALUE self, VALUE input)
392
408
  void Init_gherkin_lexer_<%= @i18n.sanitized_key %>()
393
409
  {
394
410
  mGherkin = rb_define_module("Gherkin");
395
- mLexer = rb_const_get(mGherkin, rb_intern("Lexer"));
396
- rb_eGherkinLexerError = rb_const_get(mLexer, rb_intern("LexingError"));
411
+ rb_eGherkinLexingError = rb_const_get(mGherkin, rb_intern("LexingError"));
397
412
 
398
413
  mCLexer = rb_define_module_under(mGherkin, "CLexer");
399
414
  cI18nLexer = rb_define_class_under(mCLexer, "<%= @i18n.sanitized_key.capitalize %>", rb_cObject);
@@ -401,3 +416,4 @@ void Init_gherkin_lexer_<%= @i18n.sanitized_key %>()
401
416
  rb_define_method(cI18nLexer, "initialize", CLexer_init, 1);
402
417
  rb_define_method(cI18nLexer, "scan", CLexer_scan, 1);
403
418
  }
419
+