yarrow 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d26941f2b464c551d825b73999b0365637b9e4feec159ee74de51a1abca3416e
4
- data.tar.gz: 34081172c945cf83e97ab3ee03484436ba380de6c809c2dd87caaa50f1bba315
3
+ metadata.gz: 4ea37c60e3c8bfe51e6ceb9425f0d0c81b93757a681c7987f7852c3335644824
4
+ data.tar.gz: 19cd090d3b3348f633f5d303d8b826fd2b8c054b790136355a41d9b4f0f098c4
5
5
  SHA512:
6
- metadata.gz: 6300e2c0f83c741862cf6c49acc42118636a3832401706057b483d942c07a947ea1124b2c10e056cc5052e25a28a59f896216d0156911026639d16e0faa5ba73
7
- data.tar.gz: 0b897183df8712cbc08d0a479ed1e1d129a90305ad76f9bc802ea262707026ff5ffa57890d0d26d49955f55721ffd0887510671c86430e4780564266758ebaed
6
+ metadata.gz: ebd0dd29f6d8800b20560c2e24fd2988caecf19e9ae5f3e44a3d0b427afc9201971be1b086705e72c874683d30625821b270e6ad944c5c28ca9b6cd9ecd65ea2
7
+ data.tar.gz: 92d36891c8a270ff663a299b2a217ffe3bb6985200e70d84c4c9d29ee87bb00fb7b2e824e0d932d585b5f8b4a22d578748ee58ad3ae022aa8f1b8ab04117ac6f
data/lib/yarrow/config.rb CHANGED
@@ -40,7 +40,16 @@ module Yarrow
40
40
  :middleware
41
41
  )
42
42
 
43
- class Output < Yarrow::Schema::Entity[:output]
43
+ # Yarrow::Schema.define do
44
+ # type :config_source_map, Yarrow::Schema::Types::Instance.of(Hash).accept(Symbol)
45
+ # end
46
+
47
+ class Content < Yarrow::Schema::Entity[:__config_content]
48
+ attribute :module, :string
49
+ attribute :source_map, :hash
50
+ end
51
+
52
+ class Output < Yarrow::Schema::Entity[:__config_output]
44
53
  attribute :generator, :string
45
54
  attribute :template_dir, :path
46
55
  #attribute :scripts, :array
@@ -52,7 +61,8 @@ module Yarrow
52
61
  attribute :output_dir, :path
53
62
  attribute :meta, :any
54
63
  attribute :server, :any
55
- #attribute :output, :output
64
+ attribute :content, :__config_content
65
+ #attribute :output, :__config_output
56
66
  end
57
67
  #
58
68
  # `content_dir` and `output_dir` are placeholders and should be overriden
@@ -48,6 +48,17 @@ module Yarrow
48
48
  nil
49
49
  end
50
50
 
51
+ content_obj = if config.key?(:content)
52
+ Yarrow::Config::Content.new(config[:content])
53
+ else
54
+ Yarrow::Config::Content.new({
55
+ module: "",
56
+ source_map: {
57
+ pages: :page
58
+ }
59
+ })
60
+ end
61
+
51
62
  # TODO: messy hack to get rid of Hashie::Mash, this should either be
52
63
  # automated as part of the schema types or a default value should be
53
64
  # generated here (eg: `"#{Dir.pwd}/docs"`)
@@ -58,7 +69,8 @@ module Yarrow
58
69
  output_dir: Pathname.new(File.expand_path(out_dir_or_string)),
59
70
  source_dir: Pathname.new(File.expand_path(source_dir_or_string)),
60
71
  meta: meta_obj,
61
- server: server_obj
72
+ server: server_obj,
73
+ content: content_obj
62
74
  )
63
75
  end
64
76
  end
@@ -0,0 +1,53 @@
1
+ module Yarrow
2
+ module Content
3
+ module Expansion
4
+ class Strategy
5
+ include Yarrow::Tools::FrontMatter
6
+
7
+ attr_reader :graph
8
+
9
+ def initialize(graph)
10
+ @graph = graph
11
+ end
12
+
13
+ # Extract collection level configuration/metadata from the root node for
14
+ # this content type.
15
+ def extract_metadata(node, type)
16
+ # TODO: support _index or _slug convention as well
17
+ meta_file = node.out(slug: type.to_s).first
18
+
19
+ if meta_file
20
+ # Process metadata and add it to the collection node
21
+ # TODO: pass in content converter object
22
+ # TODO: index/body content by default if extracted from frontmatter
23
+ body, data = process_content(meta_file.props[:entry])
24
+ else
25
+ # Otherwise, assume default collection behaviour
26
+ data = {}
27
+ end
28
+
29
+ # Generate a default title if not provided in metadata
30
+ unless data.key?(:title)
31
+ data[:title] = type.to_s.capitalize
32
+ end
33
+
34
+ data
35
+ end
36
+
37
+ # Workaround for handling meta and content source in multiple files or a single
38
+ # file with front matter.
39
+ def process_content(path)
40
+ case path.extname
41
+ when '.htm', '.md'
42
+ read_split_content(path.to_s, symbolize_keys: true)
43
+ # when '.md'
44
+ # body, data = read_split_content(path.to_s, symbolize_keys: true)
45
+ # [Kramdown::Document.new(body).to_html, data]
46
+ when '.yml'
47
+ [nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,90 @@
1
+ module Yarrow
2
+ module Content
3
+ module Expansion
4
+ class Tree < Strategy
5
+ def expand(policy)
6
+ #p graph.n(:root).out(:directory).to_a.count
7
+ #policy.match()
8
+
9
+ #p graph.n(:root).out(:directory).first.props[:name]
10
+ type = policy.container
11
+
12
+ # If match path represents entire content dir, then include the entire
13
+ # content dir instead of scanning from a subfolder matching the name of
14
+ # the collection.
15
+ #start_node = if policy.match_path == "."
16
+ start_node = if true
17
+ # TODO: match against source_dir
18
+ graph.n(:root).out(:directory)
19
+ else
20
+ graph.n(:root).out(name: policy.container.to_s)
21
+ end
22
+
23
+ # Extract metadata from given start node
24
+ collection_metadata = extract_metadata(start_node, policy.container)
25
+
26
+ # Collect all nested collections in the subgraph for this content type
27
+ subcollections = {}
28
+ item_links = []
29
+ index = nil
30
+
31
+ # Scan and collect all nested files from the root
32
+ start_node.depth_first.each do |node|
33
+ if node.label == :directory
34
+ # Create a collection node representing a collection of documents
35
+ index = graph.create_node do |collection_node|
36
+ collection_node.label = :collection
37
+ collection_node.props[:type] = policy.container
38
+ collection_node.props[:name] = node.props[:name]
39
+
40
+ # TODO: title needs to be defined from metadata
41
+ collection_node.props[:title] = node.props[:name].capitalize
42
+ end
43
+
44
+ # Add this collection id to the lookup table for edge construction
45
+ subcollections[node.props[:path]] = index
46
+
47
+ # Join the collection to its parent
48
+ unless node.props[:slug] == type.to_s || !subcollections.key?(node.props[:entry].parent.to_s)
49
+ graph.create_edge do |edge|
50
+ edge.label = :child
51
+ edge.from = subcollections[node.props[:entry].parent.to_s].id
52
+ edge.to = index.id
53
+ end
54
+ end
55
+ elsif node.label == :file
56
+ body, meta = process_content(node.props[:entry])
57
+
58
+ # Create an item node representing a file mapped to a unique content object
59
+ item = graph.create_node do |item_node|
60
+ item_node.label = :item
61
+ item_node.props[:type] = policy.entity
62
+ item_node.props[:name] = node.props[:entry].basename(node.props[:entry].extname).to_s
63
+ item_node.props[:body] = body if body
64
+ item_node.props[:title] = meta[:title] if meta
65
+ # TODO: better handling of metadata on node props
66
+ end
67
+
68
+ # We may not have an expanded node for the parent collection if this is a
69
+ # preorder traversal so save it for later
70
+ item_links << {
71
+ parent_path: node.props[:entry].parent.to_s,
72
+ item_id: item.id
73
+ }
74
+ end
75
+ end
76
+
77
+ # Once all files and directories have been expanded, connect all the child
78
+ # edges between collections and items
79
+ item_links.each do |item_link|
80
+ graph.create_edge do |edge|
81
+ edge.label = :child
82
+ edge.from = subcollections[item_link[:parent_path]].id
83
+ edge.to = item_link[:item_id]
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -17,11 +17,6 @@ module Yarrow
17
17
  @config = config
18
18
  end
19
19
 
20
- def expand_pages
21
- expander = Yarrow::Content::Expansion.new(Yarrow::Content::Model.new)
22
- expander.expand(graph)
23
- end
24
-
25
20
  # List of source files.
26
21
  def files
27
22
  graph.nodes(:file)
@@ -1,45 +1,22 @@
1
1
  module Yarrow
2
2
  module Content
3
- ContentSpec = Yarrow::Schema::Value.new(:namespace, :model) do
4
- def to_world
5
- "world"
6
- end
7
- end
8
-
9
- ContentPolicy = Yarrow::Schema::Value.new(
10
- :dir,
11
- :file,
12
- :expansion,
13
- :container,
14
- :record
15
- )
16
-
17
3
  class Model
18
- def initialize(spec=nil, namespace=nil)
19
- @namespace = []
20
- @namespace << namespace unless namespace.nil?
21
-
22
- @policies = if spec.nil?
23
- spec = {
24
- root: ContentPolicy.new(
25
- expansion: :tree,
26
- dir: "*",
27
- file: "*.md",
28
- :container => :pages,
29
- :record => :page
30
- )
31
- }
32
- else
33
- spec.model
4
+ def initialize(content_config)
5
+ @policies = {}
6
+ content_config.source_map.each_entry do |policy_label, policy_spec|
7
+ @policies[policy_label] = Policy.from_spec(policy_label, policy_spec)
34
8
  end
35
9
  end
36
10
 
37
- def each_policy(&block)
38
- @policies.each_value(&block)
11
+ def expand(graph)
12
+ @policies.each_value do |policy|
13
+ strategy = Expansion::Tree.new(graph)
14
+ strategy.expand(policy)
15
+ end
39
16
  end
40
17
 
41
- def policy_for(policy_key)
42
- @policies[policy_key]
18
+ def policy_for(policy_label)
19
+ @policies[policy_label]
43
20
  end
44
21
  end
45
22
  end
@@ -1,47 +1,104 @@
1
1
  module Yarrow
2
2
  module Content
3
3
  class Policy
4
- Options = Yarrow::Schema::Value.new(
5
- :container,
6
- :entity,
7
- :extensions,
8
- :match_path
9
- )
10
-
11
4
  DEFAULT_HOME_NESTING = false
12
5
 
6
+ DEFAULT_EXPANSION = :tree
7
+
13
8
  DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
14
9
 
15
10
  DEFAULT_MATCH_PATH = "."
16
11
 
17
- def self.from_name(name)
18
- new(Options.new(container: name.to_sym))
19
- end
12
+ # Construct a content policy from the given source specification.
13
+ def self.from_spec(policy_label, policy_props, module_prefix="")
14
+ # TODO: validate length, structure etc
15
+
16
+ # If the spec holds a symbol value then treat it as a container => entity mapping
17
+ if policy_props.is_a?(Symbol)
18
+ new(policy_label, policy_props, DEFAULT_EXPANSION, DEFAULT_EXTENSIONS, DEFAULT_MATCH_PATH, module_prefix)
19
+
20
+ # Otherwise scan through all the props and fill in any gaps
21
+ else
22
+ # Use explicit container name if provided
23
+ container = if policy_props.key?(:container)
24
+ policy_props[:container]
25
+ else
26
+ # If an entity name is provided use its plural for the container name
27
+ if policy_props.key?(:entity)
28
+ Yarrow::Symbols.to_plural(policy_props[:entity])
29
+ else
30
+ Yarrow::Symbols.to_plural(label)
31
+ end
32
+ end
33
+
34
+ # Use explicit entity name if provided
35
+ entity = if policy_props.key?(:entity)
36
+ policy_props[:entity]
37
+ else
38
+ if policy_props.key?(:container)
39
+ Yarrow::Symbols.to_singular(policy_props[:container])
40
+ else
41
+ Yarrow::Symbols.to_singular(label)
42
+ end
43
+ end
44
+
45
+ # Set expansion strategy
46
+ expansion = if policy_props.key?(:expansion)
47
+ policy_props[:expansion]
48
+ else
49
+ DEFAULT_EXPANSION
50
+ end
51
+
52
+ # Set list of extensions to turn into documents
53
+ extensions = if policy_props.key?(:extensions)
54
+ policy_props[:extensions]
55
+ else
56
+ DEFAULT_EXTENSIONS
57
+ end
20
58
 
21
- def initialize(properties)
22
- unless properties.respond_to?(:container) || properties.respond_to?(:entity)
23
- raise "Must provide a container name or entity name"
59
+ # TODO: handle this in expansion strategies
60
+ match_path = DEFAULT_MATCH_PATH
61
+
62
+ # Construct the new policy
63
+ new(container, entity, expansion, extensions, match_path, module_prefix)
24
64
  end
65
+ end
66
+
67
+ attr_reader :container, :entity, :expansion, :extensions, :match_path, :module
68
+
69
+ def initialize(container, entity, expansion, extensions, match_path, module_prefix)
70
+ @container = container
71
+ @entity = entity
72
+ @expansion = expansion
73
+ @extensions = extensions
74
+ @match_path = match_path
75
+ @module_prefix = module_prefix
76
+ end
77
+
78
+ def container_const
79
+ @container_const ||= Yarrow::Symbols.to_module_const([module_prefix, container])
80
+ end
25
81
 
26
- @properties = properties
82
+ def entity_const
83
+ @entity_const ||= Yarrow::Symbols.to_module_const([module_prefix, entity])
27
84
  end
28
85
 
29
- def container
86
+ def _container
30
87
  return @properties.container if @properties.container
31
88
  Yarrow::Symbols.to_plural(@properties.entity)
32
89
  end
33
90
 
34
- def entity
91
+ def _entity
35
92
  return @properties.entity if @properties.entity
36
93
  Yarrow::Symbols.to_singular(@properties.container)
37
94
  end
38
95
 
39
- def extensions
96
+ def _extensions
40
97
  return @properties.extensions if @properties.extensions
41
98
  DEFAULT_EXTENSIONS
42
99
  end
43
100
 
44
- def match_path
101
+ def _match_path
45
102
  return @properties.match_path if @properties.match_path
46
103
  DEFAULT_MATCH_PATH
47
104
  end
@@ -5,6 +5,11 @@ output_dir: docs
5
5
  meta:
6
6
  title: Default Project
7
7
  author: Default Name
8
+ content:
9
+ module: Yarrow::Content
10
+ source_map:
11
+ pages:
12
+ expansion: tree
8
13
  output:
9
14
  generator: web
10
15
  template_dir: templates
@@ -13,8 +13,8 @@ module Yarrow
13
13
  provides Content::Graph
14
14
 
15
15
  def step(content)
16
- expander = Content::Expansion.new(Yarrow::Content::Model.new)
17
- expander.expand(content.graph)
16
+ model = Content::Model.new(content.config.content)
17
+ model.expand(content.graph)
18
18
  content
19
19
  end
20
20
  end
@@ -8,7 +8,7 @@ module Yarrow
8
8
  path: Types::Instance.of(Pathname).accept(String),
9
9
  any: Types::Any.new,
10
10
  array: Types::List.of(Types::Any),
11
- hash: Types::Map.of(Symbol => Types::Any)
11
+ hash: Types::Instance.of(Hash)
12
12
  }
13
13
 
14
14
  TEMPLATE_TYPES = {
@@ -40,7 +40,7 @@ module Yarrow
40
40
 
41
41
  # Get reference to the type class we want to resolve
42
42
  template_type = TEMPLATE_TYPES[key_id]
43
-
43
+
44
44
  # Resolve the type to an instance depending on structure of its template args
45
45
  resolved_type = if value_id.is_a?(Hash)
46
46
  # Map template with two argument constructor
@@ -55,7 +55,7 @@ module Yarrow
55
55
 
56
56
  # Cache the resolved type for later reference
57
57
  DEFINED_TYPES[identifier] = resolved_type
58
-
58
+
59
59
  # Return the resolve type
60
60
  resolved_type
61
61
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module Yarrow
3
3
  APP_NAME = "Yarrow"
4
- VERSION = "0.8.1"
4
+ VERSION = "0.8.2"
5
5
  end
data/lib/yarrow.rb CHANGED
@@ -14,19 +14,15 @@ require "yarrow/schema/dictionary"
14
14
  require "yarrow/schema/entity"
15
15
  require "yarrow/schema/value"
16
16
  require "yarrow/schema/registry"
17
- require "yarrow/config"
18
- require "yarrow/configuration"
19
- require "yarrow/console_runner"
20
17
  require "yarrow/tools/front_matter"
21
18
  require "yarrow/tools/content_utils"
22
19
  require "yarrow/content/graph"
20
+ require "yarrow/content/policy"
21
+ require "yarrow/content/model"
23
22
  require "yarrow/content/source"
24
- require "yarrow/content/expansion"
25
- require "yarrow/content/expansion_strategy"
26
- require "yarrow/content/tree_expansion"
23
+ require "yarrow/content/expansion/strategy"
24
+ require "yarrow/content/expansion/tree"
27
25
  require "yarrow/content/resource"
28
- require "yarrow/content/model"
29
- require "yarrow/content/policy"
30
26
  require "yarrow/web/manifest"
31
27
  require "yarrow/web/document"
32
28
  require "yarrow/web/generator"
@@ -36,12 +32,14 @@ require "yarrow/output/web/indexed_file"
36
32
  require "yarrow/content_map"
37
33
  require "yarrow/server"
38
34
  require "yarrow/server/livereload"
39
-
40
35
  require "yarrow/process/workflow"
41
36
  require "yarrow/process/step_processor"
42
37
  require "yarrow/process/expand_content"
43
38
  require "yarrow/process/extract_source"
44
39
  require "yarrow/process/project_manifest"
40
+ require "yarrow/config"
41
+ require "yarrow/configuration"
42
+ require "yarrow/console_runner"
45
43
 
46
44
  require "yarrow/generator"
47
45
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yarrow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Rickerby
@@ -260,14 +260,13 @@ files:
260
260
  - lib/yarrow/config.rb
261
261
  - lib/yarrow/configuration.rb
262
262
  - lib/yarrow/console_runner.rb
263
- - lib/yarrow/content/expansion.rb
264
- - lib/yarrow/content/expansion_strategy.rb
263
+ - lib/yarrow/content/expansion/strategy.rb
264
+ - lib/yarrow/content/expansion/tree.rb
265
265
  - lib/yarrow/content/graph.rb
266
266
  - lib/yarrow/content/model.rb
267
267
  - lib/yarrow/content/policy.rb
268
268
  - lib/yarrow/content/resource.rb
269
269
  - lib/yarrow/content/source.rb
270
- - lib/yarrow/content/tree_expansion.rb
271
270
  - lib/yarrow/content_map.rb
272
271
  - lib/yarrow/defaults.yml
273
272
  - lib/yarrow/generator.rb
@@ -1,16 +0,0 @@
1
- module Yarrow
2
- module Content
3
- class Expansion
4
- def initialize(model)
5
- @model = model
6
- end
7
-
8
- def expand(graph)
9
- @model.each_policy do |policy|
10
- strategy = TreeExpansion.new(graph)
11
- strategy.expand(policy)
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,51 +0,0 @@
1
- module Yarrow
2
- module Content
3
- class ExpansionStrategy
4
- include Yarrow::Tools::FrontMatter
5
-
6
- attr_reader :graph
7
-
8
- def initialize(graph)
9
- @graph = graph
10
- end
11
-
12
- # Extract collection level configuration/metadata from the root node for
13
- # this content type.
14
- def extract_metadata(node, type)
15
- # TODO: support _index or _slug convention as well
16
- meta_file = node.out(slug: type.to_s).first
17
-
18
- if meta_file
19
- # Process metadata and add it to the collection node
20
- # TODO: pass in content converter object
21
- # TODO: index/body content by default if extracted from frontmatter
22
- body, data = process_content(meta_file.props[:entry])
23
- else
24
- # Otherwise, assume default collection behaviour
25
- data = {}
26
- end
27
-
28
- # Generate a default title if not provided in metadata
29
- unless data.key?(:title)
30
- data[:title] = type.to_s.capitalize
31
- end
32
-
33
- data
34
- end
35
-
36
- # Workaround for handling meta and content source in multiple files or a single
37
- # file with front matter.
38
- def process_content(path)
39
- case path.extname
40
- when '.htm', '.md'
41
- read_split_content(path.to_s, symbolize_keys: true)
42
- # when '.md'
43
- # body, data = read_split_content(path.to_s, symbolize_keys: true)
44
- # [Kramdown::Document.new(body).to_html, data]
45
- when '.yml'
46
- [nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,88 +0,0 @@
1
- module Yarrow
2
- module Content
3
- class TreeExpansion < ExpansionStrategy
4
- def expand(policy)
5
- #p graph.n(:root).out(:directory).to_a.count
6
- #policy.match()
7
-
8
- #p graph.n(:root).out(:directory).first.props[:name]
9
- type = policy.container
10
-
11
- # If match path represents entire content dir, then include the entire
12
- # content dir instead of scanning from a subfolder matching the name of
13
- # the collection.
14
- #start_node = if policy.match_path == "."
15
- start_node = if true
16
- # TODO: match against source_dir
17
- graph.n(:root).out(:directory)
18
- else
19
- graph.n(:root).out(name: policy.container.to_s)
20
- end
21
-
22
- # Extract metadata from given start node
23
- collection_metadata = extract_metadata(start_node, policy.container)
24
-
25
- # Collect all nested collections in the subgraph for this content type
26
- subcollections = {}
27
- item_links = []
28
- index = nil
29
-
30
- # Scan and collect all nested files from the root
31
- start_node.depth_first.each do |node|
32
- if node.label == :directory
33
- # Create a collection node representing a collection of documents
34
- index = graph.create_node do |collection_node|
35
- collection_node.label = :collection
36
- collection_node.props[:type] = policy.container
37
- collection_node.props[:name] = node.props[:name]
38
-
39
- # TODO: title needs to be defined from metadata
40
- collection_node.props[:title] = node.props[:name].capitalize
41
- end
42
-
43
- # Add this collection id to the lookup table for edge construction
44
- subcollections[node.props[:path]] = index
45
-
46
- # Join the collection to its parent
47
- unless node.props[:slug] == type.to_s || !subcollections.key?(node.props[:entry].parent.to_s)
48
- graph.create_edge do |edge|
49
- edge.label = :child
50
- edge.from = subcollections[node.props[:entry].parent.to_s].id
51
- edge.to = index.id
52
- end
53
- end
54
- elsif node.label == :file
55
- body, meta = process_content(node.props[:entry])
56
-
57
- # Create an item node representing a file mapped to a unique content object
58
- item = graph.create_node do |item_node|
59
- item_node.label = :item
60
- item_node.props[:type] = policy.record
61
- item_node.props[:name] = node.props[:entry].basename(node.props[:entry].extname).to_s
62
- item_node.props[:body] = body if body
63
- item_node.props[:title] = meta[:title] if meta
64
- # TODO: better handling of metadata on node props
65
- end
66
-
67
- # We may not have an expanded node for the parent collection if this is a
68
- # preorder traversal so save it for later
69
- item_links << {
70
- parent_path: node.props[:entry].parent.to_s,
71
- item_id: item.id
72
- }
73
- end
74
- end
75
-
76
- # Once all files and directories have been expanded, connect all the child
77
- # edges between collections and items
78
- item_links.each do |item_link|
79
- graph.create_edge do |edge|
80
- edge.label = :child
81
- edge.from = subcollections[item_link[:parent_path]].id
82
- edge.to = item_link[:item_id]
83
- end
84
- end
85
- end
86
- end
87
- end
88
- end