gherkin 1.0.2-i386-mingw32 → 1.0.3-i386-mingw32
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.
- data/.gitattributes +1 -0
- data/History.txt +18 -0
- data/README.rdoc +12 -1
- data/Rakefile +4 -2
- data/VERSION.yml +1 -1
- data/bin/gherkin +1 -1
- data/dotnet/.gitignore +13 -0
- data/features/feature_parser.feature +22 -2
- data/features/native_lexer.feature +1 -1
- data/features/parser_with_native_lexer.feature +1 -1
- data/features/step_definitions/gherkin_steps.rb +2 -6
- data/features/step_definitions/pretty_printer_steps.rb +2 -3
- data/features/steps_parser.feature +1 -1
- data/gherkin.gemspec +46 -18
- data/java/Gherkin.iml +2 -4
- data/java/build.xml +3 -0
- data/java/src/gherkin/FixJava.java +6 -3
- data/java/src/gherkin/I18nLexer.java +48 -0
- data/java/src/gherkin/Listener.java +3 -1
- data/java/src/gherkin/Main.java +17 -0
- data/java/src/gherkin/Parser.java +9 -3
- data/java/src/gherkin/formatter/Argument.java +39 -0
- data/java/src/gherkin/formatter/ArgumentFormat.java +17 -0
- data/java/src/gherkin/formatter/Colors.java +7 -0
- data/java/src/gherkin/formatter/Formatter.java +15 -0
- data/java/src/gherkin/formatter/PrettyFormatter.java +219 -0
- data/java/src/gherkin/parser/StateMachineReader.java +8 -3
- data/java/test/gherkin/formatter/ArgumentTest.java +17 -0
- data/lib/gherkin/csharp_lexer.rb +15 -0
- data/lib/gherkin/format/argument.rb +35 -0
- data/lib/gherkin/format/monochrome_format.rb +9 -0
- data/lib/gherkin/i18n.rb +22 -0
- data/lib/gherkin/i18n.yml +34 -20
- data/lib/gherkin/i18n_lexer.rb +57 -13
- data/lib/gherkin/lexer.rb +9 -18
- data/lib/gherkin/parser.rb +3 -3
- data/lib/gherkin/parser/meta.txt +5 -4
- data/lib/gherkin/parser/root.txt +11 -9
- data/lib/gherkin/parser/steps.txt +4 -3
- data/lib/gherkin/rb_parser.rb +13 -5
- data/lib/gherkin/tools/colors.rb +119 -0
- data/lib/gherkin/tools/files.rb +6 -1
- data/lib/gherkin/tools/pretty_listener.rb +115 -23
- data/ragel/lexer.c.rl.erb +67 -51
- data/ragel/lexer.csharp.rl.erb +240 -0
- data/ragel/lexer.java.rl.erb +27 -18
- data/ragel/lexer.rb.rl.erb +17 -17
- data/ragel/lexer_common.rl.erb +8 -8
- data/spec/gherkin/c_lexer_spec.rb +4 -4
- data/spec/gherkin/csharp_lexer_spec.rb +20 -0
- data/spec/gherkin/fixtures/comments_in_table.feature +9 -0
- data/spec/gherkin/fixtures/complex.feature +2 -0
- data/spec/gherkin/fixtures/dos_line_endings.feature +45 -0
- data/spec/gherkin/fixtures/i18n_fr.feature +1 -0
- data/spec/gherkin/fixtures/i18n_no.feature +1 -0
- data/spec/gherkin/fixtures/i18n_zh-CN.feature +1 -0
- data/spec/gherkin/format/argument_spec.rb +28 -0
- data/spec/gherkin/i18n_lexer_spec.rb +4 -4
- data/spec/gherkin/i18n_spec.rb +31 -23
- data/spec/gherkin/java_lexer_spec.rb +4 -3
- data/spec/gherkin/parser_spec.rb +5 -0
- data/spec/gherkin/rb_lexer_spec.rb +4 -2
- data/spec/gherkin/sexp_recorder.rb +1 -1
- data/spec/gherkin/shared/lexer_spec.rb +169 -60
- data/spec/gherkin/shared/py_string_spec.rb +6 -0
- data/spec/gherkin/shared/row_spec.rb +107 -0
- data/spec/gherkin/shared/tags_spec.rb +1 -1
- data/spec/gherkin/tools/colors_spec.rb +19 -0
- data/spec/gherkin/tools/pretty_listener_spec.rb +147 -0
- data/spec/spec_helper.rb +31 -7
- data/tasks/compile.rake +81 -7
- data/tasks/ragel_task.rb +6 -4
- data/tasks/rspec.rake +2 -2
- metadata +92 -31
- data/lib/gherkin/java_lexer.rb +0 -10
- data/spec/gherkin/shared/table_spec.rb +0 -97
data/lib/gherkin/rb_parser.rb
CHANGED
@@ -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::
|
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
|
-
|
111
|
-
|
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
|
data/lib/gherkin/tools/files.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
@
|
38
|
+
@comments ||= []
|
39
|
+
@comments << content
|
17
40
|
end
|
18
41
|
|
19
42
|
def feature(keyword, name, line)
|
20
|
-
|
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
|
-
|
31
|
-
@
|
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
|
-
|
37
|
-
@
|
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
|
-
|
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
|
-
|
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
|
50
|
-
rows
|
51
|
-
|
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
|
-
|
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
|
data/ragel/lexer.c.rl.erb
CHANGED
@@ -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
|
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
|
171
|
+
action start_row {
|
168
172
|
p = p - 1;
|
169
173
|
lexer->current_line = lexer->line_number;
|
170
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
302
|
+
raise_lexer_error(const char * at, int line)
|
305
303
|
{
|
306
|
-
rb_raise(
|
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
|
-
|
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(
|
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
|
364
|
-
|
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(
|
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
|
-
|
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
|
+
|