verku 0.8.0.p

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +49 -0
  4. data/BUILD.md +6 -0
  5. data/Gemfile +14 -0
  6. data/Gemfile.lock +82 -0
  7. data/LICENSE.md +22 -0
  8. data/README.md +149 -0
  9. data/Rakefile +72 -0
  10. data/VERSION +1 -0
  11. data/bin/verku +5 -0
  12. data/lib/verku.rb +56 -0
  13. data/lib/verku/adapters/markdown.rb +44 -0
  14. data/lib/verku/cli.rb +93 -0
  15. data/lib/verku/dependency.rb +19 -0
  16. data/lib/verku/exporter.rb +77 -0
  17. data/lib/verku/extensions/string.rb +19 -0
  18. data/lib/verku/generator.rb +55 -0
  19. data/lib/verku/parser.rb +85 -0
  20. data/lib/verku/parser/epub.rb +187 -0
  21. data/lib/verku/parser/html.rb +245 -0
  22. data/lib/verku/parser/mobi.rb +17 -0
  23. data/lib/verku/parser/pdf.rb +54 -0
  24. data/lib/verku/parser/txt.rb +1 -0
  25. data/lib/verku/stats.rb +114 -0
  26. data/lib/verku/stream.rb +27 -0
  27. data/lib/verku/toc.rb +6 -0
  28. data/lib/verku/toc/epub.rb +41 -0
  29. data/lib/verku/toc/html.rb +78 -0
  30. data/lib/verku/version.rb +10 -0
  31. data/templates/config.erb +80 -0
  32. data/templates/cover.jpg +0 -0
  33. data/templates/dp-logo.png +0 -0
  34. data/templates/epub/back.erb +22 -0
  35. data/templates/epub/copyright.erb +46 -0
  36. data/templates/epub/cover.erb +12 -0
  37. data/templates/epub/cover.html +12 -0
  38. data/templates/epub/page.erb +15 -0
  39. data/templates/epub/user.css +500 -0
  40. data/templates/extras.tex +1 -0
  41. data/templates/html/copyright.erb +46 -0
  42. data/templates/html/layout.css +352 -0
  43. data/templates/html/layout.erb +45 -0
  44. data/templates/html/syntax.css +58 -0
  45. data/templates/html/thanks.erb +21 -0
  46. data/templates/html/user.css +7 -0
  47. data/templates/latex.erb +416 -0
  48. data/templates/merovex-logo.jpg +0 -0
  49. data/templates/merovex-logo.png +0 -0
  50. data/templates/pdf/layout.erb +418 -0
  51. data/templates/rakefile.rb +103 -0
  52. data/templates/readme.erb +3 -0
  53. data/templates/text/01-Getting-Started.md +27 -0
  54. data/templates/text/02-Creating-Chapters.md +22 -0
  55. data/templates/text/03-Generating-Output.md +2 -0
  56. data/templates/text/10-Test-Markdown.md +157 -0
  57. data/test/helper.rb +34 -0
  58. data/test/test_bookmaker.rb +7 -0
  59. data/verku.gemspec +142 -0
  60. metadata +317 -0
@@ -0,0 +1,44 @@
1
+ module Kitabu
2
+ class Markdown
3
+ # Supported Markdown libraries
4
+ #
5
+ MARKDOWN_LIBRARIES = %w[Kramdown]
6
+
7
+ # Retrieve preferred Markdown processor.
8
+ # You'll need one of the following libraries:
9
+ #
10
+ # # RDiscount: https://rubygems.org/gems/rdiscount
11
+ # # Maruku: https://rubygems.org/gems/maruku
12
+ # # PEGMarkdown: https://rubygems.org/gems/rpeg-markdown
13
+ # # BlueCloth: https://rubygems.org/gems/bluecloth
14
+ # # Redcarpet: https://rubygems.org/gems/redcarpet
15
+ # # Kramdown: http://kramdown.rubyforge.org/
16
+ #
17
+ # Note: RDiscount will always be installed as Kitabu's dependency but only used when no
18
+ # alternative library is available.
19
+ #
20
+ def self.engine
21
+ @engine ||= Object.const_get(MARKDOWN_LIBRARIES.find {|lib| Object.const_defined?(lib)})
22
+ end
23
+
24
+ def self.to_latex(content)
25
+ case engine.name
26
+ when "Redcarpet"
27
+ # render = Redcarpet::Render::HTML.new(:hard_wrap => true, :xhtml => true)
28
+ # Redcarpet::Markdown.new(render).render(content)
29
+ else
30
+ engine.new(content).to_latex
31
+ end
32
+ end
33
+ # Convert Markdown to HTML.
34
+ def self.to_html(content)
35
+ case engine.name
36
+ when "Redcarpet"
37
+ render = Redcarpet::Render::HTML.new(:hard_wrap => true, :xhtml => true)
38
+ Redcarpet::Markdown.new(render).render(content)
39
+ else
40
+ engine.new(content).to_html
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/verku/cli.rb ADDED
@@ -0,0 +1,93 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'thor'
3
+ require 'verku/version'
4
+ module Verku
5
+ class Cli < Thor
6
+ FORMATS = %w[pdf draft proof html epub mobi txt]
7
+ check_unknown_options!
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+ def initialize(args = [], options = {}, config = {})
13
+ if (config[:current_task] || config[:current_command]).name == "new" && args.empty?
14
+ raise Error, "The e-Book path is required. For details run: verku help new"
15
+ end
16
+ super
17
+ end
18
+
19
+ desc "create", "Start new work"
20
+ map %w(create new) => :create
21
+ def create(path)
22
+ puts "Verku -- A Million Monkeys Writing Your Masterpiece."
23
+ generator = Generator.new
24
+ generator.destination_root = path.squish.gsub(' ','-')
25
+ generator.invoke_all
26
+ end
27
+
28
+ desc "compile [OPTIONS]", "Export e-book"
29
+ map %w(compile export build) => :export
30
+ method_option :only, :type => :string, :desc => "Can be one of: #{FORMATS.join(", ")}"
31
+ method_option :open, :type => :boolean, :desc => "Automatically open PDF (Preview.app for Mac OS X and xdg-open for Linux)"
32
+ def export
33
+ inside_ebook!
34
+ if options[:only] && !FORMATS.include?(options[:only])
35
+ raise Error, "The --only option need to be one of: #{FORMATS.join(", ")}"
36
+ end
37
+ Verku::Exporter.run(root_dir, options)
38
+ end
39
+
40
+ desc "pdf", "Export PDF only"
41
+ def pdf
42
+ inside_ebook!
43
+ Verku::Exporter.run(root_dir, {:only => 'pdf'})
44
+ end
45
+
46
+ desc "version", "Prints the Verku's version information"
47
+ map %w(-v --version) => :version
48
+ def version
49
+ say "Verku version #{Verku::Version::STRING}"
50
+ end
51
+
52
+ desc "stats", "Display some stats about your e-book"
53
+ def stats
54
+ inside_ebook!
55
+ stats = Verku::Stats.new(root_dir)
56
+
57
+ say [
58
+ # "Chapters: #{stats.chapters}",
59
+ "Goal: #{sprintf("%7d", stats.target)}",
60
+ "Words: #{sprintf("%7d", stats.words)}",
61
+ " -------",
62
+ "Left: #{sprintf("%7d", stats.remaining)}",
63
+ "\nProgress: #{sprintf("%5d", stats.today)}"
64
+ ].join("\n")
65
+ end
66
+
67
+ desc "move old_id new_id", "Move scene to new sequence."
68
+ def move(oid,nid)
69
+ s = Structure.new(root_dir)
70
+ s.move(oid,nid)
71
+ end
72
+ desc "trash [OPTIONS]", "Move scene to trash."
73
+ def trash(scene_id)
74
+ s = Structure.new(root_dir)
75
+ s.trash(scene_id)
76
+ end
77
+ private
78
+ def config
79
+ YAML.load_file(config_path).with_indifferent_access
80
+ end
81
+ def config_path
82
+ root_dir.join("_verku.yml")
83
+ end
84
+ def root_dir
85
+ @root ||= Pathname.new(Dir.pwd)
86
+ end
87
+ def inside_ebook!
88
+ unless File.exist?(config_path)
89
+ raise Error, "You have to run this command from inside an e-book directory."
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,19 @@
1
+ module Verku
2
+ class Dependency
3
+ def self.kindlegen?
4
+ @kindlegen ||= `which kindlegen` && $?.success?
5
+ end
6
+
7
+ def self.xelatex?
8
+ @xelatex ||= `which xelatex` && $?.success?
9
+ end
10
+
11
+ def self.html2text?
12
+ @html2text ||= `which html2text` && $?.success?
13
+ end
14
+
15
+ def self.pygments_rb?
16
+ @pygments_rb ||= defined?(Pygments)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ module Verku
2
+ class Exporter
3
+ def self.run(root_dir, options)
4
+ exporter = new(root_dir, options)
5
+ exporter.export!
6
+ end
7
+
8
+ attr_accessor :root_dir
9
+ attr_accessor :options
10
+
11
+ def initialize(root_dir, options)
12
+ @root_dir = root_dir
13
+ @options = options
14
+ end
15
+
16
+ def ui
17
+ @ui ||= Thor::Base.shell.new
18
+ end
19
+
20
+ def export!
21
+ helper = root_dir.join("config/helper.rb")
22
+ load(helper) if helper.exist?
23
+
24
+ raise "Missing Templates directory (_templates)" unless File.exist?("_templates")
25
+ raise "Missing Images directory (_images)" unless File.exist?("_images")
26
+ raise "Missing Output directory (builds)" unless File.exist?("builds")
27
+
28
+ puts "Missing Kindlegen" unless Dependency.kindlegen?
29
+ puts "Missing XeLatex" unless Dependency.xelatex?
30
+ # puts "Missing Html2Text" unless Dependency.html2text?
31
+
32
+ export_pdf = [nil, "pdf"].include?(options[:only])
33
+ export_html = [nil, "html", "mobi", "epub"].include?(options[:only])
34
+ export_epub = [nil, "mobi", "epub"].include?(options[:only])
35
+ export_mobi = [nil, "mobi"].include?(options[:only])
36
+ export_txt = [nil, "txt"].include?(options[:only])
37
+
38
+ exported = []
39
+ exported << Parser::PDF.parse(root_dir) if export_pdf && Dependency.xelatex?# && Dependency.prince?
40
+ exported << Parser::HTML.parse(root_dir) if export_html
41
+ epub_done = Parser::Epub.parse(root_dir) if export_epub
42
+ exported << epub_done
43
+ exported << Parser::Mobi.parse(root_dir) if export_mobi && epub_done && Dependency.kindlegen?
44
+ # exported << Parser::Txt.parse(root_dir) if export_txt && Dependency.html2text?
45
+
46
+ if exported.all?
47
+ color = :green
48
+ message = options[:auto] ? "exported!" : "** e-book has been exported"
49
+
50
+ if options[:open] && export_pdf
51
+ filepath = root_dir.join("output/#{File.basename(root_dir)}.pdf")
52
+
53
+ if RUBY_PLATFORM =~ /darwin/
54
+ IO.popen("open -a Preview.app '#{filepath}'").close
55
+ elsif RUBY_PLATFORM =~ /linux/
56
+ Process.detach(Process.spawn("xdg-open '#{filepath}'", :out => "/dev/null"))
57
+ end
58
+ end
59
+
60
+ Notifier.notify(
61
+ :image => Verku::ROOT.join("_templates/ebook.png"),
62
+ :title => "Verku",
63
+ :message => "Your \"#{config[:title]}\" e-book has been exported!"
64
+ )
65
+ else
66
+ color = :red
67
+ message = options[:auto] ? "could not be exported!" : "** e-book couldn't be exported"
68
+ end
69
+
70
+ ui.say message, color
71
+ end
72
+
73
+ def config
74
+ Verku.config(root_dir)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,19 @@
1
+ class String
2
+ def to_permalink
3
+ str = ActiveSupport::Multibyte::Chars.new(self.dup)
4
+ str = str.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s
5
+ str.gsub!(/[^-\w\d]+/xim, "-")
6
+ str.gsub!(/-+/xm, "-")
7
+ str.gsub!(/^-?(.*?)-?$/, '\1')
8
+ str.downcase!
9
+ str
10
+ end
11
+ def to_latex
12
+ require 'kramdown'
13
+ Kramdown::Document.new(self.dup).to_latex
14
+ end
15
+ def to_html
16
+ require 'kramdown'
17
+ Kramdown::Document.new(self.dup).to_html
18
+ end
19
+ end
@@ -0,0 +1,55 @@
1
+ module Verku
2
+ class Generator < Thor::Group
3
+ include Thor::Actions
4
+ def self.source_root
5
+ File.dirname(__FILE__) + "/../../templates"
6
+ end
7
+ def build_config_file
8
+ # require "uuidtools"
9
+ @title = File.basename(destination_root).gsub('-', ' ')
10
+ @name = full_name
11
+ @uuid = SecureRandom.uuid
12
+ @year = Date.today.year
13
+ template "config.erb", "_verku.yml"
14
+ template "readme.erb", "README.md"
15
+ end
16
+ def copy_templates
17
+ copy_file "pdf/layout.erb", "_templates/pdf/layout.erb"
18
+ copy_file "merovex-logo.png", "_images/logo.png"
19
+
20
+ copy_file "html/layout.erb", "_templates/html/layout.erb"
21
+ copy_file "html/thanks.erb", "_templates/html/thanks.erb"
22
+ copy_file "html/copyright.erb", "_templates/html/copyright.erb"
23
+ copy_file "html/user.css", "_templates/html/user.css"
24
+ copy_file "html/layout.css", "_templates/html/layout.css"
25
+ copy_file "html/syntax.css", "_templates/html/syntax.css"
26
+
27
+ copy_file "epub/back.erb", "_templates/epub/back.html"
28
+ copy_file "epub/copyright.erb", "_templates/epub/copyright.erb"
29
+ copy_file "epub/cover.erb", "_templates/epub/cover.erb"
30
+ copy_file "epub/cover.html", "_templates/epub/cover.html"
31
+ copy_file "epub/page.erb", "_templates/epub/page.erb"
32
+ copy_file "epub/user.css", "_templates/epub/user.css"
33
+
34
+ copy_file "cover.jpg", "_images/cover.jpg"
35
+ copy_file "rakefile.rb", "Rakefile"
36
+ copy_file "extras.tex", "_extras/dedications.tex"
37
+ end
38
+ def copy_sample_text
39
+ directory "text", "text"
40
+ end
41
+ def create_directories
42
+ empty_directory "builds"
43
+ empty_directory "docs"
44
+ empty_directory "_images"
45
+ end
46
+ private
47
+ # Retrieve user's name using finger.
48
+ # Defaults to <tt>John Doe</tt>.
49
+ #
50
+ def full_name
51
+ name = `finger $USER 2> /dev/null | grep Login | colrm 1 46`.chomp
52
+ name.empty? ? "John Doe" : name.squish
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,85 @@
1
+ require 'open3'
2
+
3
+ module Verku
4
+ module Parser
5
+ autoload :HTML , "verku/parser/html"
6
+ autoload :PDF , "verku/parser/pdf"
7
+ autoload :Epub , "verku/parser/epub"
8
+ autoload :Mobi , "verku/parser/mobi"
9
+ # autoload :Txt , "verku/parser/txt"
10
+
11
+ class Base
12
+ # The e-book directory.
13
+ #
14
+ attr_accessor :root_dir
15
+
16
+ # Where the text files are stored.
17
+ #
18
+ attr_accessor :source
19
+
20
+ def self.parse(root_dir)
21
+ new(root_dir).parse
22
+ end
23
+
24
+ def initialize(root_dir)
25
+ @root_dir = Pathname.new(root_dir)
26
+ @source = root_dir.join("text")
27
+ end
28
+
29
+ # Return directory's basename.
30
+ #
31
+ def name
32
+ File.basename(root_dir)
33
+ end
34
+
35
+ # Return the configuration file.
36
+ #
37
+ def config
38
+ Verku.config(root_dir)
39
+ end
40
+ def entries
41
+ return @entries unless @entries.nil?
42
+ files = Dir["text/**/*.tex"]
43
+ @entries = {}
44
+ files.each do |f|
45
+ k = File.dirname(f)
46
+ k.gsub!('text/','')
47
+ @entries[k] = [] if @entries[k].nil?
48
+ @entries[k] << f
49
+ end
50
+ return @entries
51
+ end
52
+ def render_template(file, locals = {})
53
+ ERB.new(File.read(file)).result OpenStruct.new(locals).instance_eval{ binding }
54
+ end
55
+ def read_content(file)
56
+ content = File.read(file)
57
+ data = {}
58
+ begin
59
+ # YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
60
+ if content =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
61
+ # content = "\n#{$'}\n"
62
+ content = $POSTMATCH
63
+ data = SafeYAML.load($1)
64
+ # data = YAML.load($1)
65
+ end
66
+ return [content, data]
67
+ rescue SyntaxError => e
68
+ puts "YAML Exception reading #{path}: #{e.message}"
69
+ rescue Exception => e
70
+ puts "Error reading file #{path}: #{e.message}"
71
+ end
72
+ end
73
+ def spawn_command(cmd)
74
+ begin
75
+ stdout_and_stderr, status = Open3.capture2e(*cmd)
76
+ rescue Errno::ENOENT => e
77
+ puts e.message
78
+ else
79
+ puts stdout_and_stderr unless status.success?
80
+ status.success?
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,187 @@
1
+ require 'fileutils'
2
+ module Verku
3
+ module Parser
4
+ class Epub < Base
5
+ def sections
6
+ @sections ||= html.css("div.chapter").each_with_index.map do |chapter, index|
7
+ OpenStruct.new({
8
+ :index => index,
9
+ :filename => "section_#{index}.html",
10
+ :filepath => tmp_dir.join("section_#{index}.html").to_s,
11
+ :html => Nokogiri::HTML(chapter.inner_html)
12
+ })
13
+ end
14
+ end
15
+ def epub; @epub ||= EeePub.make ;end
16
+ def html; @html ||= Nokogiri::HTML(html_path.read); end
17
+ def parse
18
+ puts "-- Exporting EPUB"
19
+ epub.title config["title"]
20
+ epub.language config["language"]
21
+ epub.creator config["authors"].to_sentence
22
+ epub.publisher config["publisher"]
23
+ epub.date config["published_at"]
24
+ epub.uid config["uid"]
25
+ epub.identifier config["identifier"]["id"], :scheme => config["identifier"]["type"]
26
+ if cover_image.nil?
27
+ puts " - Consider adding a cover images in /images."
28
+ else
29
+ epub.cover cover_image
30
+ end
31
+ write_coverpage!
32
+ write_thankspage!
33
+ write_copyright!
34
+ write_sections!
35
+ write_backpage!
36
+ write_toc!
37
+ epub.files cover_page + thanks_page + sections.map(&:filepath) + back_page + copyright_page + assets
38
+ epub.nav navigation
39
+
40
+ epub.save(epub_path)
41
+ true
42
+ rescue Exception
43
+ p $!, $@
44
+ false
45
+ end
46
+ def back_page
47
+ return Dir[back_path] if File.exist?(back_path)
48
+ []
49
+ end
50
+ def cover_page
51
+ Dir[cover_path]
52
+ end
53
+ def copyright_page
54
+ Dir[copyright_path]
55
+ end
56
+ def thanks_page
57
+ Dir[thanks_path]
58
+ end
59
+ def write_backpage!
60
+ contents = render_template(root_dir.join("_templates/epub/back.html"), config)
61
+ File.open(back_path,"w") do |file|
62
+ file << contents
63
+ end
64
+ end
65
+ def write_coverpage!
66
+ contents = render_template(root_dir.join("_templates/epub/cover.html"), config)
67
+ puts "Writing cover page. #{cover_path}"
68
+ #
69
+ # raise File.dirname(cover_path).inspect
70
+ FileUtils.mkdir_p(File.dirname(cover_path))
71
+ File.open(cover_path,"w") do |file|
72
+ file << contents
73
+ end
74
+ end
75
+ def write_copyright!
76
+ contents = render_template(root_dir.join("_templates/html/copyright.erb"), config)
77
+ # File.open('help.html','w').write(contents)
78
+ FileUtils.mkdir_p(File.dirname(copyright_path))
79
+ File.open(copyright_path,"w") do |file|
80
+ file << contents
81
+ end
82
+ end
83
+ def write_thankspage!
84
+ contents = render_template(root_dir.join("_templates/html/thanks.erb"), config)
85
+ # File.open('help.html','w').write(contents)
86
+ FileUtils.mkdir_p(File.dirname(thanks_path))
87
+ File.open(thanks_path,"w") do |file|
88
+ file << contents
89
+ end
90
+ end
91
+ def write_toc!
92
+ toc = TOC::Epub.new(navigation)
93
+ FileUtils.mkdir_p(File.dirname(toc_path))
94
+ File.open(toc_path, "w") do |file|
95
+ file << toc.to_html
96
+ end
97
+ end
98
+
99
+ def write_sections!
100
+ # First we need to get all ids, which are used as
101
+ # the anchor target.
102
+ links = sections.inject({}) do |buffer, section|
103
+ section.html.css("[id]").each do |element|
104
+ anchor = "##{element["id"]}"
105
+ buffer[anchor] = "#{section.filename}#{anchor}"
106
+ end
107
+
108
+ buffer
109
+ end
110
+
111
+ # Then we can normalize all links and
112
+ # manipulate other paths.
113
+ #
114
+ sections.each do |section|
115
+ section.html.css("a[href^='#']").each do |link|
116
+ href = link["href"]
117
+ link.set_attribute("href", links.fetch(href, href))
118
+ end
119
+
120
+ # Replace all srcs.
121
+ #
122
+ section.html.css("[src]").each do |element|
123
+ src = File.basename(element["src"]).gsub(/\.svg$/, ".png")
124
+ element.set_attribute("src", src)
125
+ element.set_attribute("alt", "")
126
+ element.node_name = "img"
127
+ end
128
+
129
+ FileUtils.mkdir_p(tmp_dir)
130
+ File.open(section.filepath, "w") do |file|
131
+ body = section.html.css("body").to_xhtml.gsub(%r[<body>(.*?)</body>]m, "\\1")
132
+ file << render_chapter(body)
133
+ end
134
+ end
135
+ end
136
+ def render_chapter(content)
137
+ locals = config.merge(:content => content)
138
+ render_template(template_path, locals)
139
+ end
140
+ def assets
141
+ @assets ||= begin
142
+ assets = Dir[root_dir.join("_templates/epub/*.css")]
143
+ assets += Dir[root_dir.join("images/**/*.{jpg,png,gif}")]
144
+ assets
145
+ end
146
+ end
147
+ def cover_image
148
+ path = Dir[root_dir.join("images/cover-#{name}.{jpg,png,gif}").to_s.downcase].first
149
+ return File.basename(path) if path && File.exist?(path)
150
+ end
151
+ def navigation
152
+ sections.map do |section| {
153
+ :label => section.html.css("h2:first-of-type").text,
154
+ :content => section.filename
155
+ }
156
+ end
157
+ end
158
+ def template_path
159
+ root_dir.join("_templates/epub/page.erb")
160
+ end
161
+ def html_path
162
+ root_dir.join("builds/#{name}.html")
163
+ end
164
+ def epub_path
165
+ root_dir.join("builds/#{name}.epub")
166
+ end
167
+ def tmp_dir
168
+ root_dir.join("builds/tmp")
169
+ end
170
+ def cover_path
171
+ tmp_dir.join("cover.html")
172
+ end
173
+ def thanks_path
174
+ tmp_dir.join("thanks.html")
175
+ end
176
+ def back_path
177
+ tmp_dir.join("back.html")
178
+ end
179
+ def copyright_path
180
+ tmp_dir.join("copyright.html")
181
+ end
182
+ def toc_path
183
+ tmp_dir.join("toc.html")
184
+ end
185
+ end
186
+ end
187
+ end