ronn 0.5 → 0.6.0

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