yarrow 0.7.2 → 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/lib/yarrow/config.rb +5 -5
- data/lib/yarrow/content/expansion.rb +6 -41
- data/lib/yarrow/content/expansion_strategy.rb +51 -0
- data/lib/yarrow/content/graph.rb +2 -2
- data/lib/yarrow/content/manifest.rb +4 -4
- data/lib/yarrow/content/model.rb +46 -0
- data/lib/yarrow/content/policy.rb +48 -0
- data/lib/yarrow/content/resource.rb +29 -0
- data/lib/yarrow/content/source.rb +70 -3
- data/lib/yarrow/content/tree_expansion.rb +15 -6
- data/lib/yarrow/generator.rb +1 -1
- data/lib/yarrow/schema/definitions.rb +30 -0
- data/lib/yarrow/schema/dictionary.rb +49 -0
- data/lib/yarrow/schema/entity.rb +51 -0
- data/lib/yarrow/schema/types.rb +73 -0
- data/lib/yarrow/schema/value.rb +46 -0
- data/lib/yarrow/schema.rb +28 -119
- data/lib/yarrow/symbols.rb +15 -0
- data/lib/yarrow/version.rb +1 -1
- data/lib/yarrow.rb +10 -3
- data/yarrow.gemspec +1 -1
- metadata +18 -12
- data/lib/yarrow/content/collection_expander.rb +0 -241
- data/lib/yarrow/content/object_type.rb +0 -42
- data/lib/yarrow/content/source_collector.rb +0 -78
@@ -0,0 +1,46 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Schema
|
3
|
+
# Value object (with comparison by value equality). This just chucks back a
|
4
|
+
# Ruby struct but wraps the constructor with method advice that handles
|
5
|
+
# type checking and conversion.
|
6
|
+
class Value
|
7
|
+
def self.new(*slots, **fields, &block)
|
8
|
+
factory(*slots, **fields, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.factory(*slots, **fields, &block)
|
12
|
+
if slots.empty? && fields.empty?
|
13
|
+
raise ArgumentError.new("missing attribute definition")
|
14
|
+
end
|
15
|
+
|
16
|
+
slots_spec, fields_spec = if fields.any?
|
17
|
+
raise ArgumentError.new("cannot use slots when field map is supplied") if slots.any?
|
18
|
+
[fields.keys, fields]
|
19
|
+
else
|
20
|
+
[slots, Hash[slots.map { |s| [s, :any]}]]
|
21
|
+
end
|
22
|
+
|
23
|
+
validator = Dictionary.new(fields_spec)
|
24
|
+
|
25
|
+
struct = Struct.new(*slots_spec, keyword_init: true, &block)
|
26
|
+
|
27
|
+
struct.define_method :initialize do |*args, **kwargs|
|
28
|
+
attr_values = if args.any?
|
29
|
+
raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
|
30
|
+
Hash[slots.zip(args)]
|
31
|
+
else
|
32
|
+
kwargs
|
33
|
+
end
|
34
|
+
|
35
|
+
validator.check(attr_values)
|
36
|
+
# TODO: type coercion or mapping decision goes here
|
37
|
+
super(**attr_values)
|
38
|
+
|
39
|
+
freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
struct
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/yarrow/schema.rb
CHANGED
@@ -1,132 +1,41 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
1
3
|
module Yarrow
|
2
4
|
module Schema
|
3
5
|
module Type
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
##
|
9
|
-
# Checks values plugged into each slot and runs any required validations
|
10
|
-
# (validations not yet implemented).
|
11
|
-
#
|
12
|
-
# Current design throws on error rather than returns a boolean result.
|
13
|
-
class Validator
|
14
|
-
# @param fields_spec [Hash] defines the slots in the schema to validate against
|
15
|
-
def initialize(fields_spec)
|
16
|
-
@spec = fields_spec
|
17
|
-
end
|
18
|
-
|
19
|
-
def check(fields)
|
20
|
-
missing_fields = @spec.keys.difference(fields.keys)
|
21
|
-
|
22
|
-
if missing_fields.any?
|
23
|
-
missing_fields.each do |field|
|
24
|
-
raise "wrong number of args" unless @spec[field].eql?(Type::Any)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
mismatching_fields = fields.keys.difference(@spec.keys)
|
29
|
-
|
30
|
-
raise "key does not exist" if mismatching_fields.any?
|
31
|
-
|
32
|
-
fields.each do |(field, value)|
|
33
|
-
raise "wrong data type" unless value.is_a?(@spec[field]) || @spec[field].eql?(Type::Any)
|
34
|
-
end
|
35
|
-
|
36
|
-
true
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
##
|
41
|
-
# Value object (with comparison by value equality). This just chucks back a
|
42
|
-
# Ruby struct but wraps the constructor with method advice that handles
|
43
|
-
# validation (and eventually type coercion if !yagni).
|
44
|
-
class Value
|
45
|
-
def self.new(*slots, **fields, &block)
|
46
|
-
factory(*slots, **fields, &block)
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.factory(*slots, **fields, &block)
|
50
|
-
if slots.empty? && fields.empty?
|
51
|
-
raise ArgumentError.new("missing attribute definition")
|
52
|
-
end
|
53
|
-
|
54
|
-
slots_spec, fields_spec = if fields.any?
|
55
|
-
raise ArgumentError.new("cannot use slots when field map is supplied") if slots.any?
|
56
|
-
[fields.keys, fields]
|
57
|
-
else
|
58
|
-
[slots, Hash[slots.map { |s| [s, Type::Any]}]]
|
59
|
-
end
|
60
|
-
|
61
|
-
validator = Validator.new(fields_spec)
|
62
|
-
|
63
|
-
struct = Struct.new(*slots_spec, keyword_init: true, &block)
|
64
|
-
|
65
|
-
struct.define_method :initialize do |*args, **kwargs|
|
66
|
-
attr_values = if args.any?
|
67
|
-
raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
|
68
|
-
Hash[slots.zip(args)]
|
69
|
-
else
|
70
|
-
kwargs
|
6
|
+
class Raw
|
7
|
+
class << self
|
8
|
+
def [](primitive)
|
9
|
+
@primitive = primitive
|
71
10
|
end
|
72
11
|
|
73
|
-
|
74
|
-
|
75
|
-
super(**attr_values)
|
76
|
-
|
77
|
-
freeze
|
78
|
-
end
|
79
|
-
|
80
|
-
struct
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
##
|
85
|
-
# Entity with comparison by reference equality. Generates attribute helpers
|
86
|
-
# for a declared set of props. Used to replace Hashie::Mash without dragging
|
87
|
-
# in a whole new library.
|
88
|
-
class Entity
|
89
|
-
class << self
|
90
|
-
def attribute(name, value_type)
|
91
|
-
# define_method("map_#{name}".to_sym) do |input|
|
92
|
-
# value_type.coerce(input)
|
93
|
-
# end
|
94
|
-
dictionary[name] = value_type
|
95
|
-
attr_reader(name)
|
96
|
-
end
|
97
|
-
|
98
|
-
def dictionary
|
99
|
-
@dictionary ||= Hash.new
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def dictionary
|
104
|
-
self.class.dictionary
|
105
|
-
end
|
106
|
-
|
107
|
-
def initialize(config)
|
108
|
-
dictionary.each_key do |name|
|
109
|
-
raise "missing declared attribute #{name}" unless dictionary.key?(name)
|
110
|
-
end
|
111
|
-
|
112
|
-
config.each_pair do |key, value|
|
113
|
-
raise "#{key} not a declared attribute" unless dictionary.key?(key)
|
114
|
-
|
115
|
-
defined_type = dictionary[key]
|
116
|
-
|
117
|
-
unless value.is_a?(defined_type)
|
118
|
-
raise "#{key} accepts #{defined_type} but #{value.class} given"
|
12
|
+
def new(input)
|
13
|
+
input
|
119
14
|
end
|
120
|
-
|
121
|
-
instance_variable_set("@#{key}", value)
|
122
15
|
end
|
123
16
|
end
|
124
|
-
end
|
125
17
|
|
126
|
-
|
127
|
-
dictionary.keys.reduce({}) do |h, name|
|
128
|
-
h[name] = instance_variable_get("@#{name}")
|
18
|
+
class Any
|
129
19
|
end
|
20
|
+
# class Attribute
|
21
|
+
# class << self
|
22
|
+
# def accepts(attr_type)
|
23
|
+
# @accepts = attr_type
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# attr_accessor :value
|
28
|
+
# alias_method :__getobj__, :value
|
29
|
+
#
|
30
|
+
# def initialize(value)
|
31
|
+
# raise "Invalid type" unless @accepts.is_a?(value.class)
|
32
|
+
# @value = value
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# class Text < Attribute
|
37
|
+
# accepts String
|
38
|
+
# end
|
130
39
|
end
|
131
40
|
end
|
132
41
|
end
|
data/lib/yarrow/symbols.rb
CHANGED
@@ -3,15 +3,30 @@ require "strings-case"
|
|
3
3
|
|
4
4
|
module Yarrow
|
5
5
|
module Symbols
|
6
|
+
# @param [Array<String>, Array<Symbol>] parts
|
7
|
+
# @return [Object]
|
8
|
+
def self.to_module_const(parts)
|
9
|
+
Object.const_get(parts.map { |atom|
|
10
|
+
Strings::Case.pascalcase(atom.to_s)
|
11
|
+
}.join("::"))
|
12
|
+
end
|
13
|
+
|
6
14
|
# Converts an atomic content identifier to a live class constant.
|
15
|
+
#
|
16
|
+
# @param [Symbol, String] atom
|
17
|
+
# @return [Object]
|
7
18
|
def self.to_const(atom)
|
8
19
|
Object.const_get(Strings::Case.pascalcase(atom.to_s).to_sym)
|
9
20
|
end
|
10
21
|
|
22
|
+
# @param [Symbol, String] atom
|
23
|
+
# @return [Symbol]
|
11
24
|
def self.to_singular(atom)
|
12
25
|
Strings::Inflection.singularize(atom.to_s).to_sym
|
13
26
|
end
|
14
27
|
|
28
|
+
# @param [Symbol, String] atom
|
29
|
+
# @return [Symbol]
|
15
30
|
def self.to_plural(atom)
|
16
31
|
Strings::Inflection.pluralize(atom.to_s).to_sym
|
17
32
|
end
|
data/lib/yarrow/version.rb
CHANGED
data/lib/yarrow.rb
CHANGED
@@ -6,18 +6,25 @@ require 'yarrow/extensions'
|
|
6
6
|
require 'yarrow/symbols'
|
7
7
|
require 'yarrow/logging'
|
8
8
|
require 'yarrow/schema'
|
9
|
+
require 'yarrow/schema/types'
|
10
|
+
require 'yarrow/schema/definitions'
|
11
|
+
require 'yarrow/schema/dictionary'
|
12
|
+
require 'yarrow/schema/entity'
|
13
|
+
require 'yarrow/schema/value'
|
9
14
|
require 'yarrow/config'
|
10
15
|
require 'yarrow/configuration'
|
11
16
|
require 'yarrow/console_runner'
|
12
17
|
require 'yarrow/tools/front_matter'
|
13
18
|
require 'yarrow/tools/content_utils'
|
14
19
|
require 'yarrow/content/graph'
|
15
|
-
require 'yarrow/content/
|
16
|
-
require 'yarrow/content/source_collector'
|
17
|
-
require 'yarrow/content/collection_expander'
|
20
|
+
require 'yarrow/content/source'
|
18
21
|
require 'yarrow/content/expansion'
|
22
|
+
require 'yarrow/content/expansion_strategy'
|
19
23
|
require 'yarrow/content/tree_expansion'
|
20
24
|
require 'yarrow/content/manifest'
|
25
|
+
require 'yarrow/content/resource'
|
26
|
+
require 'yarrow/content/model'
|
27
|
+
require 'yarrow/content/policy'
|
21
28
|
require 'yarrow/output/mapper'
|
22
29
|
require 'yarrow/output/generator'
|
23
30
|
require 'yarrow/output/context'
|
data/yarrow.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.add_runtime_dependency 'em-websocket', '~> 0.5.1'
|
21
21
|
spec.add_runtime_dependency 'strings-inflection', '~> 0.1'
|
22
22
|
spec.add_runtime_dependency 'strings-case', '~> 0.3'
|
23
|
-
spec.
|
23
|
+
spec.add_runtime_dependency 'kramdown', '~> 2.4.0'
|
24
24
|
spec.add_development_dependency 'rake', '~> 13.0'
|
25
25
|
spec.add_development_dependency 'rspec', '~> 3.11'
|
26
26
|
spec.add_development_dependency 'coveralls', '~> 0.8.23'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yarrow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Rickerby
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08
|
11
|
+
date: 2022-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mementus
|
@@ -109,19 +109,19 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0.3'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: kramdown
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- - "
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
118
|
-
type: :
|
117
|
+
version: 2.4.0
|
118
|
+
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - "
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
124
|
+
version: 2.4.0
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: rake
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -203,13 +203,14 @@ files:
|
|
203
203
|
- lib/yarrow/config.rb
|
204
204
|
- lib/yarrow/configuration.rb
|
205
205
|
- lib/yarrow/console_runner.rb
|
206
|
-
- lib/yarrow/content/collection_expander.rb
|
207
206
|
- lib/yarrow/content/expansion.rb
|
207
|
+
- lib/yarrow/content/expansion_strategy.rb
|
208
208
|
- lib/yarrow/content/graph.rb
|
209
209
|
- lib/yarrow/content/manifest.rb
|
210
|
-
- lib/yarrow/content/
|
210
|
+
- lib/yarrow/content/model.rb
|
211
|
+
- lib/yarrow/content/policy.rb
|
212
|
+
- lib/yarrow/content/resource.rb
|
211
213
|
- lib/yarrow/content/source.rb
|
212
|
-
- lib/yarrow/content/source_collector.rb
|
213
214
|
- lib/yarrow/content/tree_expansion.rb
|
214
215
|
- lib/yarrow/content_map.rb
|
215
216
|
- lib/yarrow/defaults.yml
|
@@ -228,9 +229,14 @@ files:
|
|
228
229
|
- lib/yarrow/process/step_processor.rb
|
229
230
|
- lib/yarrow/process/workflow.rb
|
230
231
|
- lib/yarrow/schema.rb
|
232
|
+
- lib/yarrow/schema/definitions.rb
|
233
|
+
- lib/yarrow/schema/dictionary.rb
|
234
|
+
- lib/yarrow/schema/entity.rb
|
235
|
+
- lib/yarrow/schema/types.rb
|
231
236
|
- lib/yarrow/schema/validations/array.rb
|
232
237
|
- lib/yarrow/schema/validations/object.rb
|
233
238
|
- lib/yarrow/schema/validations/string.rb
|
239
|
+
- lib/yarrow/schema/value.rb
|
234
240
|
- lib/yarrow/server.rb
|
235
241
|
- lib/yarrow/server/livereload.rb
|
236
242
|
- lib/yarrow/source/graph.rb
|
@@ -262,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
262
268
|
- !ruby/object:Gem::Version
|
263
269
|
version: '0'
|
264
270
|
requirements: []
|
265
|
-
rubygems_version: 3.
|
271
|
+
rubygems_version: 3.3.7
|
266
272
|
signing_key:
|
267
273
|
specification_version: 4
|
268
274
|
summary: Documentation generator based on a fluent data model.
|
@@ -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
|