ron 0.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/ron.gemspec +39 -51
- metadata +50 -77
- data/COPYING +0 -21
- data/README.md +0 -133
- data/Rakefile +0 -94
- data/bin/ron +0 -130
- data/lib/ron.rb +0 -16
- data/lib/ron/document.rb +0 -289
- data/lib/ron/layout.html +0 -75
- data/lib/ron/roff.rb +0 -180
- data/man/markdown.5 +0 -1614
- data/man/markdown.5.ron +0 -881
- data/man/ron.1 +0 -226
- data/man/ron.1.ron +0 -158
- data/man/ron.5 +0 -210
- data/man/ron.5.ron +0 -154
- data/man/ron.7 +0 -201
- data/man/ron.7.ron +0 -133
- data/test/angle_bracket_syntax.html +0 -12
- data/test/angle_bracket_syntax.ron +0 -12
- data/test/basic_document.html +0 -3
- data/test/basic_document.ron +0 -4
- data/test/custom_title_document.html +0 -3
- data/test/custom_title_document.ron +0 -5
- data/test/definition_list_syntax.html +0 -21
- data/test/definition_list_syntax.ron +0 -18
- data/test/document_test.rb +0 -88
- data/test/ron_test.rb +0 -59
- data/test/titleless_document.html +0 -2
- data/test/titleless_document.ron +0 -2
data/bin/ron
DELETED
@@ -1,130 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
## Usage: ron [ OPTIONS ] [ FILE ]
|
3
|
-
## ron --build FILE ...
|
4
|
-
## ron --install FILE ...
|
5
|
-
## ron --man FILE ...
|
6
|
-
## Convert ron FILE to roff man page or HTML and write to standard
|
7
|
-
## output. With no FILE, ron reads from standard input. The build,
|
8
|
-
## install, and man forms accept multiple FILE arguments.
|
9
|
-
##
|
10
|
-
## Modes:
|
11
|
-
## --pipe write to standard output (default behavior)
|
12
|
-
## -b, --build write to files instead of standard output
|
13
|
-
## -i, --install write to file in MAN_HOME or system man path
|
14
|
-
## -m, --man open man page like man(1)
|
15
|
-
##
|
16
|
-
## Formats:
|
17
|
-
## -r, --roff generate roff/man text; this is the default behavior
|
18
|
-
## -5, --html generate entire HTML page with layout
|
19
|
-
## -f, --fragment generate HTML fragment instead of entire HTML page
|
20
|
-
##
|
21
|
-
## Document attributes:
|
22
|
-
## --date=DATE published date in YYYY-MM-DD format;
|
23
|
-
## displayed bottom-center in footer
|
24
|
-
## --manual=NAME name of the manual this document belongs to;
|
25
|
-
## displayed top-center in header
|
26
|
-
## --organization=NAME publishing group, organization, or individual;
|
27
|
-
## displayed bottom-left in footer
|
28
|
-
##
|
29
|
-
## --help show this help message
|
30
|
-
##
|
31
|
-
require 'date'
|
32
|
-
require 'optparse'
|
33
|
-
|
34
|
-
formats = []
|
35
|
-
options = {}
|
36
|
-
build = false
|
37
|
-
install = false
|
38
|
-
man = false
|
39
|
-
groff = "groff -Wall -mtty-char -mandoc -Tascii"
|
40
|
-
pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more'
|
41
|
-
|
42
|
-
def info(message, *args)
|
43
|
-
STDERR.puts message % args
|
44
|
-
end
|
45
|
-
|
46
|
-
def usage
|
47
|
-
puts File.read(__FILE__).
|
48
|
-
grep(/^##.*/).
|
49
|
-
map { |line| line.chomp[3..-1] }.
|
50
|
-
join("\n")
|
51
|
-
end
|
52
|
-
|
53
|
-
# parse command line options
|
54
|
-
ARGV.options do |option|
|
55
|
-
# modes
|
56
|
-
option.on("--pipe") { }
|
57
|
-
option.on("-b", "--build") { build = true }
|
58
|
-
option.on("-i", "--install") { install = true }
|
59
|
-
option.on("-m", "--man") { man = true }
|
60
|
-
|
61
|
-
# format options
|
62
|
-
option.on("-r", "--roff") { formats << 'roff' }
|
63
|
-
option.on("-5", "--html") { formats << 'html' }
|
64
|
-
option.on("-f", "--fragment") { formats << 'html_fragment' }
|
65
|
-
|
66
|
-
# manual attribute options
|
67
|
-
[:name, :section, :manual, :organization, :date].each do |option_attr|
|
68
|
-
option.on("--#{option_attr}=VALUE") { |val| options[option_attr] = val }
|
69
|
-
end
|
70
|
-
|
71
|
-
option.on_tail("--help") { usage ; exit }
|
72
|
-
option.parse!
|
73
|
-
end
|
74
|
-
|
75
|
-
if ARGV.empty? && STDIN.tty?
|
76
|
-
usage
|
77
|
-
exit
|
78
|
-
elsif ARGV.empty?
|
79
|
-
ARGV.push '-'
|
80
|
-
end
|
81
|
-
|
82
|
-
# turn the --date arg into a real date object
|
83
|
-
options[:date] &&= Date.strptime(options[:date], '%Y-%m-%d')
|
84
|
-
|
85
|
-
formats = ['roff'] if formats.empty?
|
86
|
-
formats.delete('html') if formats.include?('html_fragment')
|
87
|
-
pid = nil
|
88
|
-
|
89
|
-
begin
|
90
|
-
require 'ron'
|
91
|
-
rescue LoadError
|
92
|
-
$:.unshift File.dirname(__FILE__) + "../lib"
|
93
|
-
require 'ron'
|
94
|
-
end
|
95
|
-
|
96
|
-
wr = STDOUT
|
97
|
-
ARGV.each do |file|
|
98
|
-
doc = Ron.new(file, options) { file == '-' ? STDIN.read : File.read(file) }
|
99
|
-
|
100
|
-
# setup the man pipeline if the --man option was specified
|
101
|
-
if man && !build
|
102
|
-
rd, wr = IO.pipe
|
103
|
-
if pid = fork
|
104
|
-
rd.close
|
105
|
-
else
|
106
|
-
wr.close
|
107
|
-
STDIN.reopen rd
|
108
|
-
exec "#{groff} | #{pager}"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# write output for each format
|
113
|
-
formats.each do |format|
|
114
|
-
if build
|
115
|
-
path = doc.path_for(format)
|
116
|
-
info "building: #{path}" if build
|
117
|
-
output = doc.convert(format)
|
118
|
-
File.open(path, 'wb') { |f| f.puts(output) }
|
119
|
-
system "man #{path}" if man && format == 'roff'
|
120
|
-
else
|
121
|
-
output = doc.convert(format)
|
122
|
-
wr.puts(output)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
if pid
|
127
|
-
wr.close
|
128
|
-
Process.wait
|
129
|
-
end
|
130
|
-
end
|
data/lib/ron.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
# Ron is a humane text format and toolchain for authoring manpages (and
|
2
|
-
# things that appear as manpages from a distance). Use it to build /
|
3
|
-
# install standard UNIX roff(7) formatted manpages or to generate
|
4
|
-
# beautiful HTML manpages.
|
5
|
-
module Ron
|
6
|
-
VERSION = '0.3'
|
7
|
-
|
8
|
-
require 'ron/document'
|
9
|
-
require 'ron/roff'
|
10
|
-
|
11
|
-
# Create a new Ron::Document for the given ron file. See
|
12
|
-
# Ron::Document.new for usage information.
|
13
|
-
def self.new(filename, attributes={}, &block)
|
14
|
-
Document.new(filename, attributes, &block)
|
15
|
-
end
|
16
|
-
end
|
data/lib/ron/document.rb
DELETED
@@ -1,289 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
require 'nokogiri'
|
3
|
-
require 'rdiscount'
|
4
|
-
require 'ron/roff'
|
5
|
-
|
6
|
-
module Ron
|
7
|
-
# The Document class can be used to load and inspect a ron document
|
8
|
-
# and to convert a ron document into other formats, like roff or
|
9
|
-
# HTML.
|
10
|
-
#
|
11
|
-
# Ron files may optionally follow the naming convention:
|
12
|
-
# "<name>.<section>.ron". The <name> and <section> are used in
|
13
|
-
# generated documentation unless overridden by the information
|
14
|
-
# extracted from the document's name section.
|
15
|
-
class Document
|
16
|
-
attr_reader :path, :data
|
17
|
-
|
18
|
-
# The man pages name: usually a single word name of
|
19
|
-
# a program or filename; displayed along with the section in
|
20
|
-
# the left and right portions of the header as well as the bottom
|
21
|
-
# right section of the footer.
|
22
|
-
attr_accessor :name
|
23
|
-
|
24
|
-
# The man page's section: a string whose first character
|
25
|
-
# is numeric; displayed in parenthesis along with the name.
|
26
|
-
attr_accessor :section
|
27
|
-
|
28
|
-
# Single sentence description of the thing being described
|
29
|
-
# by this man page; displayed in the NAME section.
|
30
|
-
attr_accessor :tagline
|
31
|
-
|
32
|
-
# The manual this document belongs to; center displayed in
|
33
|
-
# the header.
|
34
|
-
attr_accessor :manual
|
35
|
-
|
36
|
-
# The name of the group, organization, or individual responsible
|
37
|
-
# for this document; displayed in the left portion of the footer.
|
38
|
-
attr_accessor :organization
|
39
|
-
|
40
|
-
# The date the document was published; center displayed in
|
41
|
-
# the document footer.
|
42
|
-
attr_accessor :date
|
43
|
-
|
44
|
-
# Create a Ron::Document given a path or with the data returned by
|
45
|
-
# calling the block. The document is loaded and preprocessed before
|
46
|
-
# the intialize method returns. The attributes hash may contain values
|
47
|
-
# for any writeable attributes defined on this class.
|
48
|
-
def initialize(path=nil, attributes={}, &block)
|
49
|
-
@path = path
|
50
|
-
@basename = path.to_s =~ /^-?$/ ? nil : File.basename(path)
|
51
|
-
@reader = block || Proc.new { |f| File.read(f) }
|
52
|
-
@data = @reader.call(path)
|
53
|
-
@name, @section, @tagline = nil
|
54
|
-
@manual, @organization, @date = nil
|
55
|
-
@fragment = preprocess
|
56
|
-
attributes.each { |attr_name,value| send("#{attr_name}=", value) }
|
57
|
-
end
|
58
|
-
|
59
|
-
# Generate a file basename of the form "<name>.<section>.<type>"
|
60
|
-
# for the given file extension. Uses the name and section from
|
61
|
-
# the source file path but falls back on the name and section
|
62
|
-
# defined in the document.
|
63
|
-
def basename(type=nil)
|
64
|
-
type = nil if ['', 'roff'].include?(type.to_s)
|
65
|
-
[path_name || @name, path_section || @section, type].
|
66
|
-
compact.join('.')
|
67
|
-
end
|
68
|
-
|
69
|
-
# Construct a path for a file near the source file. Uses the
|
70
|
-
# Document#basename method to generate the basename part and
|
71
|
-
# appends it to the dirname of the source document.
|
72
|
-
def path_for(type=nil)
|
73
|
-
if @basename
|
74
|
-
File.join(File.dirname(path), basename(type))
|
75
|
-
else
|
76
|
-
basename(type)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Returns the <name> part of the path, or nil when no path is
|
81
|
-
# available. This is used as the manual page name when the
|
82
|
-
# file contents do not include a name section.
|
83
|
-
def path_name
|
84
|
-
@basename[/^[^.]+/] if @basename
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns the <section> part of the path, or nil when
|
88
|
-
# no path is available.
|
89
|
-
def path_section
|
90
|
-
$1 if @basename.to_s =~ /\.(\d\w*)\./
|
91
|
-
end
|
92
|
-
|
93
|
-
# Returns the manual page name based first on the document's
|
94
|
-
# contents and then on the path name.
|
95
|
-
def name
|
96
|
-
@name || path_name
|
97
|
-
end
|
98
|
-
|
99
|
-
# Truthful when the name was extracted from the name section
|
100
|
-
# of the document.
|
101
|
-
def name?
|
102
|
-
!name.nil?
|
103
|
-
end
|
104
|
-
|
105
|
-
# Returns the manual page section based first on the document's
|
106
|
-
# contents and then on the path name.
|
107
|
-
def section
|
108
|
-
@section || path_section
|
109
|
-
end
|
110
|
-
|
111
|
-
# True when the section number was extracted from the name
|
112
|
-
# section of the document.
|
113
|
-
def section?
|
114
|
-
!section.nil?
|
115
|
-
end
|
116
|
-
|
117
|
-
# The date the man page was published. If not set explicitly,
|
118
|
-
# this is the file's modified time or, if no file is given,
|
119
|
-
# the current time.
|
120
|
-
def date
|
121
|
-
return @date if @date
|
122
|
-
return File.mtime(path) if File.exist?(path)
|
123
|
-
Time.now
|
124
|
-
end
|
125
|
-
|
126
|
-
# Convert the document to :roff, :html, or :html_fragment and
|
127
|
-
# return the result as a string.
|
128
|
-
def convert(format)
|
129
|
-
send "to_#{format}"
|
130
|
-
end
|
131
|
-
|
132
|
-
# Convert the document to roff and return the result as a string.
|
133
|
-
def to_roff
|
134
|
-
RoffFilter.new(
|
135
|
-
to_html_fragment,
|
136
|
-
name,
|
137
|
-
section,
|
138
|
-
tagline,
|
139
|
-
manual,
|
140
|
-
organization,
|
141
|
-
date
|
142
|
-
).to_s
|
143
|
-
end
|
144
|
-
|
145
|
-
# Convert the document to HTML and return the result as a string.
|
146
|
-
def to_html
|
147
|
-
layout_filter(to_html_fragment)
|
148
|
-
end
|
149
|
-
|
150
|
-
# Convert the document to HTML and return the result
|
151
|
-
# as a string. The HTML does not include <html>, <head>,
|
152
|
-
# or <style> tags.
|
153
|
-
def to_html_fragment
|
154
|
-
buf = []
|
155
|
-
if name? && section?
|
156
|
-
buf << "<h2 id='NAME'>NAME</h2>"
|
157
|
-
buf << "<p><code>#{name}</code> -- #{tagline}</p>"
|
158
|
-
elsif tagline
|
159
|
-
buf << "<h1>#{[name, tagline].compact.join(' -- ')}</h1>"
|
160
|
-
end
|
161
|
-
buf << @fragment.to_s
|
162
|
-
buf.join("\n")
|
163
|
-
end
|
164
|
-
|
165
|
-
protected
|
166
|
-
# Parse the document and extract the name, section, and tagline
|
167
|
-
# from its contents. This is called while the object is being
|
168
|
-
# initialized.
|
169
|
-
def preprocess
|
170
|
-
[
|
171
|
-
:angle_quote_pre_filter,
|
172
|
-
:markdown_filter,
|
173
|
-
:angle_quote_post_filter,
|
174
|
-
:definition_list_filter
|
175
|
-
].inject(data) { |res,filter| send(filter, res) }
|
176
|
-
end
|
177
|
-
|
178
|
-
# Apply the standard HTML layout template.
|
179
|
-
def layout_filter(html)
|
180
|
-
template_file = File.dirname(__FILE__) + "/layout.html"
|
181
|
-
template = File.read(template_file)
|
182
|
-
eval("%Q{#{template}}", binding, template_file)
|
183
|
-
end
|
184
|
-
|
185
|
-
# Convert special format unordered lists to definition lists.
|
186
|
-
def definition_list_filter(html)
|
187
|
-
doc = parse_html(html)
|
188
|
-
# process all unordered lists depth-first
|
189
|
-
doc.search('ul').to_a.reverse.each do |ul|
|
190
|
-
items = ul.search('li')
|
191
|
-
next if items.any? { |item| item.text.split("\n", 2).first !~ /:$/ }
|
192
|
-
|
193
|
-
ul.name = 'dl'
|
194
|
-
items.each do |item|
|
195
|
-
if item.child.name == 'p'
|
196
|
-
wrap = '<p></p>'
|
197
|
-
container = item.child
|
198
|
-
else
|
199
|
-
wrap = '<dd></dd>'
|
200
|
-
container = item
|
201
|
-
end
|
202
|
-
term, definition = container.inner_html.split(":\n", 2)
|
203
|
-
|
204
|
-
dt = item.before("<dt>#{term}</dt>").previous_sibling
|
205
|
-
dt['class'] = 'flush' if dt.content.length <= 7
|
206
|
-
|
207
|
-
item.name = 'dd'
|
208
|
-
container.swap(wrap.sub(/></, ">#{definition}<"))
|
209
|
-
end
|
210
|
-
end
|
211
|
-
doc
|
212
|
-
end
|
213
|
-
|
214
|
-
# Perform angle quote (<THESE>) post filtering.
|
215
|
-
def angle_quote_post_filter(html)
|
216
|
-
doc = parse_html(html)
|
217
|
-
# convert all angle quote vars nested in code blocks
|
218
|
-
# back to the original text
|
219
|
-
doc.search('code text()').each do |node|
|
220
|
-
next unless node.to_s.include?('var>')
|
221
|
-
new = node.document.create_text_node(
|
222
|
-
node.to_s.
|
223
|
-
gsub('<var>', '<').
|
224
|
-
gsub("</var>", '>')
|
225
|
-
)
|
226
|
-
node.replace(new)
|
227
|
-
end
|
228
|
-
doc
|
229
|
-
end
|
230
|
-
|
231
|
-
# Run markdown on the data and extract name, section, and
|
232
|
-
# tagline.
|
233
|
-
def markdown_filter(data)
|
234
|
-
html = Markdown.new(data).to_html
|
235
|
-
@tagline, html = html.split("</h1>\n", 2)
|
236
|
-
if html.nil?
|
237
|
-
html = @tagline
|
238
|
-
@tagline = nil
|
239
|
-
else
|
240
|
-
# grab name and section from title
|
241
|
-
@tagline.sub!('<h1>', '')
|
242
|
-
if @tagline =~ /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*--?\s*(.*)/
|
243
|
-
@name = $1
|
244
|
-
@section = $2
|
245
|
-
@tagline = $3
|
246
|
-
elsif @tagline =~ /([\w_.\[\]~+=@:-]+)\s+--\s+(.*)/
|
247
|
-
@name = $1
|
248
|
-
@tagline = $2
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
html.to_s
|
253
|
-
end
|
254
|
-
|
255
|
-
# Convert all <WORD> to <var>WORD</var> but only if WORD
|
256
|
-
# isn't an HTML tag.
|
257
|
-
def angle_quote_pre_filter(data)
|
258
|
-
data.gsub(/\<([^:.\/]+?)\>/) do |match|
|
259
|
-
contents = $1
|
260
|
-
tag, attrs = contents.split(' ', 2)
|
261
|
-
if attrs =~ /\/=/ ||
|
262
|
-
HTML.include?(tag.sub(/^\//, '')) ||
|
263
|
-
data.include?("</#{tag}>")
|
264
|
-
match.to_s
|
265
|
-
else
|
266
|
-
"<var>#{contents}</var>"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
HTML = %w[
|
272
|
-
a abbr acronym b bdo big br cite code dfn
|
273
|
-
em i img input kbd label q samp select
|
274
|
-
small span strong sub sup textarea tt var
|
275
|
-
address blockquote div dl fieldset form
|
276
|
-
h1 h2 h3 h4 h5 h6 hr noscript ol p pre
|
277
|
-
table ul
|
278
|
-
].to_set
|
279
|
-
|
280
|
-
private
|
281
|
-
def parse_html(html)
|
282
|
-
if html.kind_of?(Nokogiri::HTML::DocumentFragment)
|
283
|
-
html
|
284
|
-
else
|
285
|
-
Nokogiri::HTML.fragment(html.to_s)
|
286
|
-
end
|
287
|
-
end
|
288
|
-
end
|
289
|
-
end
|
data/lib/ron/layout.html
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html>
|
3
|
-
<head>
|
4
|
-
<meta http-equiv='content-type' value='text/html;charset=utf8'>
|
5
|
-
<meta name='generator' value='Ron/v#{Ron::VERSION}'>
|
6
|
-
<title>#{name}(#{section})#{tagline ? " -- " + tagline : ''}</title>
|
7
|
-
<style type='text/css'>
|
8
|
-
body {margin:0}
|
9
|
-
#man, #man code, #man pre, #man tt, #man kbd, #man samp {
|
10
|
-
font-family:consolas,monospace;
|
11
|
-
font-size:16px;
|
12
|
-
line-height:1.3;
|
13
|
-
color:#343331;
|
14
|
-
background:#fff; }
|
15
|
-
#man { max-width:89ex; text-align:justify; margin:0 25px 25px 25px }
|
16
|
-
#man h1, #man h2, #man h3 { color:#232221;clear:left }
|
17
|
-
#man h1 { font-size:28px; margin:15px 0 30px 0; text-align:center }
|
18
|
-
#man h2 { font-size:18px; margin-bottom:0; margin-top:10px; line-height:1.3; }
|
19
|
-
#man h3 { font-size:16px; margin:0 0 0 4ex; }
|
20
|
-
#man p, #man ul, #man ol, #man dl, #man pre { margin:0 0 18px 0; }
|
21
|
-
#man pre {
|
22
|
-
color:#333231;
|
23
|
-
background:#edeceb;
|
24
|
-
padding:5px 7px;
|
25
|
-
margin:0px 0 20px 0;
|
26
|
-
border-left:2ex solid #ddd}
|
27
|
-
#man pre + h2, #man pre + h3 {
|
28
|
-
margin-top:22px;
|
29
|
-
}
|
30
|
-
#man h2 + pre, #man h3 + pre {
|
31
|
-
margin-top:5px;
|
32
|
-
}
|
33
|
-
#man > p, #man > ul, #man > ol, #man > dl, #man > pre { margin-left:8ex; }
|
34
|
-
#man dt { margin:0; clear:left }
|
35
|
-
#man dt.flush { float:left; width:8ex }
|
36
|
-
#man dd { margin:0 0 0 9ex }
|
37
|
-
#man code, #man strong, #man b { font-weight:bold; color:#131211; }
|
38
|
-
#man pre code { font-weight:normal; color:#232221; background:inherit }
|
39
|
-
#man em, var, u {
|
40
|
-
font-style:normal; color:#333231; border-bottom:1px solid #999; }
|
41
|
-
#man h1.man-title { display:none; }
|
42
|
-
#man ol.man, #man ol.man li { margin:2px 0 10px 0; padding:0;
|
43
|
-
float:left; width:33%; list-style-type:none;
|
44
|
-
text-transform:uppercase; font-size:18px; color:#999;
|
45
|
-
letter-spacing:1px;}
|
46
|
-
#man ol.man { width:100%; }
|
47
|
-
#man ol.man li.tl { text-align:left }
|
48
|
-
#man ol.man li.tc { text-align:center;letter-spacing:4px }
|
49
|
-
#man ol.man li.tr { text-align:right }
|
50
|
-
#man ol.man a { color:#999 }
|
51
|
-
#man ol.man a:hover { color:#333231 }
|
52
|
-
</style>
|
53
|
-
</head>
|
54
|
-
<body>
|
55
|
-
<div id='man'>
|
56
|
-
|
57
|
-
<h1 class='man-title'>#{name}(#{section})</h1>
|
58
|
-
|
59
|
-
<ol class='head man'>
|
60
|
-
<li class='tl'>#{name if section}#{"("+section+")" if name and section}</li>
|
61
|
-
<li class='tc'>#{manual}</li>
|
62
|
-
<li class='tr'>#{name if section}#{"("+section+")" if name and section}</li>
|
63
|
-
</ol>
|
64
|
-
|
65
|
-
#{html}
|
66
|
-
|
67
|
-
<ol class='foot man'>
|
68
|
-
<li class='tl'>#{organization}</li>
|
69
|
-
<li class='tc'>#{date.strftime('%B %Y')}</li>
|
70
|
-
<li class='tr'>#{name if section}#{"("+section+")" if name and section}</li>
|
71
|
-
</ol>
|
72
|
-
|
73
|
-
</div>
|
74
|
-
</body>
|
75
|
-
</html>
|