ronn 0.6.6 → 0.7.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 (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 {