nanoc-core 4.11.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|