fbtxt-parser 0.9.0

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/Manifest.txt +50 -0
  4. data/README.md +22 -0
  5. data/Rakefile +53 -0
  6. data/lib/fbtxt/parser/debuggable.rb +53 -0
  7. data/lib/fbtxt/parser/lexer-logger.rb +20 -0
  8. data/lib/fbtxt/parser/lexer-on_goal.rb +167 -0
  9. data/lib/fbtxt/parser/lexer-on_group_def.rb +31 -0
  10. data/lib/fbtxt/parser/lexer-on_prop_lineup.rb +79 -0
  11. data/lib/fbtxt/parser/lexer-on_prop_misc.rb +123 -0
  12. data/lib/fbtxt/parser/lexer-on_prop_penalties.rb +40 -0
  13. data/lib/fbtxt/parser/lexer-on_round_def.rb +37 -0
  14. data/lib/fbtxt/parser/lexer-on_top.rb +133 -0
  15. data/lib/fbtxt/parser/lexer-prep_doc.rb +131 -0
  16. data/lib/fbtxt/parser/lexer-prep_line.rb +63 -0
  17. data/lib/fbtxt/parser/lexer-tokenize.rb +468 -0
  18. data/lib/fbtxt/parser/lexer.rb +231 -0
  19. data/lib/fbtxt/parser/lexer_buffer.rb +68 -0
  20. data/lib/fbtxt/parser/lexer_token.rb +126 -0
  21. data/lib/fbtxt/parser/parse_tree--core.rb +29 -0
  22. data/lib/fbtxt/parser/parse_tree-match.rb +309 -0
  23. data/lib/fbtxt/parser/parse_tree-props.rb +233 -0
  24. data/lib/fbtxt/parser/parse_tree.rb +202 -0
  25. data/lib/fbtxt/parser/parser-runtime.rb +379 -0
  26. data/lib/fbtxt/parser/parser-top.rb +102 -0
  27. data/lib/fbtxt/parser/parser.rb +2343 -0
  28. data/lib/fbtxt/parser/token-date--helpers.rb +130 -0
  29. data/lib/fbtxt/parser/token-date--names.rb +108 -0
  30. data/lib/fbtxt/parser/token-date.rb +200 -0
  31. data/lib/fbtxt/parser/token-date_duration.rb +171 -0
  32. data/lib/fbtxt/parser/token-geo.rb +134 -0
  33. data/lib/fbtxt/parser/token-goals--helpers.rb +114 -0
  34. data/lib/fbtxt/parser/token-goals.rb +306 -0
  35. data/lib/fbtxt/parser/token-group.rb +29 -0
  36. data/lib/fbtxt/parser/token-note.rb +40 -0
  37. data/lib/fbtxt/parser/token-prop.rb +309 -0
  38. data/lib/fbtxt/parser/token-prop_name.rb +83 -0
  39. data/lib/fbtxt/parser/token-round.rb +88 -0
  40. data/lib/fbtxt/parser/token-score--helpers.rb +189 -0
  41. data/lib/fbtxt/parser/token-score.rb +60 -0
  42. data/lib/fbtxt/parser/token-score_full.rb +331 -0
  43. data/lib/fbtxt/parser/token-score_fuller.rb +434 -0
  44. data/lib/fbtxt/parser/token-score_legs.rb +59 -0
  45. data/lib/fbtxt/parser/token-status.rb +192 -0
  46. data/lib/fbtxt/parser/token-status_inline.rb +112 -0
  47. data/lib/fbtxt/parser/token-text.rb +221 -0
  48. data/lib/fbtxt/parser/token-time.rb +144 -0
  49. data/lib/fbtxt/parser/token.rb +224 -0
  50. data/lib/fbtxt/parser/version.rb +24 -0
  51. data/lib/fbtxt/parser.rb +140 -0
  52. metadata +145 -0
@@ -0,0 +1,134 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+ ##
6
+ # allow Cote'd Ivoir or such
7
+ ## e.g. add '
8
+
9
+
10
+ ## todo/fix - make geo text regex more generic
11
+ ## only care about two space rule
12
+
13
+
14
+ GEO_TEXT_RE = %r{
15
+ ## must start with alpha (allow unicode letters!!)
16
+ (?<text>
17
+ ## positive lookbehind - for now space (or beginning of line - for testing) only
18
+ ## (MUST be fixed number of chars - no quantifier e.g. +? etc.)
19
+ (?<= [ ,›>\[\]]|^)
20
+ (?:
21
+ # opt 1 - start with alpha
22
+ \p{L}+ ## all unicode letters (e.g. [a-z])
23
+ |
24
+ # opt 2 - start with num!! -
25
+ \d+ # check for num lookahead (MUST be space or dot)
26
+ ## MAY be followed by (optional space) !
27
+ ## MUST be follow by a to z!!!!
28
+ [ ]? ## make space optional too - why? why not?
29
+ ## yes - eg. 1st, 2nd, 5th etc.
30
+ \p{L}+
31
+ |
32
+ ## opt 3 - add another weirdo case
33
+ ## e.g. 's Gravenwezel-Schilde
34
+ ## add more letters (or sequences here - why? why not?)
35
+ '\p{L}+
36
+ )
37
+
38
+ ##
39
+ ## todo/check - find a different "more intuitive" regex/rule if possible?
40
+ ## for single spaces only (and _/ MUST not be surround by spaces)
41
+
42
+ (?:
43
+ (?:
44
+ [ ]? # only single (inline) space allowed - double spaces are breaks!!!
45
+ (?:
46
+ \p{L} | \d | [.&'°]
47
+ |
48
+ (?: (?<! [ ]) ## no space allowed before (but possible after)
49
+ [-]
50
+ )
51
+ |
52
+ (?: (?<! [ ]) ## no spaces allowed around these characters
53
+ [_/]
54
+ (?! [ ])
55
+ )
56
+ )+
57
+ )
58
+ |
59
+ ## for now allow auto-add optional
60
+ ## parenthesis enclosed closed text
61
+ ## e.g. Dublin (Dalymount Park)
62
+ ## Bucuresti (23 August)
63
+ ## Paris (Parc des Princes)
64
+ ## Ost-Berlin (Walter-Ulbricht)
65
+ ## Athinai (OAKA - Maroussi)
66
+ ##
67
+ ## or Valencia (Spain) or Solna
68
+ (?:
69
+ [ ]
70
+ \(
71
+ [^()\[\],;:›<>]+ ## todo - add more special chars
72
+ ## maybe list only allowed ones??
73
+ ## make pattern more strict - why? why not?
74
+ \)
75
+ )
76
+ )*
77
+
78
+
79
+ ## must NOT end with space or dash(-)
80
+ ## todo/fix - possible in regex here
81
+ ## only end in alphanum a-z0-9 (not dot or & ???)
82
+
83
+ ## add lookahead/lookbehind
84
+ ## must be space!!!
85
+ ## (or comma or start/end of string)
86
+ ## kind of \b !!!
87
+ ## POSITIVE lookahead
88
+ (?=[ ,›>\[\]]|$)
89
+
90
+ )
91
+ }ix
92
+
93
+
94
+
95
+
96
+
97
+
98
+ ## note - add "hacky" check for comma that is followed by a prop(erty)
99
+ ##
100
+ ## make sure to NOT match
101
+ ## props e.g. att: 18000
102
+ ## July 10 @ Paris, Parc des Princes, att: 18000
103
+ ## July 10 @ Paris, Parc des Princes, att: 18000
104
+ ##
105
+
106
+
107
+ GEO_END_RE = %r{
108
+ (?<geo_end>
109
+ ,
110
+ )
111
+ ## POSITIVE lookahead for props
112
+ ## todo/fix - use generic [a-z]+ - why? why not?
113
+ (?=
114
+ [ ]* ## optional spaces
115
+ (?: attendance|att
116
+ | referee?s|refs?
117
+ )
118
+ :
119
+ )
120
+ }ix
121
+
122
+
123
+
124
+
125
+ GEO_RE = Regexp.union(
126
+ SPACES_RE,
127
+ GEO_END_RE,
128
+ GEO_TEXT_RE,
129
+ / (?<sym> [,›>\[▪] ) /x,
130
+ ANY_RE,
131
+ )
132
+
133
+ end # class Lexer
134
+ end # module Fbtxt
@@ -0,0 +1,114 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+
6
+ def self._build_goal_minute( m )
7
+ minute = {}
8
+
9
+ minute[:m] = m[:value].to_i(10) ## always required
10
+
11
+ ## stoppage/injury time (offset)
12
+ minute[:offset] = m[:value2].to_i(10) if m[:value2]
13
+
14
+ minute[:og] = true if m[:og]
15
+ minute[:pen] = true if m[:pen]
16
+ minute[:freekick] = true if m[:fk]
17
+ minute[:header] = true if m[:hdr]
18
+
19
+ minute[:secs] = m[:secs].to_i(10) if m[:secs]
20
+
21
+ minute
22
+ end
23
+
24
+ def self._build_goal_minute_na( m )
25
+ minute = {}
26
+
27
+ minute[:m] = '?' ## or use nil or 999 or -1 or ???
28
+
29
+ minute[:og] = true if m[:og]
30
+ minute[:pen] = true if m[:pen]
31
+ minute[:freekick] = true if m[:fk]
32
+ minute[:header] = true if m[:hdr]
33
+
34
+ minute
35
+ end
36
+
37
+
38
+
39
+ def self._build_minute( m )
40
+ minute = {}
41
+ minute[:m] = m[:value].to_i(10) ## always required
42
+
43
+ ## stoppage/injury time (offset)
44
+ minute[:offset] = m[:value2].to_i(10) if m[:value2]
45
+
46
+ minute
47
+ end
48
+
49
+
50
+ def self._build_goal_count( m )
51
+ count = {}
52
+ count[:count] = m[:value].to_i(10) if m[:value]
53
+ count[:og] = m[:og_value] ? m[:og_value].to_i(10) : 1 if m[:og] ## check flag
54
+ count[:pen] = m[:pen_value] ? m[:pen_value].to_i(10) : 1 if m[:pen] ## check flag
55
+ count
56
+ end
57
+
58
+ def self._build_goal_type( m )
59
+ goal = {}
60
+ goal[:og] = true if m[:og]
61
+ goal[:pen] = true if m[:pen]
62
+ goal[:freekick] = true if m[:fk]
63
+ goal[:header] = true if m[:hdr]
64
+ goal
65
+ end
66
+
67
+
68
+ def _build_goal_minute( m ) self.class._build_goal_minute( m ); end
69
+ def _build_goal_minute_na( m ) self.class._build_goal_minute_na( m ); end
70
+ def _build_minute( m ) self.class._build_minute( m ); end
71
+ def _build_goal_count( m ) self.class._build_goal_count( m ); end
72
+ def _build_goal_type( m ) self.class._build_goal_type( m ); end
73
+
74
+
75
+
76
+
77
+
78
+ ###
79
+ # parse helpers
80
+
81
+ def self._parse_goal_minute( str )
82
+ ## note - strip - leading/trailing spaces
83
+ m = GOAL_MINUTE_RE.match( str.strip )
84
+ if m && m.pre_match == '' && m.post_match == ''
85
+ _build_goal_minute( m )
86
+ elsif m
87
+ ## note - match BUT not anchored to start and end-of-string!!!
88
+ ## report, error somehow??
89
+ nil
90
+ else
91
+ nil ## no match - return nil
92
+ end
93
+ end
94
+
95
+ def self._parse_goal_count( str )
96
+ ## note - strip - leading/trailing spaces
97
+ m = GOAL_COUNT_RE.match( str.strip )
98
+ if m && m.pre_match == '' && m.post_match == ''
99
+ _build_goal_count( m )
100
+ elsif m
101
+ ## note - match BUT not anchored to start and end-of-string!!!
102
+ ## report, error somehow??
103
+ nil
104
+ else
105
+ nil ## no match - return nil
106
+ end
107
+ end
108
+
109
+
110
+
111
+
112
+
113
+ end # class Lexer
114
+ end # module Fbtxt
@@ -0,0 +1,306 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+ ######################################################
6
+ ## goal mode
7
+ ## note - must be enclosed in ()!!!
8
+ ##
9
+ ## todo - add () in basics - why? why not?
10
+
11
+
12
+
13
+
14
+ ## note - assume lines starting with opening ( are goal lines!!!!
15
+ ## note - use \A (instead of ^) - \A strictly matches the start of the string.
16
+ ##
17
+ ## note - check for negative lookahead
18
+ ## to exclude ord (numbers) e.g. (1), (42), etc.!!!
19
+ ##
20
+ ## todo/fix -- exclude (a), (h), (n) - TEAM_AWAY, TEAM_HOME, TEAM_NEUTRAL tokens!!
21
+
22
+ START_GOAL_LINE_RE = %r{
23
+ \A
24
+ [ ]* ## ignore leading spaces (if any)
25
+ \(
26
+
27
+ # check NEGATIVE lookahead
28
+ (?!
29
+ ## exclude (a), (h), (n)
30
+ ## TEAM_AWAY, TEAM_HOME, TEAM_NEUTRAL
31
+ (?: a|h|n )
32
+ \)
33
+ )
34
+
35
+ }xi
36
+
37
+
38
+
39
+ #############
40
+ ## check for goal compat(ility) "legacy" line
41
+ ## e.g.
42
+ ## (6' Puskás 0-1, 9' Czibor 0-2, 11' Morlock 1-2, 18' Rahn 2-2,
43
+ ## 84' Rahn 3-2)
44
+ ## (6 Puskás 0-1, 9 Czibor 0-2, 11 Morlock 1-2, 18 Rahn 2-2,
45
+ ## 84 Rahn 3-2)
46
+
47
+
48
+ START_GOAL_LINE_COMPAT_RE = %r{
49
+ \A
50
+ [ ]* ## ignore leading spaces (if any)
51
+ \(
52
+
53
+ ## (i) check NEGATIVE lookahead
54
+ ## exclude score e.g. 1-1 etc.
55
+ (?! [ ]* \b \d-\d \b)
56
+
57
+ ## (ii) check POSITIVE lookahead
58
+ (?= [ ]*
59
+ \d{1,3}
60
+ '? ## optional minute marker
61
+ (?: \+
62
+ \d{1,2}
63
+ '? ## optional minute marker
64
+ )?
65
+ )
66
+ }xi
67
+
68
+
69
+
70
+ ###
71
+ ## check for goal line (alternate syntax)
72
+ ## (1-0 Player, 1-1 Player, ...)
73
+ # must start-off OR yes, include score
74
+ ##
75
+ ## note - allow "centered" style e.g.
76
+ ## ( Player 44' (p) 1-0
77
+ ## 1-1 Player 64' )
78
+ START_GOAL_LINE_ALT_RE = %r{
79
+ \A
80
+ [ ]* ## ignore leading spaces (if any)
81
+ \(
82
+
83
+ # check POSITIVE lookahead
84
+ (?= .*? ## note - non-greedy
85
+ \b \d-\d \b ## score e.g. 0-1
86
+ )
87
+ }xi
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+ ###
96
+ # note - alternate goal separator dash (-) MUST have leading and trailing space!!!
97
+ # e.g. (Metzger 83 - Krämer 29, 88, Cichy 33, Rahn 37)
98
+ # e.g. (Metzger - Krämer (2), Cichy, Rahn)
99
+ # (Brunnenmeier 17 - Gerwien 74)
100
+ # (Brunnenmeier - Gerwien)
101
+ # that is, NOT allowed
102
+ # e.g. (Metzger 83-Krämer 29, 88, Cichy 33, Rahn 37)
103
+ # (Brunnenmeier 17-Gerwien 74)
104
+ # (Brunnenmeier-Gerwien)
105
+ #
106
+ # note - allow split by - e.g.
107
+ # Frankfurt 4-2 Schalke (Kreß 45, Solz 55, Trimhold 58, Huberts 73 p -
108
+ # Berz 7, Herrmann 74)
109
+
110
+
111
+ GOAL_SEP_ALT_RE = %r{
112
+ (?<goal_sep_alt>
113
+ (?<=[ ]) ## positive lookbehind - space required
114
+ -
115
+ (?=[ ]|\z) ## positive lookahead - speace required
116
+ )}x
117
+
118
+
119
+ ## e.g. (2)
120
+ ## (2/p), (2/pen.), (3/2p), (3/ 2 pen.)
121
+ ## -or- (2,1pen), (3, 2 pens)
122
+ ##
123
+ ## (p), (pen.) (2 pen.), (2p)
124
+ ## (og), (o.g.),
125
+ ## (2og), (2 o.g.), (2ogs)
126
+ #
127
+ ##
128
+
129
+ GOAL_COUNT_RE = %r{
130
+ (?<goal_count>
131
+ \(
132
+ (?:
133
+ ## opt penalties
134
+ (?<pen>
135
+ (?: (?<pen_value> \d{1,2}) [ ]? )?
136
+ (?:pens|pen\.?|p)
137
+ )
138
+ |
139
+ ## opt own goals (og)
140
+ (?<og>
141
+ (?: (?<og_value> \d{1,2}) [ ]? )?
142
+ (?:ogs?|o\.g\.|o)
143
+ )
144
+ |
145
+ ## opt fallback - classic count/number
146
+ (?: (?<value> [1-9])
147
+ ## check for option penalties
148
+ (?<pen>
149
+ [,/] [ ]*
150
+ (?: (?<pen_value> \d{1,2}) [ ]? )?
151
+ (?:pens|pen\.?|p)
152
+ )?
153
+ )
154
+ )
155
+ \)
156
+ )}ix
157
+
158
+
159
+
160
+
161
+ ##
162
+ ## note - inline \b check in MINUTE_RE excludes
163
+ ## 85pen or 90+4pen or 38p
164
+ ## (possible and NOT excluded in GOAL_MINUTE_RE !!!)
165
+ ##
166
+ ## minute with optional stoppage (offset)
167
+
168
+ MINUTE_RE = %r{
169
+ (?<minute>
170
+ \b
171
+ (?<value>\d{1,3}) ## constrain numbers to 0 to 999!!!
172
+ \b
173
+ '? ## optional minute marker
174
+
175
+ (?: \+ (?<value2>\d{1,2})
176
+ \b
177
+ '? ## optional minute marker
178
+ )?
179
+ )
180
+ }ix
181
+
182
+
183
+
184
+ ##
185
+ ## keep separate? or add simply inside GOAL_MINUTE_RE - why? why not?
186
+ ## fix-fix-fix - move into GOAL_MINUTE_RE !!!
187
+
188
+ GOAL_MINUTE_NA_RE = %r{
189
+ (?<goal_minute_na>
190
+
191
+ # positive lookbehind
192
+ (?<=[ ,;])
193
+
194
+ (?<value> \?{1,2})
195
+ '? ## optional minute marker
196
+ ## note - add goal minute qualifiers here inline!!!
197
+ (?:
198
+ (?: [ ]? (?<og> (?: \((?:og|o\.g\.|o)\)) ## allow (og)
199
+ |
200
+ (?: (?:og|o\.g\.|o)) ## allow plain og
201
+ )
202
+ )
203
+ |
204
+ (?: [ ]? (?<pen> (?: \((?:pen\.?|p)\)) ## allow ()
205
+ |
206
+ (?: (?:pen\.?|p))
207
+ )
208
+ )
209
+ |
210
+ ## add experimental header qualifier
211
+ (?: [ ]? (?<hdr> \( (?:hdr\.?|h ) \) | (?: hdr\.?|h ) ))
212
+ |
213
+ ## add experimental free kick qualifier
214
+ (?: [ ]? (?<fk> \( (?:fk\.?|f ) \) | (?: fk\.?|f) ))
215
+ )?
216
+
217
+ ## note - check positive lookahead
218
+ (?=[ ,;)]|$)
219
+ )
220
+ }ix
221
+
222
+
223
+ ## goal types
224
+ # (pen.) or (pen) or (p.) or (p)
225
+ ## (o.g.) or (og)
226
+ ## todo/check - keep case-insensitive
227
+ ## or allow OG or P or PEN or
228
+ ## only lower case - why? why not?
229
+ ##
230
+ ## add (gg) for golden goal - why? why not?
231
+ ## add (sg) for silver goal - why? why not??
232
+
233
+ GOAL_MINUTE_RE = %r{
234
+ (?<goal_minute>
235
+ \b
236
+ (?<value>\d{1,3}) ## constrain numbers to 0 to 999!!!
237
+ '? ## optional minute marker
238
+
239
+ (?: \+ (?<value2>\d{1,2})
240
+ '? ## optional minute marker
241
+ )?
242
+
243
+ ## note - add goal minute qualifiers here inline!!!
244
+ (?:
245
+ (?: [ ]? (?<og> (?: \((?:og|o\.g\.|o)\)) ## allow (og)
246
+ |
247
+ (?: (?:og|o\.g\.|o)) ## allow plain og
248
+ )
249
+ )
250
+ |
251
+ (?: [ ]? (?<pen> (?: \((?:pen\.?|p)\)) ## allow ()
252
+ |
253
+ (?: (?:pen\.?|p))
254
+ )
255
+ )
256
+ |
257
+ ## add experimental header qualifier
258
+ (?: [ ]? (?<hdr> \( (?:hdr\.?|h ) \) | (?: hdr\.?|h ) ))
259
+ |
260
+ ## add experimental free kick qualifier
261
+ (?: [ ]? (?<fk> \( (?:fk\.?|f ) \) | (?: fk\.?|f) ))
262
+ )?
263
+
264
+ ## add experimental seconds
265
+ ## e.g. (95 secs) or (95sec) etc.
266
+ (?: [ ]* \(
267
+ (?<secs>\d{1,3})
268
+ [ ]?secs?
269
+ \)
270
+ )?
271
+ )
272
+
273
+ ## note - check positive lookahead
274
+ (?=[ ,;)]|$)
275
+ }ix
276
+
277
+
278
+
279
+
280
+
281
+ ###
282
+ ## more regex for goal alt
283
+
284
+
285
+ GOAL_TYPE_RE = %r{
286
+ (?<goal_type>
287
+ \(
288
+ (?:
289
+ (?<og> og|o\.g\.|o )
290
+ |
291
+ (?<pen> pen\.?|p )
292
+ |
293
+ ## add experimental header qualifier
294
+ (?<hdr> hdr\.?|h )
295
+ |
296
+ ## add experimental free kick qualifier
297
+ (?<fk> fk\.?|f )
298
+ )
299
+ \)
300
+ )}xi
301
+
302
+
303
+
304
+
305
+ end # class Lexer
306
+ end # module Fbtxt
@@ -0,0 +1,29 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+ ###
6
+ # check for start of group def line e.g.
7
+ # Group A | ...
8
+ # Group 1 : ....
9
+ # Group A2 | ....
10
+ ## note - use \A (instead of ^) - \A strictly matches the start of the string.
11
+
12
+ START_WITH_GROUP_DEF_LINE_RE = %r{
13
+ \A
14
+ [ ]* ## ignore leading spaces (if any)
15
+ (?<group_def>
16
+ Group
17
+ [ ]
18
+ [a-z0-9]+ ## todo/check - allow dot (.) too e.g. 1.A etc.- why? why not?
19
+ )
20
+ ### positive lookahead MUST be : OR |
21
+ (?= [ ]*
22
+ [:|]
23
+ [ ]) ## note: requires space for now after [:|] - keep - why? why not?
24
+ }ix
25
+
26
+
27
+
28
+ end # class Lexer
29
+ end # module Fbtxt
@@ -0,0 +1,40 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+ ### fix - use (?<text>) - text capture for inner text!!
6
+ ## use (?<note> for complete match as a convention!! )
7
+ NOTE_RE = %r{
8
+ \[
9
+ (?<note>
10
+ [^\[\]\#]*? ## note - non-greedy/lazy operator
11
+ ## exclude comments inside note block - why? why not?
12
+ )
13
+ \]
14
+ }xi
15
+
16
+
17
+ ####
18
+ ## fix - change NOTE_RE to MATCH_NOTE_RE !!!!
19
+ ## and change NOTA_BENE_RE to NOTE_RE !!!
20
+
21
+
22
+
23
+ ## check for "literal" (multi-line) note blocks
24
+ ## eg. nb: or note:
25
+ ## space required after double colon - why? why not?
26
+ ##
27
+ ## note - use \A (instead of ^) - \A strictly matches the start of the string.
28
+ NOTA_BENE_RE = %r{ \A
29
+ [ ]* ## ignore leading spaces (if any)
30
+ (?: nb | note) [ ]* : [ ]+
31
+ (?<nota_bene>
32
+ .+? ## use non-greedy
33
+ )
34
+ [ ]* ## ignore trailing spaces (if any)
35
+ \z
36
+ }xi
37
+
38
+
39
+ end # class Lexer
40
+ end # module Fbtxt