ronn 0.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +7 -0
- data/CHANGES +128 -0
- data/README.md +64 -79
- data/Rakefile +81 -28
- data/bin/ronn +124 -65
- data/config.ru +15 -0
- data/lib/ronn.rb +13 -5
- data/lib/ronn/document.rb +87 -13
- data/lib/ronn/roff.rb +43 -18
- data/lib/ronn/server.rb +70 -0
- data/lib/ronn/template.rb +157 -0
- data/lib/ronn/template/80c.css +6 -0
- data/lib/ronn/template/dark.css +21 -0
- data/lib/ronn/template/darktoc.css +17 -0
- data/lib/ronn/template/default.html +43 -0
- data/lib/ronn/template/man.css +100 -0
- data/lib/ronn/template/print.css +5 -0
- data/lib/ronn/template/screen.css +105 -0
- data/lib/ronn/template/toc.css +27 -0
- data/man/ronn.1 +160 -93
- data/man/ronn.1.ronn +206 -89
- data/man/ronn.5 +94 -96
- data/man/ronn.5.ronn +96 -91
- data/man/ronn.7 +50 -84
- data/man/ronn.7.ronn +64 -79
- data/ronn.gemspec +26 -11
- data/test/angle_bracket_syntax.html +4 -1
- data/test/basic_document.html +4 -1
- data/test/contest.rb +68 -0
- data/test/custom_title_document.html +4 -1
- data/test/definition_list_syntax.html +4 -1
- data/test/definition_list_syntax.roff +26 -0
- data/test/document_test.rb +51 -4
- data/test/entity_encoding_test.html +4 -1
- data/test/entity_encoding_test.roff +1 -1
- data/test/markdown_syntax.html +955 -0
- data/test/markdown_syntax.roff +1467 -0
- data/{man/markdown.5.ronn → test/markdown_syntax.ronn} +0 -0
- data/test/middle_paragraph.html +5 -2
- data/test/middle_paragraph.roff +2 -2
- data/test/ronn_test.rb +19 -4
- data/test/section_reference_links.html +15 -0
- data/test/section_reference_links.roff +10 -0
- data/test/section_reference_links.ronn +12 -0
- data/test/titleless_document.html +3 -0
- metadata +34 -13
- data/lib/ronn/layout.html +0 -75
- data/man/markdown.5 +0 -1639
data/bin/ronn
CHANGED
@@ -1,90 +1,120 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
2
|
+
#/ Usage: ronn <options> <file>...
|
3
|
+
#/ ronn -m|--man <file> ...
|
4
|
+
#/ ronn -S|--server <file> ...
|
5
|
+
#/ ronn --pipe [<file>...]
|
6
|
+
#/ Convert ronn source <file>s to roff or HTML manpage. In the first synopsis form,
|
7
|
+
#/ build HTML and roff output files based on the input file names.
|
8
|
+
#/
|
9
|
+
#/ Mode options alter the default file generating behavior:
|
10
|
+
#/ --pipe write to standard output instead of generating files
|
11
|
+
#/ -m, --man show manual like with man(1)
|
12
|
+
#/ -S, --server serve <file>s at http://localhost:1207/
|
13
|
+
#/
|
14
|
+
#/ Format options control which files are generated:
|
15
|
+
#/ -r, --roff generate roff output
|
16
|
+
#/ -5, --html generate entire HTML page with layout
|
17
|
+
#/ -f, --fragment generate HTML fragment
|
18
|
+
#/
|
19
|
+
#/ Document attributes:
|
20
|
+
#/ --date=DATE published date in YYYY-MM-DD format (bottom-center)
|
21
|
+
#/ --manual=NAME name of the manual (top-center)
|
22
|
+
#/ --organization=NAME publishing group or individual (bottom-left)
|
23
|
+
#/
|
24
|
+
#/ Misc options:
|
25
|
+
#/ -w, --warnings show troff warnings on stderr
|
26
|
+
#/ -W disable previously enabled troff warnings
|
27
|
+
#/ --help show this help message
|
28
|
+
#/
|
29
|
+
#/ A <file> named example.1.ronn generates example.1.html (HTML manpage)
|
30
|
+
#/ and example.1 (roff manpage) by default.
|
31
31
|
require 'date'
|
32
32
|
require 'optparse'
|
33
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
34
|
def usage
|
47
35
|
puts File.readlines(__FILE__).
|
48
|
-
grep(
|
36
|
+
grep(/^#\/.*/).
|
49
37
|
map { |line| line.chomp[3..-1] }.
|
50
38
|
join("\n")
|
51
39
|
end
|
52
40
|
|
53
|
-
|
54
|
-
|
41
|
+
##
|
42
|
+
# Argument defaults
|
43
|
+
|
44
|
+
build = true
|
45
|
+
view = false
|
46
|
+
server = false
|
47
|
+
formats = nil
|
48
|
+
options = {}
|
49
|
+
styles = %w[man]
|
50
|
+
groff = "groff -Wall -mtty-char -mandoc -Tascii"
|
51
|
+
pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more'
|
52
|
+
|
53
|
+
##
|
54
|
+
# Environment variables
|
55
|
+
|
56
|
+
%w[manual organization date].each do |attribute|
|
57
|
+
value = ENV["RONN_#{attribute.upcase}"]
|
58
|
+
next if value.nil? or value.empty?
|
59
|
+
options[attribute] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Argument parsing
|
64
|
+
|
65
|
+
ARGV.options do |argv|
|
55
66
|
# modes
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
argv.on("--pipe") { build = server = false }
|
68
|
+
argv.on("-b", "--build") { build = true; server = false }
|
69
|
+
argv.on("-m", "--man") { build = server = false; view = true }
|
70
|
+
argv.on("-S", "--server") { build = view = false; server = true }
|
71
|
+
argv.on("-w", "--warnings") { groff += ' -ww' }
|
72
|
+
argv.on("-W") { groff += ' -Ww' }
|
60
73
|
|
61
74
|
# format options
|
62
|
-
|
63
|
-
|
64
|
-
|
75
|
+
argv.on("-r", "--roff") { (formats ||= []) << 'roff' }
|
76
|
+
argv.on("-5", "--html") { (formats ||= []) << 'html' }
|
77
|
+
argv.on("-f", "--fragment") { (formats ||= []) << 'html_fragment' }
|
78
|
+
|
79
|
+
# html output options
|
80
|
+
argv.on("-s", "--style=V") { |val| styles += val.split(/[, \n]+/) }
|
65
81
|
|
66
82
|
# manual attribute options
|
67
|
-
[
|
68
|
-
|
83
|
+
%w[name section manual organization date].each do |attribute|
|
84
|
+
argv.on("--#{attribute}=VALUE") { |val| options[attribute] = val }
|
69
85
|
end
|
70
86
|
|
71
|
-
|
72
|
-
|
87
|
+
argv.on_tail("--help") { usage ; exit 0 }
|
88
|
+
argv.parse!
|
73
89
|
end
|
74
90
|
|
75
|
-
|
91
|
+
##
|
92
|
+
# Modes, Formats, Options
|
93
|
+
|
94
|
+
case
|
95
|
+
when ARGV.empty? && $stdin.tty?
|
76
96
|
usage
|
77
|
-
exit
|
78
|
-
|
97
|
+
exit 2
|
98
|
+
when ARGV.empty? && !server
|
79
99
|
ARGV.push '-'
|
100
|
+
build = false
|
101
|
+
formats ||= %w[roff]
|
102
|
+
when view
|
103
|
+
formats ||= %w[roff]
|
104
|
+
when build
|
105
|
+
formats ||= %w[roff html]
|
80
106
|
end
|
107
|
+
formats ||= []
|
108
|
+
formats.delete('html') if formats.include?('html_fragment')
|
81
109
|
|
82
110
|
# turn the --date arg into a real date object
|
83
|
-
options[
|
111
|
+
options['date'] &&= Date.strptime(options['date'], '%Y-%m-%d')
|
84
112
|
|
85
|
-
|
86
|
-
|
87
|
-
|
113
|
+
# pass the styles option
|
114
|
+
options['styles'] = styles
|
115
|
+
|
116
|
+
##
|
117
|
+
# Libraries and LOAD_PATH shenanigans
|
88
118
|
|
89
119
|
begin
|
90
120
|
require 'hpricot'
|
@@ -97,6 +127,8 @@ rescue LoadError
|
|
97
127
|
end
|
98
128
|
end
|
99
129
|
|
130
|
+
# load ronn libs, setting up the load path if something fails and
|
131
|
+
# we're in a development environment.
|
100
132
|
begin
|
101
133
|
require 'ronn'
|
102
134
|
rescue LoadError
|
@@ -110,12 +142,25 @@ rescue LoadError
|
|
110
142
|
raise
|
111
143
|
end
|
112
144
|
|
145
|
+
##
|
146
|
+
# Server
|
147
|
+
|
148
|
+
if server
|
149
|
+
require 'ronn/server'
|
150
|
+
Ronn::Server.run(ARGV, options)
|
151
|
+
exit 0
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Build Pipeline
|
156
|
+
|
157
|
+
pid = nil
|
113
158
|
wr = STDOUT
|
114
159
|
ARGV.each do |file|
|
115
160
|
doc = Ronn.new(file, options) { file == '-' ? STDIN.read : File.read(file) }
|
116
161
|
|
117
162
|
# setup the man pipeline if the --man option was specified
|
118
|
-
if
|
163
|
+
if view && !build
|
119
164
|
rd, wr = IO.pipe
|
120
165
|
if pid = fork
|
121
166
|
rd.close
|
@@ -130,16 +175,30 @@ ARGV.each do |file|
|
|
130
175
|
formats.each do |format|
|
131
176
|
if build
|
132
177
|
path = doc.path_for(format)
|
133
|
-
|
178
|
+
case format
|
179
|
+
when 'html'
|
180
|
+
warn "%5s: %-48s%15s" % [format, path, '+' + doc.styles.join(',')]
|
181
|
+
when 'roff', 'html_fragment'
|
182
|
+
warn "%5s: %-48s" % [format, path]
|
183
|
+
end
|
184
|
+
|
134
185
|
output = doc.convert(format)
|
135
186
|
File.open(path, 'wb') { |f| f.puts(output) }
|
136
|
-
|
187
|
+
|
188
|
+
if format == 'roff'
|
189
|
+
if view
|
190
|
+
system "man #{path}"
|
191
|
+
else
|
192
|
+
system "#{groff} <#{path} >/dev/null"
|
193
|
+
end
|
194
|
+
end
|
137
195
|
else
|
138
196
|
output = doc.convert(format)
|
139
197
|
wr.puts(output)
|
140
198
|
end
|
141
199
|
end
|
142
200
|
|
201
|
+
# wait for children to exit
|
143
202
|
if pid
|
144
203
|
wr.close
|
145
204
|
Process.wait
|
data/config.ru
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#\ -p 1207
|
2
|
+
$: << File.expand_path(__FILE__, '../lib')
|
3
|
+
|
4
|
+
require 'ronn'
|
5
|
+
require 'ronn/server'
|
6
|
+
|
7
|
+
# use Rack::Lint
|
8
|
+
|
9
|
+
options = {
|
10
|
+
:styles => %w[man toc],
|
11
|
+
:organization => "Ronn v#{Ronn::VERSION}"
|
12
|
+
}
|
13
|
+
files = Dir['man/*.ronn'] + Dir['test/*.ronn']
|
14
|
+
|
15
|
+
run Ronn::Server.new(files, options)
|
data/lib/ronn.rb
CHANGED
@@ -3,14 +3,22 @@
|
|
3
3
|
# install standard UNIX roff(7) formatted manpages or to generate
|
4
4
|
# beautiful HTML manpages.
|
5
5
|
module Ronn
|
6
|
-
VERSION = '0.5'
|
7
|
-
|
8
|
-
require 'ronn/document'
|
9
|
-
require 'ronn/roff'
|
10
|
-
|
11
6
|
# Create a new Ronn::Document for the given ronn file. See
|
12
7
|
# Ronn::Document.new for usage information.
|
13
8
|
def self.new(filename, attributes={}, &block)
|
14
9
|
Document.new(filename, attributes, &block)
|
15
10
|
end
|
11
|
+
|
12
|
+
# bring REV up to date with: rake rev
|
13
|
+
REV = '0.6.0'
|
14
|
+
VERSION = REV[/(?:[\d.]+)(?:-\d+)?/].tr('-', '.')
|
15
|
+
|
16
|
+
def self.release?
|
17
|
+
REV != '' && !REV.include?('-')
|
18
|
+
end
|
19
|
+
|
20
|
+
autoload :Document, 'ronn/document'
|
21
|
+
autoload :Roff, 'ronn/roff'
|
22
|
+
autoload :Server, 'ronn/server'
|
23
|
+
autoload :Template, 'ronn/template'
|
16
24
|
end
|
data/lib/ronn/document.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'cgi'
|
2
3
|
require 'hpricot'
|
3
4
|
require 'rdiscount'
|
4
5
|
require 'ronn/roff'
|
6
|
+
require 'ronn/template'
|
5
7
|
|
6
8
|
module Ronn
|
7
9
|
# The Document class can be used to load and inspect a ronn document
|
@@ -41,6 +43,9 @@ module Ronn
|
|
41
43
|
# the document footer.
|
42
44
|
attr_accessor :date
|
43
45
|
|
46
|
+
# Array of style modules to apply to the document.
|
47
|
+
attr_accessor :styles
|
48
|
+
|
44
49
|
# Create a Ronn::Document given a path or with the data returned by
|
45
50
|
# calling the block. The document is loaded and preprocessed before
|
46
51
|
# the intialize method returns. The attributes hash may contain values
|
@@ -53,6 +58,8 @@ module Ronn
|
|
53
58
|
@name, @section, @tagline = nil
|
54
59
|
@manual, @organization, @date = nil
|
55
60
|
@fragment = preprocess
|
61
|
+
@styles = %w[man]
|
62
|
+
|
56
63
|
attributes.each { |attr_name,value| send("#{attr_name}=", value) }
|
57
64
|
end
|
58
65
|
|
@@ -123,6 +130,21 @@ module Ronn
|
|
123
130
|
Time.now
|
124
131
|
end
|
125
132
|
|
133
|
+
# Retrieve a list of top-level section headings in the document and return
|
134
|
+
# as an array of +[id, text]+ tuples, where +id+ is the element's generated
|
135
|
+
# id and +text+ is the inner text of the heading element.
|
136
|
+
def section_heads
|
137
|
+
parse_html(to_html_fragment).search('h2[@id]').map do |heading|
|
138
|
+
[heading.attributes['id'], heading.inner_text]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Styles to insert in the generated HTML output. This is a simple Array of
|
143
|
+
# string module names or file paths.
|
144
|
+
def styles=(styles)
|
145
|
+
@styles = (%w[man] + styles).uniq
|
146
|
+
end
|
147
|
+
|
126
148
|
# Convert the document to :roff, :html, or :html_fragment and
|
127
149
|
# return the result as a string.
|
128
150
|
def convert(format)
|
@@ -132,7 +154,7 @@ module Ronn
|
|
132
154
|
# Convert the document to roff and return the result as a string.
|
133
155
|
def to_roff
|
134
156
|
RoffFilter.new(
|
135
|
-
to_html_fragment,
|
157
|
+
to_html_fragment(wrap_class=nil),
|
136
158
|
name,
|
137
159
|
section,
|
138
160
|
tagline,
|
@@ -144,42 +166,79 @@ module Ronn
|
|
144
166
|
|
145
167
|
# Convert the document to HTML and return the result as a string.
|
146
168
|
def to_html
|
147
|
-
|
169
|
+
if layout = ENV['RONN_LAYOUT']
|
170
|
+
if !File.exist?(layout_path = File.expand_path(layout))
|
171
|
+
warn "warn: can't find #{layout}, using default layout."
|
172
|
+
layout_path = nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
template = Ronn::Template.new(self)
|
177
|
+
template.render(layout_path || 'default')
|
148
178
|
end
|
149
179
|
|
150
180
|
# Convert the document to HTML and return the result
|
151
181
|
# as a string. The HTML does not include <html>, <head>,
|
152
182
|
# or <style> tags.
|
153
|
-
def to_html_fragment
|
183
|
+
def to_html_fragment(wrap_class='mp')
|
184
|
+
wrap_class = nil if wrap_class.to_s.empty?
|
154
185
|
buf = []
|
186
|
+
buf << "<div class='#{wrap_class}'>" if wrap_class
|
155
187
|
if name? && section?
|
156
188
|
buf << "<h2 id='NAME'>NAME</h2>"
|
157
|
-
buf << "<p><code>#{name}</code>
|
189
|
+
buf << "<p><code>#{name}</code> - #{tagline}</p>"
|
158
190
|
elsif tagline
|
159
|
-
buf << "<h1>#{[name, tagline].compact.join('
|
191
|
+
buf << "<h1>#{[name, tagline].compact.join(' - ')}</h1>"
|
160
192
|
end
|
161
193
|
buf << @fragment.to_s
|
194
|
+
buf << "</div>" if wrap_class
|
162
195
|
buf.join("\n")
|
163
196
|
end
|
164
197
|
|
165
198
|
protected
|
199
|
+
# The preprocessed markdown source text.
|
200
|
+
attr_reader :markdown
|
201
|
+
|
166
202
|
# Parse the document and extract the name, section, and tagline
|
167
203
|
# from its contents. This is called while the object is being
|
168
204
|
# initialized.
|
169
205
|
def preprocess
|
170
206
|
[
|
207
|
+
:heading_anchor_pre_filter,
|
171
208
|
:angle_quote_pre_filter,
|
172
209
|
:markdown_filter,
|
173
210
|
:angle_quote_post_filter,
|
174
|
-
:definition_list_filter
|
211
|
+
:definition_list_filter,
|
212
|
+
:heading_anchor_filter,
|
213
|
+
:annotate_bare_links_filter
|
175
214
|
].inject(data) { |res,filter| send(filter, res) }
|
176
215
|
end
|
177
216
|
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
217
|
+
# Add a 'data-bare-link' attribute to hyperlinks
|
218
|
+
# whose text labels are the same as their href URLs.
|
219
|
+
def annotate_bare_links_filter(html)
|
220
|
+
doc = parse_html(html)
|
221
|
+
doc.search('a[@href]').each do |node|
|
222
|
+
href = node.attributes['href']
|
223
|
+
text = node.inner_text
|
224
|
+
|
225
|
+
if href == text ||
|
226
|
+
href[0] == ?# ||
|
227
|
+
CGI.unescapeHTML(href) == "mailto:#{CGI.unescapeHTML(text)}"
|
228
|
+
then
|
229
|
+
node.set_attribute('data-bare-link', 'true')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
doc
|
233
|
+
end
|
234
|
+
|
235
|
+
# Add URL anchors to all HTML heading elements.
|
236
|
+
def heading_anchor_filter(html)
|
237
|
+
doc = parse_html(html)
|
238
|
+
doc.search('h1|h2|h3|h4|h5|h6').not('[@id]').each do |heading|
|
239
|
+
heading.set_attribute('id', heading.inner_text.gsub(/\W+/, '-'))
|
240
|
+
end
|
241
|
+
doc
|
183
242
|
end
|
184
243
|
|
185
244
|
# Convert special format unordered lists to definition lists.
|
@@ -230,6 +289,7 @@ module Ronn
|
|
230
289
|
# Run markdown on the data and extract name, section, and
|
231
290
|
# tagline.
|
232
291
|
def markdown_filter(data)
|
292
|
+
@markdown = data
|
233
293
|
html = Markdown.new(data).to_html
|
234
294
|
@tagline, html = html.split("</h1>\n", 2)
|
235
295
|
if html.nil?
|
@@ -238,11 +298,11 @@ module Ronn
|
|
238
298
|
else
|
239
299
|
# grab name and section from title
|
240
300
|
@tagline.sub!('<h1>', '')
|
241
|
-
if @tagline =~ /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s
|
301
|
+
if @tagline =~ /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*-+\s*(.*)/
|
242
302
|
@name = $1
|
243
303
|
@section = $2
|
244
304
|
@tagline = $3
|
245
|
-
elsif @tagline =~ /([\w_.\[\]~+=@:-]+)\s
|
305
|
+
elsif @tagline =~ /([\w_.\[\]~+=@:-]+)\s+-+\s+(.*)/
|
246
306
|
@name = $1
|
247
307
|
@tagline = $2
|
248
308
|
end
|
@@ -267,6 +327,20 @@ module Ronn
|
|
267
327
|
end
|
268
328
|
end
|
269
329
|
|
330
|
+
# Add [id]: #ANCHOR elements to the markdown source text for all sections.
|
331
|
+
# This lets us use the [SECTION-REF][] syntax
|
332
|
+
def heading_anchor_pre_filter(data)
|
333
|
+
first = true
|
334
|
+
data.split("\n").grep(/^[#]{2,5} +[\w '-]+[# ]*$/).each do |line|
|
335
|
+
data << "\n\n" if first
|
336
|
+
first = false
|
337
|
+
title = line.gsub(/[^\w -]/, '').strip
|
338
|
+
anchor = title.gsub(/\W+/, '-').gsub(/(^-+|-+$)/, '')
|
339
|
+
data << "[#{title}]: ##{anchor} \"#{title}\"\n"
|
340
|
+
end
|
341
|
+
data
|
342
|
+
end
|
343
|
+
|
270
344
|
HTML = %w[
|
271
345
|
a abbr acronym b bdo big br cite code dfn
|
272
346
|
em i img input kbd label q samp select
|