mucgly 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Version history
2
+
3
+ [0.0.1] Initial version.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 tero.isannainen@gmail.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,292 @@
1
+ = Mucgly
2
+
3
+ == Introduction
4
+
5
+ Mucgly is a macro expander for inline macros that exist in the middle of
6
+ body text. The macros are mostly regular Ruby code, but a few special
7
+ commands is also provided.
8
+
9
+ A very simple example:
10
+ Adding 1 + 3 results: -<write (1+3).to_s>-
11
+
12
+ After macro expansion the results is:
13
+ Adding 1 + 3 results: 4
14
+
15
+ By default macro starts with "-<" and ends with ">-". These limiters
16
+ are called hooks, hookbeg and hookend respectively. The code between
17
+ hooks is regular Ruby code. The "write" call writes to selected IO
18
+ stream.
19
+
20
+ Ruby code is executed within a class instance reserved for this
21
+ purpose. The instance is called the Execution Environment. It enables
22
+ values from one macro to be visible in others.
23
+
24
+ Previous example with multiple macros:
25
+ -<@a = 1>--<@b = 3>-\
26
+ Adding 1 + 3 results: -<write (@a+@b).to_s>-
27
+
28
+ Result is exactly the same as in the previous execution. The first
29
+ macro "-<@a = 1>-" produces no output, it just sets the variable "@a"
30
+ to "1". Second macro is similar. The default "escape" character is
31
+ "\\". When placed before newline character, it "eats" the newline and
32
+ nothing is output. Thus the first line outputs nothing.
33
+
34
+ The second line refers to settings from previous macros. Instance
35
+ variables has to be used to maintain the data between macro calls (due
36
+ to instance evaluation).
37
+
38
+
39
+ == Features
40
+
41
+ * User settable hooks to define macro boundaries. Can be set from
42
+ command line, configuration files, or from macro file.
43
+
44
+ * Multiple sources for configuration: default config, environment
45
+ variable, command line.
46
+
47
+ * Multiple passes for macro file.
48
+
49
+ * Convention based output file naming.
50
+
51
+ * Multiple convenience functions for macros to use.
52
+
53
+ * Macro file introspection: line number, file name
54
+
55
+ * Output stream de-muxing.
56
+
57
+ * Many special commands: include, source, etc...
58
+
59
+
60
+
61
+ == Applications
62
+
63
+ * Replacement for M4 macro processor.
64
+
65
+ * Code generation.
66
+
67
+ * Document formatting.
68
+
69
+ * Etc.
70
+
71
+
72
+
73
+ == Special characters
74
+
75
+ === Hooks
76
+
77
+ Hooks start and end the macro definition. By default hookbeg is "-<"
78
+ and hookend is ">-". These values are not easily conflicting with the
79
+ macro file body text.
80
+
81
+ If literal hook string is required, the escape character should be
82
+ place before the hook. For example "\-<" will produce literal "-<" to
83
+ the output.
84
+
85
+ User can set the hooks to whatever string value desired. The hooks can
86
+ also have the same value. However nested macros are not possible
87
+ then. Hooks can also be the same as the escape character, but this
88
+ makes usage somewhat complicated.
89
+
90
+ === Escape
91
+
92
+ Default escape character is "\\". It can be used to escape hooks,
93
+ cancel space (" ") output, and cancel newline ("\\n") output.
94
+
95
+ User can set the escape character to any character. Only single
96
+ character value is accepted.
97
+
98
+ === Multipass
99
+
100
+ If the macro starts with "#" character, the macro is not expanded. One
101
+ "#" character is removed and the rest of the macro is output as it
102
+ is. Each pass will remove one "#" character and when all are removed,
103
+ the macro is evaluated. Usually two passes are enough and one "#"
104
+ character is used to save a macro to the later pass.
105
+
106
+ Multipass can be used for example to create a Table Of Contents. First
107
+ pass collects information about document content and second pass will
108
+ insert the Table Of Contents.
109
+
110
+ Example, with functions (insert_toc, header...) defined elsewhere:
111
+ -<#insert_toc>-
112
+ -<header(1, "My header 1")>-
113
+ Paragraph1
114
+ -<header(2, "My header 1.1")>-
115
+ Paragraph2
116
+ -<header(1, "My header 2")>-
117
+ Paragraph3
118
+
119
+
120
+ == Special commands
121
+
122
+ In addition to regular Ruby code the macros are allowed to include so
123
+ called Special Commands. These commands start with the ":" characters
124
+ and with ".".
125
+
126
+ Example:
127
+ ...
128
+ -<:hook [ ]->\
129
+ ...
130
+
131
+ Would change the hookbeg and hookend to "[" and "]" respectively. One
132
+ special command is allowed per macro and it can't be mixed normal Ruby
133
+ code. Command name is separated by ":" in the beginning and by space
134
+ (" ") in the end. The rest of the macro is taken as argument to the
135
+ macro. Note that a command without an argument has to end with space
136
+ as well.
137
+
138
+ List of ":" style special commands:
139
+
140
+ [include] Include reverts the input stream to the file given in the
141
+ argument. Command can be used to multiplex multiple files
142
+ into one, for example.
143
+
144
+ [output] Select a new output file. Use with "close" command, when the
145
+ new output file is ready.
146
+
147
+ [comment] Comment is used to add comments into the macro file. No
148
+ output is produced from comment macros.
149
+
150
+ [source] Source reads a plain Ruby file into the Execution Environment.
151
+
152
+ [hook] Sets both hookbeg and hookend to values separated by space. The
153
+ new hook values are valid immediately after the enclosing
154
+ macro.
155
+
156
+ [hookbeg] Sets hookbeg.
157
+
158
+ [hookend] Sets hookend.
159
+
160
+ [escape] Sets escape character.
161
+
162
+ [exit] Aborts the macro file.
163
+
164
+ Variable value reference command starts with "." and is followed by
165
+ the (instance) variable name.
166
+
167
+ For example:
168
+ ... -<.my_name>- ...
169
+
170
+ Would write out the value of the "@my_name" variable, thus it is equal
171
+ to writing:
172
+ ... -<write @my_name>- ...
173
+
174
+
175
+
176
+ == API methods
177
+
178
+ API methods are instance methods of the Execution Environment,
179
+ i.e. they are predefined and visible for the macro code. These methods
180
+ are organized into two categories: Published and Hidden.
181
+
182
+ === Published methods
183
+
184
+ Published methods are the most commonly used methods in macros. These are:
185
+
186
+ [write] Writes string into selected output IO.
187
+ [puts] Writes string (+newline) into selected output IO.
188
+ [source] Sources a plain Ruby file.
189
+
190
+
191
+ === Hidden methods
192
+
193
+ Hidded methods start with underscore ("_"). The naming is used to
194
+ prevent poluting the Execution Environment's namespace.
195
+
196
+ [_processFilePair] Process a pair of files. First half is input file
197
+ and second is output file. File pair argument is an
198
+ Array of two ([<fileI>, <fileO>]).
199
+
200
+ [_processFilePairs] Uses "_processFilePair" to process a list of file pairs.
201
+
202
+ [_openInput] Sets input stream to given file. When this file is
203
+ closed, the input stream is reverted to the original
204
+ file.
205
+
206
+ [_openOutput] Sets output stream to given file. When this file is
207
+ closed, the input stream is reverted to the original
208
+ file.
209
+
210
+ [_openString] Open a StringIO file for output.
211
+
212
+ [_pushInput] Makes given file as top input stream. When this stream is
213
+ closed, the input stream is reverted to the original
214
+ file.
215
+
216
+ [_pushOutput] Makes given file as top output stream. When this stream
217
+ is closed, the output stream is reverted to the original
218
+ file.
219
+
220
+ [_closeInput] Close input stream.
221
+
222
+ [_closeOutput] Close output stream.
223
+
224
+ [_ofilename] Output file name as String.
225
+
226
+ [_olinenumber] Output file line number.
227
+
228
+ [_ifilename] Input file name as String.
229
+
230
+ [_ilinenumber] Input file line number.
231
+
232
+ [_eval] Perform instance_eval on the given argument.
233
+
234
+ [_inIO] Handle to input stream (get/set).
235
+
236
+ [_outIO] Handle to output stream (get/set).
237
+
238
+ [_separators] Handle to Separators object. Separotors object includes
239
+ the "escapeChar", "hookBegChars", and "hookEndChars"
240
+ setter and getter methods.
241
+
242
+
243
+ == Command line interface
244
+
245
+ Please execute:
246
+ mucgly -h
247
+
248
+ for an overview of the command line options.
249
+
250
+
251
+ === Input and Output files
252
+
253
+ Input files can be listed to "-i" option. The default input stream for
254
+ Mucgly is STDIN.
255
+
256
+ Output files can be listed to "-o" option. The default output stream
257
+ for Mucgly is STDOUT.
258
+
259
+ If "-o" option is given without arguments and the input file list
260
+ includes ".rx" in each name, the output file list is created by
261
+ extracting ".rx" from each input file name.
262
+
263
+ For example with:
264
+ mucgly -i foo.rx.txt bar.rx.txt -o
265
+
266
+ Two files, foo.txt and bar.txt, would be created. This holds true
267
+ unless the output streams are manipulated with macro commands.
268
+
269
+ If the number of input and output files match, then they are used in pairs.
270
+
271
+
272
+ == Configuration
273
+
274
+ Mucgly checks for the existance of the "MUCGLY" environment
275
+ variable. If the variable exists, the file in variable value is read
276
+ in as plain Ruby.
277
+
278
+ User configuration is searched from the users home directory:
279
+ $HOME/.mucgly
280
+ It is read as plain Ruby file if it exists.
281
+
282
+ Configuration files can also be given on command line using the "-c"
283
+ option. These settings can be used to override the settings above.
284
+
285
+ Additionally plain Ruby code can be given straight on the command line
286
+ with "-l" option.
287
+
288
+
289
+ == More information
290
+
291
+ Check out the "test" directory within installation for detailed
292
+ examples about usage.
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ t.libs << 'lib'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
10
+
11
+ task :cleanup_test do
12
+ sh "rm -rf test/test"
13
+ end
14
+
15
+ task :build => :doc do
16
+ sh "gem build mucgly.gemspec"
17
+ end
18
+
19
+ task :doc do
20
+ sh "yardoc lib/* - README.rdoc CHANGELOG.rdoc"
21
+ end
22
+
23
+ task :publish do
24
+ if Dir.glob('mucgly-*gem').length == 1
25
+ sh "gem push mucgly*.gem"
26
+ else
27
+ raise "Multiple gems in the directory..."
28
+ end
29
+ end
data/bin/mucgly ADDED
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'como'
4
+ include Como
5
+ require_relative '../lib/easyfile'
6
+ require_relative '../lib/mucgly'
7
+
8
+
9
+ # Define command line arguments:
10
+ Spec.command( "mucgly", "Tero Isannainen", "2009, 2013",
11
+ [
12
+ [ :opt_multi, "inputs", "-i", "Input file (default: <STDIN>)." ],
13
+ [ :opt_multi, "configs", "-c", "Ruby configure file(s)" ],
14
+ [ :opt_any, "output", "-o", "Output file (default: <STDOUT>)." ],
15
+ [ :opt_multi, "cl_cmds", "-l", "Command line configuration(s)." ],
16
+ [ :opt_any, "multipass", "-m", "Multipass rounds (default without arg: 2)." ],
17
+ [ :opt_single, "beg_sep", "-sb", "Set hookbeg separator (default: '-<')." ],
18
+ [ :opt_single, "end_sep", "-se", "Set hookend separator (default: '->')." ],
19
+ [ :opt_single, "seps", "-s", "Set both hookbeg and hookend separators." ],
20
+ [ :opt_single, "esc_char", "-e", "Set escape character (default: '\\')." ],
21
+ [ :opt_single, "all_sep", "-sa", "Set all separators and escape." ],
22
+ [ :switch, "no_inits", nil, "No initializations." ],
23
+ [ :switch, "no_user_init", nil, "No user initialization." ],
24
+ [ :switch, "no_env_init", nil, "No env initialization." ],
25
+ [ :switch, "warn_only", "-w", "Warnings only." ],
26
+ [ :switch, "debug", "-d", "Display debug messages." ],
27
+ ], { :tab => 15, } )
28
+
29
+
30
+ filePairs = []
31
+
32
+
33
+ # Set input streams.
34
+ unless Opt['inputs'].given
35
+ filePairs.push [ '<STDIN>', nil ]
36
+ else
37
+ Opt['inputs'].value.each do |i|
38
+ filePairs.push [ i, nil ]
39
+ end
40
+ end
41
+
42
+
43
+ # Set output streams.
44
+ if Opt['output'].given
45
+
46
+ if Opt['output'].value.empty?
47
+
48
+ # Map all input files to output files where '.rx' is removed
49
+ # in the name.
50
+
51
+ # Check that all inputs have '.rx' in the name.
52
+ noRx = false
53
+ name = nil
54
+ filePairs.each do |pair|
55
+ if /\.rx/.match( pair[0] )
56
+ pair[1] = pair[0].gsub( /\.rx/, '' )
57
+ else
58
+ noRx = true
59
+ name = pair[0]
60
+ break
61
+ end
62
+ end
63
+
64
+ if noRx
65
+ Mucgly::error( "Input file must have '.rx' in name\
66
+ when automapping: \"#{name}\"" )
67
+ end
68
+
69
+ elsif Opt['output'].value.length == filePairs.length
70
+
71
+ filePairs.each_index do |idx|
72
+ filePairs[ idx ][1] = Opt['output'].value[ idx ]
73
+ end
74
+
75
+ else
76
+
77
+ Mucgly::error( "Input and output file counts does not match:\
78
+ #{Opt['output'].value.length}/#{filePairs.length}" )
79
+
80
+ end
81
+
82
+ else
83
+
84
+ # Default output, STDOUT.
85
+ filePairs[0][1] = '<STDOUT>'
86
+
87
+ end
88
+
89
+
90
+ execEnv = Mucgly::Env.new
91
+ execEnv._separators = Mucgly::Separators.new
92
+
93
+
94
+ # Change separators:
95
+ if Opt['beg_sep'].given
96
+ execEnv._separators.hookBegChars = Opt['beg_sep'].value
97
+ end
98
+
99
+ if Opt['end_sep'].given
100
+ execEnv._separators.hookEndChars = Opt['end_sep'].value
101
+ end
102
+
103
+ if Opt['seps'].given
104
+ rest = Opt['seps'].value
105
+
106
+ if rest.split.length == 2
107
+ execEnv._separators.hookBegChars = rest.split[0]
108
+ execEnv._separators.hookEndChars = rest.split[1]
109
+ else
110
+ execEnv._separators.hookBegChars = rest
111
+ execEnv._separators.hookEndChars = rest
112
+ end
113
+ end
114
+
115
+
116
+ # Change escape:
117
+ if Opt['esc_char'].given
118
+ execEnv._separators.escapeChar = Opt['esc_char'].value
119
+ end
120
+
121
+
122
+ # Change all specials.
123
+ if Opt['all_sep'].given
124
+ execEnv._separators.hookBegChars = Opt['all_sep'].value
125
+ execEnv._separators.hookEndChars = Opt['all_sep'].value
126
+ execEnv._separators.escapeChar = Opt['all_sep'].value
127
+ end
128
+
129
+
130
+ # Read Mucgly env config.
131
+ unless ( Opt['no_env_init'].given || Opt['no_inits'].given )
132
+ p = ENV["MUCGLY"]
133
+ execEnv.source( p ) if p
134
+ end
135
+
136
+
137
+ # Read default user config.
138
+ unless ( Opt['no_user_init'].given || Opt['no_inits'].given )
139
+ userInit = "#{ENV['HOME']}/.mucgly"
140
+ if FileTest.exist? userInit
141
+ # load userInit
142
+ execEnv.source( userInit )
143
+ end
144
+ end
145
+
146
+
147
+ # Process provided configure files.
148
+ Opt['configs'].given do |value|
149
+ value.each do |i|
150
+ execEnv.source( i )
151
+ end
152
+ end
153
+
154
+
155
+ # Process command line.
156
+ Opt['cl_cmds'].given do |value|
157
+ value.each do |i|
158
+ execEnv.eval( i )
159
+ end
160
+ end
161
+
162
+
163
+ # Multipass processing.
164
+ if Opt['multipass'].given
165
+
166
+ if Opt['multipass'].value.empty?
167
+ passes = 2
168
+ else
169
+ passes = Opt['multipass'].value[0].to_i
170
+ end
171
+
172
+
173
+ if passes <= 1
174
+
175
+ execEnv._processFilePairs( filePairs )
176
+
177
+ else
178
+
179
+ filePairs.each do |filePair|
180
+
181
+ pass = 0
182
+
183
+ # Pair (ping-pong) of StringIO handles.
184
+ sio = [nil]*2
185
+
186
+ while pass < passes
187
+
188
+ # Use StringIO to pass content between passes.
189
+ if pass == 0
190
+ # First pass.
191
+ execEnv._openInput( filePair[ 0 ] )
192
+ sio[(pass+0)%2] = execEnv._openString( "_pass0" )
193
+ sio[(pass+1)%2] = execEnv._openString( "_pass1" )
194
+ execEnv._outIO = sio[(pass+0)%2]
195
+ elsif pass >= ( passes-1 )
196
+ # Last pass.
197
+ execEnv._inIO = sio[(pass+1)%2]
198
+ execEnv._inIO.rewind
199
+ execEnv._openOutput( filePair[ 1 ] )
200
+ else
201
+ # Middle pass.
202
+ execEnv._inIO = sio[(pass+1)%2]
203
+ execEnv._inIO.rewind
204
+ execEnv._outIO = sio[(pass+0)%2]
205
+ end
206
+
207
+ Mucgly::MucglyFile.new( execEnv )
208
+ pass += 1
209
+
210
+ end
211
+
212
+ end
213
+
214
+ end
215
+
216
+ else
217
+
218
+ execEnv._processFilePairs( filePairs )
219
+
220
+ end