nanoc-core 4.11.1

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 (42) hide show
  1. checksums.yaml +7 -0
  2. data/NEWS.md +3 -0
  3. data/README.md +3 -0
  4. data/lib/nanoc/core/binary_content.rb +12 -0
  5. data/lib/nanoc/core/checksummer.rb +287 -0
  6. data/lib/nanoc/core/code_snippet.rb +57 -0
  7. data/lib/nanoc/core/configuration-schema.json +122 -0
  8. data/lib/nanoc/core/configuration.rb +206 -0
  9. data/lib/nanoc/core/content.rb +46 -0
  10. data/lib/nanoc/core/context.rb +70 -0
  11. data/lib/nanoc/core/contracts_support.rb +131 -0
  12. data/lib/nanoc/core/core_ext/array.rb +54 -0
  13. data/lib/nanoc/core/core_ext/hash.rb +58 -0
  14. data/lib/nanoc/core/core_ext/string.rb +20 -0
  15. data/lib/nanoc/core/data_source.rb +170 -0
  16. data/lib/nanoc/core/directed_graph.rb +195 -0
  17. data/lib/nanoc/core/document.rb +124 -0
  18. data/lib/nanoc/core/error.rb +9 -0
  19. data/lib/nanoc/core/identifiable_collection.rb +142 -0
  20. data/lib/nanoc/core/identifier.rb +218 -0
  21. data/lib/nanoc/core/item.rb +11 -0
  22. data/lib/nanoc/core/item_collection.rb +15 -0
  23. data/lib/nanoc/core/item_rep.rb +92 -0
  24. data/lib/nanoc/core/layout.rb +11 -0
  25. data/lib/nanoc/core/layout_collection.rb +15 -0
  26. data/lib/nanoc/core/lazy_value.rb +38 -0
  27. data/lib/nanoc/core/notification_center.rb +97 -0
  28. data/lib/nanoc/core/pattern.rb +37 -0
  29. data/lib/nanoc/core/processing_action.rb +23 -0
  30. data/lib/nanoc/core/processing_actions/filter.rb +40 -0
  31. data/lib/nanoc/core/processing_actions/layout.rb +40 -0
  32. data/lib/nanoc/core/processing_actions/snapshot.rb +50 -0
  33. data/lib/nanoc/core/processing_actions.rb +12 -0
  34. data/lib/nanoc/core/regexp_pattern.rb +28 -0
  35. data/lib/nanoc/core/snapshot_def.rb +22 -0
  36. data/lib/nanoc/core/string_pattern.rb +29 -0
  37. data/lib/nanoc/core/temp_filename_factory.rb +53 -0
  38. data/lib/nanoc/core/textual_content.rb +41 -0
  39. data/lib/nanoc/core/version.rb +7 -0
  40. data/lib/nanoc/core.rb +60 -0
  41. data/lib/nanoc-core.rb +3 -0
  42. metadata +152 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Content
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ contract C::None => C::Maybe[String]
9
+ attr_reader :filename
10
+
11
+ contract C::Maybe[String] => C::Any
12
+ def initialize(filename)
13
+ if filename && Pathname.new(filename).relative?
14
+ raise ArgumentError, 'Content filename is not absolute'
15
+ end
16
+
17
+ @filename = filename
18
+ end
19
+
20
+ contract C::None => self
21
+ def freeze
22
+ super
23
+ @filename.freeze
24
+ self
25
+ end
26
+
27
+ contract C::Or[self, String, Proc], C::KeywordArgs[binary: C::Optional[C::Bool], filename: C::Optional[C::Maybe[String]]] => self
28
+ def self.create(content, binary: false, filename: nil)
29
+ if content.nil?
30
+ raise ArgumentError, 'Cannot create nil content'
31
+ elsif content.is_a?(Nanoc::Core::Content)
32
+ content
33
+ elsif binary
34
+ Nanoc::Core::BinaryContent.new(content)
35
+ else
36
+ Nanoc::Core::TextualContent.new(content, filename: filename)
37
+ end
38
+ end
39
+
40
+ contract C::None => C::Bool
41
+ def binary?
42
+ raise NotImplementedError
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Provides a context and a binding for use in filters such as the ERB and
6
+ # Haml ones.
7
+ class Context
8
+ # Creates a new context based off the contents of the hash.
9
+ #
10
+ # Each pair in the hash will be converted to an instance variable and an
11
+ # instance method. For example, passing the hash `{ :foo => 'bar' }` will
12
+ # cause `@foo` to have the value `"bar"`, and the instance method `#foo`
13
+ # to return the same value `"bar"`.
14
+ #
15
+ # @param [Hash] hash A list of key-value pairs to make available
16
+ #
17
+ # @example Defining a context and accessing values
18
+ #
19
+ # context = Nanoc::Core::Context.new(
20
+ # :name => 'Max Payne',
21
+ # :location => 'in a cheap motel'
22
+ # )
23
+ # context.instance_eval do
24
+ # "I am #{name} and I am hiding #{@location}."
25
+ # end
26
+ # # => "I am Max Payne and I am hiding in a cheap motel."
27
+ def initialize(hash)
28
+ hash.each_pair do |key, value|
29
+ instance_variable_set('@' + key.to_s, value)
30
+ end
31
+ end
32
+
33
+ # Returns a binding for this instance.
34
+ #
35
+ # @return [Binding] A binding for this instance
36
+ # rubocop:disable Naming/AccessorMethodName
37
+ def get_binding
38
+ binding
39
+ end
40
+ # rubocop:enable Naming/AccessorMethodName
41
+
42
+ def method_missing(method, *args, &blk)
43
+ ivar_name = '@' + method.to_s
44
+ if instance_variable_defined?(ivar_name)
45
+ instance_variable_get(ivar_name)
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def respond_to_missing?(method, include_all)
52
+ ivar_name = '@' + method.to_s
53
+
54
+ valid_ivar_name =
55
+ if defined?(Contracts)
56
+ ivar_name =~ /\A@[A-Za-z_]+\z/
57
+ else
58
+ true # probably good enough
59
+ end
60
+
61
+ (valid_ivar_name && instance_variable_defined?(ivar_name)) || super
62
+ end
63
+
64
+ def include(mod)
65
+ metaclass = class << self; self; end
66
+ metaclass.instance_eval { include(mod) }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module ContractsSupport
6
+ class Ignorer
7
+ include Singleton
8
+
9
+ def method_missing(*_args) # rubocop:disable Style/MethodMissingSuper
10
+ self
11
+ end
12
+
13
+ def respond_to_missing?(*_args)
14
+ true
15
+ end
16
+ end
17
+
18
+ module DisabledContracts
19
+ Any = Ignorer.instance
20
+ Bool = Ignorer.instance
21
+ Num = Ignorer.instance
22
+ KeywordArgs = Ignorer.instance
23
+ Args = Ignorer.instance
24
+ Optional = Ignorer.instance
25
+ Maybe = Ignorer.instance
26
+ None = Ignorer.instance
27
+ ArrayOf = Ignorer.instance
28
+ Or = Ignorer.instance
29
+ Func = Ignorer.instance
30
+ RespondTo = Ignorer.instance
31
+ Named = Ignorer.instance
32
+ IterOf = Ignorer.instance
33
+ HashOf = Ignorer.instance
34
+ AbsolutePathString = Ignorer.instance
35
+
36
+ def contract(*args); end
37
+ end
38
+
39
+ module EnabledContracts
40
+ class AbstractContract
41
+ def self.[](*vals)
42
+ new(*vals)
43
+ end
44
+ end
45
+
46
+ class Named < AbstractContract
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+
51
+ def valid?(val)
52
+ val.is_a?(Kernel.const_get(@name))
53
+ end
54
+
55
+ def inspect
56
+ "#{self.class}(#{@name})"
57
+ end
58
+ end
59
+
60
+ class IterOf < AbstractContract
61
+ def initialize(contract)
62
+ @contract = contract
63
+ end
64
+
65
+ def valid?(val)
66
+ val.respond_to?(:each) && val.all? { |v| Contract.valid?(v, @contract) }
67
+ end
68
+
69
+ def inspect
70
+ "#{self.class}(#{@contract})"
71
+ end
72
+ end
73
+
74
+ class AbsolutePathString < AbstractContract
75
+ def self.valid?(val)
76
+ val.is_a?(String) && Pathname.new(val).absolute?
77
+ end
78
+ end
79
+
80
+ def contract(*args)
81
+ Contract(*args)
82
+ end
83
+ end
84
+
85
+ def self.setup_once
86
+ @_contracts_support__setup ||= false
87
+ return @_contracts_support__should_enable if @_contracts_support__setup
88
+
89
+ @_contracts_support__setup = true
90
+
91
+ contracts_loadable =
92
+ begin
93
+ require 'contracts'
94
+ true
95
+ rescue LoadError
96
+ false
97
+ end
98
+
99
+ @_contracts_support__should_enable = contracts_loadable && !ENV.key?('DISABLE_CONTRACTS')
100
+
101
+ if @_contracts_support__should_enable
102
+ # FIXME: ugly
103
+ ::Contracts.const_set('Named', EnabledContracts::Named)
104
+ ::Contracts.const_set('IterOf', EnabledContracts::IterOf)
105
+ ::Contracts.const_set('AbsolutePathString', EnabledContracts::AbsolutePathString)
106
+ end
107
+
108
+ @_contracts_support__should_enable
109
+ end
110
+
111
+ def self.enabled?
112
+ setup_once
113
+ end
114
+
115
+ def self.included(base)
116
+ should_enable = setup_once
117
+
118
+ if should_enable
119
+ unless base.include?(::Contracts::Core)
120
+ base.include(::Contracts::Core)
121
+ base.extend(EnabledContracts)
122
+ base.const_set('C', ::Contracts)
123
+ end
124
+ else
125
+ base.extend(DisabledContracts)
126
+ base.const_set('C', DisabledContracts)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CoreExt
6
+ module ArrayExtensions
7
+ # Returns a new array where all items' keys are recursively converted to
8
+ # symbols by calling {Nanoc::ArrayExtensions#__nanoc_symbolize_keys_recursively} or
9
+ # {Nanoc::HashExtensions#__nanoc_symbolize_keys_recursively}.
10
+ #
11
+ # @return [Array] The converted array
12
+ def __nanoc_symbolize_keys_recursively
13
+ array = []
14
+ each do |element|
15
+ array << (element.respond_to?(:__nanoc_symbolize_keys_recursively) ? element.__nanoc_symbolize_keys_recursively : element)
16
+ end
17
+ array
18
+ end
19
+
20
+ def __nanoc_stringify_keys_recursively
21
+ array = []
22
+ each do |element|
23
+ array << (element.respond_to?(:__nanoc_stringify_keys_recursively) ? element.__nanoc_stringify_keys_recursively : element)
24
+ end
25
+ array
26
+ end
27
+
28
+ # Freezes the contents of the array, as well as all array elements. The
29
+ # array elements will be frozen using {#__nanoc_freeze_recursively} if they respond
30
+ # to that message, or #freeze if they do not.
31
+ #
32
+ # @see Hash#__nanoc_freeze_recursively
33
+ #
34
+ # @return [void]
35
+ def __nanoc_freeze_recursively
36
+ return if frozen?
37
+
38
+ freeze
39
+ each do |value|
40
+ if value.respond_to?(:__nanoc_freeze_recursively)
41
+ value.__nanoc_freeze_recursively
42
+ else
43
+ value.freeze
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ class Array
53
+ include Nanoc::Core::CoreExt::ArrayExtensions
54
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CoreExt
6
+ module HashExtensions
7
+ # Returns a new hash where all keys are recursively converted to symbols by
8
+ # calling {Nanoc::ArrayExtensions#__nanoc_symbolize_keys_recursively} or
9
+ # {Nanoc::HashExtensions#__nanoc_symbolize_keys_recursively}.
10
+ #
11
+ # @return [Hash] The converted hash
12
+ def __nanoc_symbolize_keys_recursively
13
+ hash = {}
14
+ each_pair do |key, value|
15
+ new_key = key.respond_to?(:to_sym) ? key.to_sym : key
16
+ new_value = value.respond_to?(:__nanoc_symbolize_keys_recursively) ? value.__nanoc_symbolize_keys_recursively : value
17
+ hash[new_key] = new_value
18
+ end
19
+ hash
20
+ end
21
+
22
+ def __nanoc_stringify_keys_recursively
23
+ hash = {}
24
+ each_pair do |key, value|
25
+ new_key = key.is_a?(Symbol) ? key.to_s : key
26
+ new_value = value.respond_to?(:__nanoc_stringify_keys_recursively) ? value.__nanoc_stringify_keys_recursively : value
27
+ hash[new_key] = new_value
28
+ end
29
+ hash
30
+ end
31
+
32
+ # Freezes the contents of the hash, as well as all hash values. The hash
33
+ # values will be frozen using {#__nanoc_freeze_recursively} if they respond to
34
+ # that message, or #freeze if they do not.
35
+ #
36
+ # @see Array#__nanoc_freeze_recursively
37
+ #
38
+ # @return [void]
39
+ def __nanoc_freeze_recursively
40
+ return if frozen?
41
+
42
+ freeze
43
+ each_pair do |_key, value|
44
+ if value.respond_to?(:__nanoc_freeze_recursively)
45
+ value.__nanoc_freeze_recursively
46
+ else
47
+ value.freeze
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ class Hash
57
+ include Nanoc::Core::CoreExt::HashExtensions
58
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ module CoreExt
6
+ module StringExtensions
7
+ # Transforms string into an actual identifier
8
+ #
9
+ # @return [String] The identifier generated from the receiver
10
+ def __nanoc_cleaned_identifier
11
+ "/#{self}/".gsub(/^\/+|\/+$/, '/')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class String
19
+ include Nanoc::Core::CoreExt::StringExtensions
20
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Responsible for loading site data. It is the (abstract) superclass for all
6
+ # data sources. Subclasses must at least implement the data reading methods
7
+ # ({#items} and {#layouts}).
8
+ #
9
+ # Apart from the methods for loading and storing data, there are the {#up}
10
+ # and {#down} methods for bringing up and tearing down the connection to the
11
+ # data source. These should be overridden in subclasses. The {#loading}
12
+ # method wraps {#up} and {#down}. {#loading} is a convenience method for the
13
+ # more low-level methods {#use} and {#unuse}, which respectively increment
14
+ # and decrement the reference count; when the reference count goes from 0 to
15
+ # 1, the data source will be loaded ({#up} will be called) and when the
16
+ # reference count goes from 1 to 0, the data source will be unloaded
17
+ # ({#down} will be called).
18
+ #
19
+ # @abstract Subclasses should at least implement {#items} and {#layouts}.
20
+ class DataSource
21
+ # @return [String] The root path where items returned by this data source
22
+ # should be mounted.
23
+ attr_reader :items_root
24
+
25
+ # @return [String] The root path where layouts returned by this data
26
+ # source should be mounted.
27
+ attr_reader :layouts_root
28
+
29
+ # @return [Hash] The configuration for this data source. For example,
30
+ # online data sources could contain authentication details.
31
+ attr_reader :config
32
+
33
+ extend DDPlugin::Plugin
34
+
35
+ def initialize(site_config, items_root, layouts_root, config)
36
+ @site_config = site_config
37
+ @items_root = items_root
38
+ @layouts_root = layouts_root
39
+ @config = config || {}
40
+
41
+ @references = 0
42
+ end
43
+
44
+ # Marks the data source as used by the caller.
45
+ #
46
+ # Calling this method increases the internal reference count. When the
47
+ # data source is used for the first time (first {#use} call), the data
48
+ # source will be loaded ({#up} will be called).
49
+ #
50
+ # @return [void]
51
+ def use
52
+ up if @references.zero?
53
+ @references += 1
54
+ end
55
+
56
+ # Marks the data source as unused by the caller.
57
+ #
58
+ # Calling this method decreases the internal reference count. When the
59
+ # reference count reaches zero, i.e. when the data source is not used any
60
+ # more, the data source will be unloaded ({#down} will be called).
61
+ #
62
+ # @return [void]
63
+ def unuse
64
+ @references -= 1
65
+ down if @references.zero?
66
+ end
67
+
68
+ # Brings up the connection to the data. Depending on the way data is
69
+ # stored, this may not be necessary. This is the ideal place to connect to
70
+ # the database, for example.
71
+ #
72
+ # Subclasses may override this method, but are not required to do so; the
73
+ # default implementation simply does nothing.
74
+ #
75
+ # @return [void]
76
+ def up; end
77
+
78
+ # Brings down the connection to the data. This method should undo the
79
+ # effects of the {#up} method. For example, a database connection
80
+ # established in {#up} should be closed in this method.
81
+ #
82
+ # Subclasses may override this method, but are not required to do so; the
83
+ # default implementation simply does nothing.
84
+ #
85
+ # @return [void]
86
+ def down; end
87
+
88
+ # Returns the collection of items (represented by {Nanoc::Core::Item}) in
89
+ # this site. The default implementation simply returns an empty array.
90
+ #
91
+ # Subclasses should not prepend `items_root` to the item's identifiers, as
92
+ # this will be done automatically.
93
+ #
94
+ # Subclasses may override this method, but are not required to do so; the
95
+ # default implementation simply does nothing.
96
+ #
97
+ # @return [Enumerable] The collection of items
98
+ def items
99
+ []
100
+ end
101
+
102
+ # @api private
103
+ def item_changes
104
+ warn "Caution: Data source #{self.class.identifier.inspect} does not implement #item_changes; live compilation will not pick up changes in this data source."
105
+ Enumerator.new { |_y| sleep }
106
+ end
107
+
108
+ # @api private
109
+ def layout_changes
110
+ warn "Caution: Data source #{self.class.identifier.inspect} does not implement #layout_changes; live compilation will not pick up changes in this data source."
111
+ Enumerator.new { |_y| sleep }
112
+ end
113
+
114
+ # Returns the collection of layouts (represented by {Nanoc::Core::Layout}) in
115
+ # this site. The default implementation simply returns an empty array.
116
+ #
117
+ # Subclasses should prepend `layout_root` to the layout's identifiers,
118
+ # since this is not done automatically.
119
+ #
120
+ # Subclasses may override this method, but are not required to do so; the
121
+ # default implementation simply does nothing.
122
+ #
123
+ # @return [Enumerable] The collection of layouts
124
+ def layouts
125
+ []
126
+ end
127
+
128
+ # Creates a new in-memory item instance. This is intended for use within
129
+ # the {#items} method.
130
+ #
131
+ # @param [String, Proc] content The uncompiled item content
132
+ # (if it is a textual item) or the path to the filename containing the
133
+ # content (if it is a binary item).
134
+ #
135
+ # @param [Hash, Proc] attributes A hash containing this item's attributes.
136
+ #
137
+ # @param [String] identifier This item's identifier.
138
+ #
139
+ # @param [Boolean] binary Whether or not this item is binary
140
+ #
141
+ # @param [String, nil] checksum_data
142
+ #
143
+ # @param [String, nil] content_checksum_data
144
+ #
145
+ # @param [String, nil] attributes_checksum_data
146
+ def new_item(content, attributes, identifier, binary: false, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil)
147
+ content = Nanoc::Core::Content.create(content, binary: binary)
148
+ Nanoc::Core::Item.new(content, attributes, identifier, checksum_data: checksum_data, content_checksum_data: content_checksum_data, attributes_checksum_data: attributes_checksum_data)
149
+ end
150
+
151
+ # Creates a new in-memory layout instance. This is intended for use within
152
+ # the {#layouts} method.
153
+ #
154
+ # @param [String] raw_content The raw content of this layout.
155
+ #
156
+ # @param [Hash] attributes A hash containing this layout's attributes.
157
+ #
158
+ # @param [String] identifier This layout's identifier.
159
+ #
160
+ # @param [String, nil] checksum_data
161
+ #
162
+ # @param [String, nil] content_checksum_data
163
+ #
164
+ # @param [String, nil] attributes_checksum_data
165
+ def new_layout(raw_content, attributes, identifier, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil)
166
+ Nanoc::Core::Layout.new(raw_content, attributes, identifier, checksum_data: checksum_data, content_checksum_data: content_checksum_data, attributes_checksum_data: attributes_checksum_data)
167
+ end
168
+ end
169
+ end
170
+ end