lmt 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ # Lmt Regular Expressions
2
+
3
+ Our Lmt language depends is a regular language and depends on a few regular expressions, we are listing them here because both the tangler and weave care about them.
4
+
5
+ ## The Include Expression
6
+
7
+ The first regular expression handles the detection of include directives. It recognizes lines like `! include [some description](some-file)` and extracts `some-file`.
8
+
9
+ ###### Code Block: Include Expression
10
+
11
+ ``` ruby
12
+ /^!\s+include\s+\[.*\]\((.*)\)\s*$/
13
+ ```
14
+
15
+ ## The Code Block Expression
16
+
17
+ The second regular expression is intended to note when whe enter or leave a code block. It detects markdown code fences and processes the special directives. It has four groups. The first identifies white space at the beginning of the line. The second detects the language. The third determines if this is a replacement. The fourth is the name of the block (if applicable).
18
+
19
+ ###### Code Block: Code Block Expression
20
+
21
+ ``` ruby
22
+ /^([s]*)``` ?([\w]*) ?(=?)([-\w]*)?/
23
+ ```
24
+
25
+ ## The Macro Substitution Expression
26
+
27
+ The third expression identifies macro expansions surrounded with `⦅` and `⦆`. The first bit deals with making sure that the opening `⦅` isn't escaped. Then there is one group which contains the name of the macro combined and any filters which are being used.
28
+
29
+ ###### Code Block: Macro Substitution Expression
30
+
31
+ ``` ruby
32
+ /(?<!\\)⦅ *([-\w | ]*) *⦆/
33
+ ```
@@ -0,0 +1,9 @@
1
+ # A file to be included in Lmt-Ruby
2
+
3
+ This is just a file to be included in lmt-Ruby. The contents of this file will be included in the output of lmt but not lmm. Any blocks defined here will be available for use in Lmt.
4
+
5
+ ###### Replacing Code Block: Included Block
6
+
7
+ ``` ruby
8
+ included_string = "I came from lmt_include.lmd"
9
+ ```
@@ -0,0 +1,396 @@
1
+ # Lmw-Ruby
2
+
3
+ ###### Code Block: Description
4
+
5
+ ``` text
6
+ A literate Markdown weave tool written in Ruby.
7
+ ```
8
+
9
+ Lmw is a literate Markdown weave program for [literate programing](https://en.wikipedia.org/wiki/Literate_programming). This is a fairly simple program designed to turn a literate Markdown file into a more normal Markdown file without the special semantics. It is interprets the Markdown as described in [lmt-ruby](lmt.lmd). The primary changes to the output is that the header for code blocks is extracted and rendered in standard Markdown. File names are also changed from .lmd to .md. This change is also applied to links.
10
+
11
+ ## Features
12
+
13
+ In order to effectively weave a lmt file we must:
14
+
15
+ 1) Replace the lmt headers with something that a standard markdown parser will make sense of.
16
+ 2) Replace include directives with a more informative text.
17
+ 3) Update all links to .lmd files with a similar link to a .md file.
18
+
19
+ A few nice to have features:
20
+
21
+ 1) Links between reopenings of a given block in the lmw output.
22
+ 2) Add links from macro substitutions to the body of the macro.
23
+ 3) syntax verification: check for balanced code fences, make sure that all reopenings of a block are in the same language, etc.
24
+
25
+ Ideally, any links between and to blocks would also go to included files.
26
+
27
+ Currently, it puts headers on blocks, and replaces include directives with a more human version. We still need to handle the parts and linking. We also need to handle the link updating.
28
+
29
+ ## Interface
30
+
31
+ We need to know where to get the input from and where to send the output to. For that, we will use the following command line options
32
+
33
+ ###### Code Block: Options
34
+
35
+ ``` ruby
36
+ on("--file FILE", "-f", "Required: input file")
37
+ on("--output FILE", "-o", "Required: output file")
38
+ on("--dev", "disables self test failure for development")
39
+ ```
40
+
41
+ Of which, both are required
42
+
43
+ ###### Code Block: Options
44
+
45
+ ``` ruby
46
+ required(:file, :output)
47
+ ```
48
+
49
+ ## Implementation and Example
50
+
51
+ Now for an example in implementation. Using Ruby we can write a template as below:
52
+
53
+ ###### Output Block
54
+
55
+ ``` ruby
56
+ #!/usr/bin/env ruby
57
+ # Encoding: utf-8
58
+
59
+ ⦅includes⦆
60
+
61
+ module Lmt
62
+
63
+ class Lmw
64
+ include Methadone::Main
65
+ include Methadone::CLILogging
66
+
67
+ @dev = true
68
+
69
+ main do
70
+ check_arguments()
71
+ begin
72
+ ⦅main_body⦆
73
+ rescue Exception => e
74
+ puts "Error: #{e.message} #{extract_causes(e)}At:"
75
+ e.backtrace.each do |trace|
76
+ puts " #{trace}"
77
+ end
78
+ end
79
+ end
80
+
81
+ def self.extract_causes(error)
82
+ if (error.cause)
83
+ " Caused by: #{error.cause.message}\n#{extract_causes(error.cause)}"
84
+ else
85
+ ""
86
+ end
87
+ end
88
+
89
+ ⦅self_test⦆
90
+
91
+ ⦅report_self_test_failure⦆
92
+
93
+ ⦅weave_class⦆
94
+
95
+ ⦅option_verification⦆
96
+
97
+ description "⦅description⦆"
98
+ ⦅options⦆
99
+
100
+ version Lmt::VERSION
101
+
102
+ use_log_level_option :toggle_debug_on_signal => 'USR1'
103
+
104
+ go! if __FILE__ == $0
105
+ end
106
+
107
+ end
108
+ ```
109
+
110
+ This is a basic template using the [Ruby methadone](https://github.com/davetron5000/methadone) command line application framework and making sure that we report errors (because silent failure sucks).
111
+
112
+ The main body will first test itself then, invoke the library component, which isn't in lib as traditional because it is in this file and I don't want to move it around.
113
+
114
+ ###### Code Block: Main Body
115
+
116
+ ``` ruby
117
+ self_test()
118
+ weave = Lmw::Weave.from_file(options[:file])
119
+ weave.weave()
120
+ weave.write(options[:output])
121
+ ```
122
+
123
+ Finally, we have the dependencies. Optparse and methadone are used for cli argument handling and other niceties.
124
+
125
+ ###### Code Block: Includes
126
+
127
+ ``` ruby
128
+ require 'optparse'
129
+ require 'methadone'
130
+
131
+ require 'pry'
132
+ ```
133
+
134
+ There, now we are done with the boilerplate. On to:
135
+
136
+ ## The Actual Weaver
137
+
138
+ The weaver is defined within a class that contains the weaving implementation
139
+
140
+ ###### Code Block: Weave Class
141
+
142
+ ``` ruby
143
+ class Weave
144
+ class << self
145
+ ⦅from_file⦆
146
+ end
147
+
148
+ ⦅initializer⦆
149
+ ⦅weave⦆
150
+ ⦅write⦆
151
+
152
+ private
153
+ ⦅weave_class_privates⦆
154
+ end
155
+ ```
156
+
157
+ There may be some private methods, we need a block for them. They will be inserted where needed.
158
+
159
+ ###### Code Block: Weave Class Privates
160
+
161
+ ``` ruby
162
+ ⦅include_includes⦆
163
+ ```
164
+
165
+ ### Initializer
166
+
167
+ The initializer takes the input file and sets up our state.
168
+
169
+ ###### Code Block: Initializer
170
+
171
+ ``` ruby
172
+ def initialize(lines, file_name = "")
173
+ @file_name = file_name
174
+ @lines = lines
175
+ @weaved = false
176
+ end
177
+ ```
178
+
179
+ #### Factory
180
+
181
+ For testing, we want to be able to create an instance with a hard coded set of lines. Furthermore, because this processing is stateful, we want to make the input immutable. Reading from a file needs to be handled. A factory can do it.
182
+
183
+ ##### Reading the File
184
+
185
+ This is fairly self explanatory, though note, we are storing the file in memory as an array of lines.
186
+
187
+ ###### Code Block: From File
188
+
189
+ ``` ruby
190
+ def from_file(file)
191
+ File.open(file, 'r') do |f|
192
+ Weave.new(f.readlines, file)
193
+ end
194
+ end
195
+
196
+ ```
197
+
198
+ ### Weave
199
+
200
+ To weave a file, first we have to identify and construct metadata on all the blocks. Then we use that metadata to transform any lines containing a block declaration into an appropriate header for that block. Finally, we replace any links to an .lmd file with the equivalent .md link.
201
+
202
+ ###### Code Block: Weave
203
+
204
+ ``` ruby
205
+ def weave()
206
+ @blocks = find_blocks(@lines)
207
+ @weaved_lines = substitute_directives_and_headers(
208
+ @lines.map do |line|
209
+ replace_markdown_links(line)
210
+ end)
211
+ @weaved = true
212
+ end
213
+ ```
214
+
215
+ #### Finding the Blocks
216
+
217
+ In order to find the blocks we will need the regular expressions defined in:
218
+
219
+ **See include:** [lmt_expressions.lmd](include_file)
220
+
221
+ First, we get the lines from includes. then we filter the lines for only the headers and footers and check for unmatched headers and footers.
222
+
223
+ ###### Code Block: Weave Class Privates
224
+
225
+ ``` ruby
226
+ def find_blocks(lines)
227
+ lines_with_includes = include_includes(lines)
228
+ code_block_exp = ⦅code_block_expression⦆
229
+ headers_and_footers = lines_with_includes.filter do |(line, source_file)|
230
+ code_block_exp =~ line
231
+ end
232
+ throw "Missing code fence" if headers_and_footers.length % 2 != 0
233
+ ```
234
+
235
+ Now, we throw out all the footers and use the code_block_exp to parse them, group them by name, and generate the metadata. In this case the metadata includes 1) a count of the number of blocks with a particular name in this file. 2) the source file of each block.
236
+
237
+ We are also validating that a block only has one language.
238
+
239
+ ###### Code Block: Weave Class Privates
240
+
241
+ ``` ruby
242
+ headers_and_footers.each_slice(2).map(&:first)
243
+ .map do |(header, source_file)|
244
+ white_space, language, replacement_mark, name = code_block_exp.match(header)[1..-1]
245
+ [name, source_file, language, replacement_mark]
246
+ end.group_by do |name, _, _, _|
247
+ name
248
+ end.transform_values do |blocks|
249
+ block_name, _, block_language, _ = blocks[0]
250
+ count, _ = blocks.inject(0) do |count, (name, source_file, language, replacement_mark)|
251
+ throw "block #{block_name} has multiple languages" unless language == block_language
252
+ count + 1
253
+ end
254
+ block_locations = blocks.each_with_index.map do |(name, source_file, language, replacement_mark), index|
255
+ [name, index, source_file]
256
+ end
257
+ {:count => count, :block_locations => block_locations}
258
+ end
259
+ end
260
+ ```
261
+
262
+ ### Including the Includes
263
+
264
+ This depends on the expression in [lmt_expressions][lmt_expressions.md#The-Include-Expression]
265
+
266
+ Here we go through each line looking for an include statement. When we find one, we replace it with the lines from that file. Those lines will, of course, need to have includes processed as well. For each line, we also need to add the file that it came from.
267
+
268
+ ###### Code Block: Include Includes
269
+
270
+ ``` ruby
271
+ def include_includes(lines, current_file = @file_name, current_path = '', depth = 0)
272
+ raise "too many includes" if depth > 1000
273
+ include_exp = ⦅include_expression⦆
274
+ lines.map do |line|
275
+ match = include_exp.match(line)
276
+ if match
277
+ file = File.dirname(current_file) + '/' + match[1]
278
+ path = File.dirname(current_path) + '/' + match[1]
279
+ new_lines = File.open(file, 'r') {|f| f.readlines}
280
+ include_includes(new_lines, file, path, depth + 1)
281
+ else
282
+ [[line, current_path]]
283
+ end
284
+ end.flatten(1)
285
+ end
286
+
287
+ ```
288
+
289
+ ### Substituting the Directives and Headers
290
+
291
+ Now we need to substitute both the directives and headers with appropriate markdown replacements. To do so we need the [include expression](lmt_expressions.lmd#The-Include-Expression) and the [code block expression](lmt_expressions.lmd#The-Code-Block-Expression).
292
+
293
+ We will match the lines against the expressions and, when a match occurs, we will substitute the appropriate template. There is a little complexity when dealing with entering and exiting code fences. Specifically, we will need to toggle between entering and exiting code fence behavior.
294
+
295
+ ###### Code Block: Weave Class Privates
296
+
297
+ ``` ruby
298
+ def substitute_directives_and_headers(lines)
299
+ include_expression = ⦅include_expression⦆
300
+ code_block_expression = ⦅code_block_expression⦆
301
+ in_block = false
302
+ block_name = ""
303
+ lines.map do |line|
304
+ case line
305
+ when include_expression
306
+ include_file = $1
307
+ ["**See include:** [#{include_file}](include_file)\n"]
308
+ when code_block_expression
309
+ in_block = !in_block
310
+ if in_block
311
+ ⦅make_code_block_header⦆
312
+ else
313
+ [line]
314
+ end
315
+ else
316
+ [line]
317
+ end
318
+ end.flatten(1)
319
+ end
320
+ ```
321
+
322
+ #### The header for code blocks
323
+
324
+ Code blocks need to be headed appropriately as markdown parsing eats the code block name. Because of this we put it in a `h6` header. When the block is repeated, we add a `(part n)` to the end. We also should be adding links for the next and last version of this header.
325
+
326
+ ###### Code Block: Make Code Block Header
327
+
328
+ ``` ruby
329
+ white_space, language, replacement_mark, name =
330
+ code_block_expression.match(line)[1..-1]
331
+ human_name = name.gsub(/[-_]/, ' ').split(' ').map(&:capitalize).join(' ')
332
+ replacing = if replacement_mark == "="
333
+ " Replacing"
334
+ else
335
+ ""
336
+ end
337
+ header = if name != ""
338
+ "#######{replacing} Code Block: #{human_name}\n\n"
339
+ else
340
+ "#######{replacing} Output Block\n\n"
341
+ end
342
+ [header,
343
+ "#{white_space}``` #{language}\n"]
344
+ ```
345
+
346
+ ### Replacing the Markdown Links
347
+
348
+ ###### Code Block: Weave Class Privates
349
+
350
+ ``` ruby
351
+ def replace_markdown_links(line)
352
+ line
353
+ end
354
+ ```
355
+
356
+ ### Write The Output
357
+
358
+ Finally, write the output.
359
+
360
+ ###### Code Block: Write
361
+
362
+ ``` ruby
363
+ def write(output)
364
+ fout = File.open(output, 'w')
365
+ weave() unless @weaved
366
+ @weaved_lines.each {|line| fout << line}
367
+ end
368
+
369
+ ```
370
+
371
+ ## Option Verification
372
+
373
+ Option verification is described here:
374
+
375
+ **See include:** [option_verification.lmd](include_file)
376
+
377
+ ## Testing
378
+
379
+ Of course, we will also need a testing procedure. In this case, we will be passing a set of strings in to the weave and seeing if the output is sane.
380
+
381
+ First, we need a method to report test failures:
382
+
383
+ **See include:** [error_reporting.lmd](include_file)
384
+
385
+ ###### Code Block: Self Test
386
+
387
+ ``` ruby
388
+ def self.self_test()
389
+ end
390
+ ```
391
+
392
+ ## Fin ┐( ˘_˘)┌
393
+
394
+ And with that, we have weaved some Markdown.
395
+
396
+
@@ -0,0 +1,20 @@
1
+ # Option Verification
2
+
3
+ Sadly neither Methadone nor Optparser offer mandatory option verification, so we have to add it ourselves. (In the future, we will probably want to move this to a support library) Doing so requires two methods, required and check_arguments
4
+
5
+ ###### Code Block: Option Verification
6
+
7
+ ``` ruby
8
+ def self.required(*options)
9
+ @required_options = options
10
+ end
11
+
12
+ def self.check_arguments
13
+ missing = @required_options.select{ |p| options[p].nil?}
14
+ unless missing.empty?
15
+ message = "Missing Required Argument(s): #{missing.join(', ')}"
16
+
17
+ abort("#{message}\n\n#{opts.help()}")
18
+ end
19
+ end
20
+ ```