ronn 0.5 → 0.6.0

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.
Files changed (48) hide show
  1. data/AUTHORS +7 -0
  2. data/CHANGES +128 -0
  3. data/README.md +64 -79
  4. data/Rakefile +81 -28
  5. data/bin/ronn +124 -65
  6. data/config.ru +15 -0
  7. data/lib/ronn.rb +13 -5
  8. data/lib/ronn/document.rb +87 -13
  9. data/lib/ronn/roff.rb +43 -18
  10. data/lib/ronn/server.rb +70 -0
  11. data/lib/ronn/template.rb +157 -0
  12. data/lib/ronn/template/80c.css +6 -0
  13. data/lib/ronn/template/dark.css +21 -0
  14. data/lib/ronn/template/darktoc.css +17 -0
  15. data/lib/ronn/template/default.html +43 -0
  16. data/lib/ronn/template/man.css +100 -0
  17. data/lib/ronn/template/print.css +5 -0
  18. data/lib/ronn/template/screen.css +105 -0
  19. data/lib/ronn/template/toc.css +27 -0
  20. data/man/ronn.1 +160 -93
  21. data/man/ronn.1.ronn +206 -89
  22. data/man/ronn.5 +94 -96
  23. data/man/ronn.5.ronn +96 -91
  24. data/man/ronn.7 +50 -84
  25. data/man/ronn.7.ronn +64 -79
  26. data/ronn.gemspec +26 -11
  27. data/test/angle_bracket_syntax.html +4 -1
  28. data/test/basic_document.html +4 -1
  29. data/test/contest.rb +68 -0
  30. data/test/custom_title_document.html +4 -1
  31. data/test/definition_list_syntax.html +4 -1
  32. data/test/definition_list_syntax.roff +26 -0
  33. data/test/document_test.rb +51 -4
  34. data/test/entity_encoding_test.html +4 -1
  35. data/test/entity_encoding_test.roff +1 -1
  36. data/test/markdown_syntax.html +955 -0
  37. data/test/markdown_syntax.roff +1467 -0
  38. data/{man/markdown.5.ronn → test/markdown_syntax.ronn} +0 -0
  39. data/test/middle_paragraph.html +5 -2
  40. data/test/middle_paragraph.roff +2 -2
  41. data/test/ronn_test.rb +19 -4
  42. data/test/section_reference_links.html +15 -0
  43. data/test/section_reference_links.roff +10 -0
  44. data/test/section_reference_links.ronn +12 -0
  45. data/test/titleless_document.html +3 -0
  46. metadata +34 -13
  47. data/lib/ronn/layout.html +0 -75
  48. data/man/markdown.5 +0 -1639
@@ -7,7 +7,7 @@ module Ronn
7
7
  @buf = []
8
8
  title_heading name, section, tagline, manual, version, date
9
9
  html = Hpricot(html)
10
- block_filter(html)
10
+ block_filter(normalize_whitespace(html))
11
11
  write "\n"
12
12
  end
13
13
 
@@ -41,6 +41,29 @@ module Ronn
41
41
  macro "TH", %["#{escape(name.upcase)}" "#{section}" "#{date.strftime('%B %Y')}" "#{version}" "#{manual}"]
42
42
  end
43
43
 
44
+ def normalize_whitespace(node)
45
+ if node.kind_of?(Array) || node.kind_of?(Hpricot::Elements)
46
+ node.each { |ch| normalize_whitespace(ch) }
47
+ elsif node.doc? || (node.elem? && node.children)
48
+ normalize_whitespace(node.children)
49
+ elsif node.text? && !child_of?(node, 'pre')
50
+ node.content = node.content.gsub(/[\n ]+/m, ' ')
51
+ node_prev = previous(node)
52
+ if (node.previous && node.previous.elem?) &&
53
+ (node.next && node.next.elem?) &&
54
+ (node.content.strip == '')
55
+ node.content = ''
56
+ else
57
+ node.content = node.content.lstrip if node_prev.nil?
58
+ node.content = node.content.rstrip if node.next.nil?
59
+ end
60
+ elsif node.elem? || node.text?
61
+ else
62
+ warn "unrecognized tag: %p", node.name
63
+ end
64
+ node
65
+ end
66
+
44
67
  def block_filter(node)
45
68
  if node.kind_of?(Array) || node.kind_of?(Hpricot::Elements)
46
69
  node.each { |ch| block_filter(ch) }
@@ -94,14 +117,13 @@ module Ronn
94
117
  end
95
118
  write "\n"
96
119
 
97
- # when 'ol'
98
- # macro "IP", '1.'
99
- # block_filter(node.children)
100
- when 'ul'
120
+ when 'ol', 'ul'
101
121
  block_filter(node.children)
102
122
  macro "IP", %w["" 0]
103
123
  when 'li'
104
124
  case node.parent.name
125
+ when 'ol'
126
+ macro "IP", %W["#{node.position + 1}." 4]
105
127
  when 'ul'
106
128
  macro "IP", %w["\(bu" 4]
107
129
  end
@@ -122,20 +144,14 @@ module Ronn
122
144
  end
123
145
 
124
146
  def inline_filter(node)
147
+ return unless node # is an empty node
148
+
125
149
  if node.kind_of?(Array) || node.kind_of?(Hpricot::Elements)
126
150
  node.each { |ch| inline_filter(ch) }
127
151
 
128
152
  elsif node.text?
129
153
  prev = previous(node)
130
154
  text = node.to_html.dup
131
- text.sub!(/^\n+/m, '') if prev && prev.name == 'br'
132
- if child_of?(node, 'pre')
133
- # leave the text alone
134
- elsif node.previous.nil? && node.next.nil?
135
- text.sub!(/\n+$/m, '')
136
- else
137
- text.sub!(/\n+$/m, ' ')
138
- end
139
155
  write escape(text)
140
156
 
141
157
  elsif node.elem?
@@ -162,9 +178,17 @@ module Ronn
162
178
  when 'br'
163
179
  macro 'br'
164
180
  when 'a'
165
- write '\fI'
166
- inline_filter(node.children)
167
- write '\fR'
181
+ if node.has_attribute?('data-bare-link')
182
+ write '\fI'
183
+ inline_filter(node.children)
184
+ write '\fR'
185
+ else
186
+ inline_filter(node.children)
187
+ write ' '
188
+ write '\fI'
189
+ write escape(node.attributes['href'])
190
+ write '\fR'
191
+ end
168
192
  else
169
193
  warn "unrecognized inline tag: %p", node.name
170
194
  end
@@ -180,13 +204,14 @@ module Ronn
180
204
 
181
205
  def escape(text)
182
206
  text.
183
- gsub(/[\\-]/) { |m| "\\#{m}" }.
184
207
  gsub(' ', ' ').
185
208
  gsub('&lt;', '<').
186
209
  gsub('&gt;', '>').
210
+ gsub('&gt;', '>').
187
211
  gsub(/&#x([0-9A-Fa-f]+);/) { $1.to_i(16).chr }.
188
212
  gsub(/&#(\d+);/) { $1.to_i.chr }.
189
- gsub('&amp;', '&')
213
+ gsub('&amp;', '&').
214
+ gsub(/[\\'".-]/) { |m| "\\#{m}" }
190
215
  end
191
216
 
192
217
  def quote(text)
@@ -0,0 +1,70 @@
1
+ require 'ronn'
2
+ require 'rack'
3
+ require 'sinatra/base'
4
+
5
+ module Ronn
6
+
7
+ # Ronn HTTP server. Serves a list of .ronn files as HTML. The options Hash is
8
+ # passed to Ronn::Document.new on each invocation.
9
+ #
10
+ # Use Ronn::Server.new to create a Rack app. See the config.ru file in the
11
+ # root of the Ronn distribution for example usage.
12
+ #
13
+ # Ronn::Server.run starts a server on port
14
+ module Server
15
+ def self.new(files, options={})
16
+ files = Dir[files] if files.respond_to?(:to_str)
17
+ raise ArgumentError, "no files" if files.empty?
18
+ Sinatra.new do
19
+ set :show_exceptions, true
20
+ set :public, File.expand_path(__FILE__, '../templates')
21
+ set :static, false
22
+ set :views, File.expand_path(__FILE__, '../templates')
23
+
24
+ get '/' do
25
+ files.map do |f|
26
+ base = File.basename(f, '.ronn')
27
+ "<li><a href='./#{base}.html'>#{escape_html(base)}</a></li>"
28
+ end
29
+ end
30
+
31
+ def styles
32
+ params[:styles] ||= params[:style]
33
+ case
34
+ when params[:styles].respond_to?(:to_ary)
35
+ params[:styles]
36
+ when params[:styles]
37
+ params[:styles].split(/[, ]+/)
38
+ else
39
+ []
40
+ end
41
+ end
42
+
43
+ files.each do |file|
44
+ basename = File.basename(file, '.ronn')
45
+
46
+ get "/#{basename}.html" do
47
+ options = options.merge(:styles => styles)
48
+ %w[date manual organization].each do |attribute|
49
+ next if !params[attribute]
50
+ options[attribute] = params[attribute]
51
+ end
52
+ Ronn::Document.new(file, options).to_html
53
+ end
54
+ get "/#{basename}.roff" do
55
+ content_type 'text/plain+roff'
56
+ Ronn::Document.new(file, options.dup).to_roff
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def self.run(files, options={})
63
+ new(files, options).run!(
64
+ :server => %w[mongrel thin webrick],
65
+ :port => 1207,
66
+ :logging => true
67
+ )
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,157 @@
1
+ require 'mustache'
2
+
3
+ module Ronn
4
+ class Template < Mustache
5
+ self.template_path = File.dirname(__FILE__) + '/template'
6
+ self.template_extension = 'html'
7
+
8
+ def initialize(document, style_path=ENV['RONN_STYLE'].to_s.split(':'))
9
+ @document = document
10
+ @style_path = style_path + [Template.template_path]
11
+ end
12
+
13
+ def render(template='default')
14
+ super template[0,1] == '/' ? File.read(template) : partial(template)
15
+ end
16
+
17
+ ##
18
+ # Basic document attributes
19
+
20
+ def name
21
+ @document.name
22
+ end
23
+
24
+ def section
25
+ @document.section
26
+ end
27
+
28
+ def tagline
29
+ @document.tagline
30
+ end
31
+
32
+ def title
33
+ [page_name, tagline].compact.join(' - ')
34
+ end
35
+
36
+ def page_name
37
+ if section
38
+ "#{name}(#{section})"
39
+ else
40
+ name
41
+ end
42
+ end
43
+
44
+ def generator
45
+ "Ronn/v#{Ronn::VERSION} (http://github.com/rtomayko/ronn)"
46
+ end
47
+
48
+ def manual
49
+ @document.manual
50
+ end
51
+
52
+ def organization
53
+ @document.organization
54
+ end
55
+
56
+ def date
57
+ @document.date.strftime('%B %Y')
58
+ end
59
+
60
+ def html
61
+ @document.to_html_fragment(wrap_class=nil)
62
+ end
63
+
64
+ ##
65
+ # Section TOCs
66
+
67
+ def section_heads
68
+ @document.section_heads.map do |id, text|
69
+ {
70
+ :id => id,
71
+ :text => text
72
+ }
73
+ end
74
+ end
75
+
76
+ ##
77
+ # Styles
78
+
79
+ # Array of style module names as given on the command line.
80
+ def styles
81
+ @document.styles
82
+ end
83
+
84
+ # Array of stylesheet info hashes.
85
+ def stylesheets
86
+ styles.zip(style_files).map do |name, path|
87
+ base = File.basename(path, '.css')
88
+ fail "style not found: #{style.inspect}" if path.nil?
89
+ {
90
+ :name => name,
91
+ :path => path,
92
+ :base => File.basename(path, '.css'),
93
+ :media => (base =~ /(print|screen)$/) ? $1 : 'all'
94
+ }
95
+ end
96
+ end
97
+
98
+ # All embedded stylesheets.
99
+ def stylesheet_tags
100
+ stylesheets.
101
+ map { |style| inline_stylesheet(style[:path], style[:media]) }.
102
+ join("\n ")
103
+ end
104
+
105
+ attr_accessor :style_path
106
+
107
+ # Array of expanded stylesheet file names. If a file cannot be found, the
108
+ # resulting array will include nil elements in positions corresponding to
109
+ # the stylesheets array.
110
+ def style_files
111
+ styles.map do |name|
112
+ next name if name.include?('/')
113
+ style_path.
114
+ reject { |p| p.strip.empty? }.
115
+ map { |p| File.join(p, "#{name}.css") }.
116
+ detect { |file| File.exist?(file) }
117
+ end
118
+ end
119
+
120
+ # Array of style names for which no file could be found.
121
+ def missing_styles
122
+ style_files.
123
+ zip(files).
124
+ select { |style, file| file.nil? }.
125
+ map { |style, file| style }
126
+ end
127
+
128
+ ##
129
+ # TEMPLATE CSS LOADING
130
+
131
+ def inline_stylesheet(path, media='all')
132
+ data = File.read(path)
133
+ data.gsub!(/([;{]) *\n/m, '\1') # end-of-line whitespace
134
+ data.gsub!(/([;{]) +/m, '\1') # coalescing whitespace elsewhere
135
+ data.gsub!(/[; ]+\}/, '}') # remove superfluous trailing semi-colons
136
+ data.gsub!(%r|/\*.+?\*/|m, '') # comments
137
+ data.gsub!(/\n{2,}/m, "\n") # collapse lines
138
+ data.gsub!(/^/, ' ')
139
+ data.strip!
140
+ [
141
+ "<style type='text/css' media='#{media}'>",
142
+ "/* style: #{File.basename(path, '.css')} */",
143
+ data,
144
+ "</style>"
145
+ ].join("\n ")
146
+ end
147
+
148
+ def remote_stylesheet(name, media='all')
149
+ path = File.expand_path("../template/#{name}.css", __FILE__)
150
+ "<link rel='stylesheet' type='text/css' media='#{media}' href='#{path}'>"
151
+ end
152
+
153
+ def stylesheet(path, media='all')
154
+ inline_stylesheet(name, media)
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,6 @@
1
+ /* 80c.css - condense width to emulate a classical 80 character terminal */
2
+
3
+ .mp { max-width:86ex }
4
+
5
+ /* .mp width + padding */
6
+ .man-navigation { left:101ex }
@@ -0,0 +1,21 @@
1
+ .mp, body#manpage {
2
+ background:#080706;
3
+ color:#888;
4
+ }
5
+ .mp, .mp code, .mp pre,
6
+ .mp pre code, .mp tt, .mp kbd,
7
+ .mp samp { color:#aaa }
8
+ .mp h1, .mp h2, .mp h3, .mp h4 { color:#fff }
9
+ .man-decor, .man-decor ol li { color:#666 }
10
+ .mp code, .mp strong, .mp b { color:#fff }
11
+
12
+ .mp em, .mp var, .mp u { color:#ddd }
13
+
14
+ .mp pre {
15
+ background:#333;
16
+ border-left:1ex solid #1a1a1a;
17
+ }
18
+
19
+ .mp a, .mp a:link, .mp a:hover,
20
+ .mp a code, .mp a pre, .mp a tt,
21
+ .mp a kbd, .mp a samp { color:#fff }
@@ -0,0 +1,17 @@
1
+ /* darktoc.css - enable table of contents */
2
+
3
+ .man-navigation {
4
+ border-left:2px solid #222;
5
+ background-color:#131211;
6
+ }
7
+ .man-navigation a,
8
+ .man-navigation a:hover,
9
+ .man-navigation a:link,
10
+ .man-navigation a:visited {
11
+ color:#777;
12
+ text-decoration:none;
13
+ }
14
+ .man-navigation a:hover {
15
+ color:#fff;
16
+ text-decoration:underline;
17
+ }
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv='content-type' value='text/html;charset=utf8'>
5
+ <meta name='generator' value='{{ generator }}'>
6
+ <title>{{ title }}</title>
7
+ {{{ stylesheet_tags }}}
8
+ </head>
9
+ <!--
10
+ The following styles are deprecated and will be removed at some point:
11
+ div#man, div#man ol.man, div#man ol.head, div#man ol.man.
12
+
13
+ The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
14
+ .man-navigation should be used instead.
15
+ -->
16
+ <body id='manpage'>
17
+ <div class='mp' id='man'>
18
+
19
+ <div class='man-navigation' style='display:none'>
20
+ {{#section_heads}}
21
+ <a href="#{{id}}">{{text}}</a>
22
+ {{/section_heads}}
23
+ </div>
24
+
25
+ <h1 class='man-title'>{{ page_name }}</h1>
26
+
27
+ <ol class='man-decor man-head man head'>
28
+ <li class='tl'>{{ page_name }}</li>
29
+ <li class='tc'>{{ manual }}</li>
30
+ <li class='tr'>{{ page_name }}</li>
31
+ </ol>
32
+
33
+ {{{ html }}}
34
+
35
+ <ol class='man-decor man-foot man foot'>
36
+ <li class='tl'>{{ organization }}</li>
37
+ <li class='tc'>{{ date }}</li>
38
+ <li class='tr'>{{ page_name }}</li>
39
+ </ol>
40
+
41
+ </div>
42
+ </body>
43
+ </html>
@@ -0,0 +1,100 @@
1
+ /* man.css - core manpage styles */
2
+
3
+ /* STRUCTURE, INDENT, MARGINS */
4
+
5
+ body#manpage { margin:0 }
6
+ .mp {
7
+ max-width:100ex;
8
+ padding:0 9ex 1ex 4ex;
9
+ }
10
+
11
+ .mp p, .mp pre,
12
+ .mp ul, .mp ol, .mp dl { margin:0 0 20px 0 }
13
+ .mp h2 { margin:10px 0 0 0 }
14
+
15
+ .mp > p, .mp > pre, .mp > ul,
16
+ .mp > ol, .mp > dl { margin-left:8ex }
17
+ .mp h3 { margin:0 0 0 4ex }
18
+
19
+ .mp dt { margin:0; clear:left }
20
+ .mp dt.flush { float:left; width:8ex }
21
+ .mp dd { margin:0 0 0 9ex }
22
+ .mp h1, .mp h2, .mp h3, .mp h4 { clear:left }
23
+
24
+ .mp pre { margin-bottom:20px }
25
+ .mp pre+h2, .mp pre+h3 { margin-top:22px }
26
+ .mp h2+pre, .mp h3+pre { margin-top:5px }
27
+
28
+ .mp img { display:block;margin:auto }
29
+ .mp h1.man-title { display:none }
30
+
31
+ /* FONTS */
32
+
33
+ .mp, .mp code, .mp pre, .mp tt,
34
+ .mp kbd, .mp samp, .mp h3, .mp h4 {
35
+ font-family:monospace;
36
+ font-size:14px;
37
+ line-height:1.42857142857143;
38
+ }
39
+ .mp h2 {
40
+ font-size:16px;
41
+ line-height:1.25;
42
+ }
43
+ .mp h1 {
44
+ font-size:20px;
45
+ line-height:2;
46
+ }
47
+
48
+ /* TEXT STYLES */
49
+
50
+ .mp {
51
+ text-align:justify;
52
+ background:#fff;
53
+ }
54
+ .mp, .mp code, .mp pre,
55
+ .mp pre code, .mp tt, .mp kbd,
56
+ .mp samp { color:#131211 }
57
+ .mp h1, .mp h2, .mp h3, .mp h4 { color:#030201 }
58
+ .mp ol.man, .mp ol.man li { color:#636261 }
59
+
60
+ .mp code, .mp strong, .mp b {
61
+ font-weight:bold;
62
+ color:#131211;
63
+ }
64
+
65
+ .mp em, .mp var, .mp u {
66
+ font-style:italic;
67
+ color:#434241;
68
+ text-decoration:none;
69
+ }
70
+
71
+ .mp pre {
72
+ background:#edeceb;
73
+ padding:5px 1ex;
74
+ border-left:1ex solid #ddd;
75
+ }
76
+ .mp pre code {
77
+ font-weight:normal;
78
+ background:inherit;
79
+ }
80
+
81
+ .mp a, .mp a:link, .mp a:hover,
82
+ .mp a code, .mp a pre, .mp a tt,
83
+ .mp a kbd, .mp a samp { color:#0000ff }
84
+
85
+ /* DOCUMENT HEADER AND FOOTER AREAS */
86
+
87
+ ol.man-decor, ol.man-decor li {
88
+ margin:3px 0 10px 0;
89
+ padding:0;
90
+ float:left;
91
+ width:33%;
92
+ list-style-type:none;
93
+ text-transform:uppercase;
94
+ color:#999;
95
+ letter-spacing:1px;
96
+ }
97
+ ol.man-decor { width:100% }
98
+ ol.man-decor li.tl { text-align:left }
99
+ ol.man-decor li.tc { text-align:center; letter-spacing:4px }
100
+ ol.man-decor li.tr { text-align:right; float:right }