yarrow 0.7.2 → 0.7.4

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.
@@ -1,241 +0,0 @@
1
- module Yarrow
2
- module Content
3
- class CollectionExpander
4
- include Yarrow::Tools::FrontMatter
5
-
6
- # If a list of object types is not provided, a default `pages` type is
7
- # created.
8
- def initialize(object_types=nil)
9
- @object_types = object_types || [
10
- Yarrow::Content::ObjectType.from_name(:pages)
11
- ]
12
- end
13
-
14
- def expand(graph)
15
- @object_types.each do |object_type|
16
- expand_nested(graph, object_type)
17
- end
18
- end
19
-
20
- def expand_nested(graph, content_type)
21
- strategy = TreeExpansion.new(graph)
22
- strategy.expand(content_type)
23
- end
24
-
25
- def expand_nested_legacy(graph, content_type)
26
- type = content_type.collection
27
- exts = content_type.extensions
28
-
29
- # If match path represents entire content dir, then include the entire
30
- # content dir instead of scanning from a subfolder matching the name of
31
- # the collection.
32
- start_node = if content_type.match_path == "."
33
- graph.n(:root)
34
- else
35
- graph.n(:root).out(name: type.to_s)
36
- end
37
-
38
- # Extract metadata from given start node
39
- data = extract_metadata(start_node, type)
40
-
41
- # Collects all nested collections in the subgraph for this content type
42
- subcollections = {}
43
- index = nil
44
-
45
- # Define alias for accessing metadata in the loop
46
- metadata = data
47
-
48
- # Scan and collect all nested directories under the top level source
49
- start_node.depth_first.each do |node|
50
- if node.label == :directory
51
- # Check if this entry has metadata defined at the top level
52
- if data[:collections]
53
- item = data[:collections].find { |c| c[:slug] == node.props[:slug] }
54
- metadata = item if item
55
- end
56
-
57
- # Create a collection node representing a collection of documents
58
- index = graph.create_node do |collection_node|
59
- collection_node.label = :collection
60
- collection_node.props[:type] = type
61
- collection_node.props[:name] = node.props[:name]
62
- collection_node.props[:slug] = node.props[:slug]
63
- collection_node.props[:title] = metadata[:title]
64
-
65
- # Override default status so that mapped index collections always show
66
- # up in the resulting document manifest, when they don’t have
67
- # associated metadata. This is the opposite of how individual pieces
68
- # of content behave (default to draft status if one isn’t supplied).
69
- collection_node.props[:status] = if data[:status]
70
- data[:status]
71
- else
72
- "published"
73
- end
74
-
75
- # TODO: URL generation might need to happen elsewhere
76
- collection_node.props[:url] = if data[:url]
77
- data[:url]
78
- else
79
- "#{node.props[:path].split('./content').last}/"
80
- end
81
- end
82
-
83
- # Add this collection id to the lookup table for edge construction
84
- subcollections[node.props[:path]] = index
85
-
86
- # Join the collection to its parent
87
- unless node.props[:slug] == type.to_s || !subcollections.key?(node.props[:entry].parent.to_s)
88
- graph.create_edge do |edge|
89
- edge.label = :child
90
- edge.from = subcollections[node.props[:entry].parent.to_s].id
91
- edge.to = index.id
92
- end
93
- end
94
- end
95
- end
96
-
97
- # If there are no subcollections then we need to look at the start node
98
- # TODO: test to verify if this could be used in all cases, not just
99
- # the situation where there are subfolders to be mapped.
100
- if subcollections.empty?
101
- # Collect files that match the content type extension and group them
102
- # under a common key for each slug (this is so we can merge multiple
103
- # files with the same name together into a single content type, a
104
- # specific pattern found in some legacy content folders).
105
- #
106
- # Ideally, this code should be deleted once we have a clean workflow
107
- # and can experiment with decoupling different strategies for
108
- # expansion/enrichment of content objects.
109
- objects = start_node.out(:file).all.select do |file|
110
- file.props[:name].end_with?(*exts)
111
- end.group_by do |file|
112
- file.props[:slug]
113
- end
114
-
115
- # This is a massive hack to deal with situations where we don’t
116
- # recurse down the list of directories. The best way to clean it up
117
- # will be to document the different supported mapping formats and
118
- # URL generation strategies and break these up into separate
119
- # traversal objects for each particular style of content organisation.
120
- if index.nil?
121
- index = graph.create_node do |collection_node|
122
- collection_node.label = :collection
123
- collection_node.props[:type] = type
124
- collection_node.props[:name] = type
125
- collection_node.props[:slug] = type.to_s
126
- collection_node.props[:title] = metadata[:title]
127
-
128
- # Override default status so that mapped index collections always show
129
- # up in the resulting document manifest, when they don’t have
130
- # associated metadata. This is the opposite of how individual pieces
131
- # of content behave (default to draft status if one isn’t supplied).
132
- collection_node.props[:status] = if data[:status]
133
- data[:status]
134
- else
135
- "published"
136
- end
137
-
138
- # TODO: URL generation might need to happen elsewhere
139
- collection_node.props[:url] = if data[:url]
140
- data[:url]
141
- else
142
- "/#{type}/"
143
- end
144
- end
145
- end
146
-
147
- build_content_nodes(graph, objects, type, index)
148
- end
149
-
150
- # Go through each subcollection and expand content nodes step by step.
151
- subcollections.each do |path, index|
152
- # Group files matching the same slug under a common key
153
- objects = graph.n(path: path).out(:file).all.select do |file|
154
- file.props[:name].end_with?(*exts)
155
- end.group_by do |file|
156
- file.props[:slug]
157
- end
158
-
159
- build_content_nodes(graph, objects, type, index)
160
- end
161
- end
162
-
163
- def build_content_nodes(graph, objects, type, parent_index)
164
- # TODO: this may need to use a strategy that can be overriden
165
- content_type = Yarrow::Symbols.to_singular(type)
166
-
167
- # Process collected content objects and generate entity nodes
168
- objects.each do |name, sources|
169
- item_node = graph.create_node do |node|
170
- # TODO: Rename this to :entry and support similar fields to Atom
171
- node.label = :item
172
- node.props[:name] = name
173
- node.props[:type] = content_type
174
-
175
- meta = {}
176
- content = ""
177
-
178
- sources.each do |source|
179
- body, data = process_content(source.props[:entry])
180
- meta.merge!(data) unless data.nil?
181
- content << body unless body.nil?
182
- end
183
-
184
- if meta[:url]
185
- # If a URL is explicitly proided in metadata then use it
186
- node.props[:url] = meta[:url]
187
- elsif meta[:permalink]
188
- # Support for legacy permalink attribute
189
- node.props[:url] = meta[:permalink]
190
- else
191
- # Default URL generation strategy when no explicit URL is provided
192
- # TODO: collection nodes need URL generation too
193
- # TODO: replace this with URL generation strategy
194
- # TODO: slug vs name - why do some nodes have 2 and some 3 props?
195
- node.props[:url] = if parent_index.props[:name].to_sym == parent_index.props[:type]
196
- "/#{parent_index.props[:type]}/#{name}"
197
- else
198
- "/#{parent_index.props[:type]}/#{parent_index.props[:slug]}/#{name}"
199
- end
200
- end
201
-
202
- # For now, we are storing title, url, etc on the top-level item.
203
- node.props[:title] = meta[:title]
204
-
205
- # TODO: What belongs on the entity and what belongs on the item?
206
- entity_props = meta.merge(body: content, name: meta[:id], url: node.props[:url])
207
-
208
-
209
- # TODO: consider whether to provide `body` on the item/document or at
210
- # the custom content type level.
211
- begin
212
- content_struct = Yarrow::Symbols.to_const(content_type)
213
- rescue
214
- # No immutable struct found: fall back to slower dynamically typed open struct
215
- require "ostruct"
216
- content_struct = OpenStruct
217
- end
218
-
219
- node.props[:entity] = content_struct.new(entity_props)
220
- end
221
-
222
- # Connect entity with source content
223
- sources.each do |source|
224
- graph.create_edge do |edge|
225
- edge.label = :source
226
- edge.from = item_node
227
- edge.to = source.id
228
- end
229
- end
230
-
231
- # Connect entity with parent collection
232
- graph.create_edge do |edge|
233
- edge.label = :child
234
- edge.from = parent_index
235
- edge.to = item_node
236
- end
237
- end
238
- end
239
- end
240
- end
241
- end
@@ -1,42 +0,0 @@
1
- gem "strings-inflection"
2
-
3
- module Yarrow
4
- module Content
5
- class ObjectType
6
- Value = Yarrow::Schema::Value.new(:collection, :entity, :extensions)
7
-
8
- DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
9
-
10
- def self.from_name(name)
11
- new(Value.new(collection: name.to_sym))
12
- end
13
-
14
- def initialize(properties)
15
- unless properties.respond_to?(:collection) || properties.respond_to?(:entity)
16
- raise "Must provide a collection name or entity name"
17
- end
18
-
19
- @properties = properties
20
- end
21
-
22
- def collection
23
- return @properties.collection if @properties.collection
24
- Yarrow::Symbols.to_plural(@properties.entity)
25
- end
26
-
27
- def entity
28
- return @properties.entity if @properties.entity
29
- Yarrow::Symbols.to_singular(@properties.collection)
30
- end
31
-
32
- def extensions
33
- return @properties.extensions if @properties.extensions
34
- DEFAULT_EXTENSIONS
35
- end
36
-
37
- def match_path
38
- "."
39
- end
40
- end
41
- end
42
- end
@@ -1,78 +0,0 @@
1
- module Yarrow
2
- module Content
3
- # Collects a digraph of all directories and files underneath the given input
4
- # directory.
5
- class SourceCollector
6
- def self.collect(input_dir)
7
- Mementus::Graph.new(is_mutable: true) do
8
- root = create_node do |root|
9
- root.label = :root
10
- end
11
-
12
- root_dir_entry = Pathname.new(input_dir)
13
-
14
- root_dir = create_node do |dir|
15
- dir.label = :directory
16
- dir.props = {
17
- name: root_dir_entry.basename.to_s,
18
- path: root_dir_entry.to_s,
19
- entry: root_dir_entry
20
- }
21
- end
22
-
23
- create_edge do |child|
24
- child.label = :child
25
- child.from = root.id
26
- child.to = root_dir.id
27
- end
28
-
29
- directories = {
30
- root_dir_entry.to_s => root_dir.id
31
- }
32
-
33
- Pathname.glob("#{input_dir}/**/**").each do |entry|
34
- if entry.directory?
35
- content_node = create_node do |dir|
36
- dir.label = :directory
37
- # dir.props[:name] = entry.basename.to_s
38
- # dir.props[:slug] = entry.basename.to_s
39
- # dir.props[:path] = entry.to_s
40
- # dir.props[:entry] = entry
41
- dir.props = {
42
- name: entry.basename.to_s,
43
- path: entry.to_s,
44
- entry: entry
45
- }
46
- end
47
-
48
- directories[entry.to_s] = content_node.id
49
- else
50
- content_node = create_node do |file|
51
- file.label = :file
52
- # file.props[:name] = entry.basename.to_s
53
- # file.props[:slug] = entry.basename.sub_ext('').to_s
54
- # file.props[:path] = entry.to_s
55
- # file.props[:entry] = entry
56
-
57
- file.props = {
58
- name: entry.basename.to_s,
59
- ext: entry.extname.to_s,
60
- path: entry.to_s,
61
- entry: entry
62
- }
63
- end
64
- end
65
-
66
- if directories.key?(entry.dirname.to_s)
67
- create_edge do |edge|
68
- edge.label = :child
69
- edge.from = directories[entry.dirname.to_s]
70
- edge.to = content_node
71
- end
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end