slodown 0.1.3 → 0.2.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 49f19f4efa582699624089df1ceb43dcb0f095a9
4
+ data.tar.gz: 473dba59b4920bf8bd07bec99c5b5e3bdb01ef46
5
+ SHA512:
6
+ metadata.gz: 018e3cbab23eada8eae8f8fc3fa805cb9b5333fb2a50473a2e7330badf61af9bbce253573517a8db0e9993f9f3222c082dfe464bafe14ef265cc6178dd5ca77c
7
+ data.tar.gz: 7110e04ac94168c0d14230c812a46fa6f914a1194e6869485052ef1292f2af416e2eaa63b10c2c19f6ba55df40c7bda435d5f800eddb1d82973e9cb9f77cb05c
data/README.md CHANGED
@@ -9,14 +9,13 @@
9
9
  Here's what slodown does by default:
10
10
 
11
11
  - **render extended Markdown into HTML**. It uses the [kramdown](http://kramdown.rubyforge.org/) library, so yes, footnotes are supported!
12
- - **adds syntax highlighting to Markdown code blocks** through [CodeRay](http://coderay.rubychan.de/).
13
- - **supports super-easy rich media embeds**, [sloblog.io-style](http://sloblog.io/~hmans/qhdsk2SMoAU). Just point the Markdown image syntax at, say, a Youtube video, and slodown will fetch the complete embed code through the magic of [ruby-oembed](https://github.com/judofyr/ruby-oembed).
12
+ - **add syntax highlighting to Markdown code blocks** through [CodeRay](http://coderay.rubychan.de/).
13
+ - **support super-easy rich media embeds**, [sloblog.io-style](http://sloblog.io/~hmans/qhdsk2SMoAU). Just point the Markdown image syntax at, say, a Youtube video, and slodown will fetch the complete embed code through the magic of [ruby-oembed](https://github.com/judofyr/ruby-oembed).
14
14
  - **auto-link contained URLs** using [Rinku](https://github.com/vmg/rinku), which is smart enough to not auto-link URLs contained in, say, code blocks.
15
15
  - **sanitize the generated HTML** using the white-list based [sanitize](https://github.com/rgrove/sanitize) gem.
16
16
 
17
17
  slodown is an extraction from [sloblog.io](http://sloblog.io). It is very easy to extend or modify, as it's just a plain old Ruby class you can inherit from.
18
18
 
19
-
20
19
  ## Installation
21
20
 
22
21
  Add this line to your application's Gemfile:
@@ -33,7 +32,7 @@ Or install it yourself as:
33
32
 
34
33
  ## Usage
35
34
 
36
- For every piece of user input that needs to be rendered, create an instance of `Slodown::Formatter` with the source text and use it to perform somre or all transformations on it. Finally, call `#to_s` to get the rendered output.
35
+ For every piece of user input that needs to be rendered, create an instance of `Slodown::Formatter` with the source text and use it to perform some or all transformations on it. Finally, call `#to_s` to get the rendered output.
37
36
 
38
37
  ### Examples:
39
38
 
@@ -41,6 +40,9 @@ For every piece of user input that needs to be rendered, create an instance of `
41
40
  # let's create an instance to work with
42
41
  formatter = Slodown::Formatter.new(text)
43
42
 
43
+ # just extract metadata
44
+ formatter.extract_metadata.to_s
45
+
44
46
  # just render Markdown to HTML
45
47
  formatter.markdown.to_s
46
48
 
@@ -54,12 +56,47 @@ formatter.sanitize.to_s
54
56
  formatter.markdown.sanitize.to_s
55
57
 
56
58
  # this is the whole deal:
57
- formatter.markdown.autolink.sanitize.to_s
59
+ formatter.extract_metadata.markdown.autolink.sanitize.to_s
58
60
 
59
61
  # which is the same as:
60
62
  formatter.complete.to_s
61
63
  ~~~
62
64
 
65
+ ### Metadata
66
+
67
+ Slodown allows metadata, such as the creation date, to be defined in the text to be processed:
68
+
69
+ ~~~markdown
70
+ #+title: Slodown
71
+ #+created_at: 2014-03-01 13:51:12 CET
72
+ # Installation
73
+
74
+ Add this line to your application's Gemfile:
75
+
76
+ gem 'slodown'
77
+
78
+ ...
79
+ ~~~
80
+
81
+ Metadata can be accessed with `Slodown::Formatter#metadata`:
82
+
83
+ ~~~ruby
84
+ formatter.metadata[:title] # => "Slodown"
85
+ ~~~
86
+
87
+ ## OEmbed support
88
+
89
+ Slodown extends the Markdown image syntax to support OEmbed-based embeds.
90
+ Anything supported by the great [OEmbed gem](https://github.com/judofyr/ruby-oembed) will work. Just supply the URL:
91
+
92
+ ~~~markdown
93
+ ![youtube video](https://www.youtube.com/watch?v=oHg5SJYRHA0)
94
+ ~~~
95
+
96
+ Some OEmbed providers will return IFRAME-based embeds. If you want to control
97
+ which hosts are allowed to have IFRAMEs on your site, override the `Formatter#allowed_iframe_hosts` method to return a regular expression that will be matched against the IFRAME source URL's host. Please note that this will also apply to
98
+ IFRAME HTML tags added by the user directly.
99
+
63
100
  ## Hints
64
101
 
65
102
  * If you want to add more transformations or change the behavior of the `#complete` method, just subclass `Slodown::Formatter` and go wild. :-)
@@ -73,8 +110,27 @@ formatter.complete.to_s
73
110
 
74
111
  ## Contributing
75
112
 
76
- 1. Fork it
77
- 2. Create your feature branch (`git checkout -b my-new-feature`)
78
- 3. Commit your changes (`git commit -am 'Add some feature'`)
79
- 4. Push to the branch (`git push origin my-new-feature`)
80
- 5. Create new Pull Request
113
+ Just like with my other gems, I am trying to keep slodown as sane (and small) as possible. If you
114
+ want to contribute code, **please talk to me before writing a patch or submitting
115
+ a pull request**! I'm serious about keeping things focused and would hate to cause
116
+ unnecessary disappointment. Thank you.
117
+
118
+ If you're still set on submitting a pull request, please consider the following:
119
+
120
+ 1. Create your pull request from a _feature branch_.
121
+ 2. The pull request must only contain changes _related to the feature_.
122
+ 3. Please include specs where it makes sense.
123
+ 4. Absolutely _no_ version bumps or similar.
124
+
125
+ ## Version History
126
+
127
+ ### 0.2.0
128
+
129
+ - Slodown is now whitelisting all domains for possible iframe/embed-based media embeds by default. If you don't want this, you can override `Formatter#allowed_iframe_hosts` to return a regular expression that will match against the embed URL's host.
130
+ - Bumped minimum required version of kramdown to 1.5.0 for all the nice new syntax highlighter integrations it offers (and changes required due to deprecated/changed options.)
131
+ - Support for Twitter oEmbed (using an unfortunately deprecated API, nonetheless.)
132
+ - Added `Slodown::Formatter#kramdown_options`, returning a hash of kramdown configuration options. Overload this in order to customize the formatter's behavior.
133
+
134
+ ### 0.1.3
135
+
136
+ - first public release
@@ -9,7 +9,7 @@ class Kramdown::Converter::SlodownHtml < Kramdown::Converter::Html
9
9
  def convert_img(el, indent)
10
10
  oembed = OEmbed::Providers.get(el.attr['src'])
11
11
  %q(<div class="embedded %s %s">%s</div>) % [oembed.type, oembed.provider_name.parameterize, oembed.html]
12
- rescue OEmbed::NotFound
12
+ rescue StandardError => e
13
13
  super
14
14
  end
15
15
  end
@@ -7,7 +7,6 @@ require 'sanitize'
7
7
  require "kramdown/converter/slodown_html"
8
8
  require "slodown/version"
9
9
  require "slodown/formatter"
10
- require "slodown/embed_transformer"
11
10
 
12
11
  # Register all known oEmbed providers.
13
12
  #
@@ -1,34 +1,61 @@
1
1
  module Slodown
2
+ # This is the base Formatter class provided by Slodown. It works right
3
+ # out of the box if you want to use exactly the functionality provided by
4
+ # it, but in most projects, you'll probably want to create a new class
5
+ # inheriting from this one.
6
+ #
2
7
  class Formatter
3
8
  def initialize(source)
4
9
  @current = @source = source.to_s
5
10
  end
6
11
 
7
- # Runs the entire pipeline.
12
+ # Run the entire pipeline in a sane order.
8
13
  #
9
14
  def complete
10
- markdown.autolink.sanitize
15
+ extract_metadata.markdown.autolink.sanitize
11
16
  end
12
17
 
13
18
  # Convert the current document state from Markdown into HTML.
14
19
  #
15
20
  def markdown
16
- @current = Kramdown::Document.new(@current).to_slodown_html
17
- self
21
+ convert do |current|
22
+ Kramdown::Document.new(current, kramdown_options).to_slodown_html
23
+ end
18
24
  end
19
25
 
20
26
  # Auto-link URLs through Rinku.
21
27
  #
22
28
  def autolink
23
- @current = Rinku.auto_link(@current)
24
- self
29
+ convert do |current|
30
+ Rinku.auto_link(current)
31
+ end
25
32
  end
26
33
 
27
34
  # Sanitize HTML tags.
28
35
  #
29
36
  def sanitize
30
- @current = Sanitize.clean(@current, sanitize_config)
31
- self
37
+ convert do |current|
38
+ Sanitize.clean(current, sanitize_config)
39
+ end
40
+ end
41
+
42
+ def extract_metadata
43
+ @metadata = {}
44
+
45
+ convert do |current|
46
+ current.each_line.drop_while do |line|
47
+ next false if line !~ /^#\+([a-z_]+): (.*)/
48
+
49
+ key, value = $1, $2
50
+ @metadata[key.to_sym] = value
51
+ end.join('')
52
+ end
53
+ end
54
+
55
+ # Return a hash with the extracted metadata
56
+ #
57
+ def metadata
58
+ @metadata
32
59
  end
33
60
 
34
61
  def to_s
@@ -37,12 +64,27 @@ module Slodown
37
64
 
38
65
  private
39
66
 
67
+ # Applies a conversion of the current text state.
68
+ #
69
+ def convert(&blk)
70
+ @current = blk.call(@current)
71
+ self
72
+ end
73
+
74
+ def kramdown_options
75
+ {
76
+ syntax_highlighter: 'coderay',
77
+ syntax_highlighter_opts: {
78
+ }
79
+ }
80
+ end
81
+
40
82
  def sanitize_config
41
83
  {
42
84
  elements: %w(
43
- p a span sub sup strong em div hr abbr
85
+ p br a span sub sup strong em div hr abbr s
44
86
  ul ol li
45
- blockquote pre code
87
+ blockquote pre code kbd
46
88
  h1 h2 h3 h4 h5 h6
47
89
  img object param del
48
90
  ),
@@ -66,8 +108,43 @@ module Slodown
66
108
  'li' => {'id' => ['fn']},
67
109
  'sup' => {'id' => ['fnref']}
68
110
  },
69
- transformers: EmbedTransformer
111
+ transformers: transformers
70
112
  }
71
113
  end
114
+
115
+ def allowed_iframe_hosts
116
+ # By default, allow everything. Override this to return a regular expression
117
+ # that will be matched against the iframe/embed's src URL's host.
118
+ /.*/
119
+ end
120
+
121
+ def transformers
122
+ [embed_transformer]
123
+ end
124
+
125
+ def embed_transformer
126
+ lambda do |env|
127
+ node = env[:node]
128
+ node_name = env[:node_name]
129
+
130
+ # We're fine with a bunch of stuff -- but not <iframe> and <embed> tags.
131
+ return if env[:is_whitelisted] || !env[:node].element?
132
+ return unless %w[iframe embed].include? env[:node_name]
133
+
134
+ # We're dealing with an <iframe> or <embed> tag! Let's check its src attribute.
135
+ # If its host name matches our regular expression, we can whitelist it.
136
+ uri = URI(env[:node]['src'])
137
+ return unless uri.host =~ allowed_iframe_hosts
138
+
139
+ Sanitize.clean_node!(node, {
140
+ elements: %w[iframe embed],
141
+ attributes: {
142
+ all: %w[allowfullscreen frameborder height src width]
143
+ }
144
+ })
145
+
146
+ { node_whitelist: [node] }
147
+ end
148
+ end
72
149
  end
73
150
  end
@@ -1,3 +1,3 @@
1
1
  module Slodown
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -10,18 +10,18 @@ Gem::Specification.new do |gem|
10
10
  gem.email = ["hendrik@mans.de"]
11
11
  gem.description = %q{Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering pipeline.}
12
12
  gem.summary = %q{Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering pipeline.}
13
- gem.homepage = "http://github.com/hmans/slodown"
13
+ gem.homepage = "https://github.com/hmans/slodown"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency 'kramdown', '>= 0.14.0'
20
+ gem.add_dependency 'kramdown', '>= 1.5.0'
21
21
  gem.add_dependency 'coderay', '>= 1.0.0'
22
22
  gem.add_dependency 'sanitize', '>= 2.0.0'
23
23
  gem.add_dependency 'rinku', '>= 1.7.0'
24
- gem.add_dependency 'ruby-oembed', '~> 0.8.8'
24
+ gem.add_dependency 'ruby-oembed', '>= 0.8.8'
25
25
 
26
26
  gem.add_development_dependency 'rspec', '>= 2.12.0'
27
27
  gem.add_development_dependency 'rspec-html-matchers'
@@ -9,6 +9,6 @@ describe 'basic formatting syntax' do
9
9
  # to test the whole of kramdown here. :)~
10
10
  #
11
11
  it "renders **this** as bold text" do
12
- expect(render "**foo**").to eq "<p><strong>foo</strong></p>"
12
+ expect(render "**foo**").to eq "<p><strong>foo</strong></p>\n"
13
13
  end
14
14
  end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe '#metadata' do
4
+ let(:text) do
5
+ <<-EOF.gsub(/^\t/, '')
6
+ #+title: A document with metadata
7
+ #+created_at: 2014-03-01 12:56:31 CET
8
+ # The first headline
9
+
10
+ A paragraph.
11
+ #+with: no metadata
12
+ EOF
13
+ end
14
+
15
+ let(:formatter) { Slodown::Formatter.new(text).complete }
16
+
17
+ it 'returns metadata as a hash' do
18
+ expect(formatter.metadata).to be_a(Hash)
19
+ end
20
+
21
+ it 'contains every listed key' do
22
+ expect(formatter.metadata.keys).to match_array([:title, :created_at])
23
+ end
24
+
25
+ it 'contains every listed value' do
26
+ expect(formatter.metadata.values).to match_array(['A document with metadata',
27
+ '2014-03-01 12:56:31 CET'])
28
+ end
29
+
30
+ it 'removes metadata from the source' do
31
+ expect(formatter.to_s).to_not match(/created_at/)
32
+ end
33
+
34
+ describe 'keys occuring more than once' do
35
+ let(:text) do
36
+ <<-EOF.gsub(/^\t/, '')
37
+ #+title: ignored
38
+ #+title: foo
39
+ EOF
40
+ end
41
+
42
+ it 'uses the last definition' do
43
+ expect(formatter.metadata.fetch(:title)).to eql 'foo'
44
+ end
45
+ end
46
+ end
metadata CHANGED
@@ -1,142 +1,125 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slodown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
5
- prerelease:
4
+ version: 0.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Hendrik Mans
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-01-19 00:00:00.000000000 Z
11
+ date: 2016-02-22 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: kramdown
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
- version: 0.14.0
19
+ version: 1.5.0
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: 0.14.0
26
+ version: 1.5.0
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: coderay
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: 1.0.0
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: 1.0.0
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: sanitize
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - ">="
52
46
  - !ruby/object:Gem::Version
53
47
  version: 2.0.0
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - ">="
60
53
  - !ruby/object:Gem::Version
61
54
  version: 2.0.0
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rinku
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - ">="
68
60
  - !ruby/object:Gem::Version
69
61
  version: 1.7.0
70
62
  type: :runtime
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - ">="
76
67
  - !ruby/object:Gem::Version
77
68
  version: 1.7.0
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: ruby-oembed
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ~>
73
+ - - ">="
84
74
  - !ruby/object:Gem::Version
85
75
  version: 0.8.8
86
76
  type: :runtime
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ~>
80
+ - - ">="
92
81
  - !ruby/object:Gem::Version
93
82
  version: 0.8.8
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: rspec
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - ">="
100
88
  - !ruby/object:Gem::Version
101
89
  version: 2.12.0
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - ">="
108
95
  - !ruby/object:Gem::Version
109
96
  version: 2.12.0
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: rspec-html-matchers
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ! '>='
101
+ - - ">="
116
102
  - !ruby/object:Gem::Version
117
103
  version: '0'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ! '>='
108
+ - - ">="
124
109
  - !ruby/object:Gem::Version
125
110
  version: '0'
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: rake
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
- - - ! '>='
115
+ - - ">="
132
116
  - !ruby/object:Gem::Version
133
117
  version: '0'
134
118
  type: :development
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
- - - ! '>='
122
+ - - ">="
140
123
  - !ruby/object:Gem::Version
141
124
  version: '0'
142
125
  description: Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering
@@ -147,46 +130,46 @@ executables: []
147
130
  extensions: []
148
131
  extra_rdoc_files: []
149
132
  files:
150
- - .gitignore
151
- - .rspec
152
- - .travis.yml
133
+ - ".gitignore"
134
+ - ".rspec"
135
+ - ".travis.yml"
153
136
  - Gemfile
154
137
  - LICENSE
155
138
  - README.md
156
139
  - Rakefile
157
140
  - lib/kramdown/converter/slodown_html.rb
158
141
  - lib/slodown.rb
159
- - lib/slodown/embed_transformer.rb
160
142
  - lib/slodown/formatter.rb
161
143
  - lib/slodown/version.rb
162
144
  - slodown.gemspec
163
145
  - spec/basic_formatting_spec.rb
146
+ - spec/metadata_extraction_spec.rb
164
147
  - spec/spec_helper.rb
165
- homepage: http://github.com/hmans/slodown
148
+ homepage: https://github.com/hmans/slodown
166
149
  licenses: []
150
+ metadata: {}
167
151
  post_install_message:
168
152
  rdoc_options: []
169
153
  require_paths:
170
154
  - lib
171
155
  required_ruby_version: !ruby/object:Gem::Requirement
172
- none: false
173
156
  requirements:
174
- - - ! '>='
157
+ - - ">="
175
158
  - !ruby/object:Gem::Version
176
159
  version: '0'
177
160
  required_rubygems_version: !ruby/object:Gem::Requirement
178
- none: false
179
161
  requirements:
180
- - - ! '>='
162
+ - - ">="
181
163
  - !ruby/object:Gem::Version
182
164
  version: '0'
183
165
  requirements: []
184
166
  rubyforge_project:
185
- rubygems_version: 1.8.23
167
+ rubygems_version: 2.5.1
186
168
  signing_key:
187
- specification_version: 3
169
+ specification_version: 4
188
170
  summary: Markdown + oEmbed + Sanitize + CodeRay = the ultimate user input rendering
189
171
  pipeline.
190
172
  test_files:
191
173
  - spec/basic_formatting_spec.rb
174
+ - spec/metadata_extraction_spec.rb
192
175
  - spec/spec_helper.rb
@@ -1,26 +0,0 @@
1
- module Slodown
2
- class EmbedTransformer
3
- ALLOWED_DOMAINS = %w[youtube.com soundcloud.com vimeo.com]
4
-
5
- def self.call(env)
6
- node = env[:node]
7
- node_name = env[:node_name]
8
-
9
- return if env[:is_whitelisted] || !env[:node].element?
10
- return unless %w[iframe embed].include? env[:node_name]
11
-
12
- uri = URI(env[:node]['src'])
13
- domains = ALLOWED_DOMAINS.map { |d| Regexp.escape(d) }.join("|")
14
- return unless uri.host =~ /^(.+\.)?(#{domains})/
15
-
16
- Sanitize.clean_node!(node, {
17
- elements: %w[iframe embed],
18
- attributes: {
19
- all: %w[allowfullscreen frameborder height src width]
20
- }
21
- })
22
-
23
- { node_whitelist: [node] }
24
- end
25
- end
26
- end