ppr 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +333 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/ppr.rb +12 -0
- data/lib/ppr/bar.inc +1 -0
- data/lib/ppr/foo.inc +1 -0
- data/lib/ppr/keyword_searcher.rb +105 -0
- data/lib/ppr/ppr_core.rb +849 -0
- data/lib/ppr/safer_generator.rb +130 -0
- data/lib/ppr/test_keyword_searcher.rb +57 -0
- data/lib/ppr/test_ppr.inc +7 -0
- data/lib/ppr/test_ppr.rb +291 -0
- data/lib/ppr/test_ppr.txt +62 -0
- data/lib/ppr/test_ppr2.txt +61 -0
- data/lib/ppr/test_ppr_exp.txt +31 -0
- data/lib/ppr/test_safer_generator.rb +140 -0
- data/lib/ppr/version.rb +3 -0
- data/ppr.gemspec +39 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 71f7684269c365ceabde0b9a78723562b4e05557
|
4
|
+
data.tar.gz: 1dd28975ce62b8b8983fbeb300acf8d135c6569b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 75867d46fd7a09643f1b3c91cc48110ec5f37ce38d8175714fdec7991b2828c19071836deb4c82f9b233294ab812085a061e277380cbe63d77779c5049b6816a
|
7
|
+
data.tar.gz: 22ccdc04aaba51bae61700b8afb5c5ad51308ae47607bfe964605cb96d82c9b515ff6d39fb77e1d5eff8baeb8a4dc38e6714c29f4f64e0884d461c3b9d1b31ed
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Lovic Gauthier
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
# Ppr
|
2
|
+
|
3
|
+
Ppr (Preprocessor in Ruby) is a library for preprocessing a text with macro written in the ruby language.
|
4
|
+
|
5
|
+
Ppr has the following features:
|
6
|
+
* Support of the full Ruby language for the macros.
|
7
|
+
* Possibility to change the keywords defining the macros - this can be useful to avoid conflicts with the contents of the text being preprocessed -
|
8
|
+
* Execution of the macros in a sandbox to limit the effects of malicious code inserted in the input stream to preprocess (**do consult** the [disclaimer](#Disclaimer) section about this topic).
|
9
|
+
|
10
|
+
__Note__:
|
11
|
+
|
12
|
+
Ppr is somewhat similar to the C preprocessor (cpp), but is mainly meant to be used for code generation. For that purpose, and contrary to cpp, loops and recursion are possible. This render Ppr much more flexible, but also less safe to use: it might enter into an infinite loop whereas this is strictly impossible with cpp.
|
13
|
+
|
14
|
+
## Disclaimer
|
15
|
+
|
16
|
+
Even if the macro are executed in a sandbox environment, in the current state, I cannot guarantee their safety. Moreover, the `.load` and `.require` macros give read access to the disk.
|
17
|
+
|
18
|
+
Therefore **do not use Ppr with root (administrator) privilege**, and **do not allow the execution of Ppr by a server (web or other)** without the strictest caution.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'ppr'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install ppr
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### using Ppr
|
39
|
+
|
40
|
+
You can use Ppr in a ruby program by including `ppr.rb` in your ruby file:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
include 'ppr.rb'
|
44
|
+
```
|
45
|
+
|
46
|
+
Then, build a new preprocessor by instantiating `Ppr::Preprocessor` as follows:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
ppr = Ppr::Preprocessor.new
|
50
|
+
```
|
51
|
+
|
52
|
+
This preprocessor preprocesses the text provided as `input` stream and put the result in an `output` stream as follows:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
ppr.preprocess(input,output)
|
56
|
+
```
|
57
|
+
|
58
|
+
For the command above, the `input` stream can be any object which provides the `each_line` enumerator, and the `output` stream can be any object which provides the `<<` operator for concatenating a string.
|
59
|
+
|
60
|
+
Parameters can be passed to the preprocessor when building it using a hash associating names (string or symbol) to values. These parameters will the accessible from the macros as instance variables.
|
61
|
+
For instance, the following code will create a new preprocessor with `hey` parameter set to "Hello" and `one` parameter set 1. Then, the code of the macros will have access to them through the `@hey` and the `@one` instance variables.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
ppr = Ppr::Preprocessor.new({"hey" => "Hello", "one" => 1})
|
65
|
+
```
|
66
|
+
|
67
|
+
The keywords defining the macros can also be redefined when building a new preprocessor by passing through the constructor named arguments. For instance, the following code will rename the `.def` keyword to `.DEFINE`:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
ppr = Ppr::Preprocessor.new(defm: ".DEFINE")
|
71
|
+
```
|
72
|
+
|
73
|
+
The expansion operator `:<` (please refer to the next section) too can also be redefined when building a preprocessor through the `expand` name argument.
|
74
|
+
|
75
|
+
The list of the named arguments used for redefining a preprocessor is as follows:
|
76
|
+
|
77
|
+
| named argument | redefined keyword |
|
78
|
+
| :------------- | :---------------- |
|
79
|
+
| apply | .do |
|
80
|
+
| applyR | .doR |
|
81
|
+
| define | .def |
|
82
|
+
| defineR | .defR |
|
83
|
+
| assign | .assign |
|
84
|
+
| loadm | .load |
|
85
|
+
| requirem | .require |
|
86
|
+
| ifm | .if |
|
87
|
+
| elsem | .else |
|
88
|
+
| endifm | .endif |
|
89
|
+
| endm | .end |
|
90
|
+
| expand | :< |
|
91
|
+
| glue | ## |
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
### Rules for writing a macro
|
96
|
+
|
97
|
+
Macros can be described on a single line or on multiple lines.
|
98
|
+
|
99
|
+
The syntax of a one-line macro is the following:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
<keyword> <name> '(' <arguments> ')' <code of the macro without any new line>
|
103
|
+
```
|
104
|
+
|
105
|
+
The syntax of a multi-line macro is the following:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
<keyword> <name> '(' <arguments> ')'
|
109
|
+
<code of the macro>
|
110
|
+
'.end'
|
111
|
+
```
|
112
|
+
|
113
|
+
In the above descriptions:
|
114
|
+
* `<keyword>` is a keyword indicating the beginning of a macro (such keywords are described in the following section).
|
115
|
+
* `name` is an identifier string indicating the name of the macro. If the macro does not require a name, `<name>` must be omitted.
|
116
|
+
* `<arguments>` is a comma-separated list of arguments passed to the code of the macro, each argument being an identifier string. Only the `.def` and the `.defR` macros support arguments, for the other kind of macros `'(' <arguments> ')'` must be omitted.
|
117
|
+
* `.end` is the keyword closing a multi-line macro and must be on a separate line.
|
118
|
+
|
119
|
+
*NB*: an identifier string is an alphanumerical string starting with an alphabetic character (the `_` character is considered to be an alphabetical character).
|
120
|
+
|
121
|
+
The code of a macro is standard ruby where the `File`, `Dir` classes, the `open` and the `system` methods and the `` `command` `` construct are deactivated. Expanding a macro consists then in executing its ruby code. When the macro has arguments, they are used as standard ruby local variables referring to `String` objects.
|
122
|
+
|
123
|
+
For producing the text to be added to the output stream, the `:<` operator has to be used as follows:
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
:< <expression>
|
127
|
+
```
|
128
|
+
|
129
|
+
In the code above, `<expression>` can be any ruby expression. However, you must notice that the expression will be converted to a string (through the `to_s` method) before being added to the output stream.
|
130
|
+
|
131
|
+
### The different macros of Ppr
|
132
|
+
|
133
|
+
* __`.do`__: defines an unnamed macro that is expand on place and whose result is not preprocessed again.
|
134
|
+
|
135
|
+
* __`.doR`__: defines an unnamed macro that is expanded on place and whose result is preprocessed again.
|
136
|
+
|
137
|
+
* __`.def`__: defines a named macro that is expanded each time its name is encountered in the text and whose expansion results are not preprocessed again.
|
138
|
+
|
139
|
+
* __`.defR`__: defines a named macro that is expanded each time its name is encountered in the text and whose expansion results are preprocessed again.
|
140
|
+
|
141
|
+
* __`.assign`__: defines a named macro this is expanded on place and whose result is assigned to the instance variable corresponding to the name of the macro. This is the only kind of macro which can set an instance variable accessible to the other macros.
|
142
|
+
|
143
|
+
* __`.load`__: defines an unnamed macro whose expansion result is the name of a file whose contents is pasted on place.
|
144
|
+
|
145
|
+
* __`.require`__: defines an unnamed macro whose expansion result is the name of a file whose contents is pasted on place provided it has not been already required.
|
146
|
+
|
147
|
+
* __`.if`__: defines an unnamed macro whose expansion result is evaluated as a boolean value. If the result is true, the following text is preprocessed until an `.else` or an `.endif` keywords are met. In this case, the code between the `.else` keyword (if any) and the `.endif` keyword is ignored. If the result is false, the following text is skipped until an `.else` or an `.endif` keywords are met. Then, the text following the `.else` keyword (if any) is preprocessed.
|
148
|
+
|
149
|
+
*N.B.*:
|
150
|
+
* the `.if` macro supports nesting.
|
151
|
+
* the syntax of the `.if` macro is identical to the other kind of macros. However, it applies to the conditional only. The part following the conditional and until the `endif` keyword are considered as out of the macro.
|
152
|
+
* the `.end`, the `.else` and `.endif` keywords are to be on a separate line.
|
153
|
+
|
154
|
+
|
155
|
+
### Invoking a `.def` or `.defR` macro
|
156
|
+
|
157
|
+
Macros of the `.def` and `.defR` kinds are not expanded on place, but are expanded wherever their name is invoked in the input text using the following syntax:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
<name>'('<arguments>')'
|
161
|
+
```
|
162
|
+
In the code above, `<name>` is the name of the macro to invoke and `<arguments>` is a comma-separated list of strings where `\` is used as escape character. If the are no arguments, the parenthesis can be omitted.
|
163
|
+
|
164
|
+
*NB*: any character of a string argument is taken into account literally. For instance, its possible to have an argument consisting only of spaces.
|
165
|
+
|
166
|
+
An invocation of a macro will only be recognized if the name is not included in a larger identifier. For instance, assuming that the macro named `foo` has been defined, it will be recognized and expanded in `foo bar` but not in `foobar` nor in `barfoo`. In order to recognize macros within larger keywords, the glue operator (`##`) must be used as follows:
|
167
|
+
|
168
|
+
```
|
169
|
+
<name>##<text>
|
170
|
+
<text>##<name>
|
171
|
+
<text0>##<name>##<text1>
|
172
|
+
```
|
173
|
+
|
174
|
+
In each of the above three cases, `<name>` is the name of a macro to invoke, `<text>`, `<text0>`, `<text1>` are some text to be glued to the macro expansion result. When preprocessed, macro `<name>` will be recognized and expanded, and the glue operators will be removed.
|
175
|
+
|
176
|
+
When the `##` are to be displayed just before or after a macro invocation, they are to be escaped using the `\` character as follows:
|
177
|
+
|
178
|
+
```text
|
179
|
+
<name>\##<text>
|
180
|
+
<text>\##<name>
|
181
|
+
<text0>\##<name>\##<text1>
|
182
|
+
```
|
183
|
+
|
184
|
+
|
185
|
+
### Examples
|
186
|
+
|
187
|
+
* __.do__ example:
|
188
|
+
```ruby
|
189
|
+
Example 1:
|
190
|
+
.do
|
191
|
+
:< "Hello world!"
|
192
|
+
.end
|
193
|
+
```
|
194
|
+
Is expanded to:
|
195
|
+
```text
|
196
|
+
Example 1:
|
197
|
+
Hello world!
|
198
|
+
```
|
199
|
+
|
200
|
+
* __.def__ example:
|
201
|
+
```ruby
|
202
|
+
Example 2:
|
203
|
+
.def hello(world)
|
204
|
+
:< "Hello #{world}!"
|
205
|
+
.end
|
206
|
+
hello(Foo)
|
207
|
+
hello( Bar )
|
208
|
+
```
|
209
|
+
Is expanded to:
|
210
|
+
```text
|
211
|
+
Example 2:
|
212
|
+
Hello Foo!
|
213
|
+
Hello Bar !
|
214
|
+
```
|
215
|
+
* __.doR__ example:
|
216
|
+
```ruby
|
217
|
+
Example 3:
|
218
|
+
.def hello(world) :< "Hello #{world}!"
|
219
|
+
.doR
|
220
|
+
:< "hello(WORLD)"
|
221
|
+
.end
|
222
|
+
```
|
223
|
+
Is expanded to:
|
224
|
+
```text
|
225
|
+
Example 3:
|
226
|
+
Hello WORLD!
|
227
|
+
```
|
228
|
+
* __.defR__ example:
|
229
|
+
```ruby
|
230
|
+
Example 4:
|
231
|
+
.defR sum(num)
|
232
|
+
num = num.to_i
|
233
|
+
if num > 2 then
|
234
|
+
:< "(+ sum(#{num-1}) #{num} )"
|
235
|
+
else
|
236
|
+
:< "(+ 1 2 )"
|
237
|
+
end
|
238
|
+
.end
|
239
|
+
Some lisp: sum(5)
|
240
|
+
```
|
241
|
+
Is expanded to:
|
242
|
+
```text
|
243
|
+
Example 4:
|
244
|
+
Some lisp: (+ (+ (+ (+ 1 2 ) 3 ) 4 ) 5 )
|
245
|
+
```
|
246
|
+
* __.assign__ example:
|
247
|
+
```ruby
|
248
|
+
Example 5:
|
249
|
+
.assign he :< "Hello"
|
250
|
+
.do :< @he + " world!\n"
|
251
|
+
.def hehe :< @he+@he
|
252
|
+
hehe
|
253
|
+
```
|
254
|
+
Is expanded to:
|
255
|
+
```text
|
256
|
+
Example 5:
|
257
|
+
Hello world!
|
258
|
+
HelloHello
|
259
|
+
```
|
260
|
+
* __.load__ example:
|
261
|
+
assuming the content of the file named `foo.inc` is `foo and bar`
|
262
|
+
```ruby
|
263
|
+
Example 6:
|
264
|
+
.load :< "foo.inc"
|
265
|
+
.def foo :< "FooO"
|
266
|
+
.load :< "foo.inc"
|
267
|
+
```
|
268
|
+
Is expanded to:
|
269
|
+
```text
|
270
|
+
Example 6:
|
271
|
+
foo and bar
|
272
|
+
FooO and bar
|
273
|
+
```
|
274
|
+
* __.require__ example:
|
275
|
+
assuming the content of the file named `foo.inc` is `foo and bar`
|
276
|
+
```ruby
|
277
|
+
Example 7:
|
278
|
+
.require :< "foo.inc"
|
279
|
+
.def foo :< "FooO"
|
280
|
+
.require :< "foo.inc"
|
281
|
+
```
|
282
|
+
Is expanded to:
|
283
|
+
```text
|
284
|
+
Example 7:
|
285
|
+
foo and bar
|
286
|
+
```
|
287
|
+
* __.if__ example:
|
288
|
+
```ruby
|
289
|
+
Example 8:
|
290
|
+
.if :< (1 == 1)
|
291
|
+
.def is :< "IS"
|
292
|
+
This is true.
|
293
|
+
.else
|
294
|
+
This is false.
|
295
|
+
.endif
|
296
|
+
.if :< (1 == 0)
|
297
|
+
This is really true.
|
298
|
+
.else
|
299
|
+
This is really false.
|
300
|
+
.endif
|
301
|
+
```
|
302
|
+
Is expanded to:
|
303
|
+
```text
|
304
|
+
Example 8:
|
305
|
+
This IS true.
|
306
|
+
This IS really false.
|
307
|
+
```
|
308
|
+
|
309
|
+
## Development
|
310
|
+
|
311
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
312
|
+
|
313
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
314
|
+
|
315
|
+
## Contributing
|
316
|
+
|
317
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/civol/ppr.
|
318
|
+
|
319
|
+
|
320
|
+
## License
|
321
|
+
|
322
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
323
|
+
|
324
|
+
|
325
|
+
## To do
|
326
|
+
|
327
|
+
* Add support to default value for arguments in the `.def` and `.defR` macros.
|
328
|
+
* Address some potential performance issues for the safer execution context of the macro.
|
329
|
+
* Improve the detection of errors when the `.if` macro is used.
|
330
|
+
|
331
|
+
## Acknowledgement
|
332
|
+
|
333
|
+
The sandbox used for executing the macros is inspired from *safe\_ruby* by Uku Taht available at https://github.com/ukutaht/safe\_ruby and https://rubygems.org/gems/safe_ruby/.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ppr"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/ppr.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "ppr/version"
|
2
|
+
require "ppr/ppr_core"
|
3
|
+
|
4
|
+
## Module including the classes implementing the preprocessor in Ruby.
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# ppr = Ppr::Preprocessor.new(<some options>)
|
8
|
+
# ppr.preprocess(<input stream to preprocess>,
|
9
|
+
# <output stream where to write the preprocessing result>)}
|
10
|
+
#
|
11
|
+
module Ppr
|
12
|
+
end
|
data/lib/ppr/bar.inc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
###############################################################
|
data/lib/ppr/foo.inc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
foo and bar
|
@@ -0,0 +1,105 @@
|
|
1
|
+
######################################################################
|
2
|
+
## Map class for searching keywords in a text. ##
|
3
|
+
######################################################################
|
4
|
+
|
5
|
+
|
6
|
+
class KeywordSearcher
|
7
|
+
|
8
|
+
## Creates a new keyword searcher, where words are between +seperators+
|
9
|
+
# regular expressions.
|
10
|
+
def initialize(separator = "")
|
11
|
+
# Checks and set the separator.
|
12
|
+
@separator = Regexp.new(separator).to_s
|
13
|
+
# Initialize the inner map.
|
14
|
+
@map = {}
|
15
|
+
# Initialize the list of keywords
|
16
|
+
@keywords = []
|
17
|
+
# Initialize the keyword extraction regular expression
|
18
|
+
@keyword_extract = //
|
19
|
+
end
|
20
|
+
|
21
|
+
# Converts to an hash: actually return self.
|
22
|
+
#
|
23
|
+
# NOTE: for duck typing purpose.
|
24
|
+
def to_h
|
25
|
+
return self
|
26
|
+
end
|
27
|
+
|
28
|
+
## Adds a +keyword+ to the searcher associated with an +object+.
|
29
|
+
def []=(keyword,object)
|
30
|
+
# Ensure the keyword is a valid string.
|
31
|
+
keyword = keyword.to_str
|
32
|
+
unless /^[A-Za-z_]\w*$/.match(keyword)
|
33
|
+
raise "Invalid string for a keyword: #{keyword}."
|
34
|
+
end
|
35
|
+
# Update the map.
|
36
|
+
@map[keyword] = object
|
37
|
+
# Get the keywords sorted in reverse order (used for building the
|
38
|
+
# searching regular expressions).
|
39
|
+
@keywords = @map.keys.sort!.reverse!
|
40
|
+
# Update the searching regular expression.
|
41
|
+
@keyword_extract = Regexp.new(@keywords.join("|"))
|
42
|
+
end
|
43
|
+
|
44
|
+
## Get the object corresponding to +keyword+.
|
45
|
+
def [](keyword)
|
46
|
+
return @map[keyword.to_s]
|
47
|
+
end
|
48
|
+
|
49
|
+
## Search a keyword inside a +text+ and return the corresponding object
|
50
|
+
# if found with the range in the string where it has been found.
|
51
|
+
#
|
52
|
+
# If a keyword is in +skip+ it s ignored.
|
53
|
+
#
|
54
|
+
# NOTE: the first found object is returned.
|
55
|
+
def find(text,skip = [])
|
56
|
+
# print "skip=#{skip} @keywords=#{@keywords}\n"
|
57
|
+
# Compute the regular expression for finding the keywords.
|
58
|
+
rexp = Regexp.new( (@keywords - skip).map! do |k|
|
59
|
+
@separator + k + @separator
|
60
|
+
end.join("|") )
|
61
|
+
# print "find with @rexp=#{@rexp}\n"
|
62
|
+
# Look for the first keyword.
|
63
|
+
matched = rexp.match(text)
|
64
|
+
# Isolate the keyword from the separators.
|
65
|
+
# found = @keywords.match(matched.to_s)
|
66
|
+
found = @keyword_extract.match(matched.to_s)
|
67
|
+
if found then
|
68
|
+
found = found.to_s
|
69
|
+
# A keyword is found, adjust the range and
|
70
|
+
# return it with the corresponding object.
|
71
|
+
range = matched.offset(0)
|
72
|
+
range[0] += matched.to_s.index(found)
|
73
|
+
range[1] = range[0] + found.size - 1
|
74
|
+
return [ @map[found], range[0]..range[1] ]
|
75
|
+
else
|
76
|
+
# A keyword is not found.
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
## Search each keyword inside a +text+ and apply the block on the
|
82
|
+
# corresponding objects if found with the range in the string where it
|
83
|
+
# has been found.
|
84
|
+
#
|
85
|
+
# Returns an enumerator if no block is given.
|
86
|
+
#
|
87
|
+
# NOTE: keywords included into a longer one are ignored.
|
88
|
+
def each_in(text)
|
89
|
+
return to_enum(:each_in,text) unless block_given?
|
90
|
+
# Check and clone the text to avoid side effects.
|
91
|
+
text = text.to_s.clone
|
92
|
+
# Look for a first keyword.
|
93
|
+
macro,range = find(text)
|
94
|
+
while macro do
|
95
|
+
# Delete the range from the text.
|
96
|
+
text[range] = " " * (range.last-range.first+1)
|
97
|
+
# Apply the block
|
98
|
+
yield(macro,range)
|
99
|
+
# Look for the next macro if any
|
100
|
+
# print "text = #{text}\n"
|
101
|
+
macro,range = find(text)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|