devcenter-parser 1.4.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: 9b2a9d71cb242667543a950f575062058af76f65
4
- data.tar.gz: 74f7642f8925d9f74c5780fd140dc7ba07f7801a
2
+ SHA1:
3
+ metadata.gz: 8201b0a7264aaf1d4c4aab88c4c77051b40adde1
4
+ data.tar.gz: e4b963e76da3ff3d57f915a69a620ad7d368a7e9
5
5
  SHA512:
6
- metadata.gz: cbf842e52bfda01071bd7ca615d70cb203f3fb42a23f2ec7aeac71ff4404304177c9ab38445abfeb2cf65fb9545dff543ea91aef6ec41b58ac292d018dc50408
7
- data.tar.gz: 64334aa1b572e2c334a6e3bee6c1074d6bc556590deae8b35d180864eab7ea34ed8e5aecd65baf1a4a4fa2bb872b65a1ea6d69a11c73a4e631fc260247480805
6
+ metadata.gz: 9223802189213472cc1a6129f447c43dc2303c66034fac510f2ed525237216f99e447726f7e708a1bd612fe02f3374195e270676e63621295026441a5fa35b30
7
+ data.tar.gz: 2b9ec108ef95d7ade76690b487512e84ff4725077c188dfe7a40261e07e8ed54b3a9848e85a52ad1b3dae5fa7cae8676e7e7a985ed53b726ee38dce3dbfe097b
data/README.md CHANGED
@@ -8,16 +8,8 @@ Usage:
8
8
  require 'devcenter-parser'
9
9
 
10
10
  md = '[Dev Center](https://devcenter.heroku.com)'
11
- flavour = :github # :github or :maruku
12
11
  DevcenterParser.to_html(md, flavour)
13
12
  # => "<p><a href=\"https://devcenter.heroku.com\">Dev Center</a></p>"
14
-
15
- broken_md = '[foo](bar'
16
- begin
17
- DevcenterParser.to_html(broken_md, :maruku)
18
- rescue DevcenterParser::InvalidMarkdownError => e
19
- puts e.message # parser-dependent (sometimes cryptic) debugging info
20
- end
21
13
  ```
22
14
 
23
15
  Test:
@@ -14,7 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
15
  gem.require_paths = %w{ lib }
16
16
 
17
- gem.add_runtime_dependency 'maruku', '0.7.0'
18
17
  gem.add_runtime_dependency 'nokogiri', '>= 1.4.4'
19
18
  gem.add_runtime_dependency 'redcarpet', '3.0.0'
20
19
  gem.add_runtime_dependency 'sanitize', '2.0.6'
@@ -1,4 +1,3 @@
1
- require 'maruku'
2
1
  require 'redcarpet'
3
2
  require 'nokogiri'
4
3
  require 'uri'
@@ -6,30 +5,21 @@ require 'sanitize'
6
5
  require 'ostruct'
7
6
  require_relative './devcenter-parser/header_id_generator'
8
7
  require_relative './devcenter-parser/github_parser'
9
- require_relative './devcenter-parser/maruku_parser'
10
8
 
11
9
  module DevcenterParser
12
- AVAILABLE_FLAVOURS = [:github, :maruku]
13
-
14
10
  class InvalidMarkdownError < Exception; end
15
11
  class InvalidRawHTMLError < Exception; end
16
- class UnknownFlavourError < Exception; end
17
12
 
18
- def self.to_html(markdown, flavour)
19
- html = to_unsanitized_html(markdown, flavour.to_sym)
20
- sanitize(html)
13
+ def self.to_html(markdown)
14
+ sanitize to_unsanitized_html(markdown)
21
15
  end
22
16
 
23
- def self.to_unsanitized_html(markdown, flavour)
24
- raise(UnknownFlavourError, "Markdown flavour '#{flavour}' not supported") unless %w{ maruku github }.include?(flavour.to_s)
17
+ def self.to_unsanitized_html(markdown)
25
18
  markdown = normalize_markdown(markdown)
26
- markdown_parser = flavour.to_s == 'maruku' ? MarukuParser : GitHubParser
27
- doc = markdown_parser.parse(markdown)
19
+ doc = GitHubParser.parse(markdown)
28
20
  doc_to_html(doc)
29
21
  rescue InvalidRawHTMLError => e
30
22
  raise InvalidMarkdownError, e.message
31
- rescue => e
32
- raise InvalidMarkdownError, parse_maruku_error(e.message)
33
23
  end
34
24
 
35
25
  def self.sanitize(html)
@@ -95,7 +85,6 @@ module DevcenterParser
95
85
 
96
86
  # custom
97
87
  config[:elements] += %w{ toolbelt }
98
- config[:attributes][:all] += %w{ data-next-message }
99
88
 
100
89
  @@sanitize_config = config.merge({remove_contents: true, allow_comments: true})
101
90
  end
@@ -116,14 +105,6 @@ module DevcenterParser
116
105
  html.to_s.include?('markdown-html-error')
117
106
  end
118
107
 
119
- def self.parse_maruku_error(error_message)
120
- lines = error_message.to_s.split("\n")
121
- return lines unless lines.size > 1
122
- msg = lines[4].gsub(/\A\|(\s)+|EOF\Z/,'').strip
123
- code = lines[6].gsub(/\A\|(\s)+|EOF\Z/,'').strip
124
- "#{msg} in \"#{code}\""
125
- end
126
-
127
108
  def self.parse_raw_html_error(html)
128
109
  broken_html = html.match(/REXML could not parse this XML\/HTML\:(.+)<\/pre>/m)[1].strip rescue nil
129
110
  broken_html.nil? ? "Contains broken raw HTML." : "This raw HTML is invalid: #{CGI.unescapeHTML(broken_html)}"
@@ -1,3 +1,3 @@
1
1
  module DevcenterParser
2
- VERSION = '1.4.9'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -7,26 +7,15 @@ describe 'DevcenterParser' do
7
7
  describe '.to_unsanitized_html' do
8
8
 
9
9
  it 'returns empty string for nil input' do
10
- assert_parsing_unsanitized_result nil, :maruku, ''
11
- assert_parsing_unsanitized_result nil, :maruku, ''
10
+ assert_parsing_unsanitized_result nil, ''
12
11
  end
13
12
 
14
13
  it 'maintains script tags' do
15
- md = '<script>alert("hi")</script>'
16
- assert_parsing_unsanitized_result md, :maruku, '<script><![CDATA[alert("hi")]]></script>'
17
- assert_parsing_unsanitized_result md, :github, '<script>alert("hi")</script>'
14
+ assert_parsing_unsanitized_result '<script>alert("hi")</script>', '<script>alert("hi")</script>'
18
15
  end
19
16
 
20
17
  it 'maintains toolbelt custom element' do
21
- md = '<toolbelt />'
22
- assert_parsing_unsanitized_result md, :maruku, '<toolbelt></toolbelt>'
23
- assert_parsing_unsanitized_result md, :github, '<p><toolbelt></toolbelt></p>'
24
- end
25
-
26
- it 'maintains custom attributes' do
27
- md = '<p data-next-message="foo">hi</p>'
28
- assert_parsing_unsanitized_result md, :maruku, '<p data-next-message="foo">hi</p>'
29
- assert_parsing_unsanitized_result md, :github, '<p data-next-message="foo">hi</p>'
18
+ assert_parsing_unsanitized_result '<toolbelt />', '<p><toolbelt></toolbelt></p>'
30
19
  end
31
20
 
32
21
  end
@@ -34,36 +23,24 @@ describe 'DevcenterParser' do
34
23
  describe '.to_html' do
35
24
 
36
25
  it 'returns empty string for nil input' do
37
- assert_maruku_result nil, ''
38
- assert_github_result nil, ''
26
+ assert_parsing_result nil, ''
39
27
  end
40
28
 
41
29
  it 'does not create <em>s inside words' do
42
- md = 'foo_bar_baz'
43
- html = '<p>foo_bar_baz</p>'
44
- assert_maruku_result md, html
45
- assert_github_result md, html
46
- end
47
-
48
- it 'raises InvalidMarkdownError when parsing invalid markdown' do
49
- md = '[foo](bar'
50
- assert_raises DevcenterParser::InvalidMarkdownError do
51
- DevcenterParser.to_html(md, :maruku)
52
- end
30
+ assert_parsing_result 'foo_bar_baz', '<p>foo_bar_baz</p>'
53
31
  end
54
32
 
55
33
  it 'removes script tags and their content' do
56
34
  md = '<strong>clean<script>alert("hack!")</script></strong>'
57
35
  html = '<p><strong>clean</strong></p>'
58
- assert_maruku_result md, html
59
- assert_github_result md, html
36
+ assert_parsing_result md, html
60
37
  end
61
38
 
62
39
  it 'allows embedding vimeo videos' do
63
40
  src = <<-SRC
64
41
  <iframe src=\"https://player.vimeo.com/video/61044807?title=0&amp;byline=0&amp;portrait=0&amp;color=a086ee\" width=\"500\" height=\"281\" frameborder=\"0\" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
65
42
  SRC
66
- assert_github_result src, src
43
+ assert_parsing_result src, src
67
44
  end
68
45
 
69
46
  describe 'github markdown' do
@@ -118,14 +95,14 @@ MD
118
95
  <p>Three</p>
119
96
  HTML
120
97
 
121
- assert_github_result md, html
98
+ assert_parsing_result md, html
122
99
  end
123
100
 
124
101
 
125
102
  it 'generates apostrophes from single quotes in plain text' do
126
103
  md = "That's it"
127
104
  html = "<p>That’s it</p>"
128
- assert_github_result(md, html)
105
+ assert_parsing_result(md, html)
129
106
  end
130
107
 
131
108
  it 'generates apostrophes from single quotes in callout|warning|note blocks' do
@@ -138,39 +115,13 @@ MARKDOWN
138
115
  <p>That’s it</p>
139
116
  </div>
140
117
  HTML
141
- assert_github_result(md, html)
118
+ assert_parsing_result(md, html)
142
119
  end
143
120
 
144
121
  it 'supports code blocks, respecting indentation and adding a custom class attribute' do
145
122
  md = <<-MARKDOWN
146
123
  Paragraph
147
124
 
148
- :::term
149
- $ command
150
-
151
- indented
152
- < tag1
153
- > tag2
154
-
155
- Another paragraph
156
- MARKDOWN
157
-
158
- html = <<-HTML
159
- <p>Paragraph</p>
160
-
161
- <pre><code class="term">$ command
162
-
163
- indented
164
- &lt; tag1
165
- &gt; tag2</code></pre>
166
-
167
- <p>Another paragraph</p>
168
- HTML
169
- assert_maruku_result md, html
170
-
171
- md = <<-MARKDOWN
172
- Paragraph
173
-
174
125
  ```term
175
126
  $ command
176
127
 
@@ -194,10 +145,10 @@ MARKDOWN
194
145
 
195
146
  <p>Another paragraph</p>
196
147
  HTML
197
- assert_github_result md, html
148
+ assert_parsing_result md, html
198
149
  end
199
150
 
200
- it 'github markdown supports regular block quotes without callout|warning|note' do
151
+ it 'supports regular block quotes without callout|warning|note' do
201
152
  md = <<-MARKDOWN
202
153
  Testing
203
154
 
@@ -220,7 +171,7 @@ normal</p>
220
171
  <p>And that’s it.</p>
221
172
  HTML
222
173
 
223
- assert_github_result(md, html)
174
+ assert_parsing_result(md, html)
224
175
 
225
176
  md = <<-MARKDOWN
226
177
  Testing
@@ -244,10 +195,10 @@ normal</p>
244
195
  <p>And that’s it.</p>
245
196
  HTML
246
197
 
247
- assert_github_result(md, html)
198
+ assert_parsing_result(md, html)
248
199
  end
249
200
 
250
- it 'github markdown supports "> callout" and ">callout" and parses inner markdown' do
201
+ it 'supports "> callout" and ">callout" and parses inner markdown' do
251
202
  mds = []
252
203
  mds << <<-MARKDOWN
253
204
  Testing
@@ -281,11 +232,11 @@ normal</p>
281
232
  HTML
282
233
 
283
234
  mds.each do |md|
284
- assert_github_result(md, html)
235
+ assert_parsing_result(md, html)
285
236
  end
286
237
  end
287
238
 
288
- it 'github markdown supports "> callout" and ">callout", parses inner markdown and allows paragraphs' do
239
+ it 'supports "> callout" and ">callout", parses inner markdown and allows paragraphs' do
289
240
  mds = []
290
241
  mds << <<-MARKDOWN
291
242
  Testing
@@ -329,12 +280,12 @@ more callout</p>
329
280
  HTML
330
281
 
331
282
  mds.each do |md|
332
- assert_github_result(md, html)
283
+ assert_parsing_result(md, html)
333
284
  end
334
285
  end
335
286
  end
336
287
 
337
- it 'github markdown generates separate special blocks from blockquotes separated by empty lines' do
288
+ it 'generates separate special blocks from blockquotes separated by empty lines' do
338
289
  md = <<-MARKDOWN
339
290
  > warning
340
291
  > foo
@@ -355,10 +306,10 @@ more callout</p>
355
306
  </div>
356
307
  HTML
357
308
 
358
- assert_github_result md, html
309
+ assert_parsing_result md, html
359
310
  end
360
311
 
361
- it 'github markdown supports tables' do
312
+ it 'supports tables' do
362
313
  md = <<-MARKDOWN
363
314
  | A | B |
364
315
  | --- | --- |
@@ -368,10 +319,12 @@ more callout</p>
368
319
 
369
320
  html = <<-HTML
370
321
  <table>
371
- <thead><tr>
322
+ <thead>
323
+ <tr>
372
324
  <th>A</th>
373
325
  <th>B</th>
374
- </tr></thead>
326
+ </tr>
327
+ </thead>
375
328
  <tbody>
376
329
  <tr>
377
330
  <td>1</td>
@@ -385,50 +338,45 @@ more callout</p>
385
338
  </table>
386
339
  HTML
387
340
 
388
- assert_github_result md, html
341
+ assert_parsing_result md, html
389
342
  end
390
343
 
391
- it "does emdashes both in all flavours" do
344
+ it "does emdashes" do
392
345
  md = "foo -- bar"
393
346
  html = '<p>foo – bar</p>'
394
- assert_all_flavours_result(md, html)
347
+ assert_parsing_result md, html
395
348
  end
396
349
 
397
350
  it 'converts relative links with missing initial slashes to article links' do
398
351
  ['foo', 'foo/bar', 'foo#bar', '123'].each do |href|
399
352
  md = "[link](#{href})"
400
353
  html = "<p><a href=\"/articles/#{href}\">link</a></p>"
401
- assert_maruku_result md, html
402
- assert_github_result md, html
354
+ assert_parsing_result md, html
403
355
  end
404
356
  end
405
357
 
406
358
  it 'converts "articles/foo relative links with missing initial slashes to article links' do
407
359
  md = '[link](articles/foo)'
408
360
  html = '<p><a href="/articles/foo">link</a></p>'
409
- assert_maruku_result md, html
410
- assert_github_result md, html
361
+ assert_parsing_result md, html
411
362
 
412
363
  md = '[link](articles/foo#bar)'
413
364
  html = '<p><a href="/articles/foo#bar">link</a></p>'
414
- assert_maruku_result md, html
415
- assert_github_result md, html
365
+ assert_parsing_result md, html
416
366
  end
417
367
 
418
368
  it 'does not alter relative links with initial slashes nor absolute links nor anchor links to the same doc' do
419
369
  ['http://foo.com', 'https://foo.com', '/foo', '/foo/bar', '/foo#bar', '#foo', '/123', 'mailto:foo@foobar.com'].each do |href|
420
370
  md = "[link](#{href})"
421
371
  html = "<p><a href=\"#{href}\">link</a></p>"
422
- assert_maruku_result md, html
423
- assert_github_result md, html
372
+ assert_parsing_result md, html
424
373
  end
425
374
  end
426
375
 
427
376
  it 'does not add href attribute to links where it does not exist' do
428
377
  md = '<a name="heh"></a>'
429
378
  html = '<p><a name="heh"></a></p>'
430
- assert_maruku_result md, html
431
- assert_github_result md, html
379
+ assert_parsing_result md, html
432
380
  end
433
381
 
434
382
 
@@ -439,19 +387,7 @@ more callout</p>
439
387
  > bar
440
388
  MARKDOWN
441
389
 
442
- html_maruku = <<-HTML
443
- <blockquote>
444
- <p>foo</p>
445
- </blockquote>
446
- <p class="devcenter-parser-special-block-separator" style="display:none"> </p>
447
- <blockquote>
448
- <p>bar</p>
449
- </blockquote>
450
- HTML
451
-
452
- assert_maruku_result md, html_maruku
453
-
454
- html_github = <<-HTML
390
+ html = <<-HTML
455
391
  <blockquote>
456
392
  <p>foo</p>
457
393
  </blockquote>
@@ -463,7 +399,7 @@ more callout</p>
463
399
  </blockquote>
464
400
  HTML
465
401
 
466
- assert_github_result md, html_github
402
+ assert_parsing_result md, html
467
403
  end
468
404
 
469
405
  end
@@ -471,31 +407,18 @@ more callout</p>
471
407
 
472
408
  # helpers
473
409
 
474
- def assert_all_flavours_result(md, expected)
475
- [:github, :maruku].each { |flavour| assert_parsing_result(md, flavour, expected) }
476
- end
477
-
478
- def assert_maruku_result(md, expected)
479
- assert_parsing_result md, :maruku, expected
480
- end
481
-
482
- def assert_github_result(md, expected)
483
- assert_parsing_result md, :github, expected
484
- end
485
-
486
- def assert_parsing_result(md, flavour, expected)
487
- result = DevcenterParser.to_html(md, flavour)
488
- assert_equal expected.strip, result.strip, "Failed when parsing\n#{md}\nwith the #{flavour} flavour.\n\nExpected:\n#{expected}\n\nActual result:\n#{result}\n\n"
410
+ def assert_parsing_result(md, expected)
411
+ result = DevcenterParser.to_html(md)
412
+ assert_equal expected.strip, result.strip, "Failed when parsing\n#{md}\n.\n\nExpected:\n#{expected}\n\nActual result:\n#{result}\n\n"
489
413
  end
490
414
 
491
- def assert_parsing_unsanitized_result(md, flavour, expected)
492
- result = DevcenterParser.to_unsanitized_html(md, flavour)
493
- assert_equal expected.strip, result.strip, "Failed when parsing on unsanitized mode\n#{md}\nwith the #{flavour} flavour.\n\nExpected:\n#{expected}\n\nActual result:\n#{result}\n\n"
415
+ def assert_parsing_unsanitized_result(md, expected)
416
+ result = DevcenterParser.to_unsanitized_html(md)
417
+ assert_equal expected.strip, result.strip, "Failed when parsing on unsanitized mode\n#{md}\n.\n\nExpected:\n#{expected}\n\nActual result:\n#{result}\n\n"
494
418
  end
495
419
 
496
420
  def assert_header_id(md, header, id)
497
- assert DevcenterParser.to_html(md, :github).include?("<#{header} id=\"#{id}\">"), "GitHub does not generate a #{header} with id #{id}"
498
- assert DevcenterParser.to_html(md, :maruku).include?("<#{header} id=\"#{id}\">"), "Maruku does not generate a #{header} with id #{id}"
421
+ assert DevcenterParser.to_html(md).include?("<#{header} id=\"#{id}\">"), "GitHub does not generate a #{header} with id #{id}"
499
422
  end
500
423
 
501
424
  end
metadata CHANGED
@@ -1,41 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devcenter-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.9
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Heroku
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-09 00:00:00.000000000 Z
11
+ date: 2015-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: maruku
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '='
18
- - !ruby/object:Gem::Version
19
- version: 0.7.0
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - '='
25
- - !ruby/object:Gem::Version
26
- version: 0.7.0
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: nokogiri
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - ! '>='
17
+ - - ">="
32
18
  - !ruby/object:Gem::Version
33
19
  version: 1.4.4
34
20
  type: :runtime
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - ! '>='
24
+ - - ">="
39
25
  - !ruby/object:Gem::Version
40
26
  version: 1.4.4
41
27
  - !ruby/object:Gem::Dependency
@@ -70,28 +56,28 @@ dependencies:
70
56
  name: minitest
71
57
  requirement: !ruby/object:Gem::Requirement
72
58
  requirements:
73
- - - ! '>'
59
+ - - ">"
74
60
  - !ruby/object:Gem::Version
75
61
  version: '2.0'
76
62
  type: :development
77
63
  prerelease: false
78
64
  version_requirements: !ruby/object:Gem::Requirement
79
65
  requirements:
80
- - - ! '>'
66
+ - - ">"
81
67
  - !ruby/object:Gem::Version
82
68
  version: '2.0'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: rake
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
- - - ! '>='
73
+ - - ">="
88
74
  - !ruby/object:Gem::Version
89
75
  version: '0'
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
- - - ! '>='
80
+ - - ">="
95
81
  - !ruby/object:Gem::Version
96
82
  version: '0'
97
83
  description: Parser for Heroku Dev Center's content
@@ -109,7 +95,6 @@ files:
109
95
  - lib/devcenter-parser.rb
110
96
  - lib/devcenter-parser/github_parser.rb
111
97
  - lib/devcenter-parser/header_id_generator.rb
112
- - lib/devcenter-parser/maruku_parser.rb
113
98
  - lib/devcenter-parser/version.rb
114
99
  - test/devcenter-parser_test.rb
115
100
  - test/header_id_generator_test.rb
@@ -122,17 +107,17 @@ require_paths:
122
107
  - lib
123
108
  required_ruby_version: !ruby/object:Gem::Requirement
124
109
  requirements:
125
- - - ! '>='
110
+ - - ">="
126
111
  - !ruby/object:Gem::Version
127
112
  version: '0'
128
113
  required_rubygems_version: !ruby/object:Gem::Requirement
129
114
  requirements:
130
- - - ! '>='
115
+ - - ">="
131
116
  - !ruby/object:Gem::Version
132
117
  version: '0'
133
118
  requirements: []
134
119
  rubyforge_project:
135
- rubygems_version: 2.2.2
120
+ rubygems_version: 2.4.5.1
136
121
  signing_key:
137
122
  specification_version: 4
138
123
  summary: Parser for Heroku Dev Center's content
@@ -1,28 +0,0 @@
1
- module MarukuParser
2
- extend self
3
-
4
- def self.parse(markdown)
5
- html = Maruku.new(markdown, :on_error => :raise).to_html
6
- doc = Nokogiri::HTML::DocumentFragment.parse(html)
7
- code_blocks(doc)
8
- doc
9
- end
10
-
11
- # This parser seem to deal correctly with tags inside HTML comments
12
- def self.remove_tags_inside_html_comments(markdown)
13
- markdown
14
- end
15
-
16
- private
17
-
18
- def self.code_blocks(doc)
19
- doc.css('pre>code').each do |node|
20
- if match = node.content.match(/\A\s*:::\s*(\w+)/)
21
- lang = match[1]
22
- node.content = node.content.gsub(/\A\s*:::\s*\w+\n/, '')
23
- node['class'] = lang
24
- end
25
- end
26
- doc
27
- end
28
- end