ron 0.1
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 +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
|