srl_ruby 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -3
  3. data/CHANGELOG.md +20 -0
  4. data/README.md +326 -52
  5. data/bin/srl2ruby +95 -0
  6. data/bin/srl2ruby_cli_parser.rb +89 -0
  7. data/lib/regex/abstract_method.rb +1 -1
  8. data/lib/regex/alternation.rb +1 -1
  9. data/lib/regex/anchor.rb +3 -3
  10. data/lib/regex/atomic_expression.rb +2 -2
  11. data/lib/regex/capturing_group.rb +3 -3
  12. data/lib/regex/char_class.rb +5 -5
  13. data/lib/regex/char_range.rb +5 -5
  14. data/lib/regex/char_shorthand.rb +2 -2
  15. data/lib/regex/character.rb +6 -6
  16. data/lib/regex/compound_expression.rb +2 -2
  17. data/lib/regex/concatenation.rb +3 -3
  18. data/lib/regex/expression.rb +4 -4
  19. data/lib/regex/lookaround.rb +1 -1
  20. data/lib/regex/match_option.rb +2 -2
  21. data/lib/regex/monadic_expression.rb +3 -3
  22. data/lib/regex/multiplicity.rb +1 -1
  23. data/lib/regex/non_capturing_group.rb +1 -1
  24. data/lib/regex/polyadic_expression.rb +3 -3
  25. data/lib/regex/repetition.rb +2 -2
  26. data/lib/regex/wildcard.rb +3 -3
  27. data/lib/srl_ruby/ast_builder.rb +22 -22
  28. data/lib/srl_ruby/srl_token.rb +1 -1
  29. data/lib/srl_ruby/tokenizer.rb +7 -7
  30. data/lib/srl_ruby/version.rb +1 -1
  31. data/spec/acceptance/srl_test_suite_spec.rb +1 -1
  32. data/spec/acceptance/support/rule_file_ast_builder.rb +8 -8
  33. data/spec/acceptance/support/rule_file_token.rb +1 -1
  34. data/spec/acceptance/support/rule_file_tokenizer.rb +8 -8
  35. data/spec/regex/character_spec.rb +30 -30
  36. data/spec/regex/multiplicity_spec.rb +24 -24
  37. data/srl_ruby.gemspec +8 -5
  38. data/templates/base.erb +2 -0
  39. data/templates/srl_and_ruby.erb +9 -0
  40. data/templates/srl_block_and_ruby.erb +10 -0
  41. metadata +8 -4
  42. data/bin/srl_ruby +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 711c793cad016254dd13088c99e47e4128c54b0e
4
- data.tar.gz: d9fdec5bc246ba494554cd86ff5ff2a1851b2491
3
+ metadata.gz: f476a011aec905f943e525e2fb73770d6fe253b0
4
+ data.tar.gz: 3a93a4502442762e7101bb46f58be95ac60a7730
5
5
  SHA512:
6
- metadata.gz: 9dc549cc35d3fe9d0ee75090f4647d38376e531d0f058be1e8d07975e7d260eb14783a618ae2651f63b358f2aa6173da8fca4dadf9d1faa63e1925d0886f2e59
7
- data.tar.gz: 80edcec42446d9200cd7d500624461d31258f712a8a0749d73705e0b7f4f7ca703dac892ed3bbd1111f7200049ecb081624f4511531989308b05b7a8555a2cb2
6
+ metadata.gz: 064d58d3a68227f7f0e636b3228194262bf08b79b07d807edd9c99042fa9e22a39735c4798a9bd918adb24216095849e7e69f5500447e4b2c1fe519823cac5d8
7
+ data.tar.gz: 63e7d716017d8fc95dfbac33fc1b36e0d4b39bcf0803b192f30b7d772747f5c811619afabcec0dbcb8091788e62d378b982de5e20978bea717f054301b3c4f57
data/.rubocop.yml CHANGED
@@ -27,6 +27,9 @@ Layout/IndentationWidth:
27
27
  Layout/IndentationConsistency:
28
28
  Enabled: true
29
29
 
30
+ Layout/IndentHeredoc:
31
+ Enabled: false
32
+
30
33
  Layout/MultilineHashBraceLayout:
31
34
  Enabled: true
32
35
 
@@ -99,7 +102,7 @@ Naming/ClassAndModuleCamelCase:
99
102
  Enabled: false
100
103
 
101
104
  Naming/UncommunicativeBlockParamName:
102
- Enabled: false
105
+ Enabled: true
103
106
 
104
107
  Naming/UncommunicativeMethodParamName:
105
108
  Enabled: false
@@ -144,7 +147,7 @@ Style/ConditionalAssignment:
144
147
  Enabled: false
145
148
 
146
149
  Style/DefWithParentheses:
147
- Enabled: false
150
+ Enabled: true
148
151
 
149
152
  Style/Documentation:
150
153
  Enabled: false
@@ -159,7 +162,7 @@ Style/IfUnlessModifier:
159
162
  Enabled: false
160
163
 
161
164
  Style/InverseMethods:
162
- Enabled: false
165
+ Enabled: true
163
166
 
164
167
  Style/Next:
165
168
  Enabled: false
@@ -178,6 +181,9 @@ Style/RegexpLiteral:
178
181
 
179
182
  Style/PercentLiteralDelimiters:
180
183
  Enabled: false
184
+
185
+ Style/StderrPuts:
186
+ Enabled: false
181
187
 
182
188
  Style/StringLiterals:
183
189
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## [0.4.0] - 2018-05-13
2
+ Version bump: SrlRuby has a command-line compiler `srl2ruby`
3
+
4
+ ### Added
5
+ - File `bin/srl2ruby` A compiler that parses SRL expressions and transform them into regular Regexp.
6
+ - File `srl2ruby_cli_parser.rb` Implementation of the CLI of the `srl2ruby` compiler.
7
+ - Directory `templates` contains ERB templates that can be used to format `srl2ruby` output.
8
+
9
+ ### Changed
10
+ - File `README.md` vastly expanded in order to cover `srl2ruby` compiler and a couple of SRL examples
11
+ - File `.rubocop.yml` enabled some of the complaining cops
12
+
13
+ ### Removed
14
+ - File `srl_ruby` the previous binary of the gem is now replaced by `srl2ruby`.
15
+
16
+ ### Fixed
17
+ - Method `Tokenizer#_next_token` failed to recognize digit or integer value immediately followed by a closing parenthesis ')'.
18
+ - Many classes lightly refactored in order to please Rubocop.
19
+
20
+
1
21
  ## [0.3.5] - 2018-04-29
2
22
 
3
23
  ### Added
data/README.md CHANGED
@@ -5,72 +5,362 @@
5
5
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/SRL-Ruby/blob/master/LICENSE.txt)
6
6
 
7
7
 
8
- This project implements a [Simple Regex Language](https://simple-regex.com) parser and interpreter in Ruby.
8
+ Welcome to the first Ruby implementation of a [Simple Regex Language](https://simple-regex.com) (SRL) parser and compiler.
9
+ It allows you to write __highly-readable__ text patterns in SRL and then generate their Ruby `Regexp` counterparts.
10
+ Ever wanted to write challenging regular expressions but were intimided by their arcane, cryptic syntax?
11
+ With **srl_ruby** you can easily design your patterns in SRL and let *srl_ruby* transform them into terse `Regexp`.
12
+
13
+ ### Features:
14
+ - Command-line SRL-to-Ruby compiler with customizable output.
15
+ - Ruby API for integrating a SRL parser or compiler with your code.
16
+ - 100% pure Ruby with clean design (_not a port from some other language_)
17
+ - Minimal runtime dependency ([Rley](https://rubygems.org/gems/rley) gem). Won't drag a bunch of gems...
18
+ - Compatibility: works with 2.x+ MRI, JRuby
19
+ - Portability: tested on both Linux and Windows,...
9
20
 
10
- ## What is SRL?
11
- [SRL](https://github.com/SimpleRegex) is a small language that lets you write pattern matching expressions
21
+ ## Installation
22
+
23
+ ### ...with Bundler
24
+ Add this line to your application's Gemfile:
25
+
26
+ gem 'srl_ruby'
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ ### ...with Rubygem
33
+ Or install it directly yourself with the command line:
34
+
35
+ $ gem install srl_ruby
36
+
37
+ ## Usage
38
+ Let's test the installation by launching the __srl2ruby__ (SRL-to-Ruby) command-line compiler with the help option:
39
+
40
+ $ srl2ruby --help
41
+
42
+ It should output something similar to:
43
+
44
+ ```
45
+ Usage: srl2ruby SRL_FILE [options]
46
+
47
+ Description:
48
+ Parses a SRL file and compiles it into a Ruby Regexp literal.
49
+ Simple Regex Language (SRL) website: https://simple-regex.com
50
+
51
+ Examples:
52
+ srl2ruby example.srl
53
+ srl2ruby example.srl -o example_re.rb -t srl_and_ruby.erb
54
+
55
+ Options:
56
+ -o, --output-file PATH Output to a file with specified name.
57
+ -t, --template-file TEMPLATE Use given ERB template for the Ruby code generation. srl2ruby looks for
58
+ the template file in current dir first then in its gem's /templates dir.
59
+
60
+ --version Display the program version then quit.
61
+ -?, -h, --help Display this help then quit.
62
+ ```
63
+
64
+ ## A quick intro to SRL and srl2ruby
65
+ ### What is SRL?
66
+ SRL is a small language that lets you write pattern matching expressions
12
67
  in a readable syntax that bears some resemblance with English.
68
+ For SRL documentation and examples, we cannot but recommend you to jump to the [official SRL website](https://simple-regex.com).
69
+
70
+ ### Why SRL?
71
+ It is a well-known fact: regexes can be really hard to write and even harder to read ('decipher' verb is closer to reality).
72
+ Alas, the path of creating and maintaining regexes can be full of frustration.
73
+
74
+ There comes SRL. The intent is to let developers define self-documenting patterns with an easy syntax.
75
+ And then let your computer translate SRL expressions into terse regular expressions.
76
+
77
+ ### Our first SRL pattern
78
+ Let's succumb to the traditional 'hello world' example. True, it is a contrived example that doesn't make justice to SRL expressiveness. On the other hand, it is a starting point good enough to learn the compile cycle.
79
+
80
+ As a first step, let's create a file named `hello.srl` with just the following line:
81
+ ```
82
+ begin with literally "Hello world!"
83
+ ```
84
+
85
+ It should read as 'Match any text that begins with the exact text "Hello world!"'
86
+ Now, if one invokes the `srl2ruby` compiler with the command line...
87
+
88
+ $ srl2ruby hello.srl
89
+
90
+
91
+ ... one gets the following output:
92
+ ```
93
+ Parsing file 'hello.srl'
94
+ /^Hello world!/
95
+ ```
96
+
97
+ The last displayed line is the Ruby `Regexp` representation of the above SRL line. It can be pasted as such in your Ruby code, like in the following Ruby snippet:
98
+ ```ruby
99
+ subject = 'Hello world! Welcome to SRL...'
100
+ puts 'It matches!' if subject =~ /^Hello world!/
101
+ ```
102
+
103
+ As expected the snippet results in the message:
104
+ ```
105
+ It matches!
106
+ ```
107
+
108
+ **Quick recap:**
109
+ - `srl2ruby` expects a SRL file (typically with a .srl extension)
110
+ - It parses the _Simple Regex Language_ content...
111
+ - ... then generates the `Regexp` that is equivalent to the SRL input
112
+ - Finally, it prints the results to the console.
113
+
114
+ Feature: with the command-line `-o` option the compiler will send the output to a file with specified name.
115
+
116
+
117
+ ### Gears up
118
+ Let's admit it, our first example wasn't really impressive.
119
+ So, let's try with a more imposing example inspired from the [official SRL website](https://simple-regex.com): an email validation pattern.
120
+
121
+ ```
122
+ begin with any of (digit, letter, one of "._%+-") once or more,
123
+ literally "@",
124
+ any of (digit, letter, one of ".-") once or more,
125
+ ( literally ".",
126
+ letter at least 2 times
127
+ ) optional,
128
+ must end,
129
+ case insensitive
130
+ ```
131
+
132
+ Assume that the previous SRL pattern was put in a file named `email_validation.srl` and that we invoked `srl2ruby` with the following command-line:
133
+
134
+ $ srl2ruby email_validation.srl
135
+
136
+ Then the output should be:
137
+ ```
138
+ Parsing file 'email_validation.srl'
139
+ /(?i-mx:^(?:\d|[a-z]|[._%+\-])+@(?:\d|[a-z]|[.\-])+(?:\.[a-z]{2,})?$)/
140
+ ```
141
+
142
+ The resulting regexp isn't for the fainted hearts: who's ready to maintain it? In addition, the above pattern covers only the most frequent cases.
143
+ If you were asked to cover more exotic case, and knowing that it means an expression at least twice as complex, which version are you willing to update the SRL or the Regexp one?
144
+
145
+ #### Good to know: customizable output
146
+ In fact, if one wants to update or maintain a pattern, it would be practical to have the SRL expression and its equivalent Regexp next to each other in our Ruby source code.
147
+ Can the `srl2ruby` compiler help there? The answer is ... yes.
148
+ First, it is good to know that the output of the `srl2ruby` compiler can be tailored with an ERB template. For instance, the output of all the previous examples is relying on a default template called `base.erb`. It is bundled in the `srl_ruby` gem as another template called `srl_and_ruby.erb`. This second template will emit the SRL code (in Ruby comments) followed by the Regexp literal.
149
+ So let's use it with our email validation example:
150
+
151
+ $ srl2ruby email_validation.srl --template-file srl_and_ruby.erb
152
+
153
+ The shorter option `-t` syntax is also possible:
154
+
155
+ $ srl2ruby email_validation.srl -t srl_and_ruby.erb
156
+
157
+ The compiler's output contains now the original SRL expression in comments:
158
+
159
+ ```
160
+ Parsing file 'email_validation.srl'
161
+ # SRL expression follows:
162
+ # begin with any of (digit, letter, one of "._%+-") once or more,
163
+ # literally "@",
164
+ # any of (digit, letter, one of ".-") once or more,
165
+ # ( literally ".",
166
+ # letter at least 2 times
167
+ # ) optional,
168
+ # must end,
169
+ # case insensitive
170
+ #
171
+ # ... and its Regexp equivalent:
172
+ /(?i-mx:^(?:\d|[a-z]|[._%+\-])+@(?:\d|[a-z]|[.\-])+(?:\.[a-z]{2,})?$)/
173
+ ```
174
+ The above SRL code in comments can be safely inserted in a Ruby file.
175
+
176
+ **Quick recap:**
177
+ - SRL can be used to specify much more challenging patterns than our boring 'Hello world!'.
178
+ - The `srl2ruby` compiler uses a ERB template to format its output.
179
+ - It is possible to choose a specific template via the `-t` option.
180
+
181
+ _Feature_: When given the name of a template via the `-t` option, the compiler will look first for such a template in the current directory, then, if not found, in its `templates` directory. This gives the opportunity to use customized local template files.
182
+
183
+ ## Time for yet another example
13
184
 
14
- As an example, let's assume that our mission is to create a regular expression
15
- that matches the time in 12 hour clock format.
16
- Here is a SRL expression that matches the _hh:mm AM/PM_ time format:
185
+ As an example, let's assume that we are asked to create a regular expression that matches the time in 12 hour clock format (say, _hh:mm AM/PM_).
186
+ In addition, the hour and minute values must be put (= captured) in a variable named `hour` and `min` respectively.
187
+
188
+ We will proceed in multiple iterations of increasing complexity.
189
+ However, for those that are always in a hurry and like movie spoils, here is the requested `Regexp`:
190
+ ```ruby
191
+ /(?i-mx:^(?<hour>(?:(?:0?\d)|(?:1[01]))):(?<min>(?:0?|[1-5])\d)\s?[AP]M$)/
192
+ ```
193
+ Want to jump directly to the latest [iteration](#iteration-5)?...
194
+
195
+
196
+ ### Iteration 1
197
+ Here is a very naive SRL expression that matches the requested time format:
17
198
  ```
18
- digit from 0 to 1,
19
- digit optional,
199
+ begin with digit twice,
20
200
  literally ":",
21
- digit from 0 to 5, digit,
201
+ digit twice
22
202
  literally " ",
23
- one of "AP", literally "M"
203
+ one of "AP", literally "M",
204
+ must end
24
205
  ```
25
206
 
26
- ### Learn more about Simple Regex Language
27
- Here are a couple of hyperlinks of interest:
28
- [Main SRL website](https://simple-regex.com)
29
- [SRL libraries](https://github.com/SimpleRegex)
207
+ If one compiles the above SRL expression with `srl2ruby` as explained earlier in ['Our first SRL pattern'](#our-first-srl-pattern) section, it will generate the following Regexp literal:
208
+ ```ruby
209
+ /^\d{2}:\d{2} [AP]M$/
210
+ ```
30
211
 
212
+ When I want to test regular expressions, one of my favorite tool is the
213
+ [Rubular website](http://rubular.com/). Tom Lovitt created a great Regexp editor and tester specifically for the Ruby community.
31
214
 
32
- ## Why SRL?
33
- Even without knowing SRL, a reader can easily grasp the details of the above SRL expression.
34
- This is where SRL shines over the traditional regular expressions: high readability.
35
- For instance, a regex for the clock format problem may look like this:
215
+ By the way, perhaps, some lynx-eyed readers spotted a small "mistake" on the third line of the SRL snippet: it doesn't end with a comma.
216
+ My apologies... For style consistency this line should be written as:
36
217
  ```
37
- /[0-1]?\d:[0-5]\d [AP]M/
218
+ digit twice,
38
219
  ```
220
+ In reality, SRL happily ignores comma. Well..., most of the time. There is one exception: for the `any of` construct commas are used to separate alternatives (see example in [Iteration 3](#iteration-3)
39
221
 
40
- In terms of terseness, regexes are hard to beat. But it is also a well-known fact: regexes can be really hard to write and even harder to read ('decipher' verb is closer to reality).
41
- Alas, the path of creating and maintaining regexes can be full of frustration.
222
+ ### Iteration 2
223
+ Tests won't take a long time to show that the previous pattern is much too 'lenient' and will accept grossly incorrect entries such as 45:67 PM.
42
224
 
43
- There comes SRL. The intent is to let developers define self-documenting patterns with an easy syntax.
44
- And then let your computer translate SRL expressions into regular expressions.
45
- `srl_ruby` allows you to craft self-documenting patterns in SRL and then generate
46
- their Ruby regular expression representation.
225
+ For our next iteration, we keep note that:
226
+ - The first digit (from the left) can take the values 0 or 1 only.
227
+ - The third digit may run from 0 to 5 since the highest value for the minutes is 59.
228
+
229
+ Here is the improved SRL version:
230
+ ```
231
+ begin with digit from 0 to 1,
232
+ digit,
233
+ literally ":",
234
+ digit from 0 to 5,
235
+ digit,
236
+ literally " ",
237
+ one of "AP", literally "M"
238
+ must end
239
+ ```
240
+
241
+ `srl2ruby` will swallow the SRL file and will spit out the next Regexp:
242
+ ```ruby
243
+ /^[0-1]\d:[0-5]\d [AP]M$/
244
+ ```
245
+ ### Iteration 3
246
+ Erroneous values like 45:67 PM are no more accepted this time. That's definitively better...
247
+ But other tests will reveal that our pattern is still too permissive since it accepts values like 17:23 PM. A hour value of 17 is OK in 24 hour format but here we fail meeting our requirements...
248
+
249
+ So, for our third try, we keep note that:
250
+ - If the first hour digit is 1, then the second digit can take the values 0 or 1 only.
47
251
 
252
+ Let's refactor our pattern:
253
+ ```
254
+ begin with any of (
255
+ (literally "0", digit),
256
+ (literally "1", one of "01")
257
+ )
258
+ literally ":",
259
+ digit from 0 to 5,
260
+ digit,
261
+ literally " ",
262
+ one of "AP", literally "M"
263
+ must end
264
+ ```
48
265
 
49
- Ah, by the way, our clock format pattern isn't completely correct. It will match invalid times like 19:34 PM.
50
- The problem arises when the first digit in the hour field is a one: in that case the second digit can only be
51
- zero or one.
266
+ Remarks:
267
+ - The indentation isn't required by SRL, but I find that it contributes to the readability...
52
268
 
53
- Let's fix the issue in SRL:
269
+ `srl2ruby` will transform this into:
270
+ ```ruby
271
+ /^(?:(?:0\d)|(?:1[01])):[0-5]\d [AP]M$/
54
272
  ```
55
- any of (
273
+
274
+ ### Iteration 4
275
+ This time the pattern works correctly. But in the meantime, our customer changed his requirements (*of course, such things never happen in real life...*). He asks for more flexibility in the pattern:
276
+ - If the most significant digit value is zero, it is optional (i.e. some clock models won't display it).
277
+ - The space between the minute value and the AM/PM indicator is now optional.
278
+ - The AM/PM indicator can sometimes be written in small letters (am/pm).
279
+
280
+ Let's go for another tour:
281
+ ```
282
+ begin with any of (
56
283
  (literally "0" optional, digit),
57
284
  (literally "1", one of "01")
285
+ )
286
+ literally ":",
287
+ any of (
288
+ literally "0" optional,
289
+ digit from 1 to 5
58
290
  ),
291
+ digit,
292
+ whitespace optional,
293
+ one of "AP", literally "M"
294
+ must end,
295
+ case insensitive
296
+ ```
297
+
298
+ Here is the Regexp counterpart:
299
+ ```ruby
300
+ /(?i-mx:^(?:(?:0?\d)|(?:1[01])):(?:0?|[1-5])\d\s?[AP]M$)/
301
+ ```
302
+ ### Iteration 5
303
+ Are we done? No: we were asked to capture the values of hours and minutes.
304
+
305
+ SRL allows for named captures, so here is the updated version:
306
+ ```
307
+ begin with capture(
308
+ any of (
309
+ (literally "0" optional, digit),
310
+ (literally "1", one of "01")
311
+ )
312
+ ) as "hour",
59
313
  literally ":",
60
- digit from 0 to 5, digit,
61
- literally " ",
314
+ capture(
315
+ any of (
316
+ literally "0" optional,
317
+ digit from 1 to 5
318
+ ),
319
+ digit
320
+ ) as "min",
321
+ whitespace optional,
62
322
  one of "AP", literally "M"
323
+ must end,
324
+ case insensitive
63
325
  ```
64
326
 
65
- And there is the equivalent regex found by `srl_ruby`:
327
+ `srl2ruby` will swiftly swallow the above SRL pattern and generate the following Regexp:
328
+ ```ruby
329
+ /(?i-mx:^(?<hour>(?:(?:0?\d)|(?:1[01]))):(?<min>(?:0?|[1-5])\d)\s?[AP]M$)/
66
330
  ```
67
- /(?:(?:0?\d)|(?:1[01])):[0-5]\d [AP]M/
331
+
332
+ That's becoming insane...
333
+
334
+
335
+ #### Does this last Regexp really work?
336
+ Glad you asked... Here is a Ruby snippet that can be used to test the last generated Regexp:
337
+
338
+ ```ruby
339
+ # Next Regexp was copy-pasted from srl2ruby output
340
+ pattern = /(?i-mx:^(?<hour>(?:(?:0?\d)|(?:1[01]))):(?<min>(?:0?|[1-5])\d)\s?[AP]M$)/
341
+ text = '1:43am'
342
+
343
+ matching = pattern.match(text)
344
+ if matching
345
+ print 'Capture names: '; p(matching.names) # => Capture names: ["hour", "min"]
346
+ puts "Value of 'hour': #{matching[:hour]}" # => Value of 'hour': 1
347
+ puts "Value of 'min': #{matching[:min]}" # => Value of 'min': 43
348
+ else
349
+ puts "Text '#{text}' doesn't match."
350
+ end
68
351
  ```
69
352
 
353
+ Running this snippet, gives the following output:
354
+ ```
355
+ Capture names: ["hour", "min"]
356
+ Value of 'hour': 1
357
+ Value of 'min': 43
358
+ ```
359
+ As one can see, from the input '1:43am', the Regexp captured the hour and minute values in the appropriate capture variable. Mission accomplished...
70
360
 
71
- ## Usage
361
+ ## srl_ruby API
72
362
 
73
- The method `SrlRuby#parse` accepts a Simple Regex Language string as input, and returns the corresponding regular expression.
363
+ The method `SrlRuby#parse` accepts a Simple Regex Language string as input, and returns the corresponding regular expression as a `Regexp` instance.
74
364
 
75
365
  For instance, the following snippet...
76
366
 
@@ -106,22 +396,6 @@ puts 'Equivalent regexp: /' + result + '/'
106
396
  Equivalent regexp: /(?:19|20)\d{2}-(?:(?:0\d)|(?:1[012]))-(?:(?:0\d)|(?:[12]\d)|(?:3[01]))/
107
397
  ```
108
398
 
109
- ## Installation
110
-
111
- Add this line to your application's Gemfile:
112
-
113
- ```ruby
114
- gem 'srl_ruby'
115
- ```
116
-
117
- And then execute:
118
-
119
- $ bundle
120
-
121
- Or install it yourself as:
122
-
123
- $ gem install srl_ruby
124
-
125
399
 
126
400
  ## Contributing
127
401
 
data/bin/srl2ruby ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+ require 'erb'
3
+ require 'rubygems'
4
+ require 'pathname'
5
+
6
+ require_relative '../lib/srl_ruby'
7
+ require_relative 'srl2ruby_cli_parser'
8
+
9
+ class Srl2RubyProg
10
+ DefaultSRLExtension = 'srl'.freeze
11
+ DefDirname = '/templates'.freeze
12
+ DefTemplate = 'base.erb'.freeze
13
+ attr_reader(:cli_options)
14
+ attr_reader(:template)
15
+
16
+ def initialize(prog_name, args)
17
+ my_version = SrlRuby::VERSION
18
+ cli = Srl2RubyCLIParser.new(prog_name, my_version)
19
+ @cli_options = cli.parse!(args)
20
+ @template = valid_template
21
+ end
22
+
23
+ def run!(file_names)
24
+ file_names.each do |srl_file|
25
+ fname = validate_filename(srl_file)
26
+ next unless file_exist?(fname)
27
+ puts "Parsing file '#{fname}'"
28
+ srl_source = File.read(fname)
29
+ result = SrlRuby.parse(srl_source)
30
+ destination = $stdout
31
+ if cli_options.include?(:output)
32
+ filepath = cli_options[:output]
33
+ destination = File.open(filepath, 'w')
34
+ end
35
+ puts "Writing to file '#{filepath}'" unless destination == $stdout
36
+ destination.puts emit_code(template, srl_source, result)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def valid_template
43
+ t_filepath = nil
44
+ bindir = Gem.bin_path('srl_ruby', File.basename(__FILE__))
45
+ gem_dir = Pathname.new(bindir.sub(/(?<=\/)[^\/]+$/, '')).parent
46
+ def_template_dir = gem_dir.expand_path.to_s + DefDirname
47
+
48
+ if cli_options.include?(:template)
49
+ fname = cli_options[:template]
50
+ exists = File.exist?(fname)
51
+ if exists
52
+ t_filepath = Dir.getwd + '/' + fname
53
+ else
54
+ t_filepath = def_template_dir + '/' + fname
55
+ exit(1) unless file_exist?(t_filepath)
56
+ end
57
+ else
58
+ t_filepath = def_template_dir + '/' + DefTemplate
59
+ end
60
+ path = Pathname.new(t_filepath)
61
+ erb_template = ERB.new(path.read, nil, '<>')
62
+
63
+ erb_template
64
+ end
65
+
66
+ def validate_filename(raw_fname)
67
+ # When necessary add extension to file name
68
+ fname = raw_fname.dup
69
+ basename = File.basename(fname)
70
+ has_extension = basename =~ /(?<=[^.])\.[^.]+$/
71
+ fname << '.' << DefaultSRLExtension unless has_extension
72
+
73
+ fname
74
+ end
75
+
76
+ def file_exist?(fname)
77
+ exists = File.exist?(fname)
78
+ $stderr.puts "No such file '#{fname}'" unless exists
79
+
80
+ exists
81
+ end
82
+
83
+ def emit_code(template, srl_source, regexp)
84
+ template.result(binding)
85
+ end
86
+ end # class
87
+
88
+ ########################################
89
+ # ENTRY POINT
90
+ ########################################
91
+ program = Srl2RubyProg.new(File.basename(__FILE__), ARGV)
92
+
93
+ # All options from CLI gobbled from ARGV, remains only file name
94
+ program.run!(ARGV)
95
+ # End of file
@@ -0,0 +1,89 @@
1
+ require 'optparse' # Use standard OptionParser class for command-line parsing
2
+
3
+ # A command-line option parser for the srl2ruby compiler.
4
+ # It is a specialisation of the OptionParser class.
5
+ class Srl2RubyCLIParser < OptionParser
6
+ # @return [Hash{Symbol=>String, Array}]
7
+ attr_reader(:parsed_options)
8
+
9
+ # Constructor.
10
+ def initialize(prog_name, ver)
11
+ super()
12
+ reset(prog_name, ver)
13
+
14
+ heading
15
+ separator 'Options:'
16
+ add_o_option
17
+ add_t_option
18
+ separator ''
19
+ add_tail_options
20
+ end
21
+
22
+ def parse!(args)
23
+ super
24
+ parsed_options
25
+ end
26
+
27
+ private
28
+
29
+ def reset(prog_name, ver)
30
+ @program_name = prog_name
31
+ @version = ver
32
+ @banner = "Usage: #{prog_name} SRL_FILE [options]"
33
+ @parsed_options = {}
34
+ end
35
+
36
+ def description
37
+ descr = <<-DESCR
38
+ Description:
39
+ Parses a SRL file and compiles it into a Ruby Regexp literal.
40
+ Simple Regex Language (SRL) website: https://simple-regex.com
41
+
42
+ Examples:
43
+ #{program_name} example.srl
44
+ #{program_name} example.srl -o example_re.rb -t srl_and_ruby.erb
45
+ DESCR
46
+
47
+ descr
48
+ end
49
+
50
+ def heading
51
+ banner
52
+ separator ''
53
+ separator description
54
+ separator ''
55
+ end
56
+
57
+ def add_o_option
58
+ explanation = 'Output to a file with specified name.'
59
+
60
+ on '-o', '--output-file PATH', explanation do |pathname|
61
+ @parsed_options[:output] = pathname
62
+ end
63
+ end
64
+
65
+ def add_t_option
66
+ explanation = <<-EXPLANATION
67
+ Use given ERB template for the Ruby code generation. srl2ruby looks for
68
+ the template file in current dir first then in its gem's /templates dir.
69
+ EXPLANATION
70
+
71
+ on '-t', '--template-file TEMPLATE', explanation do |template|
72
+ @parsed_options[:template] = template
73
+ end
74
+ end
75
+
76
+ def add_tail_options
77
+ on_tail('--version', 'Display the program version then quit.') do
78
+ puts version
79
+ exit(0)
80
+ end
81
+
82
+ on_tail('-?', '-h', '--help', 'Display this help then quit.') do
83
+ puts help
84
+ exit(0)
85
+ end
86
+ end
87
+ end # class
88
+
89
+ # End of file