ppr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.14.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ppr.gemspec
4
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
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