legal_markdown 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .ruby*
1
2
  *.gem
2
3
  *.rbc
3
4
  .bundle
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - jruby-19mode # JRuby in 1.9 mode
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ # Status
2
+
3
+ [![Build Status](https://travis-ci.org/compleatang/legal-markdown.png)](https://travis-ci.org/compleatang/legal-markdown)[![Code Climate](https://codeclimate.com/github/compleatang/legal-markdown.png)](https://codeclimate.com/github/compleatang/legal-markdown)[![Dependency Status](https://gemnasium.com/compleatang/legal-markdown.png)](https://gemnasium.com/compleatang/legal-markdown)
4
+
1
5
  # Introduction
2
6
 
3
7
  This gem will parse YAML Front Matter of Markdown Documents. Typically, this gem would be called with a md renderer, such as [Pandoc](http://johnmacfarlane.net/pandoc/), that would turn the md into a document such as a .pdf file or a .docx file. By combining this pre-processing with a markdown renderer, you can ensure that both the structured content and the structured styles necessary for your firm or organization are more strictly enforced. Plus you won't have to deal with Word any longer, and every lawyer should welcome that. Why? Because Word is awful.
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
2
3
 
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/test*.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => [:test]
data/bin/legal2md CHANGED
@@ -4,4 +4,4 @@
4
4
  # legal2md input_file output_file
5
5
 
6
6
  require 'legal_markdown'
7
- LegalToMarkdown.main(ARGV)
7
+ LegalMarkdown::parse(ARGV)
@@ -1,440 +1,19 @@
1
1
  #! ruby
2
- require 'yaml'
3
- require File.dirname(__FILE__) + '/legal_markdown/roman-numerals'
4
2
  require File.dirname(__FILE__) + '/legal_markdown/version'
5
3
  require File.dirname(__FILE__) + '/legal_markdown/make_yaml_frontmatter.rb'
4
+ require File.dirname(__FILE__) + '/legal_markdown/legal_to_markdown.rb'
6
5
 
7
- class LegalToMarkdown
6
+ module LegalMarkdown
8
7
 
9
- def self.main(*args)
10
- if(!ARGV[0])
8
+ def self.parse(*args)
9
+ args = ARGV.dup
10
+ if(!args[0])
11
11
  STDERR.puts "Sorry, I didn't understand that. Please give me your legal_markdown filenames or \"-\" for stdin."
12
12
  exit 0
13
- elsif ARGV.include?("--headers")
14
- MakeYamlFrontMatter.new(ARGV)
13
+ elsif args.include?("--headers")
14
+ LegalMarkdown::MakeYamlFrontMatter.new(args)
15
15
  else
16
- args = ARGV.dup
17
- LegalToMarkdown.new(args)
18
- end
19
- end # main
20
-
21
- def initialize(args)
22
- data = load(args) # Get the Content
23
- parsed_content = parse_file(data) # Load the YAML front matter
24
- mixed_content = mixing_in(parsed_content[0], parsed_content[1]) # Run the Mixins
25
- headed_content = headers_on(mixed_content[0], mixed_content[1]) # Run the Headers
26
- file = write_it( headed_content ) # Write the file
27
- end
28
-
29
- private
30
- # ----------------------
31
- # | Step 1 |
32
- # ----------------------
33
- # Parse Options & Load File
34
- def load(args)
35
- @output_file = args[-1]
36
- @input_file = args[-2] ? args[-2] : args[-1]
37
- begin
38
- if @input_file != "-"
39
- source_file = File::read(@input_file) if File::exists?(@input_file) && File::readable?(@input_file)
40
- elsif @input_file == "-"
41
- source_file = STDIN.read
42
- end
43
- source_file.scan(/(@include (.+)$)/).each do |set|
44
- partial_file = set[1]
45
- to_replace = set[0]
46
- partial_contents = File::read(partial_file) if File::exists?(partial_file) && File::readable?(partial_file)
47
- source_file.gsub!(to_replace, partial_contents)
48
- end
49
- return source_file
50
- rescue => e
51
- puts "Sorry, I could not read the input file #{@input_file}: #{e.message}."
52
- exit 0
53
- end
54
- end
55
-
56
- # ----------------------
57
- # | Step 2 |
58
- # ----------------------
59
- # Load YAML Front-matter
60
-
61
- def parse_file(source)
62
- begin
63
- if source[/@today/]
64
- require 'date'
65
- d = Date.today.strftime("%-d %B, %Y")
66
- source.gsub!($&, d)
67
- end
68
- yaml_pattern = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
69
- parts = source.partition( yaml_pattern )
70
- if parts[1] != ""
71
- headers = YAML.load(parts[1])
72
- content = parts[2]
73
- else
74
- headers = {}
75
- content = source
76
- end
77
- rescue => e
78
- puts "Sorry, something went wrong when I was loading the YAML front matter: #{e.message}."
79
- end
80
- return [headers, content]
81
- end
82
-
83
- # ----------------------
84
- # | Step 3 |
85
- # ----------------------
86
- # Mixins
87
-
88
- def mixing_in( mixins, content )
89
-
90
- def clauses_mixins( mixins, content )
91
- clauses_to_delete = []
92
- clauses_to_mixin = []
93
-
94
- mixins.each do | mixin, replacer |
95
- replacer = replacer.to_s.downcase
96
- clauses_to_delete << mixin if replacer == "false"
97
- clauses_to_mixin << mixin if replacer == "true"
98
- end
99
-
100
- clauses_to_delete.each { |m| mixins.delete(m) }
101
- clauses_to_mixin.each { |m| mixins.delete(m) }
102
-
103
- until clauses_to_delete.size == 0
104
- clauses_to_delete.each do | mixin |
105
- pattern = /(\[{{#{mixin}}}\s*?)(.*?\n*?)(\])/m
106
- sub_pattern = /\[{{(\S+?)}}\s*?/
107
- content[pattern]
108
- get_it_all = $& || ""
109
- sub_clause = $2 || ""
110
- if sub_clause[sub_pattern] && clauses_to_delete.include?($1)
111
- next
112
- elsif sub_clause[sub_pattern]
113
- pattern = /\[{{#{mixin}}}\s*?.*?\n*?\].*?\n*?\]/m
114
- content[pattern]; get_it_all = $& || ""
115
- end
116
- content = content.gsub( get_it_all, "" )
117
- clauses_to_delete.delete( mixin ) unless content[pattern]
118
- end
119
- end
120
-
121
- until clauses_to_mixin.size == 0
122
- clauses_to_mixin.each do | mixin |
123
- pattern = /(\[{{#{mixin}}}\s*?)(.*?\n*?)(\])/m
124
- sub_pattern = /\[{{(\S+?)}}\s*?/
125
- content[pattern]
126
- get_it_all = $& || ""
127
- sub_clause = $2 || ""
128
- next if sub_clause[sub_pattern] && clauses_to_mixin.include?($1)
129
- content = content.gsub( get_it_all, sub_clause.lstrip )
130
- clauses_to_mixin.delete( mixin ) unless content[pattern]
131
- end
132
- end
133
-
134
- return [ mixins, content ]
135
- end
136
-
137
- def text_mixins( mixins, content )
138
- mixins.each do | mixin, replacer |
139
- unless mixin =~ /level-\d/ or mixin =~ /no-reset/ or mixin =~ /no-indent/ or mixin =~ /level-style/
140
- replacer = replacer.to_s
141
- mixin_pattern = /({{#{mixin}}})/
142
- content = content.gsub( $1, replacer ) if content =~ mixin_pattern
143
- mixins.delete( mixin )
144
- end
145
- end
146
- return [ mixins, content ]
147
- end
148
-
149
- clauses_mixed = clauses_mixins( mixins, content )
150
- fully_mixed = text_mixins( clauses_mixed[0], clauses_mixed[1] )
151
- fully_mixed[1].gsub!(/(\n\n+)/, "\n\n")
152
- fully_mixed[1].squeeze!(" ")
153
- return [ fully_mixed[0], fully_mixed[1] ]
154
- end
155
-
156
- # ----------------------
157
- # | Step 4 |
158
- # ----------------------
159
- # Headers
160
-
161
- def headers_on( headers, content )
162
-
163
- def set_the_subs_arrays( value )
164
- # takes a core value from the hash pulled from the yaml
165
- # returns an array with a type symbol and a precursor string
166
- if value =~ /([IVXLCDM]+)\.\z/ # type1 : {{ I. }}
167
- return[:type1, value.delete($1 + "."), "", $1, "."]
168
- elsif value =~ /\(([IVXLCDM]+)\)\z/ # type2 : {{ (I) }}
169
- return[:type2, value.delete("(" + $1 + ")"), "(", $1, ")"]
170
- elsif value =~ /([ivxlcdm]+)\.\z/ # type3 : {{ i. }}
171
- return[:type3, value.delete($1 + "."), "", $1, "."]
172
- elsif value =~ /\(([ivxlcdm]+)\)\z/ # type4 : {{ (i) }}
173
- return[:type4, value.delete("(" + $1 + ")"), "(", $1, ")"]
174
- elsif value =~ /([A-Z]+)\.\z/ # type5 : {{ A. }}
175
- return[:type5, value.delete($1 + "."), "", $1, "."]
176
- elsif value =~ /\(([A-Z]+)\)\z/ # type6 : {{ (A) }}
177
- return[:type6, value.delete("(" + $1 + ")"), "(", $1, ")"]
178
- elsif value =~ /([a-z]+)\.\z/ # type7 : {{ a. }}
179
- return[:type7, value.delete($1 + "."), "", $1, "."]
180
- elsif value =~ /\(([a-z]+)\)\z/ # type8 : {{ (a) }}
181
- return[:type8, value.delete("(" + $1 + ")"), "(", $1, ")"]
182
- elsif value =~ /\((\d+)\)\z/ # type9 : {{ (1) }}
183
- return[:type9, value.delete("(" + $1 + ")"), "(", $1, ")"]
184
- else value =~ /(\d+)\.\z/ # type0 : {{ 1. }} ... also default
185
- return[:type0, value.delete($1 + "."), "", $1, "."]
186
- end
187
- end
188
-
189
- def get_the_substitutions( headers )
190
- # find the headers in the remaining YAML
191
- # parse out the headers into level-X and pre-X headers
192
- # then combine them into a coherent package
193
- # returns a hash with the keys as the l., ll. searches
194
- # and the values as the replacements in the form of
195
- # an array where the first value is a symbol and the
196
- # second value is the precursor
197
-
198
- # @substitutions hash example
199
- # {"ll." || "l2."=>[:type8, "Article ", "(", "1", ")", :no_reset || nil, " ", :preval || :pre || nil]}
200
-
201
- @substitutions = {}
202
-
203
- if headers.has_key?("level-style")
204
- headers["level-style"] =~ /l1/ ? @deep_leaders = true : @deep_leaders = false
205
- else
206
- @deep_leaders = false
207
- end
208
-
209
- if headers.has_key?("no-indent") && headers["no-indent"]
210
- no_indent_array = headers["no-indent"].split(", ")
211
- no_indent_array.include?("l." || "l1.") ? @offset = no_indent_array.size : @offset = no_indent_array.size + 1
212
- else
213
- @offset = 1
214
- end
215
-
216
- headers.each do | header, value |
217
- if @deep_leaders
218
- search = "l" + header[-1] + "." if header =~ /level-\d/
219
- else
220
- search = "l" * header[-1].to_i + "." if header =~ /level-\d/
221
- end
222
-
223
- if header =~ /level-\d/
224
- @substitutions[search]= set_the_subs_arrays(value.to_s)
225
- @deep_leaders ? spaces = (search[1].to_i - @offset) : spaces = (search.size - @offset - 1)
226
- spaces < 0 ? spaces = 0 : spaces = spaces * 2
227
- @substitutions[search][6] = " " * spaces
228
- if value =~ /\s*preval\s*/
229
- @substitutions[search][1].gsub!(/preval\s*/, "")
230
- @substitutions[search][7] = :preval
231
- elsif value =~ /\s*pre\s*/
232
- @substitutions[search][1].gsub!(/pre\s*/, "")
233
- @substitutions[search][7] = :pre
234
- end
235
- end
236
- end
237
-
238
- if headers["no-reset"]
239
- no_subs_array = headers["no-reset"].split(", ")
240
- no_subs_array.each{ |e| @substitutions[e][5] = :no_reset unless e == "l." || e == "l1."}
241
- end
242
-
243
- return @substitutions
244
- end
245
-
246
- def find_the_block( content )
247
- block_pattern = /(^```+\s*\n?)(.*?\n?)(^```+\s*\n?)/m
248
- parts = content.partition( block_pattern )
249
- if parts[1] != ""
250
- block = $2.chomp
251
- content = parts[0] + "{{block}}" + parts[2]
252
- else
253
- block = ""
254
- content = content
255
- end
256
- return [ block, content ]
257
- end
258
-
259
- def chew_on_the_block( old_block )
260
- # takes a hash of substitutions to make from the #get_the_substitutions method
261
- # and a block of text returned from the #find_the_block method
262
- # iterates over the block to make the appropriate substitutions
263
- # returns a block of text
264
-
265
- # method will take the old_block and iterate through the lines.
266
- # First it will find the leading indicator. Then it will
267
- # find the appropriate substitution from the @substitutions
268
- # hash. After that it will rebuild the leading matter from the
269
- # sub hash. It will drop markers if it is going down the tree.
270
- # It will reset the branches if it is going up the tree.
271
- # sub_it is an array w/ type[0] & lead_string[1] & id's[2..4]
272
-
273
- # @substitutions hash example
274
- # {"ll."OR "l2."=>[:type8, "Article ", "(", "1", ")", :no_reset || nil, :no_indent || nil, :preval || :pre || nil],}
275
-
276
- def romans_takedown( array_to_sub )
277
- if array_to_sub[0] == :type1 || array_to_sub[0] == :type2
278
- @r_u = true
279
- elsif array_to_sub[0] == :type3 || array_to_sub[0] == :type4
280
- @r_l = true
281
- end
282
- if @r_l || @r_u
283
- array_to_sub[3] = RomanNumerals.to_decimal_string(array_to_sub[3])
284
- end
285
- return array_to_sub
286
- end
287
-
288
- def romans_setup( array_to_sub )
289
- if @r_l || @r_u
290
- array_to_sub[3] = RomanNumerals.to_roman_upper(array_to_sub[3]) if @r_u
291
- array_to_sub[3] = RomanNumerals.to_roman_lower(array_to_sub[3]) if @r_l
292
- end
293
- @r_l = false; @r_u = false
294
- return array_to_sub
295
- end
296
-
297
- def increment_the_branch( array_to_sub, selector, next_selector )
298
- if selector > next_selector #going up the tree and reset
299
- selectors_to_reset = @substitutions.inject([]){ |m,(k,v)| m << k if k > next_selector; m }
300
- selectors_to_reset.each do | this_selector |
301
- substitutor = @substitutions[this_selector]
302
- substitutor = romans_takedown( substitutor )
303
- substitutor[3].next! if this_selector == selector
304
- if substitutor[0] == :type5 || substitutor[0] == :type6
305
- substitutor[3] = "A" unless substitutor[5] == :no_reset
306
- elsif substitutor[0] == :type7 || substitutor[0] == :type8
307
- substitutor[3] = "a" unless substitutor[5] == :no_reset
308
- else
309
- substitutor[3] = "1" unless substitutor[5] == :no_reset
310
- end
311
- substitutor = romans_setup( substitutor )
312
- @substitutions[this_selector]= substitutor
313
- end
314
- array_to_sub = @substitutions[selector]
315
- else #not going up tree
316
- array_to_sub = romans_takedown( array_to_sub )
317
- array_to_sub[3].next!
318
- array_to_sub = romans_setup( array_to_sub )
319
- end
320
-
321
- return array_to_sub
322
- end
323
-
324
- def get_selector_above( selector )
325
- if @deep_leaders
326
- selector_above = "l" + (selector[1].to_i-1).to_s + "."
327
- selector_above = "l1." if selector_above == "l0."
328
- else
329
- selector_above = selector[1..-1]
330
- selector_above = "l." if selector_above == "."
331
- end
332
- return selector_above
333
- end
334
-
335
- def find_parent_reference( selector_above )
336
- leading_prov = @substitutions[selector_above].clone
337
- leading_prov = romans_takedown( leading_prov )
338
- if leading_prov[0] == :type5 || leading_prov[0] == :type6
339
- leading_prov[3] = leading_prov[3][0..-2] + (leading_prov[3][-1].ord-1).chr
340
- elsif leading_prov[0] == :type7 || leading_prov[0] == :type8
341
- leading_prov[3] = leading_prov[3][0..-2] + (leading_prov[3][-1].ord-1).chr
342
- else
343
- leading_prov[3] = (leading_prov[3].to_i-1).to_s
344
- end
345
- leading_prov = romans_setup( leading_prov )
346
- return leading_prov
347
- end
348
-
349
- def preval_substitution( array_to_sub, selector )
350
- array_to_sub.pop unless array_to_sub.last == :preval
351
- selector_above = get_selector_above( selector )
352
- leading_prov = find_parent_reference( selector_above )[3]
353
- trailing_prov = array_to_sub[3].clone
354
- trailing_prov.prepend("0") if trailing_prov.size == 1
355
- array_to_sub << array_to_sub[2] + leading_prov.to_s + trailing_prov.to_s + array_to_sub[4]
356
- array_to_sub.last.gsub!($1, "(") if array_to_sub.last[/(\.\()/]
357
- return array_to_sub
358
- end
359
-
360
- def pre_substitution( array_to_sub, selector )
361
- array_to_sub.pop unless array_to_sub.last == :pre
362
- selector_above = get_selector_above( selector )
363
- leading_prov = @substitutions[selector_above][8] || find_parent_reference( selector_above )[2..4].join
364
- trailing_prov = array_to_sub[2..4].join
365
- array_to_sub << leading_prov + trailing_prov
366
- array_to_sub.last.gsub!($1, "(") if array_to_sub.last[/(\.\()/]
367
- return array_to_sub
368
- end
369
-
370
- cross_references = {}
371
- arrayed_block = []
372
- old_block.each_line do |line|
373
- next if line[/^\s*\n/]
374
- line[/(^l+\.|^l\d\.)\s*(\|.*?\|)*\s*(.*)$/] ? arrayed_block << [$1, $3, $2] : arrayed_block.last[1] << ("\n" + line.rstrip)
375
- end
376
- old_block = "" # for large files
377
-
378
- new_block = arrayed_block.inject("") do |block, arrayed_line|
379
-
380
- selector = arrayed_line[0]
381
- next_selector = ( arrayed_block[arrayed_block.index(arrayed_line)+1] || arrayed_block.last ).first
382
- sub_it = @substitutions[selector]
383
-
384
- if arrayed_line[1] =~ /\n/
385
- arrayed_line[1].gsub!("\n", "\n\n" + sub_it[6])
386
- end
387
-
388
- if sub_it[7] == :preval
389
- sub_it = preval_substitution(sub_it, selector)
390
- reference = sub_it.last
391
- elsif sub_it[7] == :pre
392
- sub_it = pre_substitution(sub_it, selector)
393
- reference = sub_it.last
394
- else
395
- reference = sub_it[2..4].join
396
- end
397
-
398
- block << sub_it[6] + sub_it[1] + reference + " " + arrayed_line[1] + "\n\n"
399
- if arrayed_line[2]
400
- cross_references[arrayed_line[2]]= sub_it[1] + reference
401
- cross_references[arrayed_line[2]].gsub!(/\A\*|\#+ |\.\z/, "") #guard against formatting of headers into txt
402
- end
403
- @substitutions[selector]= increment_the_branch(sub_it, selector, next_selector)
404
-
405
- block
406
- end
407
-
408
- cross_references.each_key{|k| new_block.gsub!(k, cross_references[k]) }
409
- return new_block
410
- end
411
-
412
- headers = get_the_substitutions( headers )
413
- block_found = find_the_block( content )
414
- block = block_found[0]
415
- not_the_block = block_found[1]
416
- block_found = "" # for long documents
417
-
418
- if block == ""
419
- block_redux = ""
420
- elsif headers == {}
421
- block_redux = block
422
- else
423
- block_redux = chew_on_the_block( block )
424
- end
425
- headed = not_the_block.gsub("{{block}}", block_redux )
426
- end
427
-
428
- # ----------------------
429
- # | Step 6 |
430
- # ----------------------
431
- # Write the file
432
- def write_it( final_content )
433
- final_content = final_content.gsub(/ +\n/, "\n")
434
- if @output_file && @output_file != "-"
435
- File.open(@output_file, "w") {|f| f.write( final_content ) }
436
- else
437
- STDOUT.write final_content
16
+ LegalMarkdown::LegalToMarkdown.parse(args)
438
17
  end
439
18
  end
440
19
  end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/legal_to_markdown/load_source.rb'
2
+ require File.dirname(__FILE__) + '/legal_to_markdown/mixins.rb'
3
+ require File.dirname(__FILE__) + '/legal_to_markdown/leaders.rb'
4
+ require File.dirname(__FILE__) + '/legal_to_markdown/writer.rb'
5
+ require File.dirname(__FILE__) + '/roman_numerals'
6
+
7
+ module LegalToMarkdown
8
+
9
+ def parse(args)
10
+ @output_file = args[-1]
11
+ @input_file = args[-2] ? args[-2] : args[-1]
12
+ source = FileToParse.new(@input_file)
13
+ source.run_mixins if source.mixins
14
+ source.run_leaders if source.leaders
15
+ write_it(source.content)
16
+ end
17
+ end
@@ -0,0 +1,259 @@
1
+ module LegalToMarkdown
2
+ extend self
3
+
4
+ module Leaders
5
+
6
+ def run_leaders
7
+ get_the_substitutions
8
+ find_the_block
9
+ @block = chew_on_the_block
10
+ clean_up_leaders
11
+ end
12
+
13
+ def get_the_substitutions
14
+ # find the headers in the remaining YAML
15
+ # parse out the headers into level-X and pre-X headers
16
+ # then combine them into a coherent package
17
+ # returns a hash with the keys as the l., ll. searches
18
+ # and the values as the replacements in the form of
19
+ # an array where the first value is a symbol and the
20
+ # second value is the precursor
21
+ #
22
+ # @substitutions hash example
23
+ # {"ll." || "l2."=>[:type8, "Article ", "(", "1", ")", :no_reset || nil, " ", :preval || :pre || nil]}
24
+
25
+ @substitutions = {}
26
+
27
+ if @headers.has_key?("level-style")
28
+ @headers["level-style"] =~ /l1/ ? @deep_leaders = true : @deep_leaders = false
29
+ else
30
+ @deep_leaders = false
31
+ end
32
+
33
+ if @headers.has_key?("no-indent") && @headers["no-indent"]
34
+ no_indent_array = @headers["no-indent"].split(", ")
35
+ no_indent_array.include?("l." || "l1.") ? @offset = no_indent_array.size : @offset = no_indent_array.size + 1
36
+ else
37
+ @offset = 1
38
+ end
39
+
40
+ @headers.each do | header, value |
41
+ if @deep_leaders
42
+ search = "l" + header[-1] + "." if header =~ /level-\d/
43
+ else
44
+ search = "l" * header[-1].to_i + "." if header =~ /level-\d/
45
+ end
46
+
47
+ if header =~ /level-\d/
48
+ @substitutions[search]= set_the_subs_arrays(value.to_s)
49
+ @deep_leaders ? spaces = (search[1].to_i - @offset) : spaces = (search.size - @offset - 1)
50
+ spaces < 0 ? spaces = 0 : spaces = spaces * 2
51
+ @substitutions[search][6] = " " * spaces
52
+ if value =~ /\s*preval\s*/
53
+ @substitutions[search][1].gsub!(/preval\s*/, "")
54
+ @substitutions[search][7] = :preval
55
+ elsif value =~ /\s*pre\s*/
56
+ @substitutions[search][1].gsub!(/pre\s*/, "")
57
+ @substitutions[search][7] = :pre
58
+ end
59
+ end
60
+ end
61
+
62
+ if @headers["no-reset"]
63
+ no_subs_array = @headers["no-reset"].split(", ")
64
+ no_subs_array.each{ |e| @substitutions[e][5] = :no_reset unless e == "l." || e == "l1."}
65
+ end
66
+ end
67
+
68
+ def find_the_block
69
+ block_pattern = /(^```+\s*\n?)(.*?\n?)(^```+\s*\n?)/m
70
+ parts = @content.partition( block_pattern )
71
+ if parts[1] != ""
72
+ @block = $2.chomp
73
+ @content = parts[0] + "{{block}}" + parts[2]
74
+ else
75
+ @block = nil
76
+ @content = @content
77
+ end
78
+ end
79
+
80
+ def chew_on_the_block
81
+ # takes a hash of substitutions to make from the #get_the_substitutions method
82
+ # and a block of text returned from the #find_the_block method
83
+ # iterates over the block to make the appropriate substitutions
84
+ # returns a block of text
85
+
86
+ # method will take the old_block and iterate through the lines.
87
+ # First it will find the leading indicator. Then it will
88
+ # find the appropriate substitution from the @substitutions
89
+ # hash. After that it will rebuild the leading matter from the
90
+ # sub hash. It will drop markers if it is going down the tree.
91
+ # It will reset the branches if it is going up the tree.
92
+ # sub_it is an array w/ type[0] & lead_string[1] & id's[2..4]
93
+
94
+ # @substitutions hash example
95
+ # {"ll."OR "l2."=>[:type8, "Article ", "(", "1", ")", :no_reset || nil, :no_indent || nil, :preval || :pre || nil],}
96
+
97
+ cross_references = {}
98
+ arrayed_block = []
99
+ @block.each_line do |line|
100
+ next if line[/^\s*\n/]
101
+ line[/(^l+\.|^l\d\.)\s*(\|.*?\|)*\s*(.*)$/] ? arrayed_block << [$1, $3, $2] : arrayed_block.last[1] << ("\n" + line.rstrip)
102
+ end
103
+
104
+ new_block = arrayed_block.inject("") do |block, arrayed_line|
105
+
106
+ selector = arrayed_line[0]
107
+ next_selector = ( arrayed_block[arrayed_block.index(arrayed_line)+1] || arrayed_block.last ).first
108
+ sub_it = @substitutions[selector]
109
+
110
+ if arrayed_line[1] =~ /\n/
111
+ arrayed_line[1].gsub!("\n", "\n\n" + sub_it[6])
112
+ end
113
+
114
+ if sub_it[7] == :preval
115
+ sub_it = preval_substitution(sub_it, selector)
116
+ reference = sub_it.last
117
+ elsif sub_it[7] == :pre
118
+ sub_it = pre_substitution(sub_it, selector)
119
+ reference = sub_it.last
120
+ else
121
+ reference = sub_it[2..4].join
122
+ end
123
+
124
+ block << sub_it[6] + sub_it[1] + reference + " " + arrayed_line[1] + "\n\n"
125
+ if arrayed_line[2]
126
+ cross_references[arrayed_line[2]]= sub_it[1] + reference
127
+ cross_references[arrayed_line[2]].gsub!(/\A\*|\#+ |\.\z/, "") #guard against formatting of headers into txt
128
+ end
129
+ @substitutions[selector]= increment_the_branch(sub_it, selector, next_selector)
130
+ block
131
+ end
132
+
133
+ cross_references.each_key{|k| new_block.gsub!(k, cross_references[k]) }
134
+ return new_block
135
+ end
136
+
137
+ def clean_up_leaders
138
+ @content.gsub!("{{block}}", @block )
139
+ end
140
+
141
+ def set_the_subs_arrays( value )
142
+ # takes a core value from the hash pulled from the yaml
143
+ # returns an array with a type symbol and a precursor string
144
+ if value =~ /([IVXLCDM]+)\.\z/ # type1 : {{ I. }}
145
+ return[:type1, value.delete($1 + "."), "", $1, "."]
146
+ elsif value =~ /\(([IVXLCDM]+)\)\z/ # type2 : {{ (I) }}
147
+ return[:type2, value.delete("(" + $1 + ")"), "(", $1, ")"]
148
+ elsif value =~ /([ivxlcdm]+)\.\z/ # type3 : {{ i. }}
149
+ return[:type3, value.delete($1 + "."), "", $1, "."]
150
+ elsif value =~ /\(([ivxlcdm]+)\)\z/ # type4 : {{ (i) }}
151
+ return[:type4, value.delete("(" + $1 + ")"), "(", $1, ")"]
152
+ elsif value =~ /([A-Z]+)\.\z/ # type5 : {{ A. }}
153
+ return[:type5, value.delete($1 + "."), "", $1, "."]
154
+ elsif value =~ /\(([A-Z]+)\)\z/ # type6 : {{ (A) }}
155
+ return[:type6, value.delete("(" + $1 + ")"), "(", $1, ")"]
156
+ elsif value =~ /([a-z]+)\.\z/ # type7 : {{ a. }}
157
+ return[:type7, value.delete($1 + "."), "", $1, "."]
158
+ elsif value =~ /\(([a-z]+)\)\z/ # type8 : {{ (a) }}
159
+ return[:type8, value.delete("(" + $1 + ")"), "(", $1, ")"]
160
+ elsif value =~ /\((\d+)\)\z/ # type9 : {{ (1) }}
161
+ return[:type9, value.delete("(" + $1 + ")"), "(", $1, ")"]
162
+ else value =~ /(\d+)\.\z/ # type0 : {{ 1. }} ... also default
163
+ return[:type0, value.delete($1 + "."), "", $1, "."]
164
+ end
165
+ end
166
+
167
+ def romans_takedown( array_to_sub )
168
+ if array_to_sub[0] == :type1 || array_to_sub[0] == :type2
169
+ @r_u = true
170
+ elsif array_to_sub[0] == :type3 || array_to_sub[0] == :type4
171
+ @r_l = true
172
+ end
173
+ if @r_l || @r_u
174
+ array_to_sub[3] = RomanNumerals.to_decimal_string(array_to_sub[3])
175
+ end
176
+ return array_to_sub
177
+ end
178
+
179
+ def romans_setup( array_to_sub )
180
+ if @r_l || @r_u
181
+ array_to_sub[3] = RomanNumerals.to_roman_upper(array_to_sub[3]) if @r_u
182
+ array_to_sub[3] = RomanNumerals.to_roman_lower(array_to_sub[3]) if @r_l
183
+ end
184
+ @r_l = false; @r_u = false
185
+ return array_to_sub
186
+ end
187
+
188
+ def increment_the_branch( array_to_sub, selector, next_selector )
189
+ if selector > next_selector #going up the tree and reset
190
+ selectors_to_reset = @substitutions.inject([]){ |m,(k,v)| m << k if k > next_selector; m }
191
+ selectors_to_reset.each do | this_selector |
192
+ substitutor = @substitutions[this_selector]
193
+ substitutor = romans_takedown( substitutor )
194
+ substitutor[3].next! if this_selector == selector
195
+ if substitutor[0] == :type5 || substitutor[0] == :type6
196
+ substitutor[3] = "A" unless substitutor[5] == :no_reset
197
+ elsif substitutor[0] == :type7 || substitutor[0] == :type8
198
+ substitutor[3] = "a" unless substitutor[5] == :no_reset
199
+ else
200
+ substitutor[3] = "1" unless substitutor[5] == :no_reset
201
+ end
202
+ substitutor = romans_setup( substitutor )
203
+ @substitutions[this_selector]= substitutor
204
+ end
205
+ array_to_sub = @substitutions[selector]
206
+ else #not going up tree
207
+ array_to_sub = romans_takedown( array_to_sub )
208
+ array_to_sub[3].next!
209
+ array_to_sub = romans_setup( array_to_sub )
210
+ end
211
+
212
+ return array_to_sub
213
+ end
214
+
215
+ def get_selector_above( selector )
216
+ if @deep_leaders
217
+ selector_above = "l" + (selector[1].to_i-1).to_s + "."
218
+ selector_above = "l1." if selector_above == "l0."
219
+ else
220
+ selector_above = selector[1..-1]
221
+ selector_above = "l." if selector_above == "."
222
+ end
223
+ return selector_above
224
+ end
225
+
226
+ def find_parent_reference( selector_above )
227
+ leading_prov = @substitutions[selector_above].clone
228
+ leading_prov = romans_takedown( leading_prov )
229
+ if leading_prov[0] == ( :type5 || :type6 || :type7 || :type8 )
230
+ leading_prov[3] = leading_prov[3][0..-2] + (leading_prov[3][-1].ord-1).chr
231
+ else
232
+ leading_prov[3] = (leading_prov[3].to_i-1).to_s
233
+ end
234
+ leading_prov = romans_setup( leading_prov )
235
+ return leading_prov
236
+ end
237
+
238
+ def preval_substitution( array_to_sub, selector )
239
+ array_to_sub.pop unless array_to_sub.last == :preval
240
+ selector_above = get_selector_above( selector )
241
+ leading_prov = find_parent_reference( selector_above )[3]
242
+ trailing_prov = array_to_sub[3].clone
243
+ trailing_prov = "0" + trailing_prov if trailing_prov.size == 1
244
+ array_to_sub << array_to_sub[2] + leading_prov.to_s + trailing_prov.to_s + array_to_sub[4]
245
+ array_to_sub.last.gsub!($1, "(") if array_to_sub.last[/(\.\()/]
246
+ return array_to_sub
247
+ end
248
+
249
+ def pre_substitution( array_to_sub, selector )
250
+ array_to_sub.pop unless array_to_sub.last == :pre
251
+ selector_above = get_selector_above( selector )
252
+ leading_prov = @substitutions[selector_above][8] || find_parent_reference( selector_above )[2..4].join
253
+ trailing_prov = array_to_sub[2..4].join
254
+ array_to_sub << leading_prov + trailing_prov
255
+ array_to_sub.last.gsub!($1, "(") if array_to_sub.last[/(\.\()/]
256
+ return array_to_sub
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,74 @@
1
+ module LegalToMarkdown
2
+ extend self
3
+
4
+ class FileToParse
5
+
6
+ attr_accessor :headers, :content, :mixins, :leaders
7
+
8
+ def initialize(file)
9
+ @input_file = file; @headers = nil; @content = ""
10
+ load; get_the_partials; parse; set_the_parsers
11
+ end
12
+
13
+ private
14
+
15
+ def load
16
+ if @input_file != "-"
17
+ @content = get_file(@input_file)
18
+ elsif @input_file == "-"
19
+ @content = STDIN.read
20
+ else
21
+ raise "No input file or stdin specified. Please specify a file or \"-\" for stdin."
22
+ exit 0
23
+ end
24
+ end
25
+
26
+ def get_the_partials
27
+ if @content[/^@include/]
28
+ @content.scan(/^(@include (.+)$)/).each do |set|
29
+ partial_file = set[1]
30
+ to_replace = set[0]
31
+ partial_contents = get_file partial_file
32
+ @content.gsub!(to_replace, partial_contents)
33
+ end
34
+ end
35
+ end
36
+
37
+ def parse
38
+ require 'yaml'
39
+ today_is_the_day if @content[/@today/]
40
+ yaml_pattern = /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
41
+ parts = @content.partition yaml_pattern
42
+ if parts[1] != ""
43
+ @headers = YAML.load parts[1]
44
+ @content = parts[2]
45
+ end
46
+ end
47
+
48
+ def set_the_parsers
49
+ if @content[/\{\{/] && @headers
50
+ self.extend LegalToMarkdown::Mixins
51
+ @mixins = true
52
+ end
53
+ if @content[/^```/] && @headers
54
+ self.extend LegalToMarkdown::Leaders
55
+ @leaders = true
56
+ end
57
+ end
58
+
59
+ def get_file( file )
60
+ begin
61
+ f = File::read(file) if File::exists?(file) && File::readable?(file)
62
+ rescue => e
63
+ puts "Sorry, I could not read the file #{file}: #{e.message}."
64
+ exit 0
65
+ end
66
+ end
67
+
68
+ def today_is_the_day
69
+ require 'date'
70
+ d = Date.today.strftime("%-d %B, %Y")
71
+ @content.gsub!(/@today/, d)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,74 @@
1
+ module LegalToMarkdown
2
+ extend self
3
+
4
+ module Mixins
5
+
6
+ def run_mixins
7
+ clauses_mixins
8
+ text_mixins
9
+ clean_up_mixins
10
+ end
11
+
12
+ def clauses_mixins
13
+ clauses_to_delete = []
14
+ clauses_to_mixin = []
15
+
16
+ @headers.each do | mixin, replacer |
17
+ replacer = replacer.to_s.downcase
18
+ clauses_to_delete << mixin if replacer == "false"
19
+ clauses_to_mixin << mixin if replacer == "true"
20
+ end
21
+
22
+ clauses_to_delete.each { |m| @headers.delete(m) }
23
+ clauses_to_mixin.each { |m| @headers.delete(m) }
24
+
25
+ until clauses_to_delete.size == 0
26
+ clauses_to_delete.each do | mixin |
27
+ pattern = /(\[\{\{#{mixin}\}\}\s*?)(.*?\n*?)(\])/m
28
+ sub_pattern = /\[\{\{(\S+?)\}\}\s*?/
29
+ @content[pattern]
30
+ get_it_all = $& || ""
31
+ sub_clause = $2 || ""
32
+ if sub_clause[sub_pattern] && clauses_to_delete.include?($1)
33
+ next
34
+ elsif sub_clause[sub_pattern]
35
+ pattern = /\[\{\{#{mixin}\}\}\s*?.*?\n*?\].*?\n*?\]/m
36
+ @content[pattern]; get_it_all = $& || ""
37
+ end
38
+ @content = @content.gsub( get_it_all, "" )
39
+ clauses_to_delete.delete( mixin ) unless @content[pattern]
40
+ end
41
+ end
42
+
43
+ until clauses_to_mixin.size == 0
44
+ clauses_to_mixin.each do | mixin |
45
+ pattern = /(\[\{\{#{mixin}\}\}\s*?)(.*?\n*?)(\])/m
46
+ sub_pattern = /\[\{\{(\S+?)\}\}\s*?/
47
+ @content[pattern]
48
+ get_it_all = $& || ""
49
+ sub_clause = $2 || ""
50
+ next if sub_clause[sub_pattern] && clauses_to_mixin.include?($1)
51
+ @content = @content.gsub( get_it_all, sub_clause.lstrip )
52
+ clauses_to_mixin.delete( mixin ) unless @content[pattern]
53
+ end
54
+ end
55
+ end
56
+
57
+ def text_mixins
58
+ @headers.each do | mixin, replacer |
59
+ unless mixin =~ /level-\d/ or mixin =~ /no-reset/ or mixin =~ /no-indent/ or mixin =~ /level-style/
60
+ replacer = replacer.to_s
61
+ mixin_pattern = /(\{\{#{mixin}\}\})/
62
+ @content = @content.gsub( $1, replacer ) if @content =~ mixin_pattern
63
+ @headers.delete( mixin )
64
+ end
65
+ end
66
+ end
67
+
68
+ def clean_up_mixins
69
+ @content.gsub!(/(\n\n+)/, "\n\n")
70
+ @content.squeeze!(" ")
71
+ @headers = nil if @headers.empty?
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,12 @@
1
+ module LegalToMarkdown
2
+ extend self
3
+
4
+ def write_it( final_content )
5
+ final_content = final_content.gsub(/ +\n/, "\n")
6
+ if @output_file && @output_file != "-"
7
+ File.open(@output_file, "w") {|f| f.write( final_content ) }
8
+ else
9
+ STDOUT.write final_content
10
+ end
11
+ end
12
+ end
@@ -1,3 +1,3 @@
1
1
  module LegalMarkdown
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  #! ruby
2
2
  require 'test/unit'
3
3
  require 'tempfile'
4
- require_relative '../lib/legal_markdown.rb'
4
+ require 'legal_markdown'
5
5
 
6
6
  class TestLegalMarkdownToMarkdown < Test::Unit::TestCase
7
7
  # load all the .lmd files in the tests folder into an array
@@ -11,8 +11,8 @@ class TestLegalMarkdownToMarkdown < Test::Unit::TestCase
11
11
  # if assert_equal is false then stop....
12
12
 
13
13
  def setup
14
- Dir.chdir "./tests"
15
- @lmdfiles = Dir.glob"*.lmd"
14
+ Dir.chdir File.dirname(__FILE__) + "/tests"
15
+ @lmdfiles = Dir.glob "*.lmd"
16
16
  @lmdfiles.sort!
17
17
  end
18
18
 
@@ -35,7 +35,7 @@ class TestLegalMarkdownToMarkdown < Test::Unit::TestCase
35
35
  puts "Testing => #{lmd_file}"
36
36
  temp_file = create_temp
37
37
  benchmark_file = File.basename(lmd_file, ".lmd") + ".md"
38
- LegalToMarkdown.new( [ lmd_file, temp_file ] )
38
+ LegalToMarkdown.parse( [ lmd_file, temp_file ] )
39
39
  assert_equal(get_file(benchmark_file).chomp, get_file(temp_file).chomp, "This file through an error => #{benchmark_file}")
40
40
  destroy_temp temp_file
41
41
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legal_markdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -27,8 +27,7 @@ extensions: []
27
27
  extra_rdoc_files: []
28
28
  files:
29
29
  - .gitignore
30
- - .ruby-gemset
31
- - .ruby-version
30
+ - .travis.yml
32
31
  - Gemfile
33
32
  - LICENSE
34
33
  - README.md
@@ -36,8 +35,13 @@ files:
36
35
  - bin/legal2md
37
36
  - legal_markdown.gemspec
38
37
  - lib/legal_markdown.rb
38
+ - lib/legal_markdown/legal_to_markdown.rb
39
+ - lib/legal_markdown/legal_to_markdown/leaders.rb
40
+ - lib/legal_markdown/legal_to_markdown/load_source.rb
41
+ - lib/legal_markdown/legal_to_markdown/mixins.rb
42
+ - lib/legal_markdown/legal_to_markdown/writer.rb
39
43
  - lib/legal_markdown/make_yaml_frontmatter.rb
40
- - lib/legal_markdown/roman-numerals.rb
44
+ - lib/legal_markdown/roman_numerals.rb
41
45
  - lib/legal_markdown/version.rb
42
46
  - test/test_legal_markdown_to_markdown.rb
43
47
  - test/tests/00.load_write_no_action.lmd
@@ -113,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
113
117
  version: '0'
114
118
  segments:
115
119
  - 0
116
- hash: -4263874591834672768
120
+ hash: 366661756436547466
117
121
  required_rubygems_version: !ruby/object:Gem::Requirement
118
122
  none: false
119
123
  requirements:
@@ -122,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
126
  version: '0'
123
127
  segments:
124
128
  - 0
125
- hash: -4263874591834672768
129
+ hash: 366661756436547466
126
130
  requirements: []
127
131
  rubyforge_project:
128
132
  rubygems_version: 1.8.25
data/.ruby-gemset DELETED
@@ -1 +0,0 @@
1
- legalmd
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- ruby-1.9.3-p429