middleman-presentation 0.15.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rdebugrc +7 -0
  4. data/.rspec +5 -0
  5. data/.rubocop.yml +4 -0
  6. data/.simplecov +8 -0
  7. data/.travis.yml +8 -0
  8. data/.yardopts +5 -0
  9. data/CONTRIBUTING.md +44 -0
  10. data/Gemfile +49 -0
  11. data/Gemfile.lock +328 -0
  12. data/Guardfile +13 -0
  13. data/LICENSE.software +21 -0
  14. data/README.md +601 -0
  15. data/RELEASE_NOTES.md +36 -0
  16. data/Rakefile +191 -0
  17. data/bin/middleman-presentation +7 -0
  18. data/bin/mp +7 -0
  19. data/config/license_finder.yml +13 -0
  20. data/config/rubocop/exclude.yml +25 -0
  21. data/config/rubocop/include.yml +142 -0
  22. data/cucumber.yml +2 -0
  23. data/doc/licenses/dependencies.csv +55 -0
  24. data/doc/licenses/dependencies.db +0 -0
  25. data/doc/licenses/dependencies.html +1415 -0
  26. data/doc/licenses/dependencies.md +603 -0
  27. data/doc/licenses/dependencies_detailed.csv +190 -0
  28. data/features/create_presentation-cli.feature +301 -0
  29. data/features/create_theme-cli.feature +62 -0
  30. data/features/grouping_slides.feature +121 -0
  31. data/features/ignore_slides.feature +86 -0
  32. data/features/image_gallery.feature +58 -0
  33. data/features/init_application-cli.feature +91 -0
  34. data/features/init_predefined_slides.feature +13 -0
  35. data/features/presentation.feature +113 -0
  36. data/features/show_config-cli.feature +27 -0
  37. data/features/show_style-cli.feature +21 -0
  38. data/features/show_support_information-cli.feature +12 -0
  39. data/features/slide-cli.feature +300 -0
  40. data/features/step_definitions.rb +135 -0
  41. data/features/support/ci.rb +3 -0
  42. data/features/support/env.rb +19 -0
  43. data/features/support/reporting.rb +2 -0
  44. data/features/version_number.feature +18 -0
  45. data/fixtures/images/image01.png +0 -0
  46. data/fixtures/images/image02.png +0 -0
  47. data/lib/middleman-presentation.rb +72 -0
  48. data/lib/middleman-presentation/cli/create.rb +14 -0
  49. data/lib/middleman-presentation/cli/create_presentation.rb +308 -0
  50. data/lib/middleman-presentation/cli/create_theme.rb +77 -0
  51. data/lib/middleman-presentation/cli/init.rb +34 -0
  52. data/lib/middleman-presentation/cli/reset_thor.rb +18 -0
  53. data/lib/middleman-presentation/cli/runner.rb +18 -0
  54. data/lib/middleman-presentation/cli/show.rb +37 -0
  55. data/lib/middleman-presentation/commands/presentation.rb +23 -0
  56. data/lib/middleman-presentation/commands/slide.rb +86 -0
  57. data/lib/middleman-presentation/commands/style.rb +29 -0
  58. data/lib/middleman-presentation/comparable_slide.rb +69 -0
  59. data/lib/middleman-presentation/css_class_extracter.rb +41 -0
  60. data/lib/middleman-presentation/custom_template.rb +11 -0
  61. data/lib/middleman-presentation/erb_template.rb +11 -0
  62. data/lib/middleman-presentation/existing_slide.rb +133 -0
  63. data/lib/middleman-presentation/extension.rb +12 -0
  64. data/lib/middleman-presentation/file_extensions.rb +60 -0
  65. data/lib/middleman-presentation/frontend_component.rb +82 -0
  66. data/lib/middleman-presentation/group_template.rb +11 -0
  67. data/lib/middleman-presentation/helpers.rb +11 -0
  68. data/lib/middleman-presentation/helpers/images.rb +38 -0
  69. data/lib/middleman-presentation/helpers/slides.rb +31 -0
  70. data/lib/middleman-presentation/helpers/tests.rb +13 -0
  71. data/lib/middleman-presentation/ignore_file.rb +52 -0
  72. data/lib/middleman-presentation/liquid_template.rb +11 -0
  73. data/lib/middleman-presentation/logger.rb +8 -0
  74. data/lib/middleman-presentation/main.rb +40 -0
  75. data/lib/middleman-presentation/markdown_template.rb +11 -0
  76. data/lib/middleman-presentation/new_slide.rb +147 -0
  77. data/lib/middleman-presentation/predefined_slide_templates_directory.rb +11 -0
  78. data/lib/middleman-presentation/presentation_config.rb +48 -0
  79. data/lib/middleman-presentation/slide_group.rb +38 -0
  80. data/lib/middleman-presentation/slide_list.rb +46 -0
  81. data/lib/middleman-presentation/transformers/file_keeper.rb +13 -0
  82. data/lib/middleman-presentation/transformers/group_slides.rb +34 -0
  83. data/lib/middleman-presentation/transformers/ignore_slides.rb +28 -0
  84. data/lib/middleman-presentation/transformers/remove_duplicate_slides.rb +32 -0
  85. data/lib/middleman-presentation/transformers/sort_slides.rb +13 -0
  86. data/lib/middleman-presentation/version.rb +8 -0
  87. data/lib/middleman_extension.rb +2 -0
  88. data/locales/de.yml +9 -0
  89. data/locales/en.yml +18 -0
  90. data/middleman-presentation.gemspec +38 -0
  91. data/script/bootstrap +11 -0
  92. data/script/ci +3 -0
  93. data/spec/css_class_extracter_spec.rb +28 -0
  94. data/spec/existing_slide_spec.rb +158 -0
  95. data/spec/frontend_resource_spec.rb +87 -0
  96. data/spec/ignore_file_spec.rb +74 -0
  97. data/spec/new_slide_spec.rb +130 -0
  98. data/spec/shared_examples/.keep +0 -0
  99. data/spec/slide_group_spec.rb +69 -0
  100. data/spec/slide_list_spec.rb +90 -0
  101. data/spec/spec_helper.rb +19 -0
  102. data/spec/support/ci.rb +20 -0
  103. data/spec/support/environment.rb +18 -0
  104. data/spec/support/filesystem.rb +22 -0
  105. data/spec/support/reporting.rb +2 -0
  106. data/spec/support/rspec.rb +9 -0
  107. data/spec/support/string.rb +2 -0
  108. data/spec/transformers/file_keeper_spec.rb +23 -0
  109. data/spec/transformers/group_slides_spec.rb +44 -0
  110. data/spec/transformers/ignore_slides_spec.rb +64 -0
  111. data/spec/transformers/remove_duplicate_slides_spec.rb +120 -0
  112. data/spec/transformers/sort_slides_spec.rb +17 -0
  113. data/templates/.bowerrc.tt +4 -0
  114. data/templates/.gitignore +5 -0
  115. data/templates/LICENSE.presentation +1 -0
  116. data/templates/Rakefile +59 -0
  117. data/templates/bower.json.tt +8 -0
  118. data/templates/config.yaml.tt +41 -0
  119. data/templates/data/config.yml.tt +9 -0
  120. data/templates/data/metadata.yml.tt +77 -0
  121. data/templates/predefined_slides.d/00.html.erb.tt +23 -0
  122. data/templates/predefined_slides.d/999980.html.erb.tt +4 -0
  123. data/templates/predefined_slides.d/999981.html.erb.tt +21 -0
  124. data/templates/predefined_slides.d/999982.html.erb.tt +3 -0
  125. data/templates/presentation_theme/bower.json.tt +20 -0
  126. data/templates/presentation_theme/images/.keep +0 -0
  127. data/templates/presentation_theme/javascripts/%theme_name%.js.tt +0 -0
  128. data/templates/presentation_theme/stylesheets/%theme_name%.scss.tt +9 -0
  129. data/templates/presentation_theme/stylesheets/_fonts.scss.tt +0 -0
  130. data/templates/presentation_theme/stylesheets/_images.scss.tt +5 -0
  131. data/templates/presentation_theme/stylesheets/_theme.scss.tt +252 -0
  132. data/templates/script/bootstrap +5 -0
  133. data/templates/script/build +3 -0
  134. data/templates/script/export +34 -0
  135. data/templates/script/presentation +3 -0
  136. data/templates/script/slide +3 -0
  137. data/templates/script/start +43 -0
  138. data/templates/slides/custom.md.tt +3 -0
  139. data/templates/slides/erb.tt +5 -0
  140. data/templates/slides/group.tt +3 -0
  141. data/templates/slides/liquid.tt +5 -0
  142. data/templates/slides/markdown.tt +3 -0
  143. data/templates/source/index.html.erb +1 -0
  144. data/templates/source/javascripts/application.js.tt +3 -0
  145. data/templates/source/layout.erb +89 -0
  146. data/templates/source/stylesheets/application.scss.tt +3 -0
  147. metadata +427 -0
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ module Cli
5
+ # Create theme
6
+ class CreateTheme < Thor::Group
7
+ include Thor::Actions
8
+
9
+ desc 'Create a new presentation theme for middleman-presentation'
10
+
11
+ class_option :theme_prefix, default: Middleman::Presentation.config.theme_prefix, desc: 'Prefix for new theme'
12
+ class_option :stylesheets_directory, default: Middleman::Presentation.config.create_stylesheets_directory, desc: 'Create stylesheets directory'
13
+ class_option :javascripts_directory, default: Middleman::Presentation.config.create_javascripts_directory, desc: 'Create javascripts directory'
14
+ class_option :images_directory, default: Middleman::Presentation.config.create_images_directory, desc: 'Create images directory'
15
+ class_option :author, default: Middleman::Presentation.config.author, desc: 'Author of theme'
16
+ class_option :email, desc: 'E-mail address of author of theme'
17
+ class_option :url, desc: 'Project url'
18
+ class_option :version, default: '0.0.1', required: true, desc: 'Project url'
19
+ class_option :year, default: Time.now.strftime('%Y'), desc: 'Copyright year for theme'
20
+ class_option :license, default: 'MIT', required: true, desc: 'License of theme'
21
+ class_option :initialize_git, type: :boolean, default: Middleman::Presentation.config.initialize_git, desc: 'Initialize git'
22
+ class_option :clean_css, type: :boolean, default: false, desc: 'Generate clean css without any classes defined'
23
+
24
+ argument :name, desc: 'Name of theme'
25
+
26
+ def add_path_to_source_paths
27
+ source_paths << File.expand_path('../../../../templates', __FILE__)
28
+ end
29
+
30
+ def build_theme_name
31
+ new_name = []
32
+ new_name << options[:theme_prefix] unless options[:theme_prefix].blank?
33
+ new_name << name
34
+
35
+ @theme_name = new_name.join('-')
36
+ end
37
+
38
+ def create_variables_for_templates
39
+ @author = options[:author]
40
+ @year = options[:year]
41
+ @license = options[:license]
42
+ @email = options[:email]
43
+ @version = options[:version]
44
+ @clean_css = options[:clean_css]
45
+ end
46
+
47
+ def create_theme_directory
48
+ empty_directory theme_name
49
+ end
50
+
51
+ def create_bower_config_file
52
+ template 'presentation_theme/bower.json.tt', File.join(theme_name, 'bower.json')
53
+ end
54
+
55
+ def create_asset_directories
56
+ directory('presentation_theme/javascripts', File.join(theme_name, 'javascripts')) if options[:javascripts_directory]
57
+ directory('presentation_theme/stylesheets', File.join(theme_name, 'stylesheets')) if options[:stylesheets_directory]
58
+ directory('presentation_theme/images', File.join(theme_name, 'images')) if options[:images_directory]
59
+ end
60
+
61
+ def initialize_git
62
+ return unless options[:initialize_git]
63
+
64
+ Dir.chdir(theme_name) do
65
+ run 'git init'
66
+ run 'git add -A .'
67
+ run 'git commit -m Init'
68
+ end
69
+ end
70
+
71
+ no_commands do
72
+ attr_reader :theme_name
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ module Cli
5
+ # This class provides an 'presentation init' command for the middleman CLI.
6
+ class Init < Thor
7
+ include Thor::Actions
8
+
9
+ desc 'application ', 'Initialize system for use of middleman-presentation'
10
+ option :configuration_file, default: Middleman::Presentation.config.preferred_configuration_file, desc: 'Path to configuration file'
11
+ option :force, type: :boolean, desc: 'Force creation of config file'
12
+ def application
13
+ source_paths << File.expand_path('../../../../templates', __FILE__)
14
+
15
+ @version = Middleman::Presentation::VERSION
16
+ @config = Middleman::Presentation.config
17
+
18
+ opts = options.dup.deep_symbolize_keys
19
+ template 'config.yaml.tt', opts.delete(:configuration_file), **opts
20
+ end
21
+
22
+ desc 'predefined_slides ', 'Initialize predefined_slides'
23
+ option :directory, default: PredefinedSlideTemplateDirectory.new.preferred_template_directory, desc: 'Directory where the predefined templates should be stored'
24
+ def predefined_slides
25
+ source_paths << File.expand_path('../../../../templates/predefined_slides.d', __FILE__)
26
+
27
+ PredefinedSlideTemplateDirectory.new(working_directory: File.expand_path('../../../../templates', __FILE__)).template_files.each do |file|
28
+ copy_file file, File.join(options[:directory], File.basename(file))
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # Restore original behaviour of thor
2
+ # rubocop:disable Style/ClassAndModuleChildren
3
+ class ::Thor
4
+ # rubocop:enable Style/ClassAndModuleChildren
5
+ module Actions
6
+ # Create file helper class
7
+ class CreateFile
8
+ def on_conflict_behavior(&block)
9
+ if identical?
10
+ say_status :identical, :blue
11
+ else
12
+ options = base.options.merge(config)
13
+ force_or_skip_or_conflict(options[:force], options[:skip], &block)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ module Cli
5
+ # Run command
6
+ class Runner < Thor
7
+ desc 'init', 'Initialize system, presentation, ...'
8
+ subcommand 'init', Init
9
+
10
+ desc 'show', 'Show information ...'
11
+ subcommand 'show', Show
12
+
13
+ desc 'create', 'Create something...'
14
+ subcommand 'create', Create
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ module Cli
5
+ # This class provides an 'presentation init' command for the middleman CLI.
6
+ class Show < Thor
7
+ include Thor::Actions
8
+
9
+ desc 'support_information', 'Collect information for support'
10
+ def support_information
11
+ puts FeduxOrgStdlib::SupportInformation.new.to_s
12
+ end
13
+
14
+ desc 'config', 'Show configuration'
15
+ option :defaults, type: :boolean, desc: 'Show default configuration'
16
+ def config
17
+ if options[:defaults]
18
+ capture :stderr do
19
+ puts Middleman::Presentation::PresentationConfig.new(file: nil).to_s
20
+ end
21
+ else
22
+ puts Middleman::Presentation.config.to_s
23
+ end
24
+ end
25
+
26
+ desc 'style', 'Show available styles'
27
+ def style
28
+ css_classes = Middleman::Presentation::CssClassExtracter.new.extract Middleman::Presentation.stylable_files, ignore: %w(slides reveal)
29
+
30
+ puts "Available css classes in templates used by middleman-presentation:\n"
31
+ css_classes.each { |klass| puts format ' %20s: %s', klass.name, klass.files.to_list }
32
+ puts
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ module Cli
5
+ # This class provides an 'presentation' command for the middleman CLI.
6
+ class Presentation < Thor
7
+ include Thor::Actions
8
+
9
+ def self.check_unknown_options?(*)
10
+ false
11
+ end
12
+
13
+ namespace :presentation
14
+
15
+ desc 'presentation ', 'Initialize a new presentation'
16
+ def presentation(*)
17
+ warn('The use of this command is deprecated. Please use `middleman-presentation create presentation` instead.')
18
+ exit 1
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ require 'middleman-presentation'
3
+
4
+ module Middleman
5
+ module Cli
6
+ # This class provides an 'slide' command for the middleman CLI.
7
+ class Slide < Thor
8
+ include Thor::Actions
9
+
10
+ check_unknown_options!
11
+
12
+ namespace :slide
13
+
14
+ def self.source_root
15
+ ENV['MM_ROOT']
16
+ end
17
+
18
+ # Tell Thor to exit with a nonzero exit code on failure
19
+ def self.exit_on_failure?
20
+ true
21
+ end
22
+
23
+ desc 'slide NAME(S)', 'Create a new slide(s) or edit existing ones. If you want to create multiple slides enter them with a space between the names "01 02 03".'
24
+ option :edit, default: Middleman::Presentation.config.edit, desc: 'Start ENV["EDITOR"] to edit slide.', aliases: %w(-e)
25
+ option :editor_command, default: Middleman::Presentation.config.editor_command, desc: 'editor command to be used, e.g. ENV["EDITOR"] --servername presentation --remote-tab'
26
+ option :error_on_duplicates, type: :boolean, default: Middleman::Presentation.config.error_on_duplicates, desc: 'Raise an error if a slide of the same base name alread exists, e.g. Filename: 01.html.erb => Basename: 01'
27
+ option :title, desc: 'Title of slide'
28
+ def slide(*names)
29
+ fail ArgumentError, I18n.t('errors.missing_argument', argument: 'name') if names.blank?
30
+
31
+ shared_instance = ::Middleman::Application.server.inst
32
+
33
+ # This only exists when the config.rb sets it!
34
+ if shared_instance.extensions.key? :presentation
35
+ presentation_inst = shared_instance.extensions[:presentation]
36
+
37
+ existing_slides = Middleman::Presentation::SlideList.new(
38
+ Dir.glob(File.join(shared_instance.source_dir, presentation_inst.options.slides_directory, '**', '*')),
39
+ slide_builder: Middleman::Presentation::ExistingSlide,
40
+ base_path: shared_instance.source_dir
41
+ ) do |l|
42
+ l.transform_with Middleman::Presentation::Transformers::FileKeeper.new
43
+ end
44
+
45
+ slide_list = Middleman::Presentation::SlideList.new(
46
+ names,
47
+ slide_builder: Middleman::Presentation::NewSlide,
48
+ base_path: File.join(shared_instance.source_dir, presentation_inst.options.slides_directory)
49
+ ) do |l|
50
+ l.transform_with Middleman::Presentation::Transformers::RemoveDuplicateSlides.new(additional_slides: existing_slides, raise_error: options[:error_on_duplicates])
51
+ l.transform_with Middleman::Presentation::Transformers::SortSlides.new
52
+ end
53
+
54
+ slide_list.each_old do |slide|
55
+ $stderr.puts format('%-20s %-s', 'exist'.colorize(color: :blue, mode: :bold), slide.relative_path)
56
+ end
57
+
58
+ slide_list.each_new do |slide|
59
+ $stderr.puts format('%-20s %-s', 'create'.colorize(color: :green, mode: :bold), slide.relative_path)
60
+ slide.write(title: options[:title])
61
+ end
62
+
63
+ data = if shared_instance.data.respond_to? :metadata
64
+ shared_instance.data.metadata.dup
65
+ else
66
+ OpenStruct.new
67
+ end
68
+
69
+ if options[:edit]
70
+ editor = []
71
+ begin
72
+ editor << Erubis::Eruby.new(options[:editor_command]).result(data)
73
+ rescue NameError => e
74
+ $stderr.puts I18n.t('errors.missing_data_attribute', message: e.message)
75
+ end
76
+ editor.concat slide_list.existing_slides
77
+
78
+ system(editor.join(' '))
79
+ end
80
+ else
81
+ fail Thor::Error, 'You need to activate the presentation extension in config.rb before you can create a slide.'
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Cli
4
+ # This class provides an 'slide' command for the middleman CLI.
5
+ class Style < Thor
6
+ include Thor::Actions
7
+
8
+ namespace :style
9
+
10
+ def self.source_root
11
+ ENV['MM_ROOT']
12
+ end
13
+
14
+ # Tell Thor to exit with a nonzero exit code on failure
15
+ def self.exit_on_failure?
16
+ true
17
+ end
18
+
19
+ desc 'style', 'Show available styles'
20
+ def style
21
+ css_classes = Middleman::Presentation::CssClassExtracter.new.extract Middleman::Presentation.stylable_files, ignore: %w(slides reveal)
22
+
23
+ puts "Available css classes in templates used by middleman-presentation:\n"
24
+ css_classes.each { |klass| puts format ' %20s: %s', klass.name, klass.files.to_list }
25
+ puts
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ # All methods needed to compare slides
5
+ module ComparableSlide
6
+ include Comparable
7
+
8
+ # Needs to be implemented to make the other methods work
9
+ def path
10
+ fail MethodNotImplemented
11
+ end
12
+
13
+ # Needs to be implemented to make the other methods work
14
+ def base_name
15
+ fail MethodNotImplemented
16
+ end
17
+
18
+ # Needs to be implemented to make the other methods work
19
+ def group
20
+ fail MethodNotImplemented
21
+ end
22
+
23
+ # @private
24
+ def <=>(other)
25
+ path <=> other.path
26
+ end
27
+
28
+ # @private
29
+ def eql?(other)
30
+ path.eql? other.path
31
+ end
32
+
33
+ # Is slide similar to another slide
34
+ def similar?(other)
35
+ return true if eql? other
36
+
37
+ base_name?(other.base_name) && group?(other.group)
38
+ end
39
+
40
+ # @private
41
+ def hash
42
+ path.hash
43
+ end
44
+
45
+ # Checks if slide is in group
46
+ def group?(g)
47
+ group == g
48
+ end
49
+
50
+ # Check if string/regex matches path
51
+ def match?(string_or_regex)
52
+ regex = if string_or_regex.is_a? String
53
+ Regexp.new(string_or_regex)
54
+ else
55
+ string_or_regex
56
+ end
57
+
58
+ # rubocop:disable Style/CaseEquality:
59
+ regex === relative_path.to_s
60
+ # rubocop:enable Style/CaseEquality:
61
+ end
62
+
63
+ # Check if basename is equal
64
+ def base_name?(b)
65
+ base_name == b
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ module Middleman
3
+ module Presentation
4
+ # Extract css classes from html files
5
+ class CssClassExtracter
6
+ # Extracted css class
7
+ class CssClass
8
+ attr_reader :name, :files
9
+
10
+ def initialize(name:, files: [])
11
+ @name = name
12
+ @files = files.to_a
13
+ end
14
+ end
15
+
16
+ def extract(paths, ignore: [])
17
+ classes = build(paths)
18
+ classes.delete_if { |klass| ignore.include? klass }.sort_by { |klass, _| klass }.map { |klass, files| CssClass.new(name: klass, files: files) }
19
+ end
20
+
21
+ private
22
+
23
+ def build(paths)
24
+ paths.each_with_object({}) do |f, a|
25
+ page = Nokogiri::HTML(open(f))
26
+
27
+ page.traverse do |n|
28
+ if n['class']
29
+ klasses = n['class'].split(/ /)
30
+
31
+ klasses.each do |k|
32
+ a[k] ||= Set.new
33
+ a[k] << File.basename(f)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end