mangdown 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mangdown/page.rb CHANGED
@@ -5,20 +5,10 @@ module Mangdown
5
5
  attr_reader :name, :uri
6
6
 
7
7
  def initialize(name, uri)
8
- name.gsub!(/_/, ' ') && name.gsub!(/-/, '')
9
- @name = name.sub(/([^\d])(\d+)(\.\w+)$/) {
10
- "#{Regexp.last_match[1]}" +
11
- "#{Regexp.last_match[2].to_s.rjust(3, '0')}" +
12
- "#{Regexp.last_match[3]}"
13
- }
8
+ @name = name
14
9
  @uri = Mangdown::Uri.new(uri)
15
10
  end
16
11
 
17
- # space ship operator for sorting
18
- def <=>(other)
19
- self.name <=> other.name
20
- end
21
-
22
12
  # explicit conversion to page
23
13
  def to_page
24
14
  self
@@ -26,13 +16,15 @@ module Mangdown
26
16
 
27
17
  # downloads to specified directory
28
18
  def download_to(dir = Dir.pwd)
29
- path = dir + '/' + name
30
- # don't download again
31
- return if File.exist?(path)
32
- image = open(uri).read
33
- Tools.no_time_out { File.open(path, 'wb') { |file| file.write(image) } }
19
+ return if File.exist?(path = file_path(dir))
20
+ File.open(path, 'wb') { |file| file.write(Tools.get(uri)) }
21
+ FileUtils.mv(path, "#{path}.#{Tools.file_type(path)}")
34
22
  rescue SocketError => error
35
23
  STDERR.puts( "#{error.message} | #{name} | #{uri}" )
36
24
  end
25
+
26
+ def file_path(dir)
27
+ Tools.relative_or_absolute_path(dir, name)
28
+ end
37
29
  end
38
30
  end
@@ -1,87 +1,25 @@
1
1
  module Mangdown
2
2
  class Properties
3
3
 
4
- attr_reader :info, :type
5
-
6
- def initialize(site)
7
- @info = Hash.new
8
-
9
- case site.to_s
10
- when /mangareader/
11
- @type = :mangareader
12
- mangareader
13
- when /mangapanda/
14
- #mangapanda is a mirror of mangareader
15
- #that being said, I really don't think this works
16
- #especially with @info[:root]
17
- @type = :mangapanda
18
- mangapanda
19
- when /mangafox/
20
- @type = :mangafox
21
- mangafox
4
+ def initialize(uri, site = nil, doc = nil)
5
+ adapter_class = ADAPTERS.find { |adapter|
6
+ (site||uri).to_s =~ /#{adapter.to_s.split('::').last.downcase}/
7
+ }
8
+ if adapter_class
9
+ @adapter = adapter_class.new(uri, doc)
22
10
  else
23
- nil
11
+ raise ArgumentError,
12
+ "Bad Site: No Properties Specified for Site <#{uri}>"
24
13
  end
25
14
  end
26
15
 
27
- def mangareader
28
- @info[:manga_list_css_klass] = 'ul.series_alpha li a'
29
- @info[:manga_css_klass] = 'div#chapterlist td a'
30
- @info[:chapter_klass] = MRChapter
31
- @info[:root] ||= 'http://www.mangareader.net'
32
- @info[:manga_link_prefix] ||= @info[:root]
33
- @info[:reverse] = false
34
- @info[:manga_uri_regex] =
35
- /#{@info[:root]}(\/\d+)?(\/[^\/]+)(\.html)?/i
36
- @info[:chapter_uri_regex] =
37
- /#{@info[:root]}(\/[^\/]+){1,2}\/(\d+|chapter-\d+\.html)/i
38
- @info[:page_uri_regex] = /.+\.(png|jpg|jpeg)$/i
39
- end
40
-
41
- def mangapanda
42
- @info[:root] = 'http://www.mangapanda.com'
43
- @info[:manga_link_prefix] = @info[:root]
44
- mangareader
45
- end
46
-
47
- def mangafox
48
- @info[:manga_list_css_klass] = 'div.manga_list li a'
49
- @info[:manga_css_klass] = 'a.tips'
50
- @info[:chapter_klass] = MFChapter
51
- @info[:root] = 'http://mangafox.me'
52
- @info[:manga_link_prefix] = ''
53
- @info[:reverse] = true
54
- @info[:manga_uri_regex] =
55
- /#{@info[:root]}\/manga\/[^\/]+?\//i
56
- @info[:chapter_uri_regex] = /
57
- #{@info[:manga_uri_regex]}
58
- (v\d+\/)?(c\d+\/)(1\.html)
59
- /xi
60
- @info[:page_uri_regex] = /.+\.(png|jpg|jpeg)$/i
61
- end
62
-
63
- def is_manga?(obj)
64
- obj.uri.slice(@info[:manga_uri_regex]) == obj.uri
65
- end
66
-
67
- def is_chapter?(obj)
68
- obj.uri.slice(@info[:chapter_uri_regex]) == obj.uri
69
- end
70
-
71
- def is_page?(obj)
72
- obj.uri.slice(@info[:page_uri_regex]) == obj.uri
73
- end
74
-
75
- def empty?
76
- @info.empty?
77
- end
78
-
79
- private
16
+ private
80
17
  def method_missing(method, *args, &block)
81
- # this should probably be if @info.has_key?(method)
82
- # or more consisely @info.fetch(method) { super }
83
- return @info[method] unless @info[method].nil?
84
- super
18
+ if @adapter.respond_to?(method)
19
+ @adapter.__send__(method, *args, &block)
20
+ else
21
+ super
22
+ end
85
23
  end
86
24
  end
87
25
  end
@@ -1,21 +1,83 @@
1
- require 'timeout'
1
+ require 'pathname'
2
+ require 'typhoeus'
2
3
 
3
4
  module Mangdown
4
5
  module Tools
5
6
  extend self
6
7
 
7
8
  def get_doc(uri)
8
- @doc = ::Nokogiri::HTML(open(uri))
9
+ @doc = ::Nokogiri::HTML(get(uri))
10
+ end
11
+
12
+ def get(uri)
13
+ Typhoeus.get(uri).body
9
14
  end
10
15
 
11
16
  def get_root(uri)
12
17
  @root = ::URI::join(uri, "/").to_s[0..-2]
13
18
  end
14
-
15
- def no_time_out(tries = 3, &block)
16
- timeout(30) { yield }
17
- rescue Timeout::Error
18
- retry if (tries -= 1) >= 0
19
+
20
+ def relative_or_absolute_path(*sub_paths)
21
+ root = (sub_paths.first.to_s =~ /^\// ? sub_paths.shift : Dir.pwd)
22
+ Pathname.new(root).join(*sub_paths)
23
+ end
24
+
25
+ def file_type(path)
26
+ FileMagic.new.file(path.to_s).slice(/^\w+/).downcase
27
+ end
28
+
29
+ def hydra_streaming(objects)
30
+ hydra = Typhoeus::Hydra.hydra
31
+
32
+ requests = objects.map {|obj|
33
+ next unless yield(:before, obj)
34
+
35
+ request = Typhoeus::Request.new(obj.uri)
36
+ request.on_headers do |response|
37
+ yield((response.success? ? :succeeded : :failed), obj)
38
+ end
39
+ request.on_body do |chunk|
40
+ yield(:body, obj, chunk)
41
+ end
42
+ request.on_complete do |response|
43
+ yield(:complete, obj)
44
+ end
45
+
46
+ hydra.queue(request)
47
+ request
48
+ }.compact
49
+
50
+ hydra.run
51
+ requests
52
+ end
53
+
54
+ def hydra(objects)
55
+ hydra = Typhoeus::Hydra.hydra
56
+
57
+ requests = objects.map {|obj|
58
+ request = Typhoeus::Request.new(obj.uri)
59
+ request.on_complete do |response|
60
+ if response.success?
61
+ yield(obj, response.body) if block_given?
62
+ elsif response.timed_out?
63
+ STDERR.puts "#{obj.uri}: got a time out"
64
+ elsif response.code == 0
65
+ STDERR.puts "#{obj.uri}: #{response.return_message}"
66
+ else
67
+ STDERR.puts "#{obj.uri}: " +
68
+ "HTTP request failed: #{response.code.to_s} \n" +
69
+ "But maybe it succeeded..."
70
+ if response.body
71
+ yield(obj, response.body) if block_given?
72
+ end
73
+ end
74
+ end
75
+ hydra.queue(request)
76
+ request
77
+ }
78
+
79
+ hydra.run
80
+ requests
19
81
  end
20
82
  end
21
83
  end
data/lib/mangdown.rb CHANGED
@@ -1,12 +1,17 @@
1
1
  require 'uri'
2
- require 'open-uri'
3
2
  require 'nokogiri'
4
3
  require 'yaml'
5
- require 'timeout'
6
4
  require 'zip'
5
+ require 'filemagic'
7
6
 
8
7
  require_relative 'mangdown/mangdown'
9
8
 
9
+ require_relative 'mangdown/adapter'
10
+ adapters = "#{File.expand_path('../mangdown/adapter', __FILE__)}/*.rb"
11
+ Dir[adapters].each do |f|
12
+ require_relative f
13
+ end
14
+
10
15
  require_relative 'mangdown/tools'
11
16
  require_relative 'mangdown/properties'
12
17
  require_relative 'mangdown/uri'
@@ -10,7 +10,7 @@ describe Mangdown::Chapter do
10
10
 
11
11
  before do
12
12
  VCR.insert_cassette 'events', record: :new_episodes
13
- @chapter = Mangdown::MRChapter.new(chapter_name, uri)
13
+ @chapter = Mangdown::Chapter.new(chapter_name, uri)
14
14
  end
15
15
 
16
16
  after do
@@ -47,13 +47,8 @@ describe Mangdown::Manga do
47
47
  end
48
48
 
49
49
  describe 'to_chapter' do
50
- it 'must give a manga read chapter for a manga reader chapter' do
51
- @md_chapter.to_chapter.must_be_instance_of Mangdown::MRChapter
52
- end
53
-
54
- it 'must give a manga fox chapter for a manga fox chapter' do
55
- md = Mangdown::MDHash.new(uri: c2_uri, name: c2_name)
56
- md.to_chapter.must_be_instance_of Mangdown::MFChapter
50
+ it 'must give a chapter' do
51
+ @md_chapter.to_chapter.must_be_instance_of Mangdown::Chapter
57
52
  end
58
53
 
59
54
  it 'must raise an exception if not a chapter' do
@@ -10,6 +10,7 @@ describe Mangdown::Page do
10
10
  end
11
11
 
12
12
  describe 'new' do
13
+ let(:download_path) {File.expand_path('../../../../tmp', __FILE__)}
13
14
  let(:mangareader) {
14
15
  Mangdown::Page.new(
15
16
  "Naruto 1 - Page 1.jpg",
@@ -29,10 +30,30 @@ describe Mangdown::Page do
29
30
  )
30
31
  }
31
32
 
32
- it 'have a valid name' do
33
+ =begin
34
+ it 'must have a valid name' do
33
35
  [mangareader, mangafox, mangafox2].each do |type|
34
- type.name.must_match(/\d{3}\.jpg$/)
36
+ type.name.must_match(/\d{3}$/)
35
37
  end
36
38
  end
39
+ =end
40
+
41
+ it 'must download image to dir' do
42
+ [mangareader, mangafox, mangafox2].each do |type|
43
+ type.download_to(download_path)
44
+ path = "#{download_path}/#{type.name}.jpeg"
45
+ exists = File.exist?(path)
46
+ exists.must_equal true
47
+ File.delete(path) if exists
48
+ end
49
+ end
50
+
51
+ it 'must sort pages by name' do
52
+ pages = [mangareader, mangafox, mangafox2] * 20
53
+ sorted = pages.shuffle.sort
54
+ sort_by_name = pages.shuffle.sort_by(&:name)
55
+
56
+ sorted.must_equal sort_by_name
57
+ end
37
58
  end
38
59
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mangdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - jphager2
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-02 00:00:00.000000000 Z
11
+ date: 2015-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -94,6 +94,34 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.0.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: typhoeus
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.7.1
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.7.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: ruby-filemagic
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: webmock
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -136,7 +164,7 @@ dependencies:
136
164
  - - ">="
137
165
  - !ruby/object:Gem::Version
138
166
  version: '0'
139
- description: A gem to download Manga, (pg integration in dev)
167
+ description: A gem to download Manga, (now using hydra)
140
168
  email: jphager2@gmail.com
141
169
  executables: []
142
170
  extensions: []
@@ -150,6 +178,12 @@ files:
150
178
  - db/pages_script.rb
151
179
  - doc/help.txt
152
180
  - lib/mangdown.rb
181
+ - lib/mangdown/adapter.rb
182
+ - lib/mangdown/adapter/mangafox.rb
183
+ - lib/mangdown/adapter/mangahere.rb
184
+ - lib/mangdown/adapter/mangapanda.rb
185
+ - lib/mangdown/adapter/mangareader.rb
186
+ - lib/mangdown/adapter/wiemanga.rb
153
187
  - lib/mangdown/cbz.rb
154
188
  - lib/mangdown/chapter.rb
155
189
  - lib/mangdown/commands.rb
@@ -178,14 +212,6 @@ files:
178
212
  - spec/lib/mangdown/properties_spec.rb
179
213
  - spec/lib/mangdown/tools_spec.rb
180
214
  - spec/lib/mangdown/uri_spec.rb
181
- - spec/mangdown/chapter_spec.rb
182
- - spec/mangdown/commands_spec.rb
183
- - spec/mangdown/manga_list_spec.rb
184
- - spec/mangdown/manga_spec.rb
185
- - spec/mangdown/mangdown_hash_spec.rb
186
- - spec/mangdown/page_spec.rb
187
- - spec/mangdown/popular_spec.rb
188
- - spec/mangdown/tools_spec.rb
189
215
  - spec/spec_helper.rb
190
216
  homepage: https://github.com/jphager2/mangdown
191
217
  licenses:
@@ -210,5 +236,5 @@ rubyforge_project:
210
236
  rubygems_version: 2.2.2
211
237
  signing_key:
212
238
  specification_version: 4
213
- summary: Downloads Manga, 0.10.0 has some big API changes
239
+ summary: Downloads Manga (no longer MRChapter nor MFChapter)
214
240
  test_files: []
@@ -1,80 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Mangdown
4
- @@chapter_hash = MDHash.new(
5
- uri: 'http://www.mangareader.net/bleach/537',
6
- name: 'Bleach 537',
7
- manga: 'Bleach',
8
- chapter: '537',
9
- )
10
-
11
- chapter = @@chapter_hash.to_chapter
12
- chapter.download
13
-
14
- STUB_PATH = File.expand_path('../../objects/chapter.yml', __FILE__)
15
- File.open(STUB_PATH, 'w+') do |file|
16
- file.write(chapter.to_yaml)
17
- end
18
-
19
- describe MRChapter do
20
- before(:each) do
21
- print 'o'
22
- @chapter = YAML.load(File.open(Mangdown::STUB_PATH, 'r').read)
23
- end
24
-
25
- context "when chapter is initialized" do
26
- # sanity check, not necessary
27
- it "should have the right chapter uri" do
28
- expect(@chapter.uri).to eq(@@chapter_hash[:uri])
29
- end
30
-
31
- it "should get pages when initialized" do
32
- expect(@chapter.pages.size).to be > 0
33
- end
34
-
35
- it "should have the right first page at the 0 index" do
36
- expect(@chapter.pages.first.name).to eq(
37
- 'Bleach 537 - Page 1.jpg')
38
- end
39
-
40
- it "should have the right last page at the -1 index" do
41
- expect(@chapter.pages.last.name).to eq(
42
- 'Bleach 537 - Page 21.jpg')
43
- end
44
-
45
- context "as a MFChapter" do
46
- it "should have pages" do
47
- hash = MDHash.new(
48
- uri: 'http://mangafox.me/manga/kitsune_no_yomeiri/v01/c001/1.html',
49
- name: 'Kitsune no Yomeiri 1',
50
- manga: 'Kitsune no Yomeiri',
51
- chapter: 1,
52
- )
53
-
54
- chapter = hash.to_chapter
55
-
56
- expect(chapter.pages).not_to be_empty
57
- end
58
- end
59
- end
60
-
61
- context "when chapter is downloaded" do
62
- it "should have a sub directory in the current directory" do
63
- dir = Dir.pwd
64
- chapter_path = dir + '/' + @chapter.name
65
-
66
- expect(Dir.glob(dir + '/*' )).to include(chapter_path)
67
- end
68
-
69
- it "should download all it's pages" do
70
- dir = Dir.pwd + '/' + @chapter.name
71
-
72
- pages_in_dir = Dir.glob(dir + '/*.jpg').length
73
- expect(pages_in_dir).to eq(@chapter.pages.length)
74
- end
75
- end
76
- end
77
- end
78
-
79
-
80
-
@@ -1,66 +0,0 @@
1
- require 'spec_helper.rb'
2
-
3
- module M
4
-
5
- describe "commands" do
6
- before(:all) do
7
- hash = MDHash.new(
8
- uri: 'http://www.mangareader.net/6-no-trigger',
9
- name: '6 no Trigger'
10
- )
11
- @manga = hash.to_manga
12
-
13
- hash = MDHash.new(
14
- uri: 'http://mangafox.me/manga/naruto/',
15
- name: 'Naruto'
16
- )
17
- @mf_manga = hash.to_manga
18
-
19
- M.download(@manga, 1, 3)
20
- M.cbz("./#{@manga.name}")
21
- end
22
-
23
- it "should find a manga with the find command" do
24
- m = M.find('Naruto')
25
- expect(m.first[:name]).to eq('Naruto')
26
- end
27
-
28
- it "should find a list of manga with the find command" do
29
- m = M.find('Trigger')
30
- expect(m.length).to be > 1
31
- end
32
-
33
- it "should find a manga from MF" do
34
- m = M.find('naruto')
35
- mangafox_hash_found = m.find {|hash| hash[:uri] =~ /mangafox/}
36
- expect(mangafox_hash_found).not_to be_nil
37
- end
38
-
39
- #for sanity
40
- it "should create a list of MDHash" do
41
- expect(@mf_manga.chapters_list[10]).to be_kind_of(MDHash)
42
- end
43
-
44
- it "should create MFChapter for mf manga" do
45
- @mf_manga.get_chapter(10)
46
- expect(@mf_manga.chapters.first).to be_kind_of(MFChapter)
47
- end
48
-
49
- it "should download a manga from MF" do
50
- dir = Dir.pwd
51
- #@mf_manga.remove_chapters
52
- M.download(@mf_manga, 500, 501)
53
- expect(Dir.glob(dir + "/#{@mf_manga.name}/*").length).to eq(2)
54
- end
55
-
56
- it "should download a manga with the download command" do
57
- chps = Dir.glob("#{Dir.pwd}/#{@manga.name}/*/")
58
- expect(chps.length).to eq(3)
59
- end
60
-
61
- it "should cbz a manga with the cbz command" do
62
- cbz_s = Dir.glob("#{Dir.pwd}/#{@manga.name}/*.cbz")
63
- expect(cbz_s.length).to eq(3)
64
- end
65
- end
66
- end
@@ -1,17 +0,0 @@
1
- require 'spec_helper.rb'
2
-
3
- module Mangdown
4
- describe MangaList do
5
- before do
6
- @manga_list = MangaList.new(
7
- 'http://www.mangareader.net/alphabetical')
8
- end
9
-
10
- subject {@manga_list}
11
-
12
- it "should not have an empty manga list" do
13
- expect(subject.mangas).to_not be_empty
14
- end
15
- end
16
- end
17
-
@@ -1,92 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- module Mangdown
4
- @@manga_hash = MDHash.new(
5
- uri: 'http://www.mangareader.net/94/bleach.html',
6
- name: 'Bleach'
7
- )
8
-
9
- manga = @@manga_hash.to_manga
10
-
11
- MANGA_STUB_PATH = File.expand_path('../../objects/manga.yml',
12
- __FILE__)
13
-
14
- File.open(MANGA_STUB_PATH, 'w+') do |file|
15
- file.write(manga.to_yaml)
16
- end
17
-
18
-
19
- describe Manga do
20
- before(:each) do
21
- print '@'
22
- @manga = YAML.load(File.open(Mangdown::MANGA_STUB_PATH, 'r').read)
23
- end
24
-
25
- context "When a manga is initialized" do
26
- it "should not have chapters" do
27
- expect(@manga.chapters).to be_empty
28
- end
29
-
30
- it "should have chapters listed in chapters_list" do
31
- expect(@manga.chapters_list).not_to be_empty
32
- end
33
-
34
- context "as a MangaFox manga" do
35
- it "should have chapters" do
36
- hash = MDHash.new(
37
- uri: 'http://mangafox.me/manga/masca_the_beginning/',
38
- name: 'Masca: The Beginning'
39
- )
40
- manga = hash.to_manga
41
- expect(manga.chapters_list).not_to be_empty
42
- end
43
- end
44
- end
45
-
46
- context "with Bleach manga" do
47
- it "should have more that 500 chapters" do
48
- expect(@manga.chapters_list.length).to be > 500
49
- end
50
-
51
- it "should have the right first chapter" do
52
- expect(@manga.chapters_list.first).to eq({
53
- uri:'http://www.mangareader.net/94-8-1/bleach/chapter-1.html',
54
- name: 'Bleach 1',
55
- })
56
- end
57
-
58
- it "should have the right 465th chapter" do
59
- expect(@manga.chapters_list[464]).to eq({
60
- uri: 'http://www.mangareader.net/bleach/465',
61
- name: 'Bleach 465',
62
- })
63
- end
64
- end
65
-
66
- context "when a chapter is retrieved" do
67
- before(:all) do
68
- @manga2 = YAML.load(File.open(
69
- Mangdown::MANGA_STUB_PATH, 'r').read)
70
- @manga2.get_chapter(0)
71
- @mchapter = @manga2.chapters_list[0]
72
- end
73
-
74
- it "should have a chapter in chapters" do
75
- expect(@manga2.chapters.length).to eq(1)
76
- end
77
-
78
- it "should have chapter 1 in chapters" do
79
- expect(@manga2.chapters[0].name).to eq(@mchapter[:name])
80
- end
81
-
82
- it "should have the right chapter sub class" do
83
- klass = Chapter
84
- if @mchapter[:uri].include?('mangareader')
85
- klass = MRChapter
86
- end
87
-
88
- expect(@manga2.chapters[0].class).to eq(klass)
89
- end
90
- end
91
- end
92
- end
@@ -1,17 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Mangdown
4
-
5
- describe MDHash do
6
- before(:all) do
7
- @hash = MDHash.new(
8
- uri: 'http://www.mangareader.net/103/one-piece.html',
9
- name: 'One Piece'
10
- )
11
- end
12
-
13
- it "should get a manga object from get_manga" do
14
- expect(@hash.to_manga).to be_kind_of(Manga)
15
- end
16
- end
17
- end