legal_markdown 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +0 -1
- data/README.md +28 -3
- data/bin/legalmd +1 -1
- data/legal_markdown.gemspec +1 -1
- data/lib/legal_markdown/version.rb +1 -1
- data/lib/legal_markdown.rb +242 -26
- metadata +21 -5
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -88,8 +88,10 @@ Then you can describe the functionality that you require in the YAML front-matte
|
|
88
88
|
4. `level-1: (A)` will format the same as the above only with the capital letters in a parentetical;
|
89
89
|
5. `level-1: a.` will format with lowercase letters followed by a period;
|
90
90
|
6. `level-1: (a)` will format with lowercase letters within a parentethical;
|
91
|
-
7. `level-1: I.` will format with Roman numerals followed by a period;
|
92
|
-
8. `level-1: (I)` will format with Roman numerals within a parententical
|
91
|
+
7. `level-1: I.` will format with capital Roman numerals followed by a period;
|
92
|
+
8. `level-1: (I)` will format with capital Roman numerals within a parententical;
|
93
|
+
9. `level-1: i.` will format with lowercase Roman numerals followed by a period;
|
94
|
+
10. `level-1: (i)` will format with lowercase Roman numerals within a parententical..
|
93
95
|
|
94
96
|
Obviously you will replace `level-1` with `level-2`, etc. Although this functionality was built into the gem, it is generally not the best practice. A better practice is to let the gem make the replacements and reformat the markdown and then use your rendering system and its default reference documents to set the styles you need.
|
95
97
|
|
@@ -152,6 +154,25 @@ In Libreoffice you would modify your template reference.odt as you need it. You
|
|
152
154
|
|
153
155
|
Then you would save the reference.odt as a new name perhaps contract-reference.odt in your Pandoc reference folder.
|
154
156
|
|
157
|
+
### Step 3(a): Add Precursors to Headers
|
158
|
+
|
159
|
+
*Note*: I only use these when I'm using pandoc to move directly to html or pdf files (a fuller suite of this is what we're working towards at Watershed, along with the fantastic team at [FrontlineSMS](http://www.frontlinesms.com/about-us/) and others hopefully). If you want to use `legal_markdown` along with .odt or .docx files probably better to utilize the workflow described *supra*.
|
160
|
+
|
161
|
+
Now that you've been warned, here's how you use precursors. Within the text of the document nothing changes. In the YAML front matter you will leave it as it was before. All you need to do is add any word or other marker before the trigger. What `legal_markdown` will do is to look at the last two characters if the marker ends in a period or three if it ends in a paren, and then everything else it will place into a precursor. If you want to reference the preceding level (like 1.1.1 in the example above) then simply put in {pre}. I'll try to make this less fragile down the road, but for now it is what it is. So, your YAML front matter will look like this:
|
162
|
+
|
163
|
+
```yaml
|
164
|
+
---
|
165
|
+
title: Wonderful Contract
|
166
|
+
author: Your Name
|
167
|
+
date: today
|
168
|
+
level-1: Article 1.
|
169
|
+
level-2: pre 1.
|
170
|
+
level-3: pre a.
|
171
|
+
---
|
172
|
+
```
|
173
|
+
|
174
|
+
The other thing you need to be aware of if you use this method is that when `legal_markdown` parses the input markdown it have to hardcode in the numbering. This means that you'll lose any features of automatic lists and list nesting which your markdown renderer may give you if you simply placed the triggers without any precursors.
|
175
|
+
|
155
176
|
### Step 4: Run Legal-Markdown and Pandoc
|
156
177
|
|
157
178
|
Make sure when you run Pandoc you use the new reference document that you created as the basis.
|
@@ -170,7 +191,11 @@ Also, if you use the reference.odt or reference.docx to override the default for
|
|
170
191
|
|
171
192
|
[ ] - Definitions. For now these can be used as mixins but that functionality needs to improve.
|
172
193
|
|
173
|
-
[ ] - Parsing. At this point legal_markdown cannot take a markdown document and parse it to build structured headers. Legal_markdown only works with a renderer to *create* documents but not to *import* documents. I want to build this functionality out at a later date.
|
194
|
+
[ ] - Parsing. At this point legal_markdown cannot take a markdown document and parse it to build structured headers. Legal_markdown only works with a renderer to *create* documents but not to *import* documents. I want to build this functionality out at a later date. Legal_markdown is not meant as an importer for files types, there are other tools for that but I would like it to be able to parse text that is already in markdown.
|
195
|
+
|
196
|
+
[ ] - Markdown post-processing. This will cure some of the issues (class establishment and proper list nesting of structure documents) that are currently lost when using precurors.
|
197
|
+
|
198
|
+
[ ] - Different input and output files
|
174
199
|
|
175
200
|
# Contributing
|
176
201
|
|
data/bin/legalmd
CHANGED
data/legal_markdown.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
|
21
|
+
s.add_dependency "roman-numerals"
|
22
22
|
|
23
23
|
s.description = <<desc
|
24
24
|
This gem will parse YAML Front Matter of Markdown Documents. Typically, this gem would be called with a md renderer, such as 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/lib/legal_markdown.rb
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
#! ruby
|
2
2
|
require 'yaml'
|
3
3
|
require 'English'
|
4
|
+
require 'roman-numerals'
|
4
5
|
require "legal_markdown/version"
|
5
6
|
|
6
7
|
module LegalMarkdown
|
7
8
|
extend self
|
8
|
-
|
9
|
+
|
10
|
+
def markdown_preprocess(*args)
|
9
11
|
# Get the Content & Yaml Data
|
10
12
|
data = load(*args)
|
11
13
|
parsed_content = parse_file(data[0])
|
12
14
|
# Run the Mixins
|
13
15
|
mixed_content = mixing_in(parsed_content[0], parsed_content[1])
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
# Run the Pandoc Title Block
|
17
|
+
pandoc_content = pandoc_title_block( mixed_content[1] ) + mixed_content[0]
|
18
|
+
# Run the Headers
|
19
|
+
headed_content = headers_on(mixed_content[1], pandoc_content)
|
20
|
+
# Write the file
|
21
|
+
file = write_it( @filename, headed_content )
|
20
22
|
end
|
21
23
|
|
22
24
|
private
|
@@ -50,8 +52,9 @@ module LegalMarkdown
|
|
50
52
|
# ARGV.each{ |fn| output_file(OPTS, fn) }
|
51
53
|
|
52
54
|
# Load Source File
|
53
|
-
|
54
|
-
|
55
|
+
@filename = ARGV[-1]
|
56
|
+
source_file = File::read(@filename) if File::exists?(@filename) && File::readable?(@filename)
|
57
|
+
return [source_file, '']
|
55
58
|
end
|
56
59
|
|
57
60
|
# ----------------------
|
@@ -72,7 +75,7 @@ module LegalMarkdown
|
|
72
75
|
rescue => e
|
73
76
|
puts "Error reading file #{File.join(ARGV[0])}: #{e.message}"
|
74
77
|
end
|
75
|
-
return[data, content]
|
78
|
+
return [data, content]
|
76
79
|
end
|
77
80
|
|
78
81
|
# ----------------------
|
@@ -93,43 +96,256 @@ module LegalMarkdown
|
|
93
96
|
end
|
94
97
|
end
|
95
98
|
end
|
96
|
-
return[content, mixins]
|
99
|
+
return [content, mixins]
|
97
100
|
end
|
98
101
|
|
99
102
|
# ----------------------
|
100
103
|
# | Step 4 |
|
101
104
|
# ----------------------
|
102
|
-
#
|
103
|
-
|
105
|
+
# Special YAML fields
|
104
106
|
|
105
|
-
|
107
|
+
def pandoc_title_block( headers )
|
108
|
+
title_block = ""
|
109
|
+
headers.each do | header |
|
110
|
+
if header[0].casecmp("title") == 0
|
111
|
+
title_block << "% " + header[1] + "\n"
|
112
|
+
headers.delete( header )
|
113
|
+
elsif header[0].casecmp("author") == 0
|
114
|
+
title_block << "% " + header[1] + "\n"
|
115
|
+
headers.delete( header )
|
116
|
+
elsif header[0].casecmp("date") == 0
|
117
|
+
title_block << "% " + header[1] + "\n\n"
|
118
|
+
headers.delete( header )
|
119
|
+
end
|
120
|
+
end
|
121
|
+
return title_block
|
122
|
+
end
|
106
123
|
|
124
|
+
# ----------------------
|
125
|
+
# | Step 5 |
|
126
|
+
# ----------------------
|
127
|
+
# Headers
|
107
128
|
|
108
|
-
|
129
|
+
def headers_on( headers, content )
|
130
|
+
|
131
|
+
def get_the_substitutions( headers )
|
132
|
+
# find the headers in the remaining YAML
|
133
|
+
# parse out the headers into level-X and pre-X headers
|
134
|
+
# then combine them into a coherent package
|
135
|
+
# returns a hash with the keys as the l., ll. searches
|
136
|
+
# and the values as the replacements in the form of
|
137
|
+
# an array where the first value is a symbol and the
|
138
|
+
# second value is the precursor
|
139
|
+
|
140
|
+
def set_the_subs_arrays(value)
|
141
|
+
# takes a core value from the hash pulled from the yaml
|
142
|
+
# returns an array with a type symbol and a precursor string
|
143
|
+
if value =~ /I\.\z/ # type1 : {{ I. }}
|
144
|
+
return[:type1, value[0..-3]]
|
145
|
+
elsif value =~ /\(I\)\z/ # type2 : {{ (I) }}
|
146
|
+
return[:type2, value[0..-4]]
|
147
|
+
elsif value =~ /i\.\z/ # type3 : {{ i. }}
|
148
|
+
return[:type3, value[0..-3]]
|
149
|
+
elsif value =~ /\(i\)\z/ # type4 : {{ (i) }}
|
150
|
+
return[:type4, value[0..-4]]
|
151
|
+
elsif value =~ /[A-Z]\.\z/ # type5 : {{ A. }}
|
152
|
+
return[:type5, value[0..-3]]
|
153
|
+
elsif value =~ /\([A-Z]\)\z/ # type6 : {{ (A) }}
|
154
|
+
return[:type6, value[0..-4]]
|
155
|
+
elsif value =~ /[a-z]\.\z/ # type7 : {{ a. }}
|
156
|
+
return[:type7, value[0..-3]]
|
157
|
+
elsif value =~ /\([a-z]\)\z/ # type8 : {{ (a) }}
|
158
|
+
return[:type8, value[0..-4]]
|
159
|
+
elsif value =~ /\(\d\)\z/ # type9 : {{ (1) }}
|
160
|
+
return[:type9, value[0..-3]]
|
161
|
+
else value =~ /\d\.\z/ # type0 : {{ 1. }} ... also default
|
162
|
+
return[:type0, value[0..-4]]
|
163
|
+
end
|
164
|
+
end
|
109
165
|
|
166
|
+
substitutions = {}
|
167
|
+
headers.each do | header, value |
|
168
|
+
if header =~ /level-\d/
|
169
|
+
level = header[-1].to_i
|
170
|
+
base = "l"
|
171
|
+
search = base * level + "."
|
172
|
+
# substitutions hash example {"ll."=>[:type8,"Article"],}
|
173
|
+
substitutions[search]= set_the_subs_arrays(value.to_s)
|
174
|
+
end
|
175
|
+
end
|
110
176
|
|
111
|
-
|
177
|
+
return substitutions
|
178
|
+
end
|
112
179
|
|
180
|
+
def find_the_block( content )
|
181
|
+
# finds the block of text that will be processed
|
182
|
+
# returns an array with the first element as the block
|
183
|
+
# to be processed and the second element as the rest of the document
|
184
|
+
if content =~ /(^l+\.\s.+^$)/m
|
185
|
+
block = $1.chomp
|
186
|
+
content = $PREMATCH + "{{block}}\n" + $POSTMATCH
|
187
|
+
end
|
188
|
+
return[ block, content ]
|
189
|
+
end
|
113
190
|
|
114
|
-
|
115
|
-
|
191
|
+
def chew_on_the_block( substitutions, block )
|
192
|
+
# takes a hash of substitutions to make from the #get_the_substitutions method
|
193
|
+
# and a block of text returned from the #find_the_block method
|
194
|
+
# iterates over the block to make the appropriate substitutions
|
195
|
+
# returns a block of text
|
196
|
+
|
197
|
+
def get_the_subs_arrays( value )
|
198
|
+
# returns a new array for the replacements
|
199
|
+
if value[0] == :type1 # :type1 : {{ I. }}
|
200
|
+
return[:type1, value[1], "", "I", "."]
|
201
|
+
elsif value[0] == :type2 # :type2 : {{ (I) }}
|
202
|
+
return[:type2, value[1], "(", "I", ")"]
|
203
|
+
elsif value[0] == :type3 # :type3 : {{ i. }}
|
204
|
+
return[:type3, value[1], "", "i", "."]
|
205
|
+
elsif value[0] == :type4 # :type4 : {{ (i) }}
|
206
|
+
return[:type4, value[1], "(", "i", ")"]
|
207
|
+
elsif value[0] == :type5 # :type5 : {{ A. }}
|
208
|
+
return[:type5, value[1], "", "A", "."]
|
209
|
+
elsif value[0] == :type6 # :type6 : {{ (A) }}
|
210
|
+
return[:type6, value[1], "(", "A", ")"]
|
211
|
+
elsif value[0] == :type7 # :type7 : {{ a. }}
|
212
|
+
return[:type7, value[1], "", "a", "."]
|
213
|
+
elsif value[0] == :type8 # :type8 : {{ (a) }}
|
214
|
+
return[:type8, value[1], "(", "a", ")"]
|
215
|
+
elsif value[0] == :type9 # :type9 : {{ (1) }}
|
216
|
+
return[:type9, value[1], "(", "1", ")"]
|
217
|
+
else value[0] == :type0 # :type0 : {{ 1. }} ... also default
|
218
|
+
return[:type0, value[1], "", 1, "."]
|
219
|
+
end
|
220
|
+
end
|
116
221
|
|
222
|
+
def log_the_line( new_block, selector, line, array_to_sub )
|
223
|
+
substitute = array_to_sub[1..4].join
|
224
|
+
spaces = ( " " * ( (selector.size) - 1 ) * 4 )
|
225
|
+
new_block << spaces + line.gsub(selector, substitute)
|
226
|
+
end
|
117
227
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
228
|
+
def increment_the_branch( hash_of_subs, array_to_sub, selector )
|
229
|
+
romans_uppers = [ :type1, :type2 ]
|
230
|
+
romans_lowers = [ :type3, :type4 ]
|
231
|
+
romans = romans_uppers + romans_lowers
|
232
|
+
if romans.any?{ |e| e == array_to_sub[0] }
|
233
|
+
if romans_lowers.any?{ |e| e == array_to_sub[0] }
|
234
|
+
r_l = true
|
235
|
+
else
|
236
|
+
r_u = true
|
237
|
+
end
|
238
|
+
end
|
239
|
+
if r_l == true
|
240
|
+
array_to_sub[3] = array_to_sub[3].upcase
|
241
|
+
end
|
242
|
+
if r_l == true || r_u == true
|
243
|
+
array_to_sub[3] = RomanNumerals.to_decimal(array_to_sub[3])
|
244
|
+
end
|
245
|
+
array_to_sub[3] = array_to_sub[3].next
|
246
|
+
if r_l == true || r_u == true
|
247
|
+
array_to_sub[3] = RomanNumerals.to_roman(array_to_sub[3])
|
248
|
+
end
|
249
|
+
if r_l == true
|
250
|
+
array_to_sub[3] = array_to_sub[3].downcase
|
251
|
+
end
|
252
|
+
hash_of_subs[selector]= array_to_sub
|
253
|
+
return hash_of_subs
|
254
|
+
end
|
122
255
|
|
256
|
+
def reset_the_sub_branches( hash_of_subs, array_to_sub, selector )
|
257
|
+
hash_of_subs = increment_the_branch( hash_of_subs, array_to_sub, selector )
|
258
|
+
leaders_to_reset = []
|
259
|
+
hash_of_subs.each_key{ |k| leaders_to_reset << k if k > selector }
|
260
|
+
leaders_to_reset.each do | leader |
|
261
|
+
unless hash_of_subs[leader][5] == :pre
|
262
|
+
hash_of_subs[leader]= get_the_subs_arrays(hash_of_subs[leader])
|
263
|
+
else
|
264
|
+
hash_of_subs[leader]= get_the_subs_arrays(hash_of_subs[leader])
|
265
|
+
hash_of_subs[leader]= pre_setup(hash_of_subs[leader])
|
266
|
+
end
|
267
|
+
end
|
268
|
+
return hash_of_subs
|
269
|
+
end
|
123
270
|
|
124
|
-
|
271
|
+
def pre_setup( array_to_sub )
|
272
|
+
array_to_sub[5] = :pre
|
273
|
+
array_to_sub[1] = ""
|
274
|
+
return array_to_sub
|
275
|
+
end
|
125
276
|
|
277
|
+
def reform_the_block( old_block, substitutions )
|
278
|
+
# method will take the old_block and iterate through the lines.
|
279
|
+
# First it will find the leading indicator. Then it will
|
280
|
+
# find the appropriate substitution from the substitutions
|
281
|
+
# hash. After that it will rebuild the leading matter from the
|
282
|
+
# sub hash. It will drop markers if it is going down the tree.
|
283
|
+
# It will reset the branches if it is going up the tree.
|
284
|
+
# sub_it is an array w/ type[0] & lead_string[1] & id's[2..4]
|
285
|
+
new_block = ""
|
286
|
+
leader_before = ""
|
287
|
+
leader_above = ""
|
288
|
+
selector_before = ""
|
289
|
+
old_block.each_line do | line |
|
290
|
+
selector = $1.chop if line =~ /(^l+.\s)/
|
291
|
+
sub_it = substitutions[selector]
|
292
|
+
if sub_it[5] == :pre || sub_it[1] =~ /pre/
|
293
|
+
sub_it = pre_setup(sub_it)
|
294
|
+
if selector_before < selector # Going down the tree, into pre
|
295
|
+
leader_above = substitutions[selector_before][1..4].join
|
296
|
+
sub_it[1] = leader_above = leader_before
|
297
|
+
elsif selector_before > selector && substitutions[selector_before][5] == :pre
|
298
|
+
sub_it[1] = leader_above[0..-3]
|
299
|
+
else
|
300
|
+
sub_it[1] = leader_above
|
301
|
+
end
|
302
|
+
end
|
303
|
+
log_the_line( new_block, selector, line, sub_it )
|
304
|
+
leader_before = sub_it[1..4].join
|
305
|
+
if selector_before > selector # We are going up the tree.
|
306
|
+
substitutions = reset_the_sub_branches(substitutions, sub_it, selector)
|
307
|
+
else # We are at the same level.
|
308
|
+
substitutions = increment_the_branch(substitutions, sub_it, selector)
|
309
|
+
end
|
310
|
+
selector_before = selector
|
311
|
+
end
|
312
|
+
return new_block
|
313
|
+
end
|
126
314
|
|
127
|
-
|
315
|
+
if block != nil && block != "" && substitutions != nil && substitutions != {}
|
316
|
+
substitutions.each_key{ |k| substitutions[k]= get_the_subs_arrays(substitutions[k]) }
|
317
|
+
new_block = reform_the_block( block, substitutions )
|
318
|
+
end
|
319
|
+
end
|
128
320
|
|
321
|
+
headers = get_the_substitutions( headers )
|
322
|
+
block_noted = find_the_block( content )
|
323
|
+
block = block_noted[0]
|
324
|
+
not_the_block = block_noted[1]
|
325
|
+
block_noted = "" # Really only for long documents so they don't use too much memory
|
129
326
|
|
130
|
-
|
327
|
+
if headers == {}
|
328
|
+
block_redux = block
|
329
|
+
end
|
330
|
+
if block == nil || block == ""
|
331
|
+
block_redux = ""
|
332
|
+
else
|
333
|
+
block_redux = chew_on_the_block( headers, block )
|
334
|
+
end
|
131
335
|
|
336
|
+
headed = not_the_block.gsub("{{block}}", block_redux )
|
337
|
+
end
|
132
338
|
|
133
|
-
#
|
339
|
+
# ----------------------
|
340
|
+
# | Step 6 |
|
341
|
+
# ----------------------
|
342
|
+
# Write the file
|
134
343
|
|
344
|
+
def write_it( filename, final_content )
|
345
|
+
if File.writable?( filename )
|
346
|
+
File::open filename, 'w' do |f|
|
347
|
+
f.write final_content
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
135
351
|
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.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
13
|
-
dependencies:
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: roman-numerals
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
description: ! " This gem will parse YAML Front Matter of Markdown Documents. Typically,
|
15
31
|
this gem would be called with a md renderer, such as Pandoc, that would turn the
|
16
32
|
md into a document such as a .pdf file or a .docx file. By combining this pre-processing
|
@@ -47,7 +63,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
63
|
version: '0'
|
48
64
|
segments:
|
49
65
|
- 0
|
50
|
-
hash:
|
66
|
+
hash: 2506371109805164331
|
51
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
68
|
none: false
|
53
69
|
requirements:
|
@@ -56,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
72
|
version: '0'
|
57
73
|
segments:
|
58
74
|
- 0
|
59
|
-
hash:
|
75
|
+
hash: 2506371109805164331
|
60
76
|
requirements: []
|
61
77
|
rubyforge_project:
|
62
78
|
rubygems_version: 1.8.25
|