ghostwriter 1.0.1 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2868e207e695355f8e9f40521b38d1f96b72b45c9ab73207396a87e5b4d535cd
4
- data.tar.gz: cf111d734daa4bf94e4d9c2924dbd6c7d12b38b2b26a55db79110730f306fccc
3
+ metadata.gz: b87477fb4de8fc196eda6ea049eb3f1355ae583f0622c11b98543a67cfe71c8b
4
+ data.tar.gz: fa4e66701a3326ccceef69b3eb8f8591f8eea337f6f4f37c5b18a820bd120d42
5
5
  SHA512:
6
- metadata.gz: 8d71989a44d8d2da33496172c600ec38063100c53642850982a9a93ccefbea37e40847e93edfde09d3b0f0dad98f457296f01c070387d86b11cc06e2ee9e04c1
7
- data.tar.gz: 0767f0d24a895477aee922bd960380608185b741c0d87975bba65eaa03270539e2af2776bbcef2f689b604b76fde7882dbe9322e522b190858c2699359ce8a3b
6
+ metadata.gz: b5c545e83c7073b3c5923efc3ffcb6ecfd78650c472a482b5ec16fd7ef5533859ce6fb24d32378aafd20c83e35acd558c136856b46f0c7e0373beb7e3191be34
7
+ data.tar.gz: 8b259323e6653c112885ddf6fe4ee99b31c01e5625ee2fcf22d0259330dc090df11a1b7ee2dc1507c222b5f79c8322717aba9455b9bdbdca04334c0becc5959a
data/README.md CHANGED
@@ -172,9 +172,9 @@ This meal we enjoy together would be improved by one.
172
172
 
173
173
  ```
174
174
 
175
- ### Headers
175
+ ### Headings
176
176
 
177
- For now, headers are all treated the same and given a simple marker:
177
+ Headings are wrapped with a marker per heading level:
178
178
 
179
179
  ```html
180
180
  <h1>Dog Maintenance and Repair</h1>
@@ -186,10 +186,12 @@ Becomes:
186
186
 
187
187
  ```
188
188
  -- Dog Maintenance and Repair --
189
- -- Food Input Port --
190
- -- Exhaust Port Considerations --
189
+ ---- Food Input Port ----
190
+ ------ Exhaust Port Considerations ------
191
191
  ```
192
192
 
193
+ The `<header>` tag is treated like an `<h1>` tag.
194
+
193
195
  ### Lists
194
196
 
195
197
  Lists are converted, too. They are padded with newlines and are given simple markers:
@@ -211,7 +213,6 @@ Lists are converted, too. They are padded with newlines and are given simple mar
211
213
  Becomes:
212
214
 
213
215
  ```
214
-
215
216
  - Planes
216
217
  - Trains
217
218
  - Automobiles
@@ -219,7 +220,6 @@ Becomes:
219
220
  1. I get knocked down
220
221
  2. I get up again
221
222
  3. Never gonna keep me down
222
-
223
223
  ```
224
224
 
225
225
  ### Tables
@@ -265,10 +265,85 @@ Becomes:
265
265
  | Planet Express Ship | Turanga Leela |
266
266
  ```
267
267
 
268
- ### Presentation ARIA Role
268
+ ### Customizing Output
269
+
270
+ Ghostwriter has some constructor options to customize output.
271
+
272
+ You can set heading markers.
273
+
274
+ ```ruby
275
+ html = <<~HTML
276
+ <h1>Emergency Cat Procedures</h1>
277
+ HTML
278
+
279
+ writer = Ghostwriter::Writer.new(heading_marker: '#')
280
+
281
+ puts writer.textify(html)
282
+ ```
283
+
284
+ Produces:
285
+
286
+ ```
287
+ # Emergency Cat Procedures #
288
+ ```
289
+
290
+ You can also set list item markers. Ordered markers can be anything that responds to `#next` (eg. any `Enumerator`)
291
+
292
+ ```ruby
293
+ html = <<~HTML
294
+ <ol><li>Mercury</li><li>Venus</li><li>Mars</li></ol>
295
+ <ul><li>Teapot</li><li>Kettle</li></ul>
296
+ HTML
297
+
298
+ writer = Ghostwriter::Writer.new(ul_marker: '*', ol_marker: 'a')
299
+
300
+ puts writer.textify(html)
301
+ ```
302
+
303
+ Produces:
304
+
305
+ ```
306
+ a. Mercury
307
+ b. Venus
308
+ c. Mars
309
+
310
+ * Teapot
311
+ * Kettle
312
+ ```
313
+
314
+ And tables can be customized:
315
+
316
+ ```ruby
317
+ writer = Ghostwriter::Writer.new(table_row: '.',
318
+ table_column: '#',
319
+ table_corner: '+')
320
+
321
+ puts writer.textify <<~HTML
322
+ <table>
323
+ <thead>
324
+ <tr><th>Moon</th><th>Portfolio</th></tr>
325
+ </thead>
326
+ <tbody>
327
+ <tr><td>Phobos</td><td>Fear & Panic</td></tr>
328
+ <tr><td>Deimos</td><td>Dread and Terror</td></tr>
329
+ </tbody>
330
+ </table>
331
+ HTML
332
+ ```
333
+
334
+ Produces:
335
+
336
+ ```
337
+ # Moon # Portfolio #
338
+ +........+..................+
339
+ # Phobos # Fear & Panic #
340
+ # Deimos # Dread and Terror #
341
+
342
+ ```
343
+
344
+ #### Presentation ARIA Role
269
345
 
270
- Lists and tables with `role="presentation"` will be treated as a simple container and the normal behaviour will be
271
- suppressed.
346
+ Tags with `role="presentation"` will be treated as a simple container and the normal behaviour will be suppressed.
272
347
 
273
348
  ```html
274
349
 
data/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Release Notes
2
2
 
3
+ ## 1.1.0 (2021-03-23)
4
+
5
+ ### Major
6
+
7
+ * none
8
+
9
+ ### Minor
10
+
11
+ * Added customization for headings
12
+ * Headings now marked more for higher order headings
13
+ * Added customization for list markers
14
+ * Added customization for table markers
15
+ * Writer is now immutable
16
+
17
+ ### Bugfixes
18
+
19
+ * none
20
+
3
21
  ## 1.0.1 (2021-03-22)
4
22
 
5
23
  ### Major
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ghostwriter
4
- VERSION = '1.0.1'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -3,12 +3,22 @@
3
3
  module Ghostwriter
4
4
  # Main Ghostwriter converter object.
5
5
  class Writer
6
+ attr_reader :link_base, :heading_marker, :ul_marker, :ol_marker, :table_row, :table_column, :table_corner
7
+
6
8
  # Creates a new ghostwriter
7
9
  #
8
10
  # @param [String] link_base the url to prefix relative links with
9
- def initialize(link_base: '')
10
- @link_base = link_base
11
- @list_marker = '-'
11
+ def initialize(link_base: '', heading_marker: '--', ul_marker: '-', ol_marker: '1',
12
+ table_column: '|', table_row: '-', table_corner: '|')
13
+ @link_base = link_base
14
+ @heading_marker = heading_marker
15
+ @ul_marker = ul_marker
16
+ @ol_marker = ol_marker
17
+ @table_column = table_column
18
+ @table_row = table_row
19
+ @table_corner = table_corner
20
+
21
+ freeze
12
22
  end
13
23
 
14
24
  # Strips HTML down to plain text.
@@ -34,13 +44,13 @@ module Ghostwriter
34
44
  simple_replace(doc, 'br', "\n")
35
45
  simple_replace(doc, 'p', "\n\n")
36
46
 
37
- doc.text.strip.split("\n").collect(&:strip).join("\n").concat("\n")
47
+ normalize_lines(doc)
38
48
  end
39
49
 
40
50
  private
41
51
 
42
- def normalize_whitespace(html)
43
- html.gsub(/\s/, ' ').squeeze(' ')
52
+ def normalize_lines(doc)
53
+ doc.text.strip.split("\n").collect(&:strip).join("\n").concat("\n")
44
54
  end
45
55
 
46
56
  def replace_anchors(doc)
@@ -78,8 +88,16 @@ module Ghostwriter
78
88
  end
79
89
 
80
90
  def replace_headers(doc)
81
- doc.search('header, h1, h2, h3, h4, h5, h6').each do |node|
82
- node.inner_html = "-- #{ node.inner_html } --\n".squeeze(' ')
91
+ doc.search('header, h1').each do |node|
92
+ node.replace("#{ @heading_marker } #{ node.inner_html } #{ @heading_marker }\n"
93
+ .squeeze(' '))
94
+ end
95
+
96
+ (2..6).each do |n|
97
+ doc.search("h#{ n }").each do |node|
98
+ node.replace("#{ @heading_marker * n } #{ node.inner_html } #{ @heading_marker * n }\n"
99
+ .squeeze(' '))
100
+ end
83
101
  end
84
102
  end
85
103
 
@@ -97,41 +115,50 @@ module Ghostwriter
97
115
  end
98
116
 
99
117
  def replace_lists(doc)
100
- doc.search('ul, ol').each do |list_node|
101
- list_node.search('./li').each_with_index do |list_item, i|
102
- marker = if list_node.node_name == 'ol'
103
- "#{ i + 1 }."
104
- else
105
- @list_marker
106
- end
107
-
108
- list_item.inner_html = "#{ marker } #{ list_item.inner_html }\n".squeeze(' ')
109
- end
118
+ doc.search('ol').each do |list_node|
119
+ replace_list_items(list_node, @ol_marker, after_marker: '.', increment: true)
120
+ end
110
121
 
122
+ doc.search('ul').each do |list_node|
123
+ replace_list_items(list_node, @ul_marker)
124
+ end
125
+
126
+ doc.search('ul, ol').each do |list_node|
111
127
  list_node.replace("#{ list_node.inner_html }\n")
112
128
  end
113
129
  end
114
130
 
131
+ def replace_list_items(list_node, marker, after_marker: '', increment: false)
132
+ list_node.search('./li').each do |list_item|
133
+ list_item.replace("#{ marker }#{ after_marker } #{ list_item.inner_html }\n")
134
+
135
+ marker = marker.next if increment
136
+ end
137
+ end
138
+
115
139
  def replace_tables(doc)
116
140
  doc.css('table').each do |table|
141
+ # remove whitespace between nodes
142
+ table.search('//text()[normalize-space()=""]').remove
143
+
117
144
  column_sizes = calculate_column_sizes(table)
118
145
 
119
146
  table.search('./thead/tr', './tbody/tr', './tr').each do |row|
120
147
  replace_table_nodes(row, column_sizes)
121
148
 
122
- row.inner_html = "#{ row.inner_html }|\n"
149
+ row.replace("#{ row.inner_html }#{ @table_column }\n")
123
150
  end
124
151
 
125
152
  add_table_header_underline(table, column_sizes)
126
153
 
127
- table.inner_html = "#{ table.inner_html }\n"
154
+ table.replace("\n#{ table.inner_html }\n")
128
155
  end
129
156
  end
130
157
 
131
158
  def calculate_column_sizes(table)
132
159
  column_sizes = table.search('tr').collect do |row|
133
160
  row.search('th', 'td').collect do |node|
134
- node.inner_html.length
161
+ node.text.length
135
162
  end
136
163
  end
137
164
 
@@ -139,19 +166,19 @@ module Ghostwriter
139
166
  end
140
167
 
141
168
  def add_table_header_underline(table, column_sizes)
142
- table.search('./thead').each do |row|
143
- header_bottom = "|#{ column_sizes.collect { |len| ('-' * (len + 2)) }.join('|') }|"
169
+ table.search('./thead').each do |thead|
170
+ lines = column_sizes.collect { |len| @table_row * (len + 2) }
171
+ underline_row = "#{ table_corner }#{ lines.join(@table_corner) }#{ @table_corner }"
144
172
 
145
- row.inner_html = "#{ row.inner_html }#{ header_bottom }\n"
173
+ thead.replace("#{ thead.inner_html }#{ underline_row }\n")
146
174
  end
147
175
  end
148
176
 
149
177
  def replace_table_nodes(row, column_sizes)
150
178
  row.search('th', 'td').each_with_index do |node, i|
151
- new_content = "| #{ node.inner_html }".squeeze(' ')
179
+ new_content = node.text.ljust(column_sizes[i] + 1)
152
180
 
153
- # +2 for the extra spacing between text and pipe
154
- node.inner_html = new_content.ljust(column_sizes[i] + 2)
181
+ node.replace("#{ @table_column } #{ new_content }")
155
182
  end
156
183
  end
157
184
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ghostwriter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robin Miller