ronn 0.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|