ronn 0.6.6 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/CHANGES +34 -0
  2. data/INSTALLING +18 -0
  3. data/README.md +43 -69
  4. data/Rakefile +9 -10
  5. data/bin/ronn +66 -49
  6. data/config.ru +1 -1
  7. data/lib/ronn.rb +35 -9
  8. data/lib/ronn/document.rb +239 -135
  9. data/lib/ronn/index.rb +183 -0
  10. data/lib/ronn/roff.rb +48 -28
  11. data/lib/ronn/template.rb +22 -8
  12. data/lib/ronn/template/dark.css +1 -4
  13. data/lib/ronn/template/default.html +0 -2
  14. data/lib/ronn/template/man.css +12 -12
  15. data/lib/ronn/utils.rb +8 -0
  16. data/man/index.html +78 -0
  17. data/man/index.txt +15 -0
  18. data/man/{ronn.5 → ronn-format.7} +26 -30
  19. data/man/{ronn.5.ronn → ronn-format.7.ronn} +39 -39
  20. data/man/ronn.1 +47 -15
  21. data/man/ronn.1.ronn +53 -23
  22. data/ronn.gemspec +14 -8
  23. data/test/angle_bracket_syntax.html +4 -2
  24. data/test/basic_document.html +4 -2
  25. data/test/custom_title_document.html +1 -2
  26. data/test/definition_list_syntax.html +4 -2
  27. data/test/dots_at_line_start_test.roff +10 -0
  28. data/test/dots_at_line_start_test.ronn +4 -0
  29. data/test/entity_encoding_test.html +24 -2
  30. data/test/entity_encoding_test.roff +41 -1
  31. data/test/entity_encoding_test.ronn +17 -0
  32. data/test/index.txt +8 -0
  33. data/test/markdown_syntax.html +5 -3
  34. data/test/markdown_syntax.roff +4 -4
  35. data/test/middle_paragraph.html +4 -2
  36. data/test/missing_spaces.roff +3 -0
  37. data/test/section_reference_links.html +4 -2
  38. data/test/{ronn_test.rb → test_ronn.rb} +18 -5
  39. data/test/{document_test.rb → test_ronn_document.rb} +59 -8
  40. data/test/test_ronn_index.rb +73 -0
  41. data/test/titleless_document.html +7 -2
  42. data/test/titleless_document.ronn +3 -2
  43. data/test/underline_spacing_test.roff +5 -0
  44. metadata +30 -14
  45. data/man/ronn.7 +0 -168
  46. data/man/ronn.7.ronn +0 -120
@@ -0,0 +1,183 @@
1
+ require 'ronn'
2
+
3
+ module Ronn
4
+
5
+ # Maintains a list of links / references to manuals and other resources.
6
+ class Index
7
+ include Enumerable
8
+
9
+ attr_reader :path
10
+ attr_reader :references
11
+
12
+ # Retrieve an Index for <path>, where <path> is a directory or normal
13
+ # file. The index is loaded from the corresponding index.txt file if
14
+ # one exists.
15
+ def self.[](path)
16
+ (@indexes ||= {})[index_path_for_file(path)] ||=
17
+ Index.new(index_path_for_file(path))
18
+ end
19
+
20
+ def self.index_path_for_file(file)
21
+ File.expand_path(
22
+ if File.directory?(file)
23
+ File.join(file, 'index.txt')
24
+ else
25
+ File.join(File.dirname(file), 'index.txt')
26
+ end
27
+ )
28
+ end
29
+
30
+ def initialize(path, &bk)
31
+ @path = path
32
+ @references = []
33
+ @manuals = {}
34
+
35
+ if block_given?
36
+ read! yield
37
+ elsif exist?
38
+ read! File.read(path)
39
+ end
40
+ end
41
+
42
+ # Determine whether the index file exists.
43
+ def exist?
44
+ File.exist?(path)
45
+ end
46
+
47
+ # Load index data from a string.
48
+ def read!(data)
49
+ data.each_line do |line|
50
+ line = line.strip.gsub(/\s*#.*$/, '')
51
+ if !line.empty?
52
+ name, url = line.split(/ +/, 2)
53
+ @references << reference(name, url)
54
+ end
55
+ end
56
+ end
57
+
58
+ ##
59
+ # Enumerable and friends
60
+
61
+ def each(&bk)
62
+ references.each(&bk)
63
+ end
64
+
65
+ def size
66
+ references.size
67
+ end
68
+
69
+ def first
70
+ references.first
71
+ end
72
+
73
+ def last
74
+ references.last
75
+ end
76
+
77
+ def empty?
78
+ references.empty?
79
+ end
80
+
81
+ def [](name)
82
+ references.find { |ref| ref.name == name }
83
+ end
84
+
85
+ def reference(name, path)
86
+ Reference.new(self, name, path)
87
+ end
88
+
89
+ def <<(path)
90
+ raise ArgumentError, "local paths only" if path =~ /(https?|mailto):/
91
+ return self if any? { |ref| ref.path == File.expand_path(path) }
92
+ relative_path = relative_to_index(path)
93
+ @references << \
94
+ if path =~ /\.ronn?$/
95
+ reference manual(path).reference_name, relative_path
96
+ else
97
+ reference File.basename(path), relative_path
98
+ end
99
+ self
100
+ end
101
+
102
+ def add_manual(manual)
103
+ @manuals[File.expand_path(manual.path)] = manual
104
+ self << manual.path
105
+ end
106
+
107
+ def manual(path)
108
+ @manuals[File.expand_path(path)] ||= Document.new(path)
109
+ end
110
+
111
+ def manuals
112
+ select { |ref| ref.relative? && ref.ronn? }.
113
+ map { |ref| manual(ref.path) }
114
+ end
115
+
116
+ ##
117
+ # Converting
118
+
119
+ def to_text
120
+ map { |ref| [ref.name, ref.location].join(' ') }.join("\n")
121
+ end
122
+
123
+ def to_a
124
+ references
125
+ end
126
+
127
+ def to_h
128
+ to_a.map { |doc| doc.to_hash }
129
+ end
130
+
131
+ def relative_to_index(path)
132
+ path = File.expand_path(path)
133
+ index_dir = File.dirname(File.expand_path(self.path))
134
+ path.sub(/^#{index_dir}\//, '')
135
+ end
136
+ end
137
+
138
+ # An individual index reference. A reference can point to one of a few types
139
+ # of locations:
140
+ #
141
+ # - URLs: "http://man.cx/crontab(5)"
142
+ # - Relative paths to ronn manuals: "crontab.5.ronn"
143
+ #
144
+ # The #url method should be used to obtain the href value for HTML.
145
+ class Reference
146
+ attr_reader :name
147
+ attr_reader :location
148
+
149
+ def initialize(index, name, location)
150
+ @index = index
151
+ @name = name
152
+ @location = location
153
+ end
154
+
155
+ def manual?
156
+ name =~ /\([0-9]\w*\)$/
157
+ end
158
+
159
+ def ronn?
160
+ location =~ /\.ronn?$/
161
+ end
162
+
163
+ def remote?
164
+ location =~ /^(?:https?|mailto):/
165
+ end
166
+
167
+ def relative?
168
+ !remote?
169
+ end
170
+
171
+ def url
172
+ if remote?
173
+ location
174
+ else
175
+ location.chomp('.ronn') + '.html'
176
+ end
177
+ end
178
+
179
+ def path
180
+ File.expand_path(location, File.dirname(@index.path)) if relative?
181
+ end
182
+ end
183
+ end
@@ -29,20 +29,9 @@ module Ronn
29
29
  end
30
30
  end
31
31
 
32
- def child_of?(node, tag)
33
- while node
34
- if node.name && node.name.downcase == tag
35
- return true
36
- else
37
- node = node.parent
38
- end
39
- end
40
- false
41
- end
42
-
43
32
  def title_heading(name, section, tagline, manual, version, date)
44
- comment "generated with Ronn/v#{Ronn::VERSION}"
45
- comment "http://github.com/rtomayko/ronn/"
33
+ comment "generated with Ronn/v#{Ronn.version}"
34
+ comment "http://github.com/rtomayko/ronn/tree/#{Ronn.revision}"
46
35
  return if name.nil?
47
36
  macro "TH", %["#{escape(name.upcase)}" "#{section}" "#{date.strftime('%B %Y')}" "#{version}" "#{manual}"]
48
37
  end
@@ -50,7 +39,6 @@ module Ronn
50
39
  def remove_extraneous_elements!(doc)
51
40
  doc.traverse_all_element do |node|
52
41
  if node.comment? || node.procins? || node.doctype? || node.xmldecl?
53
- warn 'removing: %p' % [node]
54
42
  node.parent.children.delete(node)
55
43
  end
56
44
  end
@@ -85,7 +73,7 @@ module Ronn
85
73
  when node.doc?
86
74
  normalize_whitespace! node.children
87
75
  else
88
- warn "unexpected node during whitespace normalization: %p", node.name
76
+ warn "unexpected node during whitespace normalization: %p", node
89
77
  end
90
78
  end
91
79
 
@@ -101,6 +89,10 @@ module Ronn
101
89
 
102
90
  elsif node.elem?
103
91
  case node.name
92
+ when 'div'
93
+ block_filter(node.children)
94
+ when 'h1'
95
+ # discard
104
96
  when 'h2'
105
97
  macro "SH", quote(escape(node.html))
106
98
  when 'h3'
@@ -134,7 +126,7 @@ module Ronn
134
126
  inline_filter(node.children)
135
127
  write "\n"
136
128
  when 'dd'
137
- if node.search('p').any?
129
+ if node.at('p')
138
130
  block_filter(node.children)
139
131
  else
140
132
  inline_filter(node.children)
@@ -151,7 +143,7 @@ module Ronn
151
143
  when 'ul'
152
144
  macro "IP", %w["\(bu" 4]
153
145
  end
154
- if node.search('p|ol|ul|dl|div').any?
146
+ if node.at('p|ol|ul|dl|div')
155
147
  block_filter(node.children)
156
148
  else
157
149
  inline_filter(node.children)
@@ -174,12 +166,13 @@ module Ronn
174
166
  node.each { |ch| inline_filter(ch) }
175
167
 
176
168
  elsif node.text?
177
- prev = previous(node)
178
169
  text = node.to_html.dup
179
170
  write escape(text)
180
171
 
181
172
  elsif node.elem?
182
173
  case node.name
174
+ when 'span'
175
+ inline_filter(node.children)
183
176
  when 'code'
184
177
  if child_of?(node, 'pre')
185
178
  inline_filter(node.children)
@@ -201,8 +194,11 @@ module Ronn
201
194
 
202
195
  when 'br'
203
196
  macro 'br'
197
+
204
198
  when 'a'
205
- if node.has_attribute?('data-bare-link')
199
+ if node.classes.include?('man-ref')
200
+ inline_filter(node.children)
201
+ elsif node.has_attribute?('data-bare-link')
206
202
  write '\fI'
207
203
  inline_filter(node.children)
208
204
  write '\fR'
@@ -226,15 +222,33 @@ module Ronn
226
222
  writeln ".\n.#{[name, value].compact.join(' ')}"
227
223
  end
228
224
 
225
+ HTML_ROFF_ENTITIES = {
226
+ '&bull;' => '\(bu',
227
+ '&lt;' => '<',
228
+ '&gt;' => '>',
229
+ '&nbsp;' => '\~',
230
+ '&copy;' => '\(co',
231
+ '&rdquo;' => '\(rs',
232
+ '&mdash;' => '\(em',
233
+ '&reg;' => '\(rg',
234
+ '&sec;' => '\(sc',
235
+ '&ge;' => '\(>=',
236
+ '&le;' => '\(<=',
237
+ '&ne;' => '\(!=',
238
+ '&equiv;' => '\(=='
239
+ }
240
+
229
241
  def escape(text)
230
- text.
231
- gsub('&nbsp;', ' ').
232
- gsub('&lt;', '<').
233
- gsub('&gt;', '>').
234
- gsub(/&#x([0-9A-Fa-f]+);/) { $1.to_i(16).chr }.
235
- gsub(/&#(\d+);/) { $1.to_i.chr }.
236
- gsub('&amp;', '&').
237
- gsub(/[\\'".-]/) { |m| "\\#{m}" }
242
+ return text.to_s if text.nil? || text.empty?
243
+ ent = HTML_ROFF_ENTITIES
244
+ text = text.dup
245
+ text.gsub!(/&#x([0-9A-Fa-f]+);/) { $1.to_i(16).chr } # hex entities
246
+ text.gsub!(/&#(\d+);/) { $1.to_i.chr } # dec entities
247
+ text.gsub!('\\', '\e') # backslash
248
+ text.gsub!(/['".-]/) { |m| "\\#{m}" } # control chars
249
+ text.gsub!(/(&[A-Za-z]+;)/) { ent[$1] || $1 } # named entities
250
+ text.gsub!('&amp;', '&') # amps
251
+ text
238
252
  end
239
253
 
240
254
  def quote(text)
@@ -243,7 +257,13 @@ module Ronn
243
257
 
244
258
  # write text to output buffer
245
259
  def write(text)
246
- @buf << text unless text.nil? || text.empty?
260
+ return if text.nil? || text.empty?
261
+ # lines cannot start with a '.'. insert zero-width character before.
262
+ if text[0,2] == '\.' &&
263
+ (@buf.last && @buf.last[-1] == ?\n)
264
+ @buf << '\&'
265
+ end
266
+ @buf << text
247
267
  end
248
268
 
249
269
  # write text to output buffer on a new line.
@@ -28,9 +28,22 @@ module Ronn
28
28
  def tagline
29
29
  @document.tagline
30
30
  end
31
+ alias tagline? tagline
32
+
33
+ def name_and_section?
34
+ name && section
35
+ end
31
36
 
32
37
  def title
33
- [page_name, tagline].compact.join(' - ')
38
+ if !name_and_section? && tagline
39
+ tagline
40
+ else
41
+ [page_name, tagline].compact.join(' - ')
42
+ end
43
+ end
44
+
45
+ def custom_title?
46
+ !name_and_section? && tagline
34
47
  end
35
48
 
36
49
  def page_name
@@ -42,7 +55,7 @@ module Ronn
42
55
  end
43
56
 
44
57
  def generator
45
- "Ronn/v#{Ronn::VERSION} (http://github.com/rtomayko/ronn)"
58
+ "Ronn/v#{Ronn.version} (http://github.com/rtomayko/ronn/tree/#{Ronn.revision})"
46
59
  end
47
60
 
48
61
  def manual
@@ -57,8 +70,8 @@ module Ronn
57
70
  @document.date.strftime('%B %Y')
58
71
  end
59
72
 
60
- def html
61
- @document.to_html_fragment(wrap_class=nil)
73
+ def wrap_class_name
74
+ 'mp'
62
75
  end
63
76
 
64
77
  ##
@@ -130,12 +143,13 @@ module Ronn
130
143
 
131
144
  def inline_stylesheet(path, media='all')
132
145
  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
146
  data.gsub!(%r|/\*.+?\*/|m, '') # comments
147
+ data.gsub!(/([;{,]) *\n/m, '\1') # end-of-line whitespace
137
148
  data.gsub!(/\n{2,}/m, "\n") # collapse lines
138
- data.gsub!(/^/, ' ')
149
+ data.gsub!(/[; ]+\}/, '}') # superfluous trailing semi-colons
150
+ data.gsub!(/([{;,+])[ ]+/, '\1') # whitespace around things
151
+ data.gsub!(/[ \t]+/m, ' ') # coalescing whitespace elsewhere
152
+ data.gsub!(/^/, ' ') # indent
139
153
  data.strip!
140
154
  [
141
155
  "<style type='text/css' media='#{media}'>",
@@ -11,10 +11,7 @@
11
11
 
12
12
  .mp em, .mp var, .mp u { color:#ddd }
13
13
 
14
- .mp pre {
15
- background:#333;
16
- border-left:1ex solid #1a1a1a;
17
- }
14
+ .mp pre code { color:#ddd }
18
15
 
19
16
  .mp a, .mp a:link, .mp a:hover,
20
17
  .mp a code, .mp a pre, .mp a tt,
@@ -22,8 +22,6 @@
22
22
  {{/section_heads}}
23
23
  </div>
24
24
 
25
- <h1 class='man-title'>{{ page_name }}</h1>
26
-
27
25
  <ol class='man-decor man-head man head'>
28
26
  <li class='tl'>{{ page_name }}</li>
29
27
  <li class='tc'>{{ manual }}</li>
@@ -55,33 +55,33 @@ body#manpage { margin:0 }
55
55
  .mp pre code, .mp tt, .mp kbd,
56
56
  .mp samp { color:#131211 }
57
57
  .mp h1, .mp h2, .mp h3, .mp h4 { color:#030201 }
58
- .mp ol.man, .mp ol.man li { color:#636261 }
58
+ .mp u { text-decoration:underline; }
59
59
 
60
60
  .mp code, .mp strong, .mp b {
61
61
  font-weight:bold;
62
62
  color:#131211;
63
63
  }
64
64
 
65
- .mp em, .mp var, .mp u {
65
+ .mp em, .mp var {
66
66
  font-style:italic;
67
- color:#434241;
67
+ color:#232221;
68
68
  text-decoration:none;
69
69
  }
70
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
- }
71
+ /* LINKS */
80
72
 
81
73
  .mp a, .mp a:link, .mp a:hover,
82
74
  .mp a code, .mp a pre, .mp a tt,
83
75
  .mp a kbd, .mp a samp { color:#0000ff }
84
76
 
77
+ .mp b.man-ref { font-weight:normal;color:#434241 }
78
+
79
+ /* PREFORMATTED BLOCKS */
80
+
81
+ .mp pre { padding:0 4ex }
82
+ .mp pre code { font-weight:normal;color:#434241 }
83
+ .mp h2+pre, h3+pre { padding-left:0 }
84
+
85
85
  /* DOCUMENT HEADER AND FOOTER AREAS */
86
86
 
87
87
  ol.man-decor, ol.man-decor li {