ronn 0.4

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (C) 2009 Ryan Tomayko <tomayko.com/about>
2
+
3
+ Permission is hereby granted, free of charge, to any person ob-
4
+ taining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without restric-
6
+ tion, including without limitation the rights to use, copy, modi-
7
+ fy, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is fur-
9
+ nished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
16
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
17
+ FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,135 @@
1
+ ronn -- the opposite of roff
2
+ ===========================
3
+
4
+ ## DESCRIPTION
5
+
6
+ Ronn is a humane text format and toolchain for creating UNIX man
7
+ pages, and things that appear as man pages from a distance. Use it
8
+ to build and install standard UNIX roff man pages or to generate
9
+ nicely formatted HTML manual pages for the web.
10
+
11
+ The Ronn file format is based on Markdown. In fact, Ronn files are a
12
+ compatible subset of Markdown syntax but have a more rigid structure and
13
+ extend Markdown in some ways to provide features commonly found in man
14
+ pages (e.g., definition lists). The ronn(5) manual page defines the
15
+ format in more detail.
16
+
17
+ ## DOCUMENTATION
18
+
19
+ The `.ronn` files located under the `man/` directory show off a wide
20
+ range of ronn capabilities and are the source of Ronn's own documentation.
21
+ The source files and generated HTML / roff output files are available
22
+ at:
23
+
24
+ * [ronn(1)](http://rtomayko.github.com/ronn/ronn.1.html) -
25
+ build markdown based manual pages at the command line.
26
+ [source file](http://github.com/rtomayko/ronn/blob/master/man/ronn.1.ronn),
27
+ [roff output](http://github.com/rtomayko/ronn/blob/master/man/ronn.1)
28
+
29
+ * [ronn(5)](http://rtomayko.github.com/ronn/ronn.5.html) -
30
+ humane manual page authoring format syntax reference.
31
+ [source file](http://github.com/rtomayko/ronn/blob/master/man/ronn.5.ronn),
32
+ [roff output](http://github.com/rtomayko/ronn/blob/master/man/ronn.5)
33
+
34
+ * [markdown(5)](http://rtomayko.github.com/ronn/markdown.5.html) -
35
+ humane text markup syntax (taken from
36
+ [Markdown Syntax](http://daringfireball.net/projects/markdown/syntax),
37
+ John Gruber)
38
+ [source file](http://github.com/rtomayko/ronn/blob/master/man/markdown.5.ronn),
39
+ [roff output](http://github.com/rtomayko/ronn/blob/master/man/markdown.5)
40
+
41
+ ## INSTALL
42
+
43
+ Install with Rubygems:
44
+
45
+ $ [sudo] gem install ronn
46
+ $ ronn --help
47
+
48
+ Or, clone the git repository:
49
+
50
+ $ git clone git://github.com/rtomayko/ronn.git
51
+ $ PATH=ronn/bin:$PATH
52
+ $ ronn --help
53
+
54
+ ## BASIC USAGE
55
+
56
+ To generate a roff man page from the included
57
+ [`markdown.5.ronn`](man/markdown.5.ronn) file and open it with man(1):
58
+
59
+ $ ronn -b man/markdown.5.ronn
60
+ building: man/markdown.5
61
+ $ man man/markdown.5
62
+
63
+ To generate a standalone HTML version:
64
+
65
+ $ ronn -b --html man/markdown.5.ronn
66
+ building: man/markdown.5.html
67
+ $ open man/markdown.5.html
68
+
69
+ To build roff and HTML versions of all ronn files:
70
+
71
+ $ ronn -b --roff --html man/*.ronn
72
+
73
+ If you just want to view a ronn file as if it were a man page without
74
+ building intermediate files:
75
+
76
+ $ ronn -m man/markdown.5.ronn
77
+
78
+ The [ronn(1)](http://rtomayko.github.com/ronn/ronn.1.html) manual page
79
+ includes comprehensive documentation on `ronn` command line options.
80
+
81
+ ## ABOUT
82
+
83
+ Some people think UNIX manual pages are a poor and outdated style of
84
+ documentation. I disagree:
85
+
86
+ - Man pages follow a well defined structure that's immediately
87
+ familiar and provides a useful starting point for developers
88
+ documenting new tools, libraries, and formats.
89
+
90
+ - Man pages get to the point. Because they're written in an inverted
91
+ style, with a SYNOPSIS section followed by additional detail,
92
+ prose and references to other sources of information, man pages
93
+ provide the best of both cheat sheet and reference style
94
+ documentation.
95
+
96
+ - Man pages have extremely -- unbelievably -- limited text
97
+ formatting capabilities. You get a couple of headings, lists, bold,
98
+ underline and no more. This is a feature.
99
+
100
+ - Although two levels of section hierarchy are technically
101
+ supported, most man pages use only a single level. Unwieldy
102
+ document hierarchies complicate otherwise good documentation.
103
+ Feynman covered all of physics -- heavenly bodies through QED --
104
+ with only two levels of document hierarchy (_The Feynman Lectures
105
+ on Physics_, 1970).
106
+
107
+ - Man pages have a simple referencing syntax; e.g., sh(1), fork(2),
108
+ markdown(5). HTML versions can use this to generate links between
109
+ pages.
110
+
111
+ - The classical terminal man page display is typographically well
112
+ thought out. Big bold section headings, justified monospace text,
113
+ nicely indented paragraphs, intelligently aligned definition
114
+ lists, and an informational header and footer.
115
+
116
+ Unfortunately, trying to figure out how to create a man page is a
117
+ fairly tedious process. The roff/man macro languages are highly
118
+ extensible, fractured between multiple dialects, and include a bunch
119
+ of device specific stuff that's entirely irrelevant to modern
120
+ publishing tools.
121
+
122
+ Ronn aims to address many of the issues with man page creation while
123
+ preserving the things that makes man pages a great form of
124
+ documentation.
125
+
126
+ ## COPYING
127
+
128
+ Ronn is Copyright (C) 2009 [Ryan Tomayko](http://tomayko.com/about)
129
+ See the file COPYING for information of licensing and distribution.
130
+
131
+ ## SEE ALSO
132
+
133
+ [ronn(1)](http://rtomayko.github.com/ronn/ronn.1.html),
134
+ [ronn(5)](http://rtomayko.github.com/ronn/ronn.5.html),
135
+ [markdown(5)](http://rtomayko.github.com/ronn/markdown.5.html)
@@ -0,0 +1,114 @@
1
+ require 'rake/clean'
2
+
3
+ task :default => :test
4
+
5
+ ROOTDIR = File.expand_path('..', __FILE__).sub(/#{Dir.pwd}(?=\/)/, '.')
6
+ LIBDIR = "#{ROOTDIR}/lib"
7
+ BINDIR = "#{ROOTDIR}/bin"
8
+
9
+ task :environment do
10
+ $LOAD_PATH.unshift ROOTDIR if !$:.include?(ROOTDIR)
11
+ $LOAD_PATH.unshift LIBDIR if !$:.include?(LIBDIR)
12
+ require_library 'hpricot'
13
+ require_library 'rdiscount'
14
+ ENV['RUBYLIB'] = $LOAD_PATH.join(':')
15
+ ENV['PATH'] = "#{BINDIR}:#{ENV['PATH']}"
16
+ end
17
+
18
+ desc 'Run tests'
19
+ task :test => :environment do
20
+ require_library 'contest'
21
+ Dir['test/*_test.rb'].each { |test| require test }
22
+ end
23
+
24
+ desc 'Build the manual'
25
+ task :man => :environment do
26
+ sh "ronn -br5 --manual='Ronn Manual' --organization='Ryan Tomayko' man/*.ronn"
27
+ end
28
+
29
+ # PACKAGING ============================================================
30
+
31
+ if defined?(Gem)
32
+ $spec = eval(File.read('ronn.gemspec'))
33
+
34
+ def package(ext='')
35
+ "pkg/ronn-#{$spec.version}" + ext
36
+ end
37
+
38
+ desc 'Build packages'
39
+ task :package => %w[.gem .tar.gz].map { |ext| package(ext) }
40
+
41
+ desc 'Build and install as local gem'
42
+ task :install => package('.gem') do
43
+ sh "gem install #{package('.gem')}"
44
+ end
45
+
46
+ directory 'pkg/'
47
+ CLOBBER.include('pkg')
48
+
49
+ file package('.gem') => %w[pkg/ ronn.gemspec] + $spec.files do |f|
50
+ sh "gem build ronn.gemspec"
51
+ mv File.basename(f.name), f.name
52
+ end
53
+
54
+ file 'pkg/ron-0.4.gem' => %w[pkg/ ron.gemspec] do |f|
55
+ sh "gem build ron.gemspec"
56
+ mv 'ron-0.4.gem', f.name
57
+ end
58
+ task :package => 'pkg/ron-0.4.gem'
59
+
60
+ file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
61
+ sh <<-SH
62
+ git archive --prefix=ronn-#{source_version}/ --format=tar HEAD |
63
+ gzip > #{f.name}
64
+ SH
65
+ end
66
+ end
67
+
68
+ def source_version
69
+ line = File.read('lib/ronn.rb')[/^\s*VERSION = .*/]
70
+ line.match(/.*VERSION = '(.*)'/)[1]
71
+ end
72
+
73
+ file 'ronn.gemspec' => FileList['{lib,test,bin}/**','Rakefile'] do |f|
74
+ # read spec file and split out manifest section
75
+ spec = File.read(f.name)
76
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
77
+ # replace version and date
78
+ head.sub!(/\.version = '.*'/, ".version = '#{source_version}'")
79
+ head.sub!(/\.date = '.*'/, ".date = '#{Date.today.to_s}'")
80
+ # determine file list from git ls-files
81
+ files = `git ls-files`.
82
+ split("\n").
83
+ sort.
84
+ reject{ |file| file =~ /^\./ }.
85
+ reject { |file| file =~ /^doc/ }.
86
+ map{ |file| " #{file}" }.
87
+ join("\n")
88
+ # piece file back together and write...
89
+ manifest = " s.files = %w[\n#{files}\n ]\n"
90
+ spec = [head,manifest,tail].join(" # = MANIFEST =\n")
91
+ File.open(f.name, 'w') { |io| io.write(spec) }
92
+ puts "updated #{f.name}"
93
+ end
94
+
95
+ # Misc ===============================================================
96
+
97
+ def require_library(name)
98
+ require name
99
+ rescue LoadError => boom
100
+ if !defined?(Gem)
101
+ warn "warn: #{boom}. trying again with rubygems."
102
+ require 'rubygems'
103
+ retry
104
+ end
105
+ abort "fatal: the '#{name}' library is required (gem install #{name})"
106
+ end
107
+
108
+ # make .wrong test files right
109
+ task :right do
110
+ Dir['test/*.wrong'].each do |file|
111
+ dest = file.sub(/\.wrong$/, '')
112
+ mv file, dest
113
+ end
114
+ end
data/bin/ron ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ exec File.dirname(__FILE__) + '/ronn', *ARGV
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+ ## Usage: ronn [ OPTIONS ] [ FILE ]
3
+ ## ronn --build FILE ...
4
+ ## ronn --install FILE ...
5
+ ## ronn --man FILE ...
6
+ ## Convert ronn FILE to roff man page or HTML and write to standard
7
+ ## output. With no FILE, ronn 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.readlines(__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 'hpricot'
91
+ require 'rdiscount'
92
+ rescue LoadError
93
+ if !defined?(Gem)
94
+ warn "warn: #{$!.to_s}. trying again with rubygems."
95
+ require 'rubygems'
96
+ retry
97
+ end
98
+ end
99
+
100
+ begin
101
+ require 'ronn'
102
+ rescue LoadError
103
+ raise if $!.to_s !~ /ronn/
104
+ libdir = File.expand_path("../../lib", __FILE__).sub(/^#{Dir.pwd}/, '.')
105
+ if !$:.include?(libdir)
106
+ warn "warn: #{$!.to_s}. trying again with #{libdir} on load path."
107
+ $:.unshift libdir
108
+ retry
109
+ end
110
+ raise
111
+ end
112
+
113
+ wr = STDOUT
114
+ ARGV.each do |file|
115
+ doc = Ronn.new(file, options) { file == '-' ? STDIN.read : File.read(file) }
116
+
117
+ # setup the man pipeline if the --man option was specified
118
+ if man && !build
119
+ rd, wr = IO.pipe
120
+ if pid = fork
121
+ rd.close
122
+ else
123
+ wr.close
124
+ STDIN.reopen rd
125
+ exec "#{groff} | #{pager}"
126
+ end
127
+ end
128
+
129
+ # write output for each format
130
+ formats.each do |format|
131
+ if build
132
+ path = doc.path_for(format)
133
+ info "building: #{path}" if build
134
+ output = doc.convert(format)
135
+ File.open(path, 'wb') { |f| f.puts(output) }
136
+ system "man #{path}" if man && format == 'roff'
137
+ else
138
+ output = doc.convert(format)
139
+ wr.puts(output)
140
+ end
141
+ end
142
+
143
+ if pid
144
+ wr.close
145
+ Process.wait
146
+ end
147
+ end
@@ -0,0 +1,16 @@
1
+ # Ronn 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 Ronn
6
+ VERSION = '0.4'
7
+
8
+ require 'ronn/document'
9
+ require 'ronn/roff'
10
+
11
+ # Create a new Ronn::Document for the given ronn file. See
12
+ # Ronn::Document.new for usage information.
13
+ def self.new(filename, attributes={}, &block)
14
+ Document.new(filename, attributes, &block)
15
+ end
16
+ end
@@ -0,0 +1,288 @@
1
+ require 'set'
2
+ require 'hpricot'
3
+ require 'rdiscount'
4
+ require 'ronn/roff'
5
+
6
+ module Ronn
7
+ # The Document class can be used to load and inspect a ronn document
8
+ # and to convert a ronn document into other formats, like roff or
9
+ # HTML.
10
+ #
11
+ # Ronn files may optionally follow the naming convention:
12
+ # "<name>.<section>.ronn". 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 Ronn::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.inner_text.split("\n", 2).first !~ /:$/ }
192
+
193
+ ul.name = 'dl'
194
+ items.each do |item|
195
+ if child = item.at('p')
196
+ wrap = '<p></p>'
197
+ container = 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>").first
205
+ dt.attributes['class'] = 'flush' if dt.inner_text.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').search('text()').each do |node|
220
+ next unless node.to_html.include?('var&gt;')
221
+ new =
222
+ node.to_html.
223
+ gsub('&lt;var&gt;', '&lt;').
224
+ gsub("&lt;/var&gt;", '>')
225
+ node.swap(new)
226
+ end
227
+ doc
228
+ end
229
+
230
+ # Run markdown on the data and extract name, section, and
231
+ # tagline.
232
+ def markdown_filter(data)
233
+ html = Markdown.new(data).to_html
234
+ @tagline, html = html.split("</h1>\n", 2)
235
+ if html.nil?
236
+ html = @tagline
237
+ @tagline = nil
238
+ else
239
+ # grab name and section from title
240
+ @tagline.sub!('<h1>', '')
241
+ if @tagline =~ /([\w_.\[\]~+=@:-]+)\s*\((\d\w*)\)\s*--?\s*(.*)/
242
+ @name = $1
243
+ @section = $2
244
+ @tagline = $3
245
+ elsif @tagline =~ /([\w_.\[\]~+=@:-]+)\s+--\s+(.*)/
246
+ @name = $1
247
+ @tagline = $2
248
+ end
249
+ end
250
+
251
+ html.to_s
252
+ end
253
+
254
+ # Convert all <WORD> to <var>WORD</var> but only if WORD
255
+ # isn't an HTML tag.
256
+ def angle_quote_pre_filter(data)
257
+ data.gsub(/\<([^:.\/]+?)\>/) do |match|
258
+ contents = $1
259
+ tag, attrs = contents.split(' ', 2)
260
+ if attrs =~ /\/=/ ||
261
+ HTML.include?(tag.sub(/^\//, '')) ||
262
+ data.include?("</#{tag}>")
263
+ match.to_s
264
+ else
265
+ "<var>#{contents}</var>"
266
+ end
267
+ end
268
+ end
269
+
270
+ HTML = %w[
271
+ a abbr acronym b bdo big br cite code dfn
272
+ em i img input kbd label q samp select
273
+ small span strong sub sup textarea tt var
274
+ address blockquote div dl fieldset form
275
+ h1 h2 h3 h4 h5 h6 hr noscript ol p pre
276
+ table ul
277
+ ].to_set
278
+
279
+ private
280
+ def parse_html(html)
281
+ if html.respond_to?(:doc?) && html.doc?
282
+ html
283
+ else
284
+ Hpricot(html.to_s)
285
+ end
286
+ end
287
+ end
288
+ end