ron 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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&gt;')
221
- new = node.document.create_text_node(
222
- node.to_s.
223
- gsub('&lt;var&gt;', '<').
224
- gsub("&lt;/var&gt;", '>')
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
@@ -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>