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 +21 -0
- data/README.md +135 -0
- data/Rakefile +114 -0
- data/bin/ron +2 -0
- data/bin/ronn +147 -0
- data/lib/ronn.rb +16 -0
- data/lib/ronn/document.rb +288 -0
- data/lib/ronn/layout.html +75 -0
- data/lib/ronn/roff.rb +197 -0
- data/man/markdown.5 +1513 -0
- data/man/markdown.5.ronn +881 -0
- data/man/ronn.1 +220 -0
- data/man/ronn.1.ronn +158 -0
- data/man/ronn.5 +190 -0
- data/man/ronn.5.ronn +152 -0
- data/man/ronn.7 +195 -0
- data/man/ronn.7.ronn +135 -0
- data/ronn.gemspec +69 -0
- data/test/angle_bracket_syntax.html +13 -0
- data/test/angle_bracket_syntax.ronn +12 -0
- data/test/basic_document.html +4 -0
- data/test/basic_document.ronn +4 -0
- data/test/custom_title_document.html +4 -0
- data/test/custom_title_document.ronn +5 -0
- data/test/definition_list_syntax.html +16 -0
- data/test/definition_list_syntax.ronn +18 -0
- data/test/document_test.rb +88 -0
- data/test/entity_encoding_test.html +10 -0
- data/test/entity_encoding_test.roff +19 -0
- data/test/entity_encoding_test.ronn +8 -0
- data/test/middle_paragraph.html +10 -0
- data/test/middle_paragraph.roff +13 -0
- data/test/middle_paragraph.ronn +10 -0
- data/test/ronn_test.rb +78 -0
- data/test/titleless_document.html +2 -0
- data/test/titleless_document.ronn +2 -0
- metadata +141 -0
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.
|
data/README.md
ADDED
@@ -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)
|
data/Rakefile
ADDED
@@ -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
data/bin/ronn
ADDED
@@ -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
|
data/lib/ronn.rb
ADDED
@@ -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>')
|
221
|
+
new =
|
222
|
+
node.to_html.
|
223
|
+
gsub('<var>', '<').
|
224
|
+
gsub("</var>", '>')
|
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
|