ron 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +21 -0
- data/README +145 -0
- data/Rakefile +73 -0
- data/bin/ron +94 -0
- data/lib/ron.rb +15 -0
- data/lib/ron/document.rb +117 -0
- data/lib/ron/layout.html +39 -0
- data/lib/ron/roff.rb +147 -0
- data/man/markdown.5.ron +881 -0
- data/man/ron.1.ron +136 -0
- data/man/ron.5.ron +150 -0
- data/ron.gemspec +48 -0
- data/test/document_test.rb +35 -0
- data/test/ron_test.rb +29 -0
- data/test/simple.ron +2 -0
- metadata +103 -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
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
ron -- the opposite of roff
|
2
|
+
|
3
|
+
Ron is a humane text format and toolchain for
|
4
|
+
creating UNIX man pages -- and things that
|
5
|
+
appear as man pages from a distance. Use it
|
6
|
+
to build and install standard UNIX roff man
|
7
|
+
pages or to generate nicely formatted HTML
|
8
|
+
manual pages for the web.
|
9
|
+
|
10
|
+
The ron file format is based on Markdown. In
|
11
|
+
fact, ron files are 100% Markdown compatible
|
12
|
+
but have a more rigidly defined structure and
|
13
|
+
extend Markdown in some ways to provide
|
14
|
+
features commonly found in man pages (e.g.,
|
15
|
+
definition lists). The ron(5) manual page
|
16
|
+
included with this distribution defines the
|
17
|
+
format in more detail.
|
18
|
+
|
19
|
+
INSTALL
|
20
|
+
-------
|
21
|
+
|
22
|
+
The easiest way to install ron is with
|
23
|
+
rubygems:
|
24
|
+
|
25
|
+
$ [sudo] gem install ron -s gems.gemcutter.org
|
26
|
+
|
27
|
+
Or, clone the git repository and install from
|
28
|
+
source:
|
29
|
+
|
30
|
+
$ git clone git://github.com/rtomayko/ron.git
|
31
|
+
$ cd ron
|
32
|
+
$ rake install
|
33
|
+
|
34
|
+
EXAMPLES
|
35
|
+
--------
|
36
|
+
|
37
|
+
The .ron files located under the repository's
|
38
|
+
./man directory show off a wide range of ron
|
39
|
+
capabilities. The HTML versions of these
|
40
|
+
files are available at:
|
41
|
+
|
42
|
+
http://rtomayko.github.com/ron/ron.1.html
|
43
|
+
http://rtomayko.github.com/ron/ron.5.html
|
44
|
+
http://rtomayko.github.com/ron/markdown.5.html
|
45
|
+
|
46
|
+
BASIC USAGE
|
47
|
+
-----------
|
48
|
+
|
49
|
+
To generate a roff man page from the included
|
50
|
+
markdown.5.ron file and open it in man(1):
|
51
|
+
|
52
|
+
$ ron -b man/markdown.5.ron
|
53
|
+
building: man/markdown.5
|
54
|
+
$ man man/markdown.5
|
55
|
+
|
56
|
+
To generate a standalone HTML version:
|
57
|
+
|
58
|
+
$ ron -b --html man/markdown.5.ron
|
59
|
+
building: man/markdown.5.html
|
60
|
+
$ open man/markdown.5.html
|
61
|
+
|
62
|
+
To build roff and HTML versions of all ron
|
63
|
+
files:
|
64
|
+
|
65
|
+
$ ron -b --roff --html man/*.ron
|
66
|
+
|
67
|
+
If you just want to view a ron file as if it
|
68
|
+
were a man page without building any
|
69
|
+
intermediate files:
|
70
|
+
|
71
|
+
$ ron -m man/markdown.5.ron
|
72
|
+
|
73
|
+
The ron(1) manual page included with this
|
74
|
+
distribution includes full documentation on
|
75
|
+
ron command line options.
|
76
|
+
|
77
|
+
RATIONALE
|
78
|
+
---------
|
79
|
+
|
80
|
+
Some people think UNIX manual pages are a
|
81
|
+
poor and outdated form of documentation. I
|
82
|
+
disagree.
|
83
|
+
|
84
|
+
- Man pages typically follow a well defined
|
85
|
+
structure that's immediately familiar and
|
86
|
+
provides a useful starting point for
|
87
|
+
developers documenting new tools,
|
88
|
+
libraries, and formats.
|
89
|
+
|
90
|
+
- Man pages get to the point. Because they're
|
91
|
+
written in an inverted style, with a
|
92
|
+
SYNOPSIS section followed by additional
|
93
|
+
detail, prose, and finally references to
|
94
|
+
other sources of information, man pages
|
95
|
+
provide the best of both cheat sheet and
|
96
|
+
reference style documentation.
|
97
|
+
|
98
|
+
- Man pages have very limited text formatting
|
99
|
+
capabilities. This is a feature. You get
|
100
|
+
bold and underline, basically, and they're
|
101
|
+
typically applied consistently across man
|
102
|
+
pages.
|
103
|
+
|
104
|
+
- Most man pages use only a single level of
|
105
|
+
section hierarchy (although two levels are
|
106
|
+
technically supported). Hierarchy destroys
|
107
|
+
otherwise good documentation by adding
|
108
|
+
unnecessary complexity. Feynman described
|
109
|
+
the whole of quantum electro dynamics with
|
110
|
+
only two levels of hierarchy. How can you
|
111
|
+
possibly need more? Man pages force you to
|
112
|
+
keep it simple.
|
113
|
+
|
114
|
+
- Man pages have a simple referencing syntax;
|
115
|
+
e.g., sh(1), fork(2), markdown(5). HTML
|
116
|
+
versions can use this to generate links
|
117
|
+
between pages.
|
118
|
+
|
119
|
+
- The classical terminal man page display is
|
120
|
+
typographically well thought out. Big bold
|
121
|
+
section headings, justified monospaced
|
122
|
+
text, nicely indented paragraphs,
|
123
|
+
intelligently aligned definition lists, and
|
124
|
+
an informational header and footer.
|
125
|
+
|
126
|
+
All that being said, trying to figure out how
|
127
|
+
to create a man page can be a really tedious
|
128
|
+
process. The roff/man macro languages are
|
129
|
+
highly extensible, fractured between multiple
|
130
|
+
dialects, and include a bunch of stuff that's
|
131
|
+
entirely irrelevant to modern man page
|
132
|
+
creation. It's also horribly ugly compared to
|
133
|
+
today's humane text formats or even HTML
|
134
|
+
(just sayin').
|
135
|
+
|
136
|
+
Ron aims to address many of the issues with
|
137
|
+
man page creation while preserving the things
|
138
|
+
that makes man pages a great form of
|
139
|
+
documentation.
|
140
|
+
|
141
|
+
COPYRIGHT
|
142
|
+
---------
|
143
|
+
|
144
|
+
Ron is Copyright (C) 2009 Ryan Tomayko
|
145
|
+
See the file COPYING for more information.
|
data/Rakefile
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
task :spec => :test
|
6
|
+
|
7
|
+
# SPECS ===============================================================
|
8
|
+
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.test_files = FileList['test/*_test.rb']
|
11
|
+
t.ruby_opts = ['-rubygems'] if defined? Gem
|
12
|
+
end
|
13
|
+
|
14
|
+
# PACKAGING ============================================================
|
15
|
+
|
16
|
+
require 'rubygems/specification'
|
17
|
+
$spec = eval(File.read('ron.gemspec'))
|
18
|
+
|
19
|
+
def package(ext='')
|
20
|
+
"pkg/ron-#{$spec.version}" + ext
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Build packages'
|
24
|
+
task :package => %w[.gem .tar.gz].map { |ext| package(ext) }
|
25
|
+
|
26
|
+
desc 'Build and install as local gem'
|
27
|
+
task :install => package('.gem') do
|
28
|
+
sh "gem install #{package('.gem')}"
|
29
|
+
end
|
30
|
+
|
31
|
+
directory 'pkg/'
|
32
|
+
CLOBBER.include('pkg')
|
33
|
+
|
34
|
+
file package('.gem') => %w[pkg/ ron.gemspec] + $spec.files do |f|
|
35
|
+
sh "gem build ron.gemspec"
|
36
|
+
mv File.basename(f.name), f.name
|
37
|
+
end
|
38
|
+
|
39
|
+
file package('.tar.gz') => %w[pkg/] + $spec.files do |f|
|
40
|
+
sh <<-SH
|
41
|
+
git archive --prefix=ron-#{source_version}/ --format=tar HEAD |
|
42
|
+
gzip > #{f.name}
|
43
|
+
SH
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gemspec Helpers ====================================================
|
47
|
+
|
48
|
+
def source_version
|
49
|
+
line = File.read('lib/ron.rb')[/^\s*VERSION = .*/]
|
50
|
+
line.match(/.*VERSION = '(.*)'/)[1]
|
51
|
+
end
|
52
|
+
|
53
|
+
file 'ron.gemspec' => FileList['{lib,test}/**','Rakefile'] do |f|
|
54
|
+
# read spec file and split out manifest section
|
55
|
+
spec = File.read(f.name)
|
56
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
57
|
+
# replace version and date
|
58
|
+
head.sub!(/\.version = '.*'/, ".version = '#{source_version}'")
|
59
|
+
head.sub!(/\.date = '.*'/, ".date = '#{Date.today.to_s}'")
|
60
|
+
# determine file list from git ls-files
|
61
|
+
files = `git ls-files`.
|
62
|
+
split("\n").
|
63
|
+
sort.
|
64
|
+
reject{ |file| file =~ /^\./ }.
|
65
|
+
reject { |file| file =~ /^doc/ }.
|
66
|
+
map{ |file| " #{file}" }.
|
67
|
+
join("\n")
|
68
|
+
# piece file back together and write...
|
69
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
70
|
+
spec = [head,manifest,tail].join(" # = MANIFEST =\n")
|
71
|
+
File.open(f.name, 'w') { |io| io.write(spec) }
|
72
|
+
puts "updated #{f.name}"
|
73
|
+
end
|
data/bin/ron
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
## Usage: ron [ OPTIONS ] [ FILE ... ]
|
3
|
+
## ron --build FILE ...
|
4
|
+
## ron --install FILE ...
|
5
|
+
## ron --man FILE
|
6
|
+
## Convert ron file to roff or html.
|
7
|
+
##
|
8
|
+
## Options
|
9
|
+
## -b, --build write output to files instead of to stdout
|
10
|
+
## -i, --install write manpage to MAN_HOME or system man path
|
11
|
+
## -m, --man show man page like man(1)
|
12
|
+
##
|
13
|
+
## --roff generate roff/man text; this is the default behavior
|
14
|
+
## -5, --html generate HTML
|
15
|
+
##
|
16
|
+
## --help show this help message
|
17
|
+
##
|
18
|
+
## See the ron(2)
|
19
|
+
require 'optparse'
|
20
|
+
|
21
|
+
formats = []
|
22
|
+
build = false
|
23
|
+
install = false
|
24
|
+
man = false
|
25
|
+
groff = "groff -Wall -mtty-char -mandoc -Tascii"
|
26
|
+
pager = ENV['MANPAGER'] || ENV['PAGER'] || 'more'
|
27
|
+
|
28
|
+
def info(message, *args)
|
29
|
+
STDERR.puts message % args
|
30
|
+
end
|
31
|
+
|
32
|
+
def usage
|
33
|
+
puts File.read(__FILE__).
|
34
|
+
grep(/^##.*/).
|
35
|
+
map { |line| line.chomp[3..-1] }.
|
36
|
+
join("\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
# parse command line options
|
40
|
+
ARGV.options do |option|
|
41
|
+
option.on("--roff") { formats << 'roff' }
|
42
|
+
option.on("-5", "--html") { formats << 'html' }
|
43
|
+
option.on("-b", "--build") { build = true }
|
44
|
+
option.on("-i", "--install") { install = true }
|
45
|
+
option.on("-m", "--man") { man = true }
|
46
|
+
|
47
|
+
option.on_tail("--help") { usage ; exit }
|
48
|
+
option.parse!
|
49
|
+
end
|
50
|
+
|
51
|
+
if ARGV.empty? && STDIN.tty?
|
52
|
+
usage
|
53
|
+
exit
|
54
|
+
elsif ARGV.empty?
|
55
|
+
ARGV.push '-'
|
56
|
+
end
|
57
|
+
|
58
|
+
formats = ['roff'] if formats.empty?
|
59
|
+
pid = nil
|
60
|
+
|
61
|
+
require 'ron'
|
62
|
+
wr = STDOUT
|
63
|
+
ARGV.each do |file|
|
64
|
+
doc = Ron.new(file) { file == '-' ? STDIN.read : File.read(file) }
|
65
|
+
|
66
|
+
# setup the man pipeline if the --man option was specified
|
67
|
+
if man && !build
|
68
|
+
rd, wr = IO.pipe
|
69
|
+
if pid = fork
|
70
|
+
rd.close
|
71
|
+
else
|
72
|
+
wr.close
|
73
|
+
STDIN.reopen rd
|
74
|
+
exec "#{groff} | #{pager}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# write output for each format
|
79
|
+
formats.each do |format|
|
80
|
+
output = doc.convert(format)
|
81
|
+
if build
|
82
|
+
path = doc.path(format)
|
83
|
+
info "building: #{path}"
|
84
|
+
File.open(path, 'wb') { |f| f.write(output) }
|
85
|
+
else
|
86
|
+
wr.write(output)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if pid
|
91
|
+
wr.close
|
92
|
+
Process.wait
|
93
|
+
end
|
94
|
+
end
|
data/lib/ron.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Ron 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 Ron
|
6
|
+
VERSION = '0.1'
|
7
|
+
|
8
|
+
require 'ron/document'
|
9
|
+
require 'ron/roff'
|
10
|
+
|
11
|
+
# Create a new Ron::Document for the given ron file.
|
12
|
+
def self.new(filename, &block)
|
13
|
+
Document.new(filename, &block)
|
14
|
+
end
|
15
|
+
end
|
data/lib/ron/document.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'rdiscount'
|
3
|
+
require 'ron/roff'
|
4
|
+
|
5
|
+
module Ron
|
6
|
+
class Document
|
7
|
+
VERSION = '0.1'
|
8
|
+
attr_reader :filename, :data, :basename, :name, :section, :tagline
|
9
|
+
|
10
|
+
def initialize(filename, &block)
|
11
|
+
@filename = filename
|
12
|
+
@reader = block || Proc.new { |f| File.read(f) }
|
13
|
+
@data = @reader.call(filename)
|
14
|
+
|
15
|
+
@basename = File.basename(filename)
|
16
|
+
@name, @section =
|
17
|
+
if @basename =~ /(\w+)\.(\d\w*)\.ron/
|
18
|
+
[$1, $2]
|
19
|
+
else
|
20
|
+
[@basename[/\w+/], nil]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Construct a path to a file near the input file.
|
25
|
+
def path(extension=nil)
|
26
|
+
extension = nil if ['', 'roff'].include?(extension.to_s)
|
27
|
+
name = "#{@name}.#{section}"
|
28
|
+
name = "#{name}.#{extension}" if extension
|
29
|
+
File.join(File.dirname(filename), name)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Convert the document to :roff or :html
|
33
|
+
def convert(format)
|
34
|
+
send "to_#{format}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert the document to roff.
|
38
|
+
def to_roff
|
39
|
+
RoffFilter.new(
|
40
|
+
to_html_fragment,
|
41
|
+
name,
|
42
|
+
section,
|
43
|
+
tagline
|
44
|
+
).to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
# Convert the document to HTML and return result
|
48
|
+
# as a string.
|
49
|
+
def to_html
|
50
|
+
layout_filter(to_html_fragment)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Convert the document to HTML and return result
|
54
|
+
# as a string. The HTML does not include <html>, <head>,
|
55
|
+
# or <style> tags.
|
56
|
+
def to_html_fragment
|
57
|
+
definition_list_filter(markdown_filter(data))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Apply the standard HTML layout template.
|
61
|
+
def layout_filter(html)
|
62
|
+
template_file = File.dirname(__FILE__) + "/layout.html"
|
63
|
+
template = File.read(template_file)
|
64
|
+
eval("%Q{#{template}}", binding, template_file)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Run markdown on the data and extract name, section, and
|
68
|
+
# tagline.
|
69
|
+
def markdown_filter(data)
|
70
|
+
html = Markdown.new(data).to_html
|
71
|
+
@tagline, html = html.split("</h1>\n", 2)
|
72
|
+
@tagline.sub!('<h1>', '')
|
73
|
+
|
74
|
+
# grab name and section from title
|
75
|
+
if @tagline =~ /([\w_:-]+)\((\d\w*)\) -- (.*)/
|
76
|
+
@name, @section = $1, $2
|
77
|
+
@tagline = $3
|
78
|
+
end
|
79
|
+
|
80
|
+
"<h2 id='NAME'>NAME</h2>\n" +
|
81
|
+
"<p><code>#{@name}</code> -- #{@tagline}</p>\n" +
|
82
|
+
html
|
83
|
+
end
|
84
|
+
|
85
|
+
# Convert special format unordered lists to definition lists.
|
86
|
+
def definition_list_filter(html)
|
87
|
+
doc = Nokogiri::HTML(html)
|
88
|
+
|
89
|
+
# process all unordered lists depth-first
|
90
|
+
doc.xpath('//ul').to_a.reverse.each do |ul|
|
91
|
+
items = ul.xpath('li')
|
92
|
+
next if items.any? { |item| item.text.split("\n", 2).first !~ /:$/ }
|
93
|
+
|
94
|
+
ul.name = 'dl'
|
95
|
+
items.each do |item|
|
96
|
+
if item.child.name == 'p'
|
97
|
+
wrap = '<p></p>'
|
98
|
+
container = item.child
|
99
|
+
else
|
100
|
+
wrap = '<dd></dd>'
|
101
|
+
container = item
|
102
|
+
end
|
103
|
+
term, definition = container.inner_html.split(":\n", 2)
|
104
|
+
|
105
|
+
dt = item.before("<dt>#{term}</dt>").previous_sibling
|
106
|
+
dt['class'] = 'flush' if dt.content.length <= 10
|
107
|
+
|
108
|
+
item.name = 'dd'
|
109
|
+
container.swap(wrap.sub(/></, ">#{definition}<"))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
doc.css('html > body').inner_html
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|