yarrow 0.8.7 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CONTENT.md +228 -0
- data/README.md +8 -8
- data/lib/extensions/mementus.rb +29 -0
- data/lib/yarrow/content/expansion/aggregator.rb +91 -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 +78 -22
- 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 +115 -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/symbols.rb +6 -0
- data/lib/yarrow/version.rb +1 -1
- data/lib/yarrow/web/generator.rb +2 -1
- data/lib/yarrow/web/manifest.rb +23 -9
- data/lib/yarrow.rb +7 -9
- data/yarrow.gemspec +1 -0
- metadata +36 -3
- data/lib/yarrow/tools/content_utils.rb +0 -74
@@ -3,99 +3,35 @@ module Yarrow
|
|
3
3
|
module Expansion
|
4
4
|
class Tree < Strategy
|
5
5
|
def expand(policy)
|
6
|
-
#p graph.n(:root).out(:directory).to_a.count
|
7
|
-
#policy.match()
|
8
|
-
p policy
|
9
|
-
|
10
|
-
#p graph.n(:root).out(:directory).first.props[:name]
|
11
|
-
type = policy.container
|
12
|
-
|
13
6
|
# If match path represents entire content dir, then include the entire
|
14
7
|
# content dir instead of scanning from a subfolder matching the name of
|
15
8
|
# the collection.
|
16
9
|
#start_node = if policy.match_path == "."
|
17
|
-
start_node = if
|
10
|
+
start_node = if policy.match_path == "."
|
18
11
|
# TODO: match against source_dir
|
19
12
|
graph.n(:root).out(:directory)
|
20
13
|
else
|
21
|
-
graph.n(
|
14
|
+
graph.n(name: policy.match_path)
|
22
15
|
end
|
23
16
|
|
24
|
-
# Extract metadata from given start node
|
25
|
-
#collection_metadata = extract_metadata(start_node, policy.container)
|
26
|
-
|
27
17
|
# Collect all nested collections in the subgraph for this content type
|
28
|
-
subcollections = {}
|
29
|
-
entity_links = []
|
30
|
-
index_links = []
|
31
|
-
index = nil
|
18
|
+
@subcollections = {}
|
19
|
+
@entity_links = []
|
20
|
+
@index_links = []
|
21
|
+
@index = nil
|
32
22
|
|
33
23
|
# Scan and collect all nested files from the root
|
34
24
|
start_node.depth_first.each do |node|
|
35
25
|
if node.label == :directory
|
36
|
-
|
37
|
-
index = graph.create_node do |collection_node|
|
38
|
-
|
39
|
-
collection_attrs = {
|
40
|
-
name: node.props[:name],
|
41
|
-
title: node.props[:name].capitalize,
|
42
|
-
body: ""
|
43
|
-
}
|
44
|
-
|
45
|
-
populate_collection(collection_node, policy, collection_attrs)
|
46
|
-
end
|
47
|
-
|
48
|
-
# Add this collection id to the lookup table for edge construction
|
49
|
-
subcollections[node.props[:path]] = index
|
50
|
-
|
51
|
-
# Join the collection to its parent
|
52
|
-
unless node.props[:slug] == type.to_s || !subcollections.key?(node.props[:entry].parent.to_s)
|
53
|
-
graph.create_edge do |edge|
|
54
|
-
edge.label = :child
|
55
|
-
edge.from = subcollections[node.props[:entry].parent.to_s].id
|
56
|
-
edge.to = index.id
|
57
|
-
end
|
58
|
-
end
|
26
|
+
expand_directory(policy, node)
|
59
27
|
elsif node.label == :file
|
60
|
-
|
61
|
-
meta = {} if !meta
|
62
|
-
|
63
|
-
# TODO: document mapping convention for index pages and collection metadata
|
64
|
-
# TODO: underscore _index pattern?
|
65
|
-
bare_basename = node.props[:entry].basename(node.props[:entry].extname)
|
66
|
-
if bare_basename.to_s == "index"
|
67
|
-
index_links << {
|
68
|
-
parent_id: subcollections[node.props[:entry].parent.to_s],
|
69
|
-
index_attrs: meta.merge({ body: body})
|
70
|
-
}
|
71
|
-
else
|
72
|
-
# Create an entity node representing a file mapped to a unique content object
|
73
|
-
entity = graph.create_node do |entity_node|
|
74
|
-
|
75
|
-
entity_slug = node.props[:entry].basename(node.props[:entry].extname).to_s
|
76
|
-
|
77
|
-
entity_attrs = {
|
78
|
-
name: entity_slug,
|
79
|
-
title: entity_slug.gsub("-", " ").capitalize,
|
80
|
-
body: body
|
81
|
-
}
|
82
|
-
|
83
|
-
populate_entity(entity_node, policy, entity_attrs.merge(meta || {}))
|
84
|
-
end
|
85
|
-
|
86
|
-
# We may not have an expanded node for the parent collection if this is a
|
87
|
-
# preorder traversal so save it for later
|
88
|
-
entity_links << {
|
89
|
-
parent_id: subcollections[node.props[:entry].parent.to_s],
|
90
|
-
child_id: entity
|
91
|
-
}
|
92
|
-
end
|
28
|
+
expand_file_by_extension(policy, node)
|
93
29
|
end
|
94
30
|
end
|
95
31
|
|
96
32
|
# Once all files and directories have been expanded, connect all the child
|
97
33
|
# edges between collections and entities
|
98
|
-
entity_links.each do |entity_link|
|
34
|
+
@entity_links.each do |entity_link|
|
99
35
|
graph.create_edge do |edge|
|
100
36
|
edge.label = :child
|
101
37
|
edge.from = entity_link[:parent_id].id
|
@@ -104,10 +40,53 @@ module Yarrow
|
|
104
40
|
end
|
105
41
|
|
106
42
|
# Merge index page body and metadata with their parent collections
|
107
|
-
index_links.each do |index_link|
|
43
|
+
@index_links.each do |index_link|
|
108
44
|
merge_collection_index(index_link[:parent_id], policy, index_link[:index_attrs])
|
109
45
|
end
|
110
46
|
end
|
47
|
+
|
48
|
+
def expand_file_by_basename(policy, node)
|
49
|
+
body, meta = process_content(node.props[:entry])
|
50
|
+
meta = {} if !meta
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def expand_file_by_extension(policy, node)
|
56
|
+
body, meta = process_content(node.props[:entry])
|
57
|
+
meta = {} if !meta
|
58
|
+
|
59
|
+
# TODO: document mapping convention for index pages and collection metadata
|
60
|
+
# TODO: underscore _index pattern?
|
61
|
+
bare_basename = node.props[:entry].basename(node.props[:entry].extname)
|
62
|
+
if bare_basename.to_s == "index"
|
63
|
+
@index_links << {
|
64
|
+
parent_id: @subcollections[node.props[:entry].parent.to_s],
|
65
|
+
index_attrs: meta.merge({ body: body})
|
66
|
+
}
|
67
|
+
else
|
68
|
+
# Create an entity node representing a file mapped to a unique content object
|
69
|
+
entity = graph.create_node do |entity_node|
|
70
|
+
|
71
|
+
entity_slug = node.props[:entry].basename(node.props[:entry].extname).to_s
|
72
|
+
|
73
|
+
entity_attrs = {
|
74
|
+
name: entity_slug,
|
75
|
+
title: entity_slug.gsub("-", " ").capitalize,
|
76
|
+
body: body
|
77
|
+
}
|
78
|
+
|
79
|
+
populate_entity(entity_node, policy, entity_attrs.merge(meta || {}))
|
80
|
+
end
|
81
|
+
|
82
|
+
# We may not have an expanded node for the parent collection if this is a
|
83
|
+
# preorder traversal so save it for later
|
84
|
+
@entity_links << {
|
85
|
+
parent_id: @subcollections[node.props[:entry].parent.to_s],
|
86
|
+
child_id: entity
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
111
90
|
end
|
112
91
|
end
|
113
92
|
end
|
data/lib/yarrow/content/model.rb
CHANGED
@@ -2,8 +2,6 @@ module Yarrow
|
|
2
2
|
module Content
|
3
3
|
class Model
|
4
4
|
def initialize(content_config)
|
5
|
-
p content_config
|
6
|
-
|
7
5
|
@policies = {}
|
8
6
|
content_config.source_map.each_entry do |policy_label, policy_spec|
|
9
7
|
@policies[policy_label] = Policy.from_spec(
|
@@ -16,8 +14,9 @@ module Yarrow
|
|
16
14
|
|
17
15
|
def expand(graph)
|
18
16
|
@policies.each_value do |policy|
|
19
|
-
strategy =
|
20
|
-
|
17
|
+
#strategy = policy.expansion_strategy.new(graph)
|
18
|
+
traversal = Expansion::Traversal.new(graph, policy)
|
19
|
+
traversal.expand
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -1,13 +1,11 @@
|
|
1
1
|
module Yarrow
|
2
2
|
module Content
|
3
3
|
class Policy
|
4
|
-
|
5
|
-
|
6
|
-
DEFAULT_EXPANSION = :tree
|
4
|
+
DEFAULT_EXPANSION = :filename_map
|
7
5
|
|
8
6
|
DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
|
9
7
|
|
10
|
-
|
8
|
+
DEFAULT_SOURCE_PATH = "."
|
11
9
|
|
12
10
|
MODULE_SEPARATOR = "::"
|
13
11
|
|
@@ -15,15 +13,35 @@ module Yarrow
|
|
15
13
|
def self.from_spec(policy_label, policy_props, module_prefix="")
|
16
14
|
# TODO: validate length, structure etc
|
17
15
|
|
18
|
-
# If the spec holds a symbol value then treat it as
|
16
|
+
# If the spec holds a symbol value then treat it as an entity mapping
|
19
17
|
if policy_props.is_a?(Symbol)
|
20
|
-
new(
|
21
|
-
|
22
|
-
|
18
|
+
new(
|
19
|
+
policy_label,
|
20
|
+
policy_label,
|
21
|
+
policy_props,
|
22
|
+
DEFAULT_EXPANSION,
|
23
|
+
DEFAULT_EXTENSIONS,
|
24
|
+
policy_label.to_s,
|
25
|
+
module_prefix
|
26
|
+
)
|
27
|
+
|
28
|
+
# If the spec holds a string value then treat it as a source path mapping
|
29
|
+
elsif policy_props.is_a?(String)
|
30
|
+
new(
|
31
|
+
policy_label,
|
32
|
+
policy_label,
|
33
|
+
Yarrow::Symbols.to_singular(policy_label),
|
34
|
+
DEFAULT_EXPANSION,
|
35
|
+
DEFAULT_EXTENSIONS,
|
36
|
+
policy_props,
|
37
|
+
module_prefix
|
38
|
+
)
|
39
|
+
|
40
|
+
# Otherwise scan through the spec and fill in any gaps
|
23
41
|
else
|
24
|
-
# Use explicit
|
25
|
-
|
26
|
-
policy_props[:
|
42
|
+
# Use explicit collection name if provided
|
43
|
+
collection = if policy_props.key?(:collection)
|
44
|
+
policy_props[:collection]
|
27
45
|
else
|
28
46
|
# If an entity name is provided use its plural for the container name
|
29
47
|
if policy_props.key?(:entity)
|
@@ -33,12 +51,19 @@ module Yarrow
|
|
33
51
|
end
|
34
52
|
end
|
35
53
|
|
54
|
+
# Use explicit container name if provided
|
55
|
+
container = if policy_props.key?(:container)
|
56
|
+
policy_props[:container]
|
57
|
+
else
|
58
|
+
collection
|
59
|
+
end
|
60
|
+
|
36
61
|
# Use explicit entity name if provided
|
37
62
|
entity = if policy_props.key?(:entity)
|
38
63
|
policy_props[:entity]
|
39
64
|
else
|
40
|
-
if policy_props.key?(:
|
41
|
-
Yarrow::Symbols.to_singular(policy_props[:
|
65
|
+
if policy_props.key?(:collection)
|
66
|
+
Yarrow::Symbols.to_singular(policy_props[:collection])
|
42
67
|
else
|
43
68
|
Yarrow::Symbols.to_singular(policy_label)
|
44
69
|
end
|
@@ -58,22 +83,35 @@ module Yarrow
|
|
58
83
|
DEFAULT_EXTENSIONS
|
59
84
|
end
|
60
85
|
|
61
|
-
#
|
62
|
-
|
86
|
+
# If match path is provided, treat it as a basename
|
87
|
+
source_path = if policy_props.key?(:source_path)
|
88
|
+
policy_props[:source_path]
|
89
|
+
else
|
90
|
+
DEFAULT_SOURCE_PATH
|
91
|
+
end
|
63
92
|
|
64
93
|
# Construct the new policy
|
65
|
-
new(
|
94
|
+
new(
|
95
|
+
container,
|
96
|
+
collection,
|
97
|
+
entity,
|
98
|
+
expansion,
|
99
|
+
extensions,
|
100
|
+
source_path,
|
101
|
+
module_prefix
|
102
|
+
)
|
66
103
|
end
|
67
104
|
end
|
68
105
|
|
69
|
-
attr_reader :container, :entity, :expansion, :extensions, :
|
106
|
+
attr_reader :container, :collection, :entity, :expansion, :extensions, :source_path, :module_prefix
|
70
107
|
|
71
|
-
def initialize(container, entity, expansion, extensions,
|
108
|
+
def initialize(container, collection, entity, expansion, extensions, source_path, module_prefix)
|
72
109
|
@container = container
|
110
|
+
@collection = collection
|
73
111
|
@entity = entity
|
74
|
-
@expansion = expansion
|
112
|
+
@expansion = expansion.to_sym
|
75
113
|
@extensions = extensions
|
76
|
-
@
|
114
|
+
@source_path = source_path
|
77
115
|
@module_prefix = module_prefix.split(MODULE_SEPARATOR)
|
78
116
|
end
|
79
117
|
|
@@ -81,12 +119,30 @@ module Yarrow
|
|
81
119
|
@container_const ||= Yarrow::Symbols.to_module_const([*module_prefix, container])
|
82
120
|
end
|
83
121
|
|
84
|
-
|
85
|
-
|
122
|
+
def collection_const
|
123
|
+
begin
|
124
|
+
@collection_const ||= Yarrow::Symbols.to_module_const([*module_prefix, collection])
|
125
|
+
rescue NameError
|
126
|
+
raise NameError, "cannot map undefined entity `#{collection}`"
|
127
|
+
end
|
128
|
+
end
|
86
129
|
|
87
130
|
def entity_const
|
88
131
|
@entity_const ||= Yarrow::Symbols.to_module_const([*module_prefix, entity])
|
89
132
|
end
|
133
|
+
|
134
|
+
def aggregator_const
|
135
|
+
case expansion
|
136
|
+
when :filename_map then Expansion::FilenameMap
|
137
|
+
when :directory_merge then Expansion::DirectoryMerge
|
138
|
+
else
|
139
|
+
raise "No match strategy exists for :#{expansion}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def match_by_extension(candidate)
|
144
|
+
extensions.include?(candidate)
|
145
|
+
end
|
90
146
|
end
|
91
147
|
end
|
92
148
|
end
|
@@ -15,6 +15,7 @@ module Yarrow
|
|
15
15
|
dir.label = :directory
|
16
16
|
dir.props = {
|
17
17
|
name: root_dir_entry.basename.to_s,
|
18
|
+
basename: root_dir_entry.basename.to_s,
|
18
19
|
path: root_dir_entry.to_s,
|
19
20
|
entry: root_dir_entry
|
20
21
|
}
|
@@ -30,6 +31,7 @@ module Yarrow
|
|
30
31
|
root_dir_entry.to_s => root_dir.id
|
31
32
|
}
|
32
33
|
|
34
|
+
# TODO: merge entry and path props
|
33
35
|
Pathname.glob("#{input_dir}/**/**").each do |entry|
|
34
36
|
if entry.directory?
|
35
37
|
content_node = create_node do |dir|
|
@@ -40,6 +42,7 @@ module Yarrow
|
|
40
42
|
# dir.props[:entry] = entry
|
41
43
|
dir.props = {
|
42
44
|
name: entry.basename.to_s,
|
45
|
+
basename: entry.basename.to_s,
|
43
46
|
path: entry.to_s,
|
44
47
|
entry: entry
|
45
48
|
}
|
@@ -56,6 +59,7 @@ module Yarrow
|
|
56
59
|
|
57
60
|
file.props = {
|
58
61
|
name: entry.basename.to_s,
|
62
|
+
basename: entry.basename.sub_ext('').to_s,
|
59
63
|
ext: entry.extname.to_s,
|
60
64
|
path: entry.to_s,
|
61
65
|
entry: entry
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Format
|
3
|
+
class Markdown < ContentType[".md", ".markdown", ".htm"]
|
4
|
+
include Methods::FrontMatter
|
5
|
+
|
6
|
+
def initialize(source)
|
7
|
+
@source = source.to_s
|
8
|
+
@document = Kramdown::Document.new(@source)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
@source
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_dom
|
16
|
+
@document.root
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_html
|
20
|
+
@document.to_html
|
21
|
+
end
|
22
|
+
|
23
|
+
def links
|
24
|
+
@links ||= select_links
|
25
|
+
end
|
26
|
+
|
27
|
+
def title
|
28
|
+
@title ||= select_title
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def select_links
|
34
|
+
stack = to_dom.children
|
35
|
+
hrefs = [] # TODO: distinguish between internal and external
|
36
|
+
|
37
|
+
while !stack.empty?
|
38
|
+
next_el = stack.pop
|
39
|
+
|
40
|
+
if next_el.type == :a
|
41
|
+
hrefs << next_el.attr["href"]
|
42
|
+
else
|
43
|
+
stack.concat(next_el.children) if next_el.children
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
hrefs.reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
def select_title
|
51
|
+
stack = to_dom.children
|
52
|
+
|
53
|
+
while !stack.empty?
|
54
|
+
next_el = stack.pop
|
55
|
+
|
56
|
+
if next_el.type == :header and next_el.options[:level] == 1
|
57
|
+
return next_el.options[:raw_text]
|
58
|
+
else
|
59
|
+
stack.concat(next_el.children) if next_el.children
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
File without changes
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Format
|
3
|
+
module Methods
|
4
|
+
# Utility methods for working with text formats containing frontmatter separators.
|
5
|
+
module FrontMatter
|
6
|
+
module Syntax
|
7
|
+
YAML = "-"
|
8
|
+
YAML_DOC_END = "."
|
9
|
+
TOML = "+"
|
10
|
+
MM_JSON = ";"
|
11
|
+
JSON_START = "{"
|
12
|
+
JSON_END = "}"
|
13
|
+
|
14
|
+
PATTERN = /\A
|
15
|
+
(?<start>[\-]{3}|[\+]{3}|[;]{3}|[\{]{1})\s*\r?\n
|
16
|
+
(?<meta>.*?)\r?\n?
|
17
|
+
^(?<stop>[\-]{3}|[\+]{3}|[;]{3}|[\.]{3}|[\}]{1})\s*\r?\n?
|
18
|
+
\r?\n?
|
19
|
+
(?<body>.*)
|
20
|
+
/mx
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def read(path)
|
25
|
+
text = File.read(path, :encoding => "utf-8")
|
26
|
+
source, metadata = parse(text)
|
27
|
+
Yarrow::Format::Contents.new(new(source), metadata)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse(text)
|
31
|
+
result = Syntax::PATTERN.match(text)
|
32
|
+
|
33
|
+
return [text, nil] if result.nil?
|
34
|
+
|
35
|
+
start_chr = result[:start].chr
|
36
|
+
stop_chr = result[:stop].chr
|
37
|
+
input = result[:meta]
|
38
|
+
|
39
|
+
meta = if start_chr == stop_chr
|
40
|
+
case start_chr
|
41
|
+
when Syntax::YAML then parse_yaml(input)
|
42
|
+
when Syntax::TOML then parse_toml(input)
|
43
|
+
when Syntax::MM_JSON then parse_json(input)
|
44
|
+
end
|
45
|
+
else
|
46
|
+
if start_chr == Syntax::YAML && stop_chr == Syntax::YAML_DOC_END
|
47
|
+
parse_yaml(input)
|
48
|
+
elsif start_chr == Syntax::JSON_START && stop_chr == Syntax::JSON_END
|
49
|
+
parse_json(input)
|
50
|
+
else
|
51
|
+
raise "Unsupported frontmatter delimiters"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
[result[:body], meta]
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_yaml(raw)
|
59
|
+
YAML.load(raw, symbolize_names: true)
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_toml(raw)
|
63
|
+
TOML::Parser.new(raw).parsed
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_json(raw)
|
67
|
+
JSON.parse("{#{raw}}", symbolize_names: true)
|
68
|
+
end
|
69
|
+
|
70
|
+
def read_yfm(path)
|
71
|
+
text = File.read(path, :encoding => 'utf-8')
|
72
|
+
extract_yfm(text, symbolize_keys: true)
|
73
|
+
end
|
74
|
+
|
75
|
+
def extract_yfm(text, options={})
|
76
|
+
pattern = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
77
|
+
if text =~ pattern
|
78
|
+
content = text.sub(pattern, "")
|
79
|
+
|
80
|
+
begin
|
81
|
+
if options.key?(:symbolize_keys)
|
82
|
+
meta = YAML.load($1, symbolize_names: true)
|
83
|
+
else
|
84
|
+
meta = YAML.load($1)
|
85
|
+
end
|
86
|
+
return [content, meta]
|
87
|
+
rescue Psych::SyntaxError => error
|
88
|
+
if defined? ::Logger
|
89
|
+
# todo: application wide logger
|
90
|
+
#logger = ::Logger.new(STDOUT)
|
91
|
+
#logger.error "#{error.message}"
|
92
|
+
end
|
93
|
+
return [content, nil]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
[text, nil]
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_yfm(name, text, meta)
|
101
|
+
# Symbolized keys are better to deal with when manipulating data in
|
102
|
+
# Ruby but are an interop nightmare when serialized so here we do a
|
103
|
+
# round-trip through JSON encoding to ensure all keys are string
|
104
|
+
# encoded before dumping them to the front matter format.
|
105
|
+
File.write(name, [YAML.dump(meta.to_json), "---", text].join("\n"))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.included(base)
|
110
|
+
base.extend(ClassMethods)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Format
|
3
|
+
module Methods
|
4
|
+
module Metadata
|
5
|
+
module ClassMethods
|
6
|
+
def read(path)
|
7
|
+
text = File.read(path, :encoding => "utf-8")
|
8
|
+
Yarrow::Format::Contents.new(nil, parse(text))
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(text)
|
12
|
+
new(text)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Format
|
3
|
+
class Yaml < ContentType[".yml", ".yaml"]
|
4
|
+
include Methods::Metadata
|
5
|
+
|
6
|
+
def initialize(source)
|
7
|
+
@data = YAML.load(source, symbolize_names: true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
@data[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_h
|
15
|
+
@data
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Format
|
3
|
+
class Contents
|
4
|
+
attr_reader :document, :metadata
|
5
|
+
|
6
|
+
def initialize(document, metadata)
|
7
|
+
@document = document
|
8
|
+
@metadata = metadata || {}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Registry = {}
|
13
|
+
|
14
|
+
class ContentType
|
15
|
+
def self.[](*extensions)
|
16
|
+
@@extensions_cache = extensions
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.inherited(media_type)
|
21
|
+
return if @@extensions_cache.nil?
|
22
|
+
|
23
|
+
@@extensions_cache.each do |extname|
|
24
|
+
Registry[extname] = media_type
|
25
|
+
end
|
26
|
+
|
27
|
+
@@extensions_cache = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(source)
|
31
|
+
@source = source
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@source
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Pass in a source path and get back a parsed representation of the
|
40
|
+
# content if it is in a known text format. Mostly used as a fallback if
|
41
|
+
# a custom parser or processing chain is not configured for a content
|
42
|
+
# type.
|
43
|
+
def self.read(name)
|
44
|
+
path = if name.is_a?(Pathname)
|
45
|
+
name
|
46
|
+
else
|
47
|
+
Pathname.new(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# case path.extname
|
51
|
+
# when '.htm', '.md', '.txt', '.yfm'
|
52
|
+
# Markdown.read(path)
|
53
|
+
# when '.yml'
|
54
|
+
# [nil, YAML.load(File.read(path.to_s), symbolize_names: true)]
|
55
|
+
# when '.json'
|
56
|
+
# [nil, JSON.parse(File.read(path.to_s))]
|
57
|
+
# end
|
58
|
+
|
59
|
+
unless Registry.key?(path.extname)
|
60
|
+
raise "Unsupported format: #{path.extname} (#{path})"
|
61
|
+
end
|
62
|
+
|
63
|
+
Registry[path.extname].read(path)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
require_relative "format/methods/front_matter"
|
69
|
+
require_relative "format/methods/metadata"
|
70
|
+
require_relative "format/markdown"
|
71
|
+
require_relative "format/yaml"
|