nanoc-core 4.11.12 → 4.11.13

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/nanoc/core.rb +37 -0
  3. data/lib/nanoc/core/basic_item_rep_collection_view.rb +88 -0
  4. data/lib/nanoc/core/basic_item_rep_view.rb +83 -0
  5. data/lib/nanoc/core/basic_item_view.rb +54 -0
  6. data/lib/nanoc/core/checksummer.rb +2 -0
  7. data/lib/nanoc/core/compilation_item_rep_collection_view.rb +12 -0
  8. data/lib/nanoc/core/compilation_item_rep_view.rb +51 -0
  9. data/lib/nanoc/core/compilation_item_view.rb +47 -0
  10. data/lib/nanoc/core/compilation_phases/abstract.rb +48 -0
  11. data/lib/nanoc/core/compilation_phases/cache.rb +43 -0
  12. data/lib/nanoc/core/compilation_phases/mark_done.rb +23 -0
  13. data/lib/nanoc/core/compilation_phases/notify.rb +19 -0
  14. data/lib/nanoc/core/compilation_phases/recalculate.rb +49 -0
  15. data/lib/nanoc/core/compilation_phases/resume.rb +52 -0
  16. data/lib/nanoc/core/compilation_phases/write.rb +84 -0
  17. data/lib/nanoc/core/compilation_stages/build_reps.rb +36 -0
  18. data/lib/nanoc/core/compilation_stages/calculate_checksums.rb +42 -0
  19. data/lib/nanoc/core/compilation_stages/cleanup.rb +43 -0
  20. data/lib/nanoc/core/compilation_stages/compile_reps.rb +96 -0
  21. data/lib/nanoc/core/compilation_stages/determine_outdatedness.rb +49 -0
  22. data/lib/nanoc/core/compilation_stages/forget_outdated_dependencies.rb +20 -0
  23. data/lib/nanoc/core/compilation_stages/load_stores.rb +35 -0
  24. data/lib/nanoc/core/compilation_stages/postprocess.rb +21 -0
  25. data/lib/nanoc/core/compilation_stages/preprocess.rb +32 -0
  26. data/lib/nanoc/core/compilation_stages/prune.rb +30 -0
  27. data/lib/nanoc/core/compilation_stages/store_post_compilation_state.rb +20 -0
  28. data/lib/nanoc/core/compilation_stages/store_pre_compilation_state.rb +32 -0
  29. data/lib/nanoc/core/compiler.rb +214 -0
  30. data/lib/nanoc/core/compiler_loader.rb +48 -0
  31. data/lib/nanoc/core/config_loader.rb +95 -0
  32. data/lib/nanoc/core/config_view.rb +67 -0
  33. data/lib/nanoc/core/configuration.rb +2 -4
  34. data/lib/nanoc/core/document_view_mixin.rb +87 -0
  35. data/lib/nanoc/core/errors.rb +97 -0
  36. data/lib/nanoc/core/executor.rb +134 -0
  37. data/lib/nanoc/core/feature.rb +92 -0
  38. data/lib/nanoc/core/filter.rb +269 -0
  39. data/lib/nanoc/core/identifiable_collection_view.rb +111 -0
  40. data/lib/nanoc/core/item_collection_with_reps_view.rb +12 -0
  41. data/lib/nanoc/core/item_collection_without_reps_view.rb +12 -0
  42. data/lib/nanoc/core/item_rep_builder.rb +54 -0
  43. data/lib/nanoc/core/item_rep_selector.rb +67 -0
  44. data/lib/nanoc/core/item_rep_writer.rb +85 -0
  45. data/lib/nanoc/core/layout_collection_view.rb +12 -0
  46. data/lib/nanoc/core/layout_view.rb +9 -0
  47. data/lib/nanoc/core/mutable_config_view.rb +16 -0
  48. data/lib/nanoc/core/mutable_document_view_mixin.rb +60 -0
  49. data/lib/nanoc/core/mutable_identifiable_collection_view.rb +19 -0
  50. data/lib/nanoc/core/mutable_item_collection_view.rb +34 -0
  51. data/lib/nanoc/core/mutable_item_view.rb +9 -0
  52. data/lib/nanoc/core/mutable_layout_collection_view.rb +26 -0
  53. data/lib/nanoc/core/mutable_layout_view.rb +9 -0
  54. data/lib/nanoc/core/outdatedness_checker.rb +222 -0
  55. data/lib/nanoc/core/outdatedness_rules/attributes_modified.rb +41 -0
  56. data/lib/nanoc/core/outdatedness_rules/code_snippets_modified.rb +31 -0
  57. data/lib/nanoc/core/outdatedness_rules/content_modified.rb +21 -0
  58. data/lib/nanoc/core/outdatedness_rules/item_collection_extended.rb +20 -0
  59. data/lib/nanoc/core/outdatedness_rules/layout_collection_extended.rb +20 -0
  60. data/lib/nanoc/core/outdatedness_rules/not_written.rb +17 -0
  61. data/lib/nanoc/core/outdatedness_rules/rules_modified.rb +45 -0
  62. data/lib/nanoc/core/outdatedness_rules/uses_always_outdated_filter.rb +26 -0
  63. data/lib/nanoc/core/post_compile_item_collection_view.rb +12 -0
  64. data/lib/nanoc/core/post_compile_item_rep_collection_view.rb +12 -0
  65. data/lib/nanoc/core/post_compile_item_rep_view.rb +33 -0
  66. data/lib/nanoc/core/post_compile_item_view.rb +20 -0
  67. data/lib/nanoc/core/pruner.rb +119 -0
  68. data/lib/nanoc/core/site_loader.rb +102 -0
  69. data/lib/nanoc/core/trivial_error.rb +10 -0
  70. data/lib/nanoc/core/version.rb +1 -1
  71. data/lib/nanoc/core/view.rb +43 -0
  72. data/lib/nanoc/core/view_context_for_compilation.rb +6 -6
  73. metadata +95 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf201905e79de9d9711910668d228863a48b53e13a78694a50122932960d6189
4
- data.tar.gz: ffae70af266ec5c189b0d09976bfb9ebada587f5ea1eeb73b12dfeaebeb07d36
3
+ metadata.gz: e0e2dd5ec7f304ce59ed69129bf291b1be44dfd01740ab59634a7f3f1033ad43
4
+ data.tar.gz: dc054fcd938d0980cab9699705b622b5cff901631bc69f9aa89fae08a42c7239
5
5
  SHA512:
6
- metadata.gz: bf812e2fc282b019b2d907ee39a84fe7999b2b2f8565139cc442e3ca38cb75949ea70f27b07058ef4bd52af9a1291c0ca73e6db53790e431976a729f6a4dd3c8
7
- data.tar.gz: ef670404975dfde21013d6878afbf272e7bd41d7dee120e124cb2c61e37e1ad1f5ac64ccc1f4554f57dcde1d1303cac32ef6e088853d569c24f1c070c72abc7c
6
+ metadata.gz: 2030d8250adaeb13cc7974cf35cada04e0c6ec8a7c62e81382ca0f808dfd8e6a3b0e719ffc3bdb4131236bbbfb730e122fa4779b447bc386487f588bc90a5e01
7
+ data.tar.gz: ec172cf7c9d17bcfc399212e855cc4c5dfbf9cc627b48ead86e0fe9ff264f79a67d90089f3ead1a1cbf1ba363858974ef9eb276b198e3060a27905cd5542c419
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Ruby stdlib
4
+ require 'fiber'
5
+ require 'find'
4
6
  require 'pstore'
5
7
  require 'singleton'
6
8
  require 'tmpdir'
9
+ require 'yaml'
7
10
 
8
11
  # External gems
9
12
  require 'json_schema'
@@ -12,10 +15,32 @@ require 'ddmetrics'
12
15
  require 'ddplugin'
13
16
  require 'hamster'
14
17
  require 'slow_enumerator_tools'
18
+ require 'tomlrb'
19
+ require 'tty-platform'
15
20
  require 'zeitwerk'
16
21
 
17
22
  module Nanoc
18
23
  module Core
24
+ # Similar to `nil` except that it can only be compared against using
25
+ # `UNDEFINED.equal?(x)`. Used in places where `nil` already has meaning, and
26
+ # thus cannot be used to mean the presence of nothing.
27
+ UNDEFINED = Object.new
28
+
29
+ # @return [String] A string containing information about this Nanoc version
30
+ # and its environment (Ruby engine and version, Rubygems version if any).
31
+ #
32
+ # @api private
33
+ def self.version_information
34
+ "Nanoc #{Nanoc::VERSION} © 2007–2019 Denis Defreyne.\n" \
35
+ "Running #{RUBY_ENGINE} #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) on #{RUBY_PLATFORM} with RubyGems #{Gem::VERSION}.\n"
36
+ end
37
+
38
+ # @return [Boolean] True if the current platform is Windows, false otherwise.
39
+ #
40
+ # @api private
41
+ def self.on_windows?
42
+ @_on_windows ||= TTY::Platform.new.windows?
43
+ end
19
44
  end
20
45
  end
21
46
 
@@ -43,3 +68,15 @@ loader.eager_load
43
68
  require_relative 'core/core_ext/array'
44
69
  require_relative 'core/core_ext/hash'
45
70
  require_relative 'core/core_ext/string'
71
+
72
+ # Tracking issue:
73
+ # https://github.com/nanoc/features/issues/24
74
+ Nanoc::Core::Feature.define('live_cmd', version: '4.11')
75
+
76
+ # Tracking issue:
77
+ # https://github.com/nanoc/features/issues/40
78
+ Nanoc::Core::Feature.define('toml', version: '4.11')
79
+
80
+ # Tracking issue:
81
+ # https://github.com/nanoc/features/issues/20
82
+ Nanoc::Core::Feature.define('binary_compiled_content_cache', version: '4.11')
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class BasicItemRepCollectionView < ::Nanoc::Core::View
6
+ include Enumerable
7
+
8
+ class NoSuchItemRepError < ::Nanoc::Core::Error
9
+ def initialize(rep_name)
10
+ super("No rep named #{rep_name.inspect} was found.")
11
+ end
12
+ end
13
+
14
+ # @api private
15
+ def initialize(item_reps, context)
16
+ super(context)
17
+ @item_reps = item_reps
18
+ end
19
+
20
+ # @api private
21
+ def _unwrap
22
+ @item_reps
23
+ end
24
+
25
+ # @api private
26
+ def view_class
27
+ Nanoc::Core::BasicItemRepView
28
+ end
29
+
30
+ def to_ary
31
+ @item_reps.map { |ir| view_class.new(ir, @context) }
32
+ end
33
+
34
+ # Calls the given block once for each item rep, passing that item rep as a parameter.
35
+ #
36
+ # @yieldparam [Object] item rep view
37
+ #
38
+ # @yieldreturn [void]
39
+ #
40
+ # @return [self]
41
+ def each
42
+ @item_reps.each { |ir| yield view_class.new(ir, @context) }
43
+ self
44
+ end
45
+
46
+ # @return [Integer]
47
+ def size
48
+ @item_reps.size
49
+ end
50
+
51
+ # Return the item rep with the given name, or nil if no item rep exists.
52
+ #
53
+ # @param [Symbol] rep_name
54
+ #
55
+ # @return [nil] if no item rep with the given name was found
56
+ #
57
+ # @return [Nanoc::Core::BasicItemRepView] if an item rep with the given name was found
58
+ def [](rep_name)
59
+ case rep_name
60
+ when Symbol
61
+ res = @item_reps.find { |ir| ir.name == rep_name }
62
+ res && view_class.new(res, @context)
63
+ when Integer
64
+ raise ArgumentError, "expected BasicItemRepCollectionView#[] to be called with a symbol (you likely want `.reps[:default]` rather than `.reps[#{rep_name}]`)"
65
+ else
66
+ raise ArgumentError, 'expected BasicItemRepCollectionView#[] to be called with a symbol'
67
+ end
68
+ end
69
+
70
+ # Return the item rep with the given name, or raises an exception if there
71
+ # is no rep with the given name.
72
+ #
73
+ # @param [Symbol] rep_name
74
+ #
75
+ # @return [Nanoc::Core::BasicItemRepView]
76
+ #
77
+ # @raise if no rep was found
78
+ def fetch(rep_name)
79
+ res = @item_reps.find { |ir| ir.name == rep_name }
80
+ if res
81
+ view_class.new(res, @context)
82
+ else
83
+ raise NoSuchItemRepError.new(rep_name)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class BasicItemRepView < ::Nanoc::Core::View
6
+ # @api private
7
+ def initialize(item_rep, context)
8
+ super(context)
9
+ @item_rep = item_rep
10
+ end
11
+
12
+ # @abstract
13
+ def item_view_class
14
+ Nanoc::Core::BasicItemView
15
+ end
16
+
17
+ # @api private
18
+ def _unwrap
19
+ @item_rep
20
+ end
21
+
22
+ # @see Object#==
23
+ def ==(other)
24
+ other.respond_to?(:item) && other.respond_to?(:name) && item == other.item && name == other.name
25
+ end
26
+
27
+ # @see Object#eql?
28
+ def eql?(other)
29
+ other.is_a?(self.class) &&
30
+ item.eql?(other.item) &&
31
+ name.eql?(other.name)
32
+ end
33
+
34
+ # @see Object#hash
35
+ def hash
36
+ self.class.hash ^ item.identifier.hash ^ name.hash
37
+ end
38
+
39
+ # @return [Symbol]
40
+ def name
41
+ @item_rep.name
42
+ end
43
+
44
+ def snapshot?(name)
45
+ @context.dependency_tracker.bounce(_unwrap.item, compiled_content: true)
46
+ @item_rep.snapshot?(name)
47
+ end
48
+
49
+ # Returns the item rep’s path, as used when being linked to. It starts
50
+ # with a slash and it is relative to the output directory. It does not
51
+ # include the path to the output directory. It will not include the
52
+ # filename if the filename is an index filename.
53
+ #
54
+ # @param [Symbol] snapshot The snapshot for which the path should be
55
+ # returned.
56
+ #
57
+ # @return [String] The item rep’s path.
58
+ def path(snapshot: :last)
59
+ @context.dependency_tracker.bounce(_unwrap.item, path: true)
60
+ @item_rep.path(snapshot: snapshot)
61
+ end
62
+
63
+ # Returns the item that this item rep belongs to.
64
+ #
65
+ # @return [Nanoc::Core::CompilationItemView]
66
+ def item
67
+ item_view_class.new(@item_rep.item, @context)
68
+ end
69
+
70
+ # @api private
71
+ def binary?
72
+ snapshot_def = _unwrap.snapshot_defs.find { |sd| sd.name == :last }
73
+ raise Nanoc::Core::Errors::NoSuchSnapshot.new(_unwrap, :last) if snapshot_def.nil?
74
+
75
+ snapshot_def.binary?
76
+ end
77
+
78
+ def inspect
79
+ "<#{self.class} item.identifier=#{item.identifier} name=#{name}>"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class BasicItemView < ::Nanoc::Core::View
6
+ include Nanoc::Core::DocumentViewMixin
7
+
8
+ # Returns the children of this item. For items with identifiers that have
9
+ # extensions, returns an empty collection.
10
+ #
11
+ # @return [Enumerable<Nanoc::Core::CompilationItemView>]
12
+ def children
13
+ unless _unwrap.identifier.legacy?
14
+ raise Nanoc::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem.new(_unwrap.identifier)
15
+ end
16
+
17
+ children_pattern = Nanoc::Core::Pattern.from(_unwrap.identifier.to_s + '*/')
18
+ children = @context.items.select { |i| children_pattern.match?(i.identifier) }
19
+
20
+ children.map { |i| self.class.new(i, @context) }.freeze
21
+ end
22
+
23
+ # Returns the parent of this item, if one exists. For items with identifiers
24
+ # that have extensions, returns nil.
25
+ #
26
+ # @return [Nanoc::Core::CompilationItemView] if the item has a parent
27
+ #
28
+ # @return [nil] if the item has no parent
29
+ def parent
30
+ unless _unwrap.identifier.legacy?
31
+ raise Nanoc::Core::Errors::CannotGetParentOrChildrenOfNonLegacyItem.new(_unwrap.identifier)
32
+ end
33
+
34
+ parent_identifier = '/' + _unwrap.identifier.components[0..-2].join('/') + '/'
35
+ parent_identifier = '/' if parent_identifier == '//'
36
+
37
+ parent = @context.items[parent_identifier]
38
+
39
+ parent && self.class.new(parent, @context)
40
+ end
41
+
42
+ # @return [Boolean] True if the item is binary, false otherwise
43
+ def binary?
44
+ _unwrap.content.binary?
45
+ end
46
+
47
+ # @return [String, nil] The path to the file containing the uncompiled content of this item.
48
+ def raw_filename
49
+ @context.dependency_tracker.bounce(_unwrap, raw_content: true)
50
+ _unwrap.content.filename
51
+ end
52
+ end
53
+ end
54
+ end
@@ -95,12 +95,14 @@ module Nanoc
95
95
  define_behavior(Nanoc::Core::BinaryContent, BinaryContentUpdateBehavior)
96
96
  define_behavior(Nanoc::Core::Configuration, HashUpdateBehavior)
97
97
  define_behavior(Nanoc::Core::Context, ContextUpdateBehavior)
98
+ define_behavior(Nanoc::Core::CodeSnippet, DataUpdateBehavior)
98
99
  define_behavior(Nanoc::Core::IdentifiableCollection, ArrayUpdateBehavior)
99
100
  define_behavior(Nanoc::Core::Identifier, ToSUpdateBehavior)
100
101
  define_behavior(Nanoc::Core::Item, DocumentUpdateBehavior)
101
102
  define_behavior(Nanoc::Core::ItemRep, ItemRepUpdateBehavior)
102
103
  define_behavior(Nanoc::Core::Layout, DocumentUpdateBehavior)
103
104
  define_behavior(Nanoc::Core::TextualContent, StringUpdateBehavior)
105
+ define_behavior(Nanoc::Core::View, UnwrapUpdateBehavior)
104
106
 
105
107
  @behaviors
106
108
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class CompilationItemRepCollectionView < ::Nanoc::Core::BasicItemRepCollectionView
6
+ # @api private
7
+ def view_class
8
+ Nanoc::Core::CompilationItemRepView
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class CompilationItemRepView < ::Nanoc::Core::BasicItemRepView
6
+ # @abstract
7
+ def item_view_class
8
+ Nanoc::Core::CompilationItemView
9
+ end
10
+
11
+ # Returns the item rep’s raw path. It includes the path to the output
12
+ # directory and the full filename.
13
+ #
14
+ # @param [Symbol] snapshot The snapshot for which the path should be
15
+ # returned.
16
+ #
17
+ # @return [String] The item rep’s raw path.
18
+ def raw_path(snapshot: :last)
19
+ @context.dependency_tracker.bounce(_unwrap.item, compiled_content: true)
20
+
21
+ res = @item_rep.raw_path(snapshot: snapshot)
22
+
23
+ unless @item_rep.compiled?
24
+ Fiber.yield(Nanoc::Core::Errors::UnmetDependency.new(@item_rep, snapshot))
25
+ end
26
+
27
+ # Wait for file to exist
28
+ if res
29
+ start = Time.now
30
+ sleep 0.05 until File.file?(res) || Time.now - start > 1.0
31
+ raise Nanoc::Core::Errors::InternalInconsistency, "File did not apear in time: #{res}" unless File.file?(res)
32
+ end
33
+
34
+ res
35
+ end
36
+
37
+ # Returns the compiled content.
38
+ #
39
+ # @param [String] snapshot The name of the snapshot from which to
40
+ # fetch the compiled content. By default, the returned compiled content
41
+ # will be the content compiled right before the first layout call (if
42
+ # any).
43
+ #
44
+ # @return [String] The content at the given snapshot.
45
+ def compiled_content(snapshot: nil)
46
+ @context.dependency_tracker.bounce(_unwrap.item, compiled_content: true)
47
+ @context.compiled_content_store.compiled_content(rep: _unwrap, snapshot: snapshot)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class CompilationItemView < ::Nanoc::Core::BasicItemView
6
+ # Returns the compiled content.
7
+ #
8
+ # @param [String] rep The name of the representation
9
+ # from which the compiled content should be fetched. By default, the
10
+ # compiled content will be fetched from the default representation.
11
+ #
12
+ # @param [String] snapshot The name of the snapshot from which to
13
+ # fetch the compiled content. By default, the returned compiled content
14
+ # will be the content compiled right before the first layout call (if
15
+ # any).
16
+ #
17
+ # @return [String] The content of the given rep at the given snapshot.
18
+ def compiled_content(rep: :default, snapshot: nil)
19
+ reps.fetch(rep).compiled_content(snapshot: snapshot)
20
+ end
21
+
22
+ # Returns the item path, as used when being linked to. It starts
23
+ # with a slash and it is relative to the output directory. It does not
24
+ # include the path to the output directory. It will not include the
25
+ # filename if the filename is an index filename.
26
+ #
27
+ # @param [String] rep The name of the representation
28
+ # from which the path should be fetched. By default, the path will be
29
+ # fetched from the default representation.
30
+ #
31
+ # @param [Symbol] snapshot The snapshot for which the
32
+ # path should be returned.
33
+ #
34
+ # @return [String] The item’s path.
35
+ def path(rep: :default, snapshot: :last)
36
+ reps.fetch(rep).path(snapshot: snapshot)
37
+ end
38
+
39
+ # Returns the representations of this item.
40
+ #
41
+ # @return [Nanoc::Core::BasicItemRepCollectionView]
42
+ def reps
43
+ Nanoc::Core::CompilationItemRepCollectionView.new(@context.reps[_unwrap], @context)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CompilationPhases
6
+ class Abstract
7
+ include Nanoc::Core::ContractsSupport
8
+
9
+ def initialize(wrapped:)
10
+ @wrapped = wrapped
11
+ end
12
+
13
+ def start
14
+ @wrapped&.start
15
+ end
16
+
17
+ def stop
18
+ @wrapped&.stop
19
+ end
20
+
21
+ def call(rep, is_outdated:)
22
+ notify(:phase_started, rep)
23
+ run(rep, is_outdated: is_outdated) do
24
+ notify(:phase_yielded, rep)
25
+ @wrapped.call(rep, is_outdated: is_outdated)
26
+ notify(:phase_resumed, rep)
27
+ end
28
+ notify(:phase_ended, rep)
29
+ rescue
30
+ notify(:phase_aborted, rep)
31
+ raise
32
+ end
33
+
34
+ contract Nanoc::Core::ItemRep, C::KeywordArgs[is_outdated: C::Bool], C::Func[C::None => C::Any] => C::Any
35
+ def run(_rep, is_outdated:) # rubocop:disable Lint/UnusedMethodArgument
36
+ raise NotImplementedError
37
+ end
38
+
39
+ private
40
+
41
+ def notify(sym, rep)
42
+ name = self.class.to_s.sub(/^.*::/, '')
43
+ Nanoc::Core::NotificationCenter.post(sym, name, rep)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end