nanoc-core 4.11.1

Sign up to get free protection for your applications and to get access to all the features.
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,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Represents a directed graph. It is used by the dependency tracker for
6
+ # storing and querying dependencies between items.
7
+ #
8
+ # @example Creating and using a directed graph
9
+ #
10
+ # # Create a graph with three vertices
11
+ # graph = Nanoc::Core::DirectedGraph.new(%w( a b c d e f g ))
12
+ #
13
+ # # Add edges
14
+ # graph.add_edge('a', 'b')
15
+ # graph.add_edge('b', 'c')
16
+ # graph.add_edge('b', 'f')
17
+ # graph.add_edge('b', 'g')
18
+ # graph.add_edge('c', 'd')
19
+ # graph.add_edge('d', 'e')
20
+ #
21
+ # # Get (direct) predecessors
22
+ # graph.direct_predecessors_of('b').sort
23
+ # # => %w( a )
24
+ # graph.predecessors_of('e').sort
25
+ # # => %w( a b c d )
26
+ #
27
+ # # Modify edges
28
+ # graph.delete_edges_to('c')
29
+ #
30
+ # # Get (direct) predecessors again
31
+ # graph.direct_predecessors_of('e').sort
32
+ # # => %w( d )
33
+ # graph.predecessors_of('e').sort
34
+ # # => %w( c d )
35
+ class DirectedGraph
36
+ # @group Creating a graph
37
+
38
+ # Creates a new directed graph with the given vertices.
39
+ def initialize(vertices)
40
+ @vertices = {}
41
+ @next_vertex_idx = 0
42
+ vertices.each do |v|
43
+ @vertices[v] = @next_vertex_idx.tap { @next_vertex_idx += 1 }
44
+ end
45
+
46
+ @to_graph = {}
47
+
48
+ @edge_props = {}
49
+
50
+ invalidate_caches
51
+ end
52
+
53
+ def inspect
54
+ s = []
55
+
56
+ @vertices.each_pair do |v2, _|
57
+ direct_predecessors_of(v2).each do |v1|
58
+ s << [v1.inspect + ' -> ' + v2.inspect + ' props=' + @edge_props[[v1, v2]].inspect]
59
+ end
60
+ end
61
+
62
+ self.class.to_s + '(' + s.join(', ') + ')'
63
+ end
64
+
65
+ # @group Modifying the graph
66
+
67
+ # Adds an edge from the first vertex to the second vertex.
68
+ #
69
+ # @param from Vertex where the edge should start
70
+ #
71
+ # @param to Vertex where the edge should end
72
+ #
73
+ # @return [void]
74
+ def add_edge(from, to, props: nil)
75
+ add_vertex(from)
76
+ add_vertex(to)
77
+
78
+ @to_graph[to] ||= Set.new
79
+ @to_graph[to] << from
80
+
81
+ if props
82
+ @edge_props[[from, to]] = props
83
+ end
84
+
85
+ invalidate_caches
86
+ end
87
+
88
+ # Adds the given vertex to the graph.
89
+ #
90
+ # @param vertex The vertex to add to the graph
91
+ #
92
+ # @return [void]
93
+ def add_vertex(vertex)
94
+ return if @vertices.key?(vertex)
95
+
96
+ @vertices[vertex] = @next_vertex_idx.tap { @next_vertex_idx += 1 }
97
+ end
98
+
99
+ # Deletes all edges going to the given vertex.
100
+ #
101
+ # @param to Vertex to which all edges should be removed
102
+ #
103
+ # @return [void]
104
+ def delete_edges_to(to)
105
+ return if @to_graph[to].nil?
106
+
107
+ @to_graph[to].each do |from|
108
+ @edge_props.delete([from, to])
109
+ end
110
+ @to_graph.delete(to)
111
+
112
+ invalidate_caches
113
+ end
114
+
115
+ # @group Querying the graph
116
+
117
+ # Returns the direct predecessors of the given vertex, i.e. the vertices
118
+ # x where there is an edge from x to the given vertex y.
119
+ #
120
+ # @param to The vertex of which the predecessors should be calculated
121
+ #
122
+ # @return [Array] Direct predecessors of the given vertex
123
+ def direct_predecessors_of(to)
124
+ @to_graph.fetch(to, Set.new)
125
+ end
126
+
127
+ # Returns the predecessors of the given vertex, i.e. the vertices x for
128
+ # which there is a path from x to the given vertex y.
129
+ #
130
+ # @param to The vertex of which the predecessors should be calculated
131
+ #
132
+ # @return [Array] Predecessors of the given vertex
133
+ def predecessors_of(to)
134
+ @predecessors[to] ||= recursively_find_vertices(to, :direct_predecessors_of)
135
+ end
136
+
137
+ def props_for(from, to)
138
+ @edge_props[[from, to]]
139
+ end
140
+
141
+ # @return [Array] The list of all vertices in this graph.
142
+ def vertices
143
+ @vertices.keys.sort_by { |v| @vertices[v] }
144
+ end
145
+
146
+ # Returns an array of tuples representing the edges. The result of this
147
+ # method may take a while to compute and should be cached if possible.
148
+ #
149
+ # @return [Array] The list of all edges in this graph.
150
+ def edges
151
+ result = []
152
+ @vertices.each_pair do |v2, i2|
153
+ direct_predecessors_of(v2).map { |v1| [@vertices[v1], v1] }.each do |i1, v1|
154
+ result << [i1, i2, @edge_props[[v1, v2]]]
155
+ end
156
+ end
157
+ result
158
+ end
159
+
160
+ private
161
+
162
+ # Invalidates cached data. This method should be called when the internal
163
+ # graph representation is changed.
164
+ def invalidate_caches
165
+ @predecessors = {}
166
+ end
167
+
168
+ # Recursively finds vertices, starting at the vertex start, using the
169
+ # given method, which should be a symbol to a method that takes a vertex
170
+ # and returns related vertices (e.g. predecessors, successors).
171
+ def recursively_find_vertices(start, method)
172
+ all_vertices = Set.new
173
+
174
+ processed_vertices = Set.new
175
+ unprocessed_vertices = [start]
176
+
177
+ until unprocessed_vertices.empty?
178
+ # Get next unprocessed vertex
179
+ vertex = unprocessed_vertices.pop
180
+ next if processed_vertices.include?(vertex)
181
+
182
+ processed_vertices << vertex
183
+
184
+ # Add predecessors of this vertex
185
+ send(method, vertex).each do |v|
186
+ all_vertices << v unless all_vertices.include?(v)
187
+ unprocessed_vertices << v
188
+ end
189
+ end
190
+
191
+ all_vertices
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Document
6
+ include Nanoc::Core::ContractsSupport
7
+
8
+ # @return [Nanoc::Core::Content]
9
+ attr_reader :content
10
+
11
+ # @return [Hash]
12
+ def attributes
13
+ @attributes.value
14
+ end
15
+
16
+ # @return [Nanoc::Core::Identifier]
17
+ attr_reader :identifier
18
+
19
+ # @return [String, nil]
20
+ attr_accessor :checksum_data
21
+
22
+ # @return [String, nil]
23
+ attr_accessor :content_checksum_data
24
+
25
+ # @return [String, nil]
26
+ attr_accessor :attributes_checksum_data
27
+
28
+ c_content = C::Or[String, Nanoc::Core::Content]
29
+ c_attributes = C::Or[Hash, Proc]
30
+ c_identifier = C::Or[String, Nanoc::Core::Identifier]
31
+ c_checksum_data = C::KeywordArgs[
32
+ checksum_data: C::Optional[C::Maybe[String]],
33
+ content_checksum_data: C::Optional[C::Maybe[String]],
34
+ attributes_checksum_data: C::Optional[C::Maybe[String]],
35
+ ]
36
+
37
+ contract c_content, c_attributes, c_identifier, c_checksum_data => C::Any
38
+ # @param [String, Nanoc::Core::Content] content
39
+ #
40
+ # @param [Hash, Proc] attributes
41
+ #
42
+ # @param [String, Nanoc::Core::Identifier] identifier
43
+ #
44
+ # @param [String, nil] checksum_data
45
+ #
46
+ # @param [String, nil] content_checksum_data
47
+ #
48
+ # @param [String, nil] attributes_checksum_data
49
+ def initialize(content, attributes, identifier, checksum_data: nil, content_checksum_data: nil, attributes_checksum_data: nil)
50
+ @content = Nanoc::Core::Content.create(content)
51
+ @attributes = Nanoc::Core::LazyValue.new(attributes).map(&:__nanoc_symbolize_keys_recursively)
52
+ @identifier = Nanoc::Core::Identifier.from(identifier)
53
+
54
+ @checksum_data = checksum_data
55
+ @content_checksum_data = content_checksum_data
56
+ @attributes_checksum_data = attributes_checksum_data
57
+ end
58
+
59
+ contract C::None => self
60
+ # @return [void]
61
+ def freeze
62
+ super
63
+ @content.freeze
64
+ @attributes.freeze
65
+ self
66
+ end
67
+
68
+ contract String => self
69
+ def with_identifier_prefix(prefix)
70
+ other = dup
71
+ other.identifier = @identifier.prefix(prefix)
72
+ other
73
+ end
74
+
75
+ contract C::None => String
76
+ # @abstract
77
+ #
78
+ # @return Unique reference to this object
79
+ def reference
80
+ raise NotImplementedError
81
+ end
82
+
83
+ contract C::Or[Nanoc::Core::Identifier, String] => Nanoc::Core::Identifier
84
+ def identifier=(new_identifier)
85
+ @identifier = Nanoc::Core::Identifier.from(new_identifier)
86
+ end
87
+
88
+ contract Nanoc::Core::Content => C::Any
89
+ def content=(new_content)
90
+ @content = new_content
91
+
92
+ @checksum_data = nil
93
+ @content_checksum_data = nil
94
+ end
95
+
96
+ def set_attribute(key, value)
97
+ attributes[key] = value
98
+
99
+ @checksum_data = nil
100
+ @attributes_checksum_data = nil
101
+ end
102
+
103
+ contract C::None => String
104
+ def inspect
105
+ "<#{self.class} identifier=\"#{identifier}\">"
106
+ end
107
+
108
+ contract C::None => C::Num
109
+ def hash
110
+ self.class.hash ^ identifier.hash
111
+ end
112
+
113
+ contract C::Any => C::Bool
114
+ def ==(other)
115
+ other.respond_to?(:identifier) && identifier == other.identifier
116
+ end
117
+
118
+ contract C::Any => C::Bool
119
+ def eql?(other)
120
+ other.is_a?(self.class) && identifier == other.identifier
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ # Generic error. Superclass for all Nanoc-specific errors.
6
+ class Error < ::StandardError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class IdentifiableCollection
6
+ DDMemoize.activate(self)
7
+
8
+ include Nanoc::Core::ContractsSupport
9
+ include Enumerable
10
+
11
+ extend Forwardable
12
+
13
+ def_delegator :@objects, :each
14
+ def_delegator :@objects, :size
15
+
16
+ def initialize(*)
17
+ raise 'IdentifiableCollection is abstract and cannot be instantiated'
18
+ end
19
+
20
+ contract C::Or[Hash, C::Named['Nanoc::Core::Configuration']], C::IterOf[C::RespondTo[:identifier]], C::Maybe[String] => C::Any
21
+ def initialize_basic(config, objects = [], name = nil)
22
+ @config = config
23
+ @objects = Hamster::Vector.new(objects)
24
+ @name = name
25
+ end
26
+
27
+ contract C::None => String
28
+ def inspect
29
+ "<#{self.class}>"
30
+ end
31
+
32
+ contract C::None => self
33
+ def freeze
34
+ @objects.freeze
35
+ @objects.each(&:freeze)
36
+ build_mapping
37
+ super
38
+ end
39
+
40
+ contract C::Any => C::Maybe[C::RespondTo[:identifier]]
41
+ def [](arg)
42
+ if frozen?
43
+ get_memoized(arg)
44
+ else
45
+ get_unmemoized(arg)
46
+ end
47
+ end
48
+
49
+ contract C::Any => C::IterOf[C::RespondTo[:identifier]]
50
+ def find_all(arg)
51
+ if frozen?
52
+ find_all_memoized(arg)
53
+ else
54
+ find_all_unmemoized(arg)
55
+ end
56
+ end
57
+
58
+ contract C::None => C::ArrayOf[C::RespondTo[:identifier]]
59
+ def to_a
60
+ @objects.to_a
61
+ end
62
+
63
+ contract C::None => C::Bool
64
+ def empty?
65
+ @objects.empty?
66
+ end
67
+
68
+ contract C::RespondTo[:identifier] => self
69
+ def add(obj)
70
+ self.class.new(@config, @objects.add(obj))
71
+ end
72
+
73
+ contract C::Func[C::RespondTo[:identifier] => C::Any] => self
74
+ def reject(&block)
75
+ self.class.new(@config, @objects.reject(&block))
76
+ end
77
+
78
+ contract C::Any => C::Maybe[C::RespondTo[:identifier]]
79
+ def object_with_identifier(identifier)
80
+ if frozen?
81
+ @mapping[identifier.to_s]
82
+ else
83
+ @objects.find { |i| i.identifier == identifier }
84
+ end
85
+ end
86
+
87
+ protected
88
+
89
+ contract C::Any => C::Maybe[C::RespondTo[:identifier]]
90
+ def get_unmemoized(arg)
91
+ case arg
92
+ when Nanoc::Core::Identifier
93
+ object_with_identifier(arg)
94
+ when String
95
+ object_with_identifier(arg) || object_matching_glob(arg)
96
+ when Regexp
97
+ @objects.find { |i| i.identifier.to_s =~ arg }
98
+ else
99
+ raise ArgumentError, "don’t know how to fetch objects by #{arg.inspect}"
100
+ end
101
+ end
102
+
103
+ contract C::Any => C::Maybe[C::RespondTo[:identifier]]
104
+ memoized def get_memoized(arg)
105
+ get_unmemoized(arg)
106
+ end
107
+
108
+ contract C::Any => C::IterOf[C::RespondTo[:identifier]]
109
+ def find_all_unmemoized(arg)
110
+ pat = Nanoc::Core::Pattern.from(arg)
111
+ select { |i| pat.match?(i.identifier) }
112
+ end
113
+
114
+ contract C::Any => C::IterOf[C::RespondTo[:identifier]]
115
+ memoized def find_all_memoized(arg)
116
+ find_all_unmemoized(arg)
117
+ end
118
+
119
+ def object_matching_glob(glob)
120
+ if use_globs?
121
+ pat = Nanoc::Core::Pattern.from(glob)
122
+ @objects.find { |i| pat.match?(i.identifier) }
123
+ else
124
+ nil
125
+ end
126
+ end
127
+
128
+ def build_mapping
129
+ @mapping = {}
130
+ @objects.each do |object|
131
+ @mapping[object.identifier.to_s] = object
132
+ end
133
+ @mapping.freeze
134
+ end
135
+
136
+ contract C::None => C::Bool
137
+ def use_globs?
138
+ @config[:string_pattern_type] == 'glob'
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Identifier
6
+ include Comparable
7
+ include Nanoc::Core::ContractsSupport
8
+
9
+ class InvalidIdentifierError < ::Nanoc::Core::Error
10
+ def initialize(string)
11
+ super("Invalid identifier (does not start with a slash): #{string.inspect}")
12
+ end
13
+ end
14
+
15
+ class InvalidFullIdentifierError < ::Nanoc::Core::Error
16
+ def initialize(string)
17
+ super("Invalid full identifier (ends with a slash): #{string.inspect}")
18
+ end
19
+ end
20
+
21
+ class InvalidTypeError < ::Nanoc::Core::Error
22
+ def initialize(type)
23
+ super("Invalid type for identifier: #{type.inspect} (can be :full or :legacy)")
24
+ end
25
+ end
26
+
27
+ class InvalidPrefixError < ::Nanoc::Core::Error
28
+ def initialize(string)
29
+ super("Invalid prefix (does not start with a slash): #{string.inspect}")
30
+ end
31
+ end
32
+
33
+ class UnsupportedLegacyOperationError < ::Nanoc::Core::Error
34
+ def initialize
35
+ super('Cannot use this method on legacy identifiers')
36
+ end
37
+ end
38
+
39
+ class NonCoercibleObjectError < ::Nanoc::Core::Error
40
+ def initialize(obj)
41
+ super("#{obj.inspect} cannot be converted into a Nanoc::Core::Identifier")
42
+ end
43
+ end
44
+
45
+ contract C::Any => self
46
+ def self.from(obj)
47
+ case obj
48
+ when Nanoc::Core::Identifier
49
+ obj
50
+ when String
51
+ Nanoc::Core::Identifier.new(obj)
52
+ else
53
+ raise NonCoercibleObjectError.new(obj)
54
+ end
55
+ end
56
+
57
+ contract String, C::KeywordArgs[type: C::Optional[Symbol]] => C::Any
58
+ def initialize(string, type: :full)
59
+ @type = type
60
+
61
+ case @type
62
+ when :legacy
63
+ @string = "/#{string}/".gsub(/^\/+|\/+$/, '/').freeze
64
+ when :full
65
+ raise InvalidIdentifierError.new(string) if string !~ /\A\//
66
+ raise InvalidFullIdentifierError.new(string) if string =~ /\/\z/
67
+
68
+ @string = string.dup.freeze
69
+ else
70
+ raise InvalidTypeError.new(@type)
71
+ end
72
+ end
73
+
74
+ contract C::Any => C::Bool
75
+ def ==(other)
76
+ case other
77
+ when Nanoc::Core::Identifier, String
78
+ to_s == other.to_s
79
+ else
80
+ false
81
+ end
82
+ end
83
+
84
+ contract C::Any => C::Bool
85
+ def eql?(other)
86
+ other.is_a?(self.class) && to_s == other.to_s
87
+ end
88
+
89
+ contract C::None => C::Num
90
+ def hash
91
+ self.class.hash ^ to_s.hash
92
+ end
93
+
94
+ contract C::Any => C::Maybe[C::Num]
95
+ def =~(other)
96
+ Nanoc::Core::Pattern.from(other).match?(to_s) ? 0 : nil
97
+ end
98
+
99
+ contract C::Any => C::Bool
100
+ def match?(other)
101
+ Nanoc::Core::Pattern.from(other).match?(to_s)
102
+ end
103
+
104
+ contract C::Any => C::Num
105
+ def <=>(other)
106
+ to_s <=> other.to_s
107
+ end
108
+
109
+ contract C::None => C::Bool
110
+ # Whether or not this is a full identifier (i.e.includes the extension).
111
+ def full?
112
+ @type == :full
113
+ end
114
+
115
+ contract C::None => C::Bool
116
+ # Whether or not this is a legacy identifier (i.e. does not include the extension).
117
+ def legacy?
118
+ @type == :legacy
119
+ end
120
+
121
+ contract C::None => String
122
+ # @return [String]
123
+ def chop
124
+ to_s.chop
125
+ end
126
+
127
+ contract String => String
128
+ # @return [String]
129
+ def +(other)
130
+ to_s + other
131
+ end
132
+
133
+ contract String => self
134
+ # @return [Nanoc::Core::Identifier]
135
+ def prefix(string)
136
+ unless /\A\//.match?(string)
137
+ raise InvalidPrefixError.new(string)
138
+ end
139
+
140
+ Nanoc::Core::Identifier.new(string.sub(/\/+\z/, '') + @string, type: @type)
141
+ end
142
+
143
+ contract C::None => String
144
+ # The identifier, as string, with the last extension removed
145
+ def without_ext
146
+ unless full?
147
+ raise UnsupportedLegacyOperationError
148
+ end
149
+
150
+ extname = File.extname(@string)
151
+
152
+ if !extname.empty?
153
+ @string[0..-extname.size - 1]
154
+ else
155
+ @string
156
+ end
157
+ end
158
+
159
+ contract C::None => C::Maybe[String]
160
+ # The extension, without a leading dot
161
+ def ext
162
+ unless full?
163
+ raise UnsupportedLegacyOperationError
164
+ end
165
+
166
+ s = File.extname(@string)
167
+ s && s[1..-1]
168
+ end
169
+
170
+ contract C::None => String
171
+ # The identifier, as string, with all extensions removed
172
+ def without_exts
173
+ extname = exts.join('.')
174
+ if !extname.empty?
175
+ @string[0..-extname.size - 2]
176
+ else
177
+ @string
178
+ end
179
+ end
180
+
181
+ contract C::None => C::ArrayOf[String]
182
+ # The list of extensions, without a leading dot
183
+ def exts
184
+ unless full?
185
+ raise UnsupportedLegacyOperationError
186
+ end
187
+
188
+ s = File.basename(@string)
189
+ s ? s.split('.', -1).drop(1) : []
190
+ end
191
+
192
+ contract C::None => C::ArrayOf[String]
193
+ def components
194
+ res = to_s.split('/')
195
+ if res.empty?
196
+ []
197
+ else
198
+ res[1..-1]
199
+ end
200
+ end
201
+
202
+ contract C::None => String
203
+ def to_s
204
+ @string
205
+ end
206
+
207
+ contract C::None => String
208
+ def to_str
209
+ @string
210
+ end
211
+
212
+ contract C::None => String
213
+ def inspect
214
+ "<Nanoc::Core::Identifier type=#{@type} #{to_s.inspect}>"
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class Item < ::Nanoc::Core::Document
6
+ def reference
7
+ "item:#{identifier}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nanoc
4
+ module Core
5
+ class ItemCollection < IdentifiableCollection
6
+ def initialize(config, objects = [])
7
+ initialize_basic(config, objects, 'items')
8
+ end
9
+
10
+ def reference
11
+ 'items'
12
+ end
13
+ end
14
+ end
15
+ end