ronn 0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|