0
# Ruby Implementation
1
2
Idiomatic Ruby implementation with clean APIs, module-based organization, and full integration with Ruby BDD testing frameworks like Cucumber-Ruby and RSpec.
3
4
## Package Information
5
6
- **Gem Name**: `cucumber-cucumber-expressions`
7
- **Language**: Ruby 2.5+
8
- **Installation**: `gem install cucumber-cucumber-expressions`
9
- **Module**: `Cucumber::CucumberExpressions`
10
11
## Core Imports
12
13
```ruby
14
require 'cucumber-cucumber-expressions'
15
16
# Access classes through module
17
include Cucumber::CucumberExpressions
18
19
# Or use fully qualified names
20
Cucumber::CucumberExpressions::CucumberExpression
21
Cucumber::CucumberExpressions::ParameterType
22
Cucumber::CucumberExpressions::ParameterTypeRegistry
23
```
24
25
## Capabilities
26
27
### Expression Creation and Matching
28
29
Create and match Cucumber expressions with Ruby-style method naming and return patterns.
30
31
```ruby { .api }
32
module Cucumber::CucumberExpressions
33
# Main class for parsing and matching Cucumber expressions
34
class CucumberExpression
35
# Create a new Cucumber expression
36
# @param expression [String] The Cucumber expression string
37
# @param parameter_type_registry [ParameterTypeRegistry] Registry containing parameter types
38
def initialize(expression, parameter_type_registry)
39
end
40
41
# Match text against this expression and extract arguments
42
# @param text [String] Text to match against the expression
43
# @return [Array<Argument>, nil] Array of matched arguments or nil if no match
44
def match(text)
45
end
46
47
# Get the original expression string
48
# @return [String] The source expression
49
def source
50
end
51
52
# Get the compiled regular expression
53
# @return [Regexp] The regex pattern
54
def regexp
55
end
56
57
# String representation of the expression
58
# @return [String] Source expression
59
def to_s
60
end
61
end
62
end
63
```
64
65
**Usage Examples:**
66
67
```ruby
68
require 'cucumber-cucumber-expressions'
69
include Cucumber::CucumberExpressions
70
71
# Create registry with built-in parameter types
72
registry = ParameterTypeRegistry.new
73
74
# Simple integer parameter
75
expr1 = CucumberExpression.new('I have {int} cucumbers', registry)
76
args1 = expr1.match('I have 42 cucumbers')
77
if args1
78
puts args1[0].value # 42 (Integer)
79
end
80
81
# Multiple parameters
82
expr2 = CucumberExpression.new('User {word} has {int} items', registry)
83
args2 = expr2.match('User alice has 42 items')
84
if args2
85
username = args2[0].value # "alice" (String)
86
item_count = args2[1].value # 42 (Integer)
87
puts "User: #{username}, Items: #{item_count}"
88
end
89
90
# Optional text
91
expr3 = CucumberExpression.new('I have {int} cucumber(s)', registry)
92
puts expr3.match('I have 1 cucumber') ? 'matches' : 'no match' # matches
93
puts expr3.match('I have 5 cucumbers') ? 'matches' : 'no match' # matches
94
95
# Alternative text
96
expr4 = CucumberExpression.new('I put it in my belly/stomach', registry)
97
puts expr4.match('I put it in my belly') ? 'matches' : 'no match' # matches
98
puts expr4.match('I put it in my stomach') ? 'matches' : 'no match' # matches
99
100
# String parameters (removes quotes)
101
expr5 = CucumberExpression.new('I say {string}', registry)
102
args5 = expr5.match('I say "hello world"')
103
if args5
104
puts args5[0].value # "hello world" (without quotes)
105
end
106
107
# Expression introspection
108
puts expr1.source # "I have {int} cucumbers"
109
puts expr1.regexp # Compiled regex pattern
110
puts expr1.to_s # "I have {int} cucumbers"
111
```
112
113
### Parameter Type Definition
114
115
Define custom parameter types with Ruby-style APIs and flexible transformation blocks.
116
117
```ruby { .api }
118
module Cucumber::CucumberExpressions
119
# Defines a parameter type with name, patterns, and transformer
120
class ParameterType
121
# Pattern for illegal parameter names
122
ILLEGAL_PARAMETER_NAME_PATTERN = /([\[\]()$.|?*+])/.freeze
123
124
# Pattern for unescaping characters
125
UNESCAPE_PATTERN = /(\\([\[$.|?*+\]]))/.freeze
126
127
# Create a new parameter type
128
# @param name [String] Name used in expressions
129
# @param regexp [Regexp, Array<Regexp>, String, Array<String>] Pattern(s) to match
130
# @param type [Class] Ruby class for the result type
131
# @param transformer [Proc] Block to transform matched strings
132
# @param use_for_snippets [Boolean] Whether to use for snippet generation
133
# @param prefer_for_regexp_match [Boolean] Whether to prefer for regexp matches
134
def initialize(name, regexp, type, transformer, use_for_snippets, prefer_for_regexp_match)
135
end
136
137
# Check if parameter type name is valid
138
# @param type_name [String] Name to validate
139
# @raises [CucumberExpressions::CucumberExpressionError] if invalid
140
def self.check_parameter_type_name(type_name)
141
end
142
143
# Test if parameter type name is valid
144
# @param type_name [String] Name to test
145
# @return [Boolean] true if valid
146
def self.is_valid_parameter_type_name(type_name)
147
end
148
149
# Transform matched groups to target type
150
# @param self_obj [Object] Context object for transformation
151
# @param group_values [Array<String>] Matched string groups
152
# @return [Object] Transformed value
153
def transform(self_obj, group_values)
154
end
155
156
# Compare parameter types for sorting (implements Comparable)
157
# @param other [ParameterType] Other parameter type to compare
158
# @return [Integer] Comparison result (-1, 0, 1)
159
def <=>(other)
160
end
161
162
# Attribute readers
163
attr_reader :name, :type, :transformer, :use_for_snippets, :prefer_for_regexp_match, :regexps
164
end
165
end
166
```
167
168
**Usage Examples:**
169
170
```ruby
171
require 'cucumber-cucumber-expressions'
172
include Cucumber::CucumberExpressions
173
174
# Simple enum-like parameter type
175
color_type = ParameterType.new(
176
'color',
177
/red|green|blue|yellow/,
178
String,
179
->(color) { color.upcase },
180
true, # use_for_snippets
181
false # prefer_for_regexp_match
182
)
183
184
# Custom object parameter type
185
class Point
186
attr_reader :x, :y
187
188
def initialize(x, y)
189
@x = x.to_i
190
@y = y.to_i
191
end
192
193
def to_s
194
"(#{@x}, #{@y})"
195
end
196
end
197
198
point_type = ParameterType.new(
199
'point',
200
/\((-?\d+),\s*(-?\d+)\)/,
201
Point,
202
->(x, y) { Point.new(x, y) },
203
true,
204
false
205
)
206
207
# Date parameter type with validation
208
require 'date'
209
210
date_type = ParameterType.new(
211
'date',
212
/(\d{4})-(\d{2})-(\d{2})/,
213
Date,
214
lambda do |year, month, day|
215
year_i = year.to_i
216
month_i = month.to_i
217
day_i = day.to_i
218
219
raise ArgumentError, "Invalid month: #{month_i}" unless (1..12).include?(month_i)
220
raise ArgumentError, "Invalid day: #{day_i}" unless (1..31).include?(day_i)
221
222
Date.new(year_i, month_i, day_i)
223
end,
224
true,
225
false
226
)
227
228
# Money parameter type with BigDecimal
229
require 'bigdecimal'
230
231
class Money
232
attr_reader :amount, :currency
233
234
def initialize(amount, currency)
235
@amount = BigDecimal(amount.to_s)
236
@currency = currency.upcase
237
end
238
239
def to_s
240
"#{@amount} #{@currency}"
241
end
242
end
243
244
money_type = ParameterType.new(
245
'money',
246
/\$(\d+(?:\.\d{2})?) (USD|EUR|GBP)/,
247
Money,
248
->(amount, currency) { Money.new(amount, currency) },
249
true,
250
false
251
)
252
253
# User type with hash return
254
user_type = ParameterType.new(
255
'user',
256
/(\w+):(\d+):([^:]+)/,
257
Hash,
258
lambda do |name, age, email|
259
{
260
name: name,
261
age: age.to_i,
262
email: email
263
}
264
end,
265
true,
266
false
267
)
268
269
# Register and use parameter types
270
registry = ParameterTypeRegistry.new
271
registry.define_parameter_type(color_type)
272
registry.define_parameter_type(point_type)
273
registry.define_parameter_type(date_type)
274
registry.define_parameter_type(money_type)
275
registry.define_parameter_type(user_type)
276
277
# Use in expressions
278
color_expr = CucumberExpression.new('I have a {color} car', registry)
279
result = color_expr.match('I have a red car')
280
puts result[0].value # "RED"
281
282
point_expr = CucumberExpression.new('Move to {point}', registry)
283
result = point_expr.match('Move to (10, -5)')
284
point = result[0].value
285
puts "Point: #{point}" # "Point: (10, -5)"
286
puts "X: #{point.x}, Y: #{point.y}" # "X: 10, Y: -5"
287
288
date_expr = CucumberExpression.new('Meeting on {date}', registry)
289
result = date_expr.match('Meeting on 2023-12-25')
290
date = result[0].value
291
puts date.strftime('%B %d, %Y') # "December 25, 2023"
292
293
money_expr = CucumberExpression.new('Price is {money}', registry)
294
result = money_expr.match('Price is $29.99 USD')
295
money = result[0].value
296
puts money # "29.99 USD"
297
puts money.amount.class # BigDecimal
298
299
user_expr = CucumberExpression.new('Create {user}', registry)
300
result = user_expr.match('Create alice:25:alice@example.com')
301
user = result[0].value
302
puts "User: #{user[:name]}, Age: #{user[:age]}, Email: #{user[:email]}"
303
```
304
305
### Parameter Type Registry
306
307
Ruby-style registry with snake_case method names and Enumerable support.
308
309
```ruby { .api }
310
module Cucumber::CucumberExpressions
311
# Registry for managing parameter types
312
class ParameterTypeRegistry
313
# Create a new parameter type registry with built-in types
314
def initialize
315
end
316
317
# Look up parameter type by name
318
# @param name [String] Name of parameter type
319
# @return [ParameterType, nil] Parameter type or nil if not found
320
def lookup_by_type_name(name)
321
end
322
323
# Look up parameter type by regular expression pattern
324
# @param parameter_type_regexp [String] Regular expression to match
325
# @param expression_regexp [Regexp] Full expression regexp
326
# @param text [String] Text being matched
327
# @return [ParameterType, nil] Matching parameter type or nil
328
def lookup_by_regexp(parameter_type_regexp, expression_regexp, text)
329
end
330
331
# Register a new parameter type
332
# @param parameter_type [ParameterType] Parameter type to register
333
def define_parameter_type(parameter_type)
334
end
335
336
# Get all registered parameter types
337
# @return [Array<ParameterType>] List of parameter types
338
def parameter_types
339
end
340
end
341
end
342
```
343
344
**Usage Examples:**
345
346
```ruby
347
registry = ParameterTypeRegistry.new
348
349
# Check built-in types
350
int_type = registry.lookup_by_type_name('int')
351
puts int_type.name # "int"
352
puts int_type.type # Integer
353
354
# Iterate over all parameter types
355
registry.parameter_types.each do |param_type|
356
puts "#{param_type.name}: #{param_type.regexps.join(', ')}"
357
end
358
359
# Define and retrieve custom type
360
uuid_type = ParameterType.new(
361
'uuid',
362
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/,
363
String,
364
->(uuid) { uuid.upcase },
365
true,
366
false
367
)
368
369
registry.define_parameter_type(uuid_type)
370
retrieved = registry.lookup_by_type_name('uuid')
371
puts retrieved == uuid_type # true
372
373
# Registry acts as a collection
374
parameter_count = registry.parameter_types.size
375
puts "Total parameter types: #{parameter_count}"
376
```
377
378
### Expression Generation
379
380
Generate Cucumber expressions from example text with Ruby-style APIs.
381
382
```ruby { .api }
383
module Cucumber::CucumberExpressions
384
# Generates Cucumber expressions from example text
385
class CucumberExpressionGenerator
386
# Create generator with parameter type registry
387
# @param parameter_type_registry [ParameterTypeRegistry] Registry containing parameter types
388
def initialize(parameter_type_registry)
389
end
390
391
# Generate expressions from example text
392
# @param text [String] Example text to generate expressions from
393
# @return [Array<GeneratedExpression>] Array of generated expressions
394
def generate_expressions(text)
395
end
396
end
397
398
# Generated expression with metadata
399
class GeneratedExpression
400
# Create generated expression
401
# @param expression_template [String] Template with parameter placeholders
402
# @param parameter_types [Array<ParameterType>] Parameter types in order
403
def initialize(expression_template, parameter_types)
404
end
405
406
# Get the generated expression source
407
# @return [String] Expression string
408
def source
409
end
410
411
# Get parameter names for code generation
412
# @return [Array<String>] Parameter names
413
def parameter_names
414
end
415
416
# Get parameter types in declaration order
417
# @return [Array<ParameterType>] Parameter types
418
def parameter_types
419
end
420
end
421
end
422
```
423
424
**Usage Examples:**
425
426
```ruby
427
registry = ParameterTypeRegistry.new
428
generator = CucumberExpressionGenerator.new(registry)
429
430
# Generate from text with numbers
431
expressions = generator.generate_expressions('I have 42 cucumbers')
432
puts expressions[0].source # "I have {int} cucumbers"
433
puts expressions[0].parameter_names # ["int"]
434
435
# Generate from text with strings
436
expressions = generator.generate_expressions('I select "Premium Plan" option')
437
puts expressions[0].source # "I select {string} option"
438
439
# Generate from complex text
440
expressions = generator.generate_expressions('User alice with age 25 has balance 123.45')
441
expressions.each_with_index do |expr, index|
442
puts "Option #{index + 1}: #{expr.source}"
443
puts "Parameters: #{expr.parameter_types.map(&:name).join(', ')}"
444
end
445
446
# Generate step definition template
447
expressions = generator.generate_expressions('Order contains 5 items')
448
best = expressions[0]
449
puts "Given(/^#{best.source}$/) do |#{best.parameter_names.join(', ')}|"
450
puts " # Implementation here"
451
puts "end"
452
453
# With parameter type hints
454
param_hints = best.parameter_types.map { |pt| ruby_type_hint(pt) }
455
puts "\n# With type hints:"
456
puts "Given(/^#{best.source}$/) do |#{best.parameter_names.zip(param_hints).map { |name, type| "#{name} # #{type}" }.join(', ')}|"
457
puts " # Implementation here"
458
puts "end"
459
460
def ruby_type_hint(param_type)
461
case param_type.name
462
when 'int' then 'Integer'
463
when 'float' then 'Float'
464
when 'string', 'word' then 'String'
465
else param_type.type.name
466
end
467
end
468
```
469
470
### Other Key Classes
471
472
Supporting classes with Ruby conventions and patterns.
473
474
```ruby { .api }
475
module Cucumber::CucumberExpressions
476
# Represents a matched argument from an expression
477
class Argument
478
# Create argument with group and parameter type
479
# @param group [Group] Matched group from regex
480
# @param parameter_type [ParameterType] Parameter type for transformation
481
def initialize(group, parameter_type)
482
end
483
484
# Get the transformed value
485
# @return [Object] Transformed value
486
def value
487
end
488
489
# Get the matched group
490
# @return [Group] Group with position and text
491
def group
492
end
493
494
# Get the parameter type
495
# @return [ParameterType] Parameter type used for transformation
496
def parameter_type
497
end
498
end
499
500
# Regular expression implementation of expression interface
501
class RegularExpression
502
# Create regular expression
503
# @param regexp [Regexp] Regular expression pattern
504
# @param parameter_type_registry [ParameterTypeRegistry] Registry for parameter types
505
def initialize(regexp, parameter_type_registry)
506
end
507
508
# Match text against regular expression
509
# @param text [String] Text to match
510
# @return [Array<Argument>, nil] Array of arguments or nil if no match
511
def match(text)
512
end
513
514
# Get the source pattern
515
# @return [String] Source pattern
516
def source
517
end
518
519
# Get the regular expression
520
# @return [Regexp] Regex pattern
521
def regexp
522
end
523
end
524
525
# Factory for creating expressions with automatic type detection
526
class ExpressionFactory
527
# Create expression factory with parameter type registry
528
# @param parameter_type_registry [ParameterTypeRegistry] Registry for parameter types
529
def initialize(parameter_type_registry)
530
end
531
532
# Create expression from string or regex
533
# @param expression [String, Regexp] Expression to create
534
# @return [CucumberExpression, RegularExpression] Expression instance
535
def create_expression(expression)
536
end
537
end
538
end
539
```
540
541
**Usage Examples:**
542
543
```ruby
544
# Argument extraction
545
registry = ParameterTypeRegistry.new
546
expr = CucumberExpression.new('User {word} has {int} items', registry)
547
548
args = expr.match('User alice has 42 items')
549
if args
550
# Access values and metadata
551
username = args[0].value # "alice"
552
item_count = args[1].value # 42
553
554
# Parameter type information
555
puts args[0].parameter_type.name # "word"
556
puts args[1].parameter_type.name # "int"
557
558
# Group information
559
puts args[0].group.value # "alice"
560
puts args[0].group.start # position in text
561
puts args[0].group.end # end position
562
end
563
564
# Expression factory
565
factory = ExpressionFactory.new(registry)
566
567
# Create Cucumber expression
568
cucumber_expr = factory.create_expression('I have {int} items')
569
puts cucumber_expr.class # CucumberExpression
570
571
# Create regular expression
572
regex_expr = factory.create_expression(/^I have (\d+) items$/)
573
puts regex_expr.class # RegularExpression
574
575
# Both respond to same interface
576
puts cucumber_expr.source
577
puts regex_expr.source
578
```
579
580
### Built-in Parameter Types
581
582
Ruby implementation provides built-in parameter types with appropriate Ruby type conversion.
583
584
```ruby { .api }
585
# Built-in parameter types in Ruby implementation:
586
587
{int} # Converts to Integer
588
{float} # Converts to Float
589
{word} # Returns as String (single word)
590
{string} # Returns as String (removes quotes)
591
{bigdecimal} # Converts to BigDecimal
592
{double} # Converts to Float (64-bit)
593
{biginteger} # Converts to Integer (Ruby integers are arbitrary precision)
594
{byte} # Converts to Integer (8-bit range)
595
{short} # Converts to Integer (16-bit range)
596
{long} # Converts to Integer (64-bit range)
597
{} # Anonymous - returns as String
598
```
599
600
**Usage Examples:**
601
602
```ruby
603
require 'bigdecimal'
604
605
registry = ParameterTypeRegistry.new
606
607
# Test all built-in types
608
expr = CucumberExpression.new(
609
'Values: {int} {float} {word} {string} {bigdecimal}',
610
registry
611
)
612
613
args = expr.match('Values: 42 3.14 hello "world test" 123.456789')
614
if args
615
puts "int: #{args[0].value} (#{args[0].value.class})" # 42 (Integer)
616
puts "float: #{args[1].value} (#{args[1].value.class})" # 3.14 (Float)
617
puts "word: #{args[2].value} (#{args[2].value.class})" # hello (String)
618
puts "string: #{args[3].value} (#{args[3].value.class})" # world test (String)
619
puts "bigdecimal: #{args[4].value} (#{args[4].value.class})" # 123.456789 (BigDecimal)
620
end
621
```
622
623
### Integration with Ruby Testing Frameworks
624
625
Common patterns for integrating with Cucumber-Ruby, RSpec, and other Ruby BDD frameworks.
626
627
```ruby
628
# Cucumber-Ruby integration
629
require 'cucumber'
630
require 'cucumber-cucumber-expressions'
631
632
# Set up parameter types in env.rb or support file
633
Before do
634
@registry = Cucumber::CucumberExpressions::ParameterTypeRegistry.new
635
636
# Custom parameter types
637
color_type = Cucumber::CucumberExpressions::ParameterType.new(
638
'color',
639
/red|green|blue/,
640
String,
641
->(color) { color.upcase },
642
true,
643
false
644
)
645
@registry.define_parameter_type(color_type)
646
end
647
648
# Step definitions using built-in types
649
Given(/^I have (\d+) cucumbers$/) do |count|
650
# count is passed as string, convert manually
651
@cucumber_count = count.to_i
652
end
653
654
# Or use Cucumber-Ruby's built-in parameter type system
655
Given('I have {int} cucumbers') do |count|
656
# count is already converted to Integer
657
@cucumber_count = count
658
end
659
660
Given('I have a {color} car') do |color|
661
# color is converted using custom parameter type
662
@car_color = color
663
end
664
665
# RSpec integration example
666
require 'rspec'
667
require 'cucumber-cucumber-expressions'
668
669
RSpec.describe 'Cucumber Expressions' do
670
let(:registry) { Cucumber::CucumberExpressions::ParameterTypeRegistry.new }
671
672
it 'matches parameters correctly' do
673
expr = Cucumber::CucumberExpressions::CucumberExpression.new(
674
'User {word} has {int} items',
675
registry
676
)
677
678
args = expr.match('User alice has 42 items')
679
680
expect(args).not_to be_nil
681
expect(args[0].value).to eq('alice')
682
expect(args[1].value).to eq(42)
683
expect(args[1].value).to be_a(Integer)
684
end
685
end
686
687
# Custom matcher for RSpec
688
RSpec::Matchers.define :match_expression do |text|
689
match do |expression|
690
@result = expression.match(text)
691
!@result.nil?
692
end
693
694
failure_message do |expression|
695
"expected #{expression.source} to match '#{text}'"
696
end
697
698
chain :with_arguments do |*expected_args|
699
@expected_args = expected_args
700
end
701
702
match do |expression|
703
@result = expression.match(text)
704
return false if @result.nil?
705
706
if @expected_args
707
actual_args = @result.map(&:value)
708
actual_args == @expected_args
709
else
710
true
711
end
712
end
713
end
714
715
# Usage:
716
# expect(expression).to match_expression('User alice has 42 items').with_arguments('alice', 42)
717
```
718
719
The Ruby implementation provides idiomatic Ruby APIs with flexible parameter types, clean method naming, and seamless integration with Ruby BDD testing frameworks while maintaining full compatibility with the Cucumber Expressions specification.