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.
- checksums.yaml +7 -0
- data/NEWS.md +3 -0
- data/README.md +3 -0
- data/lib/nanoc/core/binary_content.rb +12 -0
- data/lib/nanoc/core/checksummer.rb +287 -0
- data/lib/nanoc/core/code_snippet.rb +57 -0
- data/lib/nanoc/core/configuration-schema.json +122 -0
- data/lib/nanoc/core/configuration.rb +206 -0
- data/lib/nanoc/core/content.rb +46 -0
- data/lib/nanoc/core/context.rb +70 -0
- data/lib/nanoc/core/contracts_support.rb +131 -0
- data/lib/nanoc/core/core_ext/array.rb +54 -0
- data/lib/nanoc/core/core_ext/hash.rb +58 -0
- data/lib/nanoc/core/core_ext/string.rb +20 -0
- data/lib/nanoc/core/data_source.rb +170 -0
- data/lib/nanoc/core/directed_graph.rb +195 -0
- data/lib/nanoc/core/document.rb +124 -0
- data/lib/nanoc/core/error.rb +9 -0
- data/lib/nanoc/core/identifiable_collection.rb +142 -0
- data/lib/nanoc/core/identifier.rb +218 -0
- data/lib/nanoc/core/item.rb +11 -0
- data/lib/nanoc/core/item_collection.rb +15 -0
- data/lib/nanoc/core/item_rep.rb +92 -0
- data/lib/nanoc/core/layout.rb +11 -0
- data/lib/nanoc/core/layout_collection.rb +15 -0
- data/lib/nanoc/core/lazy_value.rb +38 -0
- data/lib/nanoc/core/notification_center.rb +97 -0
- data/lib/nanoc/core/pattern.rb +37 -0
- data/lib/nanoc/core/processing_action.rb +23 -0
- data/lib/nanoc/core/processing_actions/filter.rb +40 -0
- data/lib/nanoc/core/processing_actions/layout.rb +40 -0
- data/lib/nanoc/core/processing_actions/snapshot.rb +50 -0
- data/lib/nanoc/core/processing_actions.rb +12 -0
- data/lib/nanoc/core/regexp_pattern.rb +28 -0
- data/lib/nanoc/core/snapshot_def.rb +22 -0
- data/lib/nanoc/core/string_pattern.rb +29 -0
- data/lib/nanoc/core/temp_filename_factory.rb +53 -0
- data/lib/nanoc/core/textual_content.rb +41 -0
- data/lib/nanoc/core/version.rb +7 -0
- data/lib/nanoc/core.rb +60 -0
- data/lib/nanoc-core.rb +3 -0
- 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,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,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
|