ronn 0.4

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/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