yarrow 0.7.1 → 0.7.3

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,218 +0,0 @@
1
- module Yarrow
2
- module Content
3
- class CollectionExpander
4
- include Yarrow::Tools::FrontMatter
5
-
6
- def initialize(content_types=nil)
7
- @content_types = content_types || [
8
- Yarrow::Content::ContentType.from_name(:pages)
9
- ]
10
- end
11
-
12
- def expand(graph)
13
- @content_types.each do |content_type|
14
- expand_nested(graph, content_type)
15
- end
16
- end
17
-
18
- def expand_nested(graph, content_type)
19
- type = content_type.collection
20
- exts = content_type.extensions
21
-
22
- # If match path represents entire content dir, then include the entire
23
- # content dir instead of scanning from a subfolder matching the name of
24
- # the collection.
25
- start_node = if content_type.match_path == "."
26
- graph.n(:root)
27
- else
28
- graph.n(:root).out(name: type.to_s)
29
- end
30
-
31
- # Extract metadata from given start node
32
- data = extract_metadata(start_node, type)
33
-
34
- # Collects all nested collections in the subgraph for this content type
35
- subcollections = {}
36
- index = nil
37
-
38
- # Define alias for accessing metadata in the loop
39
- metadata = data
40
-
41
- # Scan and collect all nested directories under the top level source
42
- start_node.depth_first.each do |node|
43
- if node.label == :directory
44
- # Check if this entry has metadata defined at the top level
45
- if data[:collections]
46
- item = data[:collections].find { |c| c[:slug] == node.props[:slug] }
47
- metadata = item if item
48
- end
49
-
50
- # Create a collection node representing a collection of documents
51
- index = graph.create_node do |collection_node|
52
- collection_node.label = :collection
53
- collection_node.props[:type] = type
54
- collection_node.props[:name] = node.props[:name]
55
- collection_node.props[:slug] = node.props[:slug]
56
- collection_node.props[:title] = metadata[:title]
57
-
58
- # Override default status so that mapped index collections always show
59
- # up in the resulting document manifest, when they don’t have
60
- # associated metadata. This is the opposite of how individual pieces
61
- # of content behave (default to draft status if one isn’t supplied).
62
- collection_node.props[:status] = if data[:status]
63
- data[:status]
64
- else
65
- "published"
66
- end
67
-
68
- # TODO: URL generation might need to happen elsewhere
69
- collection_node.props[:url] = if data[:url]
70
- data[:url]
71
- else
72
- "#{node.props[:path].split('./content').last}/"
73
- end
74
- end
75
-
76
- # Add this collection id to the lookup table for edge construction
77
- subcollections[node.props[:path]] = index
78
-
79
- # Join the collection to its parent
80
- unless node.props[:slug] == type.to_s || !subcollections.key?(node.props[:entry].parent.to_s)
81
- graph.create_edge do |edge|
82
- edge.label = :child
83
- edge.from = subcollections[node.props[:entry].parent.to_s].id
84
- edge.to = index.id
85
- end
86
- end
87
- end
88
- end
89
-
90
- subcollections.each do |path, index|
91
- # Group files matching the same slug under a common key
92
- objects = graph.n(path: path).out(:file).all.select do |file|
93
- file.props[:name].end_with?(*exts)
94
- end.group_by do |file|
95
- file.props[:slug]
96
- end
97
-
98
- build_content_nodes(graph, objects, type, index)
99
- end
100
- end
101
-
102
- # Extract collection level configuration/metadata from the root node for
103
- # this content type.
104
- def extract_metadata(node, type)
105
- # TODO: support _index or _slug convention as well
106
- meta_file = node.out(slug: type.to_s).first
107
-
108
- if meta_file
109
- # Process metadata and add it to the collection node
110
- # TODO: pass in content converter object
111
- # TODO: index/body content by default if extracted from frontmatter
112
- body, data = process_content(meta_file.props[:entry])
113
- else
114
- # Otherwise, assume default collection behaviour
115
- data = {}
116
- end
117
-
118
- # Generate a default title if not provided in metadata
119
- unless data.key?(:title)
120
- data[:title] = type.to_s.capitalize
121
- end
122
-
123
- data
124
- end
125
-
126
- def build_content_nodes(graph, objects, type, parent_index)
127
- # TODO: this may need to use a strategy that can be overriden
128
- content_type = Yarrow::Symbols.to_singular(type)
129
-
130
- # Process collected content objects and generate entity nodes
131
- objects.each do |name, sources|
132
- item_node = graph.create_node do |node|
133
- # TODO: Rename this to :entry and support similar fields to Atom
134
- node.label = :item
135
- node.props[:name] = name
136
- node.props[:type] = content_type
137
-
138
- meta = {}
139
- content = ""
140
-
141
- sources.each do |source|
142
- body, data = process_content(source.props[:entry])
143
- meta.merge!(data) unless data.nil?
144
- content << body unless body.nil?
145
- end
146
-
147
- if meta[:url]
148
- # If a URL is explicitly proided in metadata then use it
149
- node.props[:url] = meta[:url]
150
- elsif meta[:permalink]
151
- # Support for legacy permalink attribute
152
- node.props[:url] = meta[:permalink]
153
- else
154
- # Default URL generation strategy when no explicit URL is provided
155
- # TODO: collection nodes need URL generation too
156
- # TODO: replace this with URL generation strategy
157
- # TODO: slug vs name - why do some nodes have 2 and some 3 props?
158
- node.props[:url] = if parent_index.props[:name].to_sym == parent_index.props[:type]
159
- "/#{parent_index.props[:type]}/#{name}"
160
- else
161
- "/#{parent_index.props[:type]}/#{parent_index.props[:slug]}/#{name}"
162
- end
163
- end
164
-
165
- # For now, we are storing title, url, etc on the top-level item.
166
- node.props[:title] = meta[:title]
167
-
168
- # TODO: What belongs on the entity and what belongs on the item?
169
- entity_props = meta.merge(body: content, name: meta[:id], url: node.props[:url])
170
-
171
-
172
- # TODO: consider whether to provide `body` on the item/document or at
173
- # the custom content type level.
174
- begin
175
- content_struct = Yarrow::Symbols.to_const(content_type)
176
- rescue
177
- # No immutable struct found: fall back to slower dynamically typed open struct
178
- require "ostruct"
179
- content_struct = OpenStruct
180
- end
181
-
182
- node.props[:entity] = content_struct.new(entity_props)
183
- end
184
-
185
- # Connect entity with source content
186
- sources.each do |source|
187
- graph.create_edge do |edge|
188
- edge.label = :source
189
- edge.from = item_node
190
- edge.to = source.id
191
- end
192
- end
193
-
194
- # Connect entity with parent collection
195
- graph.create_edge do |edge|
196
- edge.label = :child
197
- edge.from = parent_index
198
- edge.to = item_node
199
- end
200
- end
201
- end
202
-
203
- # Workaround for handling meta and content source in multiple files or a single
204
- # file with front matter.
205
- def process_content(path)
206
- case path.extname
207
- when '.htm', '.md'
208
- read_split_content(path.to_s, symbolize_keys: true)
209
- # when '.md'
210
- # body, data = read_split_content(path.to_s, symbolize_keys: true)
211
- # [Kramdown::Document.new(body).to_html, data]
212
- when '.yml'
213
- [nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
214
- end
215
- end
216
- end
217
- end
218
- end
@@ -1,42 +0,0 @@
1
- gem "strings-inflection"
2
-
3
- module Yarrow
4
- module Content
5
- class ContentType
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,55 +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
- directories = {
13
- Pathname.new(input_dir).to_s => root.id
14
- }
15
-
16
- Pathname.glob("#{input_dir}/**/**").each do |entry|
17
- if entry.directory?
18
- #puts "Reading directory: #{entry}"
19
-
20
- content_node = create_node do |dir|
21
- dir.label = :directory
22
- dir.props[:name] = entry.basename.to_s
23
- dir.props[:slug] = entry.basename.to_s
24
- dir.props[:path] = entry.to_s
25
- dir.props[:entry] = entry
26
- end
27
-
28
- directories[entry.to_s] = content_node.id
29
- else
30
- #puts "Reading file: #{entry} (#{entry.basename.sub_ext('')})"
31
-
32
- content_node = create_node do |file|
33
- file.label = :file
34
- file.props[:name] = entry.basename.to_s
35
- file.props[:slug] = entry.basename.sub_ext('').to_s
36
- file.props[:path] = entry.to_s
37
- file.props[:entry] = entry
38
- end
39
- end
40
-
41
- if directories.key?(entry.dirname.to_s)
42
- #puts "Create parent edge: #{directories[entry.dirname.to_s]}"
43
-
44
- create_edge do |edge|
45
- edge.label = :child
46
- edge.from = directories[entry.dirname.to_s]
47
- edge.to = content_node
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end