yarrow 0.8.7 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTENT.md +228 -0
- data/README.md +8 -8
- data/lib/extensions/mementus.rb +29 -0
- data/lib/yarrow/content/expansion/aggregator.rb +88 -0
- data/lib/yarrow/content/expansion/basename_merge.rb +44 -0
- data/lib/yarrow/content/expansion/directory_map.rb +21 -0
- data/lib/yarrow/content/expansion/directory_merge.rb +31 -0
- data/lib/yarrow/content/expansion/file_list.rb +15 -0
- data/lib/yarrow/content/expansion/filename_map.rb +24 -0
- data/lib/yarrow/content/expansion/strategy.rb +47 -0
- data/lib/yarrow/content/expansion/traversal.rb +67 -0
- data/lib/yarrow/content/expansion/tree.rb +53 -74
- data/lib/yarrow/content/model.rb +3 -4
- data/lib/yarrow/content/policy.rb +77 -21
- data/lib/yarrow/content/source.rb +4 -0
- data/lib/yarrow/format/asciidoc.rb +0 -0
- data/lib/yarrow/format/html.rb +0 -0
- data/lib/yarrow/format/json.rb +0 -0
- data/lib/yarrow/format/markdown.rb +67 -0
- data/lib/yarrow/format/mediawiki.rb +0 -0
- data/lib/yarrow/format/methods/front_matter.rb +58 -0
- data/lib/yarrow/format/methods/metadata.rb +22 -0
- data/lib/yarrow/format/orgmode.rb +0 -0
- data/lib/yarrow/format/text.rb +0 -0
- data/lib/yarrow/format/xml.rb +0 -0
- data/lib/yarrow/format/yaml.rb +19 -0
- data/lib/yarrow/format.rb +71 -0
- data/lib/yarrow/version.rb +1 -1
- data/lib/yarrow/web/manifest.rb +15 -9
- data/lib/yarrow.rb +6 -9
- metadata +22 -3
- data/lib/yarrow/tools/content_utils.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96b575b4a93031397d38c09ab442ce577a98378114941c6be3dcaf4c2a71a3ac
|
4
|
+
data.tar.gz: 15d01eb7b8f710aee890c8879dc202a12ecab3f3cca0030cdc7df8e61619ed75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be90c39d718849c20db481c2f90d105400af237c7232d4cdd091e7260fffe3d09e4828c4d50f473424daa1d223d2cf5259ed55f4120534ad884ea7ca4d2124dc
|
7
|
+
data.tar.gz: b4e5150cd8a3da147489fe0f8077520df328ae2fc5018e4ea102ca171625ada864da3a08aeb59860a98d04b7515e8f0868a023f85fb10c1718592a066d818b1b
|
data/CONTENT.md
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# Content Management
|
2
|
+
|
3
|
+
## Source Map
|
4
|
+
|
5
|
+
The source map specifies how content is expanded into addressable resources that define the information structure of a project.
|
6
|
+
|
7
|
+
Currently only website projects are supported, so the organisation is slightly skewed towards URL-centric representations (this can be tidied up in future in order to represent e-books and print publications).
|
8
|
+
|
9
|
+
The source map is made up of a set of expansion policies, listed in the configuration as key-value pairs.
|
10
|
+
|
11
|
+
### Shorthand Format
|
12
|
+
|
13
|
+
The configuration format supports a shorthand for declaring policies based on defaults without needing to provide a specification with every single attribute filled in.
|
14
|
+
|
15
|
+
Although it’s not quite implemented this way in Ruby (yet?), you can think of the policy spec in the config as being a union of `String | Symbol | SpecStruct` where all `SpecStruct` attributes are also optional.
|
16
|
+
|
17
|
+
The simplest possible config shorthand assumes that the policy label is a plural reference to the collection type that gets expanded.
|
18
|
+
|
19
|
+
If the spec value is a symbol, this is interpreted as a singular reference to the entity type that gets attached to the parent collection for each matched content object. In this case, the policy label is overloaded to match the source directory root.
|
20
|
+
|
21
|
+
```yaml
|
22
|
+
content:
|
23
|
+
source_map:
|
24
|
+
pages: :page
|
25
|
+
blog: :post
|
26
|
+
gallery: :photo
|
27
|
+
```
|
28
|
+
|
29
|
+
This source map will be interpreted as:
|
30
|
+
|
31
|
+
- Expand a `Pages` collection of `Page` objects from the source directory `./pages`
|
32
|
+
- Expand a `Blog` collection of `Post` objects from the source directory `./blog`
|
33
|
+
- Expand a `Gallery` collection of `Photo` objects from the source directory `./gallery`
|
34
|
+
|
35
|
+
If the spec value is a string, this is interpreted as matching the source directory root for the traversal with the policy label referring to the collection type only and the entity type being a singular conversion of the plural policy label.
|
36
|
+
|
37
|
+
```yaml
|
38
|
+
content:
|
39
|
+
source_map:
|
40
|
+
pages: "about"
|
41
|
+
notes: "archive"
|
42
|
+
photos: "gallery"
|
43
|
+
```
|
44
|
+
|
45
|
+
This source map will be interpreted as:
|
46
|
+
|
47
|
+
- Expand a `Pages` collection of `Page` objects from the source directory `./about`
|
48
|
+
- Expand a `Notes` collection of `Note` objects from the source directory `./archive`
|
49
|
+
- Expand a `Photos` collection of `Photo` objects from the source directory `./gallery`
|
50
|
+
|
51
|
+
### Attribute Precedence
|
52
|
+
|
53
|
+
The shorthand format offers limited possibilities for customisation, so in many situations it’s best to provide a more detailed policy spec. All attributes specified directly take precedence over shorthand conventions and defaults.
|
54
|
+
|
55
|
+
```yaml
|
56
|
+
content:
|
57
|
+
source_map:
|
58
|
+
site:
|
59
|
+
collection: :pages
|
60
|
+
entity: :page
|
61
|
+
source_path: "about"
|
62
|
+
blog:
|
63
|
+
entity: :post
|
64
|
+
source_path: "archive"
|
65
|
+
gallery:
|
66
|
+
collection: :photos
|
67
|
+
```
|
68
|
+
|
69
|
+
This will be interpreted as:
|
70
|
+
|
71
|
+
- Expand a `Pages` collection of `Page` objects from the source directory `./about`
|
72
|
+
- Expand a `Blog` collection of `Post` objects from the source directory `./archive`
|
73
|
+
- Expand a `Photos` collection of `Photo` objects from the source directory `./gallery`
|
74
|
+
|
75
|
+
### Default Spec
|
76
|
+
|
77
|
+
```yaml
|
78
|
+
content:
|
79
|
+
source_map:
|
80
|
+
site:
|
81
|
+
container: :pages
|
82
|
+
collection: :pages
|
83
|
+
entity: :page
|
84
|
+
source_path: "."
|
85
|
+
|
86
|
+
```
|
87
|
+
|
88
|
+
## Expansion Strategies
|
89
|
+
|
90
|
+
Each policy specified in the source map triggers a traversal of the source content graph using an event-driven pattern to report each relevant file and directory to an aggregator component which expands these into collections and entity resources.
|
91
|
+
|
92
|
+
Changing the aggregator enables a variety of different structures of nested directories and files to be expanded into a well-organised content model, providing a more flexible and creative foundation for publishing and editorial design than the rigid conventions of most other static-site generators.
|
93
|
+
|
94
|
+
### Filename Map
|
95
|
+
|
96
|
+
```yml
|
97
|
+
aggregator: :filename_map
|
98
|
+
```
|
99
|
+
|
100
|
+
Expands nested directories and files with a simple 1:1 mapping to collections and files. Each matching filename becomes a single item of content.
|
101
|
+
|
102
|
+
#### Example
|
103
|
+
|
104
|
+
Source:
|
105
|
+
|
106
|
+
```
|
107
|
+
🖿 content
|
108
|
+
└──🖿 pages
|
109
|
+
├──🗎page1.md
|
110
|
+
├──🗎page2.md
|
111
|
+
├──🗎page3.md
|
112
|
+
└──🖿 children
|
113
|
+
├──🗎page4.md
|
114
|
+
└──🗎page5.md
|
115
|
+
```
|
116
|
+
|
117
|
+
Policy:
|
118
|
+
|
119
|
+
```
|
120
|
+
content:
|
121
|
+
source_map:
|
122
|
+
pages:
|
123
|
+
aggregator: :filename_map
|
124
|
+
```
|
125
|
+
|
126
|
+
Expansion:
|
127
|
+
|
128
|
+
```mermaid
|
129
|
+
graph TD;
|
130
|
+
pages((Pages: pages))-->page1(Page: page1);
|
131
|
+
pages-->page2(Page: page2);
|
132
|
+
pages-->page3(Page: page3);
|
133
|
+
pages-->children((Pages: children));
|
134
|
+
children-->page4(Page: page4);
|
135
|
+
children-->page5(Page: page5);
|
136
|
+
```
|
137
|
+
|
138
|
+
### Directory Merge
|
139
|
+
|
140
|
+
```yml
|
141
|
+
aggregator: :directory_merge
|
142
|
+
```
|
143
|
+
|
144
|
+
Expands from a list of directories, with each directory representing a single item of content.
|
145
|
+
|
146
|
+
This is a good choice for rich-content websites with more complex content layouts for articles or interactive essays where a variety of assets are developed alongside the main manuscript file.
|
147
|
+
|
148
|
+
#### Example
|
149
|
+
|
150
|
+
Source:
|
151
|
+
|
152
|
+
```
|
153
|
+
🖿 content
|
154
|
+
└──🖿 essays
|
155
|
+
└──🖿 concept1
|
156
|
+
├──🗎concept-1.md
|
157
|
+
├──🖻image1.png
|
158
|
+
├──🖻image2.svg
|
159
|
+
├──🖻image3.jpg
|
160
|
+
├──🗎data.json
|
161
|
+
└──🗎loop.mp3
|
162
|
+
```
|
163
|
+
|
164
|
+
Policy:
|
165
|
+
|
166
|
+
```yml
|
167
|
+
content:
|
168
|
+
source_map:
|
169
|
+
essays:
|
170
|
+
collection: :essays
|
171
|
+
aggregator: :directory_merge
|
172
|
+
source_path: "essays"
|
173
|
+
match_entities: [.md]
|
174
|
+
match_assets: [.png, .jpg, .svg, .json, .mp3]
|
175
|
+
```
|
176
|
+
|
177
|
+
Expansion:
|
178
|
+
|
179
|
+
```mermaid
|
180
|
+
graph TD;
|
181
|
+
essays((Essays: essays))-->concept1(Essay: concept-1);
|
182
|
+
concept1-->image1(Asset: image1.png);
|
183
|
+
concept1-->image2(Asset: image2.png);
|
184
|
+
concept1-->image3(Asset: image3.jpg);
|
185
|
+
concept1-->data(Asset: data.json);
|
186
|
+
concept1-->loop(Asset: loop.mp3);
|
187
|
+
```
|
188
|
+
|
189
|
+
### Directory
|
190
|
+
|
191
|
+
```
|
192
|
+
expansion_strategy:
|
193
|
+
aggregator: :directory_source
|
194
|
+
match_source: "essays"
|
195
|
+
match_entities: "*.md"
|
196
|
+
match_assets: "*.jpg"
|
197
|
+
```
|
198
|
+
|
199
|
+
## Collection Containers
|
200
|
+
|
201
|
+
Some content models may require collection types to be nested children of a top-level parent type. To define this in a policy you can manually specify a `container` which will override the default `collection` at the root level of the content hierarchy. Containers represent ‘collections of collections’, offering more modelling flexibility and applicability to wider variety of website use cases.
|
202
|
+
|
203
|
+
```
|
204
|
+
🖿 content
|
205
|
+
└──🖿 gallery
|
206
|
+
├──🖿 exhibition-a
|
207
|
+
│ ├──🗎1.htm
|
208
|
+
│ ├──🗎2.htm
|
209
|
+
│ ├──🗎3.htm
|
210
|
+
│ ├──🗎a-1.jpg
|
211
|
+
│ ├──🗎a-2.jpg
|
212
|
+
│ └──🗎a-3.jpg
|
213
|
+
└──🖿 exhibition-b
|
214
|
+
├──🗎1.htm
|
215
|
+
├──🗎2.htm
|
216
|
+
├──🗎b-1.jpg
|
217
|
+
└──🗎b-2.jpg
|
218
|
+
```
|
219
|
+
|
220
|
+
The following policy spec allows us to model a gallery type which holds a list of exhibitions, with each exhibition in turn holding a list of artworks.
|
221
|
+
|
222
|
+
```yml
|
223
|
+
container: :gallery
|
224
|
+
collection: :exhibitions
|
225
|
+
entity: :artwork
|
226
|
+
```
|
227
|
+
|
228
|
+
In many cases it might be fine to just nest instances of a single collection type, but this more fine-grained content modelling can be useful when it comes to templating and editorial design.
|
data/README.md
CHANGED
@@ -38,14 +38,14 @@ Roadmap
|
|
38
38
|
|
39
39
|
A rough sketch of the project direction.
|
40
40
|
|
41
|
-
| Version | Features
|
42
|
-
|
43
|
-
| `0.
|
44
|
-
| `0.
|
45
|
-
| `0.
|
46
|
-
| `0.
|
47
|
-
| `0.
|
48
|
-
| `1.0` |
|
41
|
+
| Version | Features |
|
42
|
+
|---------|-------------------------------------------------|
|
43
|
+
| `0.9` | Support standard text formats and linked assets |
|
44
|
+
| `0.10` | Custom Markdown components |
|
45
|
+
| `0.11` | Publishing support for S3 and GitHub/Netlify |
|
46
|
+
| `0.12` | Clean up local web server and watcher |
|
47
|
+
| `0.13` | Content structure transformations |
|
48
|
+
| `1.0` | Reintroduce generic command line runner |
|
49
49
|
|
50
50
|
License
|
51
51
|
-------
|
data/lib/extensions/mementus.rb
CHANGED
@@ -7,6 +7,13 @@ module Mementus
|
|
7
7
|
# methods. Mostly used for #map. API needs to be fixed in the gem itself.
|
8
8
|
include Enumerable
|
9
9
|
|
10
|
+
def traverse_by(traversal)
|
11
|
+
case traversal
|
12
|
+
when :tree then depth_first
|
13
|
+
when :list then nodes
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
10
17
|
def to
|
11
18
|
Step.new(map { |edge| edge.to }, Pipe.new(graph), graph)
|
12
19
|
end
|
@@ -36,6 +43,28 @@ module Mementus
|
|
36
43
|
"<Mementus::Graph @structure=#{@structure.inspect} " +
|
37
44
|
"nodes_count=#{nodes_count} edges_count=#{edges_count}>"
|
38
45
|
end
|
46
|
+
|
47
|
+
def to_dot
|
48
|
+
statements = []
|
49
|
+
|
50
|
+
nodes.each do |node|
|
51
|
+
label = if node.props.key?(:type)
|
52
|
+
"#{node.label}: #{node.props[:type]}:#{node.props[:resource].name}"
|
53
|
+
elsif node.props.key?(:name)
|
54
|
+
"#{node.label}: #{node.props[:name]}"
|
55
|
+
else
|
56
|
+
node.label
|
57
|
+
end
|
58
|
+
|
59
|
+
statements << "#{node.id} [label=\"#{label}\"]"
|
60
|
+
end
|
61
|
+
|
62
|
+
edges.each do |edge|
|
63
|
+
statements << "#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.label}\"];"
|
64
|
+
end
|
65
|
+
|
66
|
+
"digraph {\n#{statements.join("\n")}\n}"
|
67
|
+
end
|
39
68
|
end
|
40
69
|
|
41
70
|
class Node
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class Aggregator
|
5
|
+
attr_reader :graph
|
6
|
+
|
7
|
+
def initialize(graph)
|
8
|
+
@graph = graph
|
9
|
+
@collections = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def before_traversal(policy)
|
13
|
+
end
|
14
|
+
|
15
|
+
def expand_container(container, policy)
|
16
|
+
end
|
17
|
+
|
18
|
+
def expand_collection(collection, policy)
|
19
|
+
end
|
20
|
+
|
21
|
+
def expand_entity(entity, policy)
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_traversal(policy)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def create_collection(source_node, type, collection_const)
|
30
|
+
# Create a collection node with attached resource model
|
31
|
+
index = graph.create_node do |collection_node|
|
32
|
+
attributes = {
|
33
|
+
name: source_node.props[:name],
|
34
|
+
title: source_node.props[:name].capitalize,
|
35
|
+
body: ""
|
36
|
+
}
|
37
|
+
collection_node.label = :collection
|
38
|
+
collection_node.props[:type] = type
|
39
|
+
collection_node.props[:resource] = collection_const.new(attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add this collection id to the lookup table for edge construction
|
43
|
+
@collections[source_node.props[:path]] = index
|
44
|
+
|
45
|
+
# Join the collection to its parent
|
46
|
+
if @collections.key?(source_node.props[:entry].parent.to_s)
|
47
|
+
graph.create_edge do |edge|
|
48
|
+
edge.label = :child
|
49
|
+
edge.from = @collections[source_node.props[:entry].parent.to_s].id
|
50
|
+
edge.to = index.id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_entity(source_node, parent_path, type, entity_const)
|
56
|
+
# Create an entity node with attached resource model
|
57
|
+
entity = graph.create_node do |entity_node|
|
58
|
+
attributes = {
|
59
|
+
name: source_node.props[:basename],
|
60
|
+
title: source_node.props[:basename].capitalize,
|
61
|
+
body: ""
|
62
|
+
}
|
63
|
+
entity_node.label = :entity
|
64
|
+
entity_node.props[:type] = type
|
65
|
+
entity_node.props[:resource] = entity_const.new(attributes)
|
66
|
+
end
|
67
|
+
|
68
|
+
graph.create_edge do |edge|
|
69
|
+
edge.label = :source
|
70
|
+
edge.from = entity.id
|
71
|
+
edge.to = source_node.id
|
72
|
+
end
|
73
|
+
|
74
|
+
if @collections.key?(parent_path)
|
75
|
+
graph.create_edge do |edge|
|
76
|
+
edge.label = :child
|
77
|
+
edge.from = @collections[parent_path].id
|
78
|
+
edge.to = entity.id
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def connect_entity(entity, collection)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class BasenameMerge < Aggregator
|
5
|
+
def before_traversal(policy)
|
6
|
+
@bundles = {}
|
7
|
+
@container_collection = nil
|
8
|
+
@entity_bundles = {}
|
9
|
+
@entity_collections = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def expand_container(container, policy)
|
13
|
+
puts "create_node label=:collection type=:#{policy.collection} name='#{container.props[:basename]}'"
|
14
|
+
@container_collection = container.props[:basename]
|
15
|
+
end
|
16
|
+
|
17
|
+
def expand_collection(collection, policy)
|
18
|
+
if @container_collection == collection.props[:entry].parent.to_s
|
19
|
+
puts "create_node label=:collection type=:#{policy.entity} name='#{collection.props[:basename]}' collection='#{@container_collection}'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def expand_entity(entity, policy)
|
24
|
+
unless @entity_bundles.key?(entity.props[:basename])
|
25
|
+
@entity_bundles[entity.props[:basename]] = []
|
26
|
+
end
|
27
|
+
|
28
|
+
@entity_bundles[entity.props[:basename]] << entity
|
29
|
+
@entity_collections[entity.props[:basename]] = @container_collection
|
30
|
+
end
|
31
|
+
|
32
|
+
def after_traversal(policy)
|
33
|
+
@entity_bundles.each do |basename, bundle|
|
34
|
+
puts "create_node label=:resource type=:#{policy.entity} name='#{basename}' collection='#{@entity_collections[basename]}'"
|
35
|
+
|
36
|
+
bundle.each do |asset|
|
37
|
+
puts "create_node label=:asset extension='#{asset.props[:ext]}' resource='#{basename}'"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class DirectoryMap < Aggregator
|
5
|
+
def expand_container(container, policy)
|
6
|
+
puts "create_node label=:collection type=:#{policy.container} name='#{container.props[:basename]}'"
|
7
|
+
@current_collection = container.props[:basename]
|
8
|
+
end
|
9
|
+
|
10
|
+
def expand_collection(collection, policy)
|
11
|
+
puts "create_node label=:collection type=:#{policy.collection} name='#{collection.props[:basename]}' collection=#{@current_collection}"
|
12
|
+
@current_collection = collection.props[:basename]
|
13
|
+
end
|
14
|
+
|
15
|
+
def expand_entity(entity, policy)
|
16
|
+
puts "create_node label=:entity type=:#{policy.entity} name='#{entity.props[:basename]}' collection='#{@current_collection}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class DirectoryMerge < Aggregator
|
5
|
+
def before_traversal(policy)
|
6
|
+
@bundles = {}
|
7
|
+
@current_collection = nil
|
8
|
+
@current_entity = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def expand_container(container, policy)
|
12
|
+
create_collection(container, policy.container, policy.container_const)
|
13
|
+
@current_collection = container.props[:path]
|
14
|
+
end
|
15
|
+
|
16
|
+
def expand_collection(collection, policy)
|
17
|
+
@current_entity = collection.props[:basename]
|
18
|
+
end
|
19
|
+
|
20
|
+
def expand_entity(entity, policy)
|
21
|
+
if entity.props[:basename] == @current_entity && entity.props[:ext] == ".md"
|
22
|
+
create_entity(entity, @current_collection, policy.entity, policy.entity_const)
|
23
|
+
else
|
24
|
+
# TODO: attach static assets to the entity as well
|
25
|
+
#puts "--> create_node label=:asset type=:asset name='#{entity.props[:basename]}' entity='#{@current_entity}'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class FileList < Strategy
|
5
|
+
def expand(policy)
|
6
|
+
start_node = graph.n(:root).out(name: policy.container.to_s)
|
7
|
+
|
8
|
+
start_node.out(:files).each do |file_node|
|
9
|
+
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class FilenameMap < Aggregator
|
5
|
+
def expand_container(container, policy)
|
6
|
+
create_collection(container, policy.container, policy.container_const)
|
7
|
+
@current_collection = container.props[:path]
|
8
|
+
end
|
9
|
+
|
10
|
+
def expand_collection(collection, policy)
|
11
|
+
create_collection(collection, policy.collection, policy.collection_const)
|
12
|
+
@current_collection = collection.props[:path]
|
13
|
+
end
|
14
|
+
|
15
|
+
def expand_entity(entity, policy)
|
16
|
+
if policy.match_by_extension(entity.props[:ext])
|
17
|
+
parent_path = entity.incoming(:directory).first.props[:path]
|
18
|
+
create_entity(entity, parent_path, policy.entity, policy.entity_const)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -8,6 +8,36 @@ module Yarrow
|
|
8
8
|
|
9
9
|
def initialize(graph)
|
10
10
|
@graph = graph
|
11
|
+
@subcollections = {}
|
12
|
+
@entity_links = []
|
13
|
+
@index_links = []
|
14
|
+
@index = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Expand a directory to a collection node representing a collection of entities
|
18
|
+
def expand_directory(policy, node)
|
19
|
+
index = graph.create_node do |collection_node|
|
20
|
+
|
21
|
+
collection_attrs = {
|
22
|
+
name: node.props[:name],
|
23
|
+
title: node.props[:name].capitalize,
|
24
|
+
body: ""
|
25
|
+
}
|
26
|
+
|
27
|
+
populate_collection(collection_node, policy, collection_attrs)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add this collection id to the lookup table for edge construction
|
31
|
+
@subcollections[node.props[:path]] = index
|
32
|
+
|
33
|
+
# Join the collection to its parent
|
34
|
+
unless node.props[:slug] == policy.collection.to_s || !@subcollections.key?(node.props[:entry].parent.to_s)
|
35
|
+
graph.create_edge do |edge|
|
36
|
+
edge.label = :child
|
37
|
+
edge.from = @subcollections[node.props[:entry].parent.to_s].id
|
38
|
+
edge.to = index.id
|
39
|
+
end
|
40
|
+
end
|
11
41
|
end
|
12
42
|
|
13
43
|
# Extract collection level configuration/metadata from the root node for
|
@@ -65,6 +95,23 @@ module Yarrow
|
|
65
95
|
end
|
66
96
|
# TODO: Raise error if unsupported extname reaches here
|
67
97
|
end
|
98
|
+
|
99
|
+
def connect_expanded_entities
|
100
|
+
# Once all files and directories have been expanded, connect all the child
|
101
|
+
# edges between collections and entities
|
102
|
+
@entity_links.each do |entity_link|
|
103
|
+
graph.create_edge do |edge|
|
104
|
+
edge.label = :child
|
105
|
+
edge.from = entity_link[:parent_id].id
|
106
|
+
edge.to = entity_link[:child_id].id
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Merge index page body and metadata with their parent collections
|
111
|
+
@index_links.each do |index_link|
|
112
|
+
merge_collection_index(index_link[:parent_id], policy, index_link[:index_attrs])
|
113
|
+
end
|
114
|
+
end
|
68
115
|
end
|
69
116
|
end
|
70
117
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Content
|
3
|
+
module Expansion
|
4
|
+
class Traversal
|
5
|
+
attr_reader :graph, :policy, :aggregator
|
6
|
+
|
7
|
+
def initialize(graph, policy)
|
8
|
+
@graph = graph
|
9
|
+
@policy = policy
|
10
|
+
@aggregator = policy.aggregator_const.new(graph)
|
11
|
+
end
|
12
|
+
|
13
|
+
# If source path represents entire content dir, then include the entire
|
14
|
+
# content dir instead of scanning from a subfolder matching the name of
|
15
|
+
# the collection.
|
16
|
+
def source_node
|
17
|
+
if policy.source_path == "."
|
18
|
+
graph.n(:root).out(:directory)
|
19
|
+
else
|
20
|
+
graph.n(name: policy.source_path)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_root_visited(root_node)
|
25
|
+
aggregator.expand_container(root_node, policy)
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_directory_visited(dir_node)
|
29
|
+
# TODO: match on potential directory extension/filter
|
30
|
+
aggregator.expand_collection(dir_node, policy)
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_file_visited(file_node)
|
34
|
+
# TODO: dispatch underscore prefix or index files separately
|
35
|
+
# TODO: match on file extension
|
36
|
+
aggregator.expand_entity(file_node, policy)
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_traversal_initiated
|
40
|
+
aggregator.before_traversal(policy)
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_traversal_completed
|
44
|
+
aggregator.after_traversal(policy)
|
45
|
+
end
|
46
|
+
|
47
|
+
def expand
|
48
|
+
on_traversal_initiated
|
49
|
+
|
50
|
+
traversal = source_node.depth_first.each
|
51
|
+
|
52
|
+
on_root_visited(traversal.next)
|
53
|
+
|
54
|
+
loop do
|
55
|
+
node = traversal.next
|
56
|
+
case node.label
|
57
|
+
when :directory then on_directory_visited(node)
|
58
|
+
when :file then on_file_visited(node)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
on_traversal_completed
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|