yarrow 0.5.0 → 0.6.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/.github/workflows/ruby.yml +23 -0
- data/.gitignore +2 -1
- data/README.md +13 -19
- data/Rakefile +15 -1
- data/lib/yarrow.rb +17 -6
- data/lib/yarrow/config.rb +59 -0
- data/lib/yarrow/configuration.rb +35 -63
- data/lib/yarrow/content/collection_expander.rb +218 -0
- data/lib/yarrow/content/content_type.rb +42 -0
- data/lib/yarrow/content/graph.rb +13 -21
- data/lib/yarrow/content/source.rb +11 -0
- data/lib/yarrow/extensions.rb +1 -0
- data/lib/yarrow/extensions/mementus.rb +24 -0
- data/lib/yarrow/output/context.rb +0 -4
- data/lib/yarrow/output/generator.rb +2 -2
- data/lib/yarrow/output/web/indexed_file.rb +39 -0
- data/lib/yarrow/process/expand_content.rb +12 -0
- data/lib/yarrow/process/extract_source.rb +12 -0
- data/lib/yarrow/process/project_manifest.rb +20 -0
- data/lib/yarrow/process/step_processor.rb +43 -0
- data/lib/yarrow/process/workflow.rb +36 -0
- data/lib/yarrow/schema.rb +132 -0
- data/lib/yarrow/schema/validations/array.rb +0 -0
- data/lib/yarrow/schema/validations/object.rb +0 -0
- data/lib/yarrow/schema/validations/string.rb +0 -0
- data/lib/yarrow/server.rb +8 -5
- data/lib/yarrow/source/graph.rb +6 -0
- data/lib/yarrow/symbols.rb +19 -0
- data/lib/yarrow/tools/content_utils.rb +74 -0
- data/lib/yarrow/tools/front_matter.rb +4 -2
- data/lib/yarrow/version.rb +3 -2
- data/lib/yarrow/web/html_document.rb +9 -0
- data/lib/yarrow/web/manifest.rb +9 -0
- data/lib/yarrow/web/static_asset.rb +9 -0
- data/lib/yarrow/web/template.rb +9 -0
- data/yarrow.gemspec +6 -7
- metadata +52 -48
- data/.travis.yml +0 -16
- data/lib/yarrow/html.rb +0 -1
- data/lib/yarrow/html/asset_tags.rb +0 -59
- data/lib/yarrow/html/content_tags.rb +0 -7
- data/lib/yarrow/tools/output_file.rb +0 -40
@@ -0,0 +1,42 @@
|
|
1
|
+
gem "strings-inflection"
|
2
|
+
|
3
|
+
module Yarrow
|
4
|
+
module Content
|
5
|
+
class ContentType
|
6
|
+
Value = Yarrow::Schema::Value.new(:collection, :entity, :extensions)
|
7
|
+
|
8
|
+
DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
|
9
|
+
|
10
|
+
def self.from_name(name)
|
11
|
+
new(Value.new(collection: name.to_sym))
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(properties)
|
15
|
+
unless properties.respond_to?(:collection) || properties.respond_to?(:entity)
|
16
|
+
raise "Must provide a collection name or entity name"
|
17
|
+
end
|
18
|
+
|
19
|
+
@properties = properties
|
20
|
+
end
|
21
|
+
|
22
|
+
def collection
|
23
|
+
return @properties.collection if @properties.collection
|
24
|
+
Yarrow::Symbols.to_plural(@properties.entity)
|
25
|
+
end
|
26
|
+
|
27
|
+
def entity
|
28
|
+
return @properties.entity if @properties.entity
|
29
|
+
Yarrow::Symbols.to_singular(@properties.collection)
|
30
|
+
end
|
31
|
+
|
32
|
+
def extensions
|
33
|
+
return @properties.extensions if @properties.extensions
|
34
|
+
DEFAULT_EXTENSIONS
|
35
|
+
end
|
36
|
+
|
37
|
+
def match_path
|
38
|
+
"."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/yarrow/content/graph.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
-
require "mementus"
|
2
|
-
|
3
|
-
module Mementus
|
4
|
-
module Pipeline
|
5
|
-
class Step
|
6
|
-
# Monkeypatch extension to ensure each pipeline step supports enumerable
|
7
|
-
# methods. Mostly used for #map. API needs to be fixed in the gem itself.
|
8
|
-
include Enumerable
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
1
|
module Yarrow
|
14
2
|
module Content
|
15
3
|
# A directed graph of every element of content in the project.
|
16
4
|
class Graph
|
17
5
|
# Construct a graph collected from source content files.
|
18
6
|
def self.from_source(config)
|
19
|
-
new(SourceCollector.collect(config.
|
7
|
+
new(SourceCollector.collect(config.source), config)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :graph, :config
|
11
|
+
|
12
|
+
def initialize(graph, config)
|
13
|
+
@graph = graph
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
def expand_pages
|
18
|
+
expander = Yarrow::Content::CollectionExpander.new
|
19
|
+
expander.expand(graph)
|
20
20
|
end
|
21
21
|
|
22
22
|
# List of source files.
|
@@ -28,14 +28,6 @@ module Yarrow
|
|
28
28
|
def directories
|
29
29
|
graph.nodes(:directory)
|
30
30
|
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def initialize(graph)
|
35
|
-
@graph = graph
|
36
|
-
end
|
37
|
-
|
38
|
-
attr_reader :graph
|
39
31
|
end
|
40
32
|
end
|
41
33
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "yarrow/extensions/mementus"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "mementus"
|
2
|
+
|
3
|
+
module Mementus
|
4
|
+
module Pipeline
|
5
|
+
class Step
|
6
|
+
# Monkeypatch extension to ensure each pipeline step supports enumerable
|
7
|
+
# methods. Mostly used for #map. API needs to be fixed in the gem itself.
|
8
|
+
include Enumerable
|
9
|
+
end
|
10
|
+
end
|
11
|
+
module Structure
|
12
|
+
class IncidenceList
|
13
|
+
def inspect
|
14
|
+
"<Mementus::Structure::IncidenceList>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class Graph
|
19
|
+
def inspect
|
20
|
+
"<Mementus::Graph @structure=#{@structure.inspect} " +
|
21
|
+
"nodes_count=#{nodes_count} edges_count=#{edges_count}>"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -4,11 +4,7 @@ module Yarrow
|
|
4
4
|
#
|
5
5
|
# Methods provided by this class become available as named variables in
|
6
6
|
# Mustache templates.
|
7
|
-
#
|
8
|
-
# Includes the library of helpers for dynamically generating HTML tags.
|
9
7
|
class Context
|
10
|
-
include Yarrow::HTML::AssetTags
|
11
|
-
|
12
8
|
def initialize(attributes)
|
13
9
|
metaclass = class << self; self; end
|
14
10
|
attributes.each do |name, value|
|
@@ -8,12 +8,12 @@ module Yarrow
|
|
8
8
|
|
9
9
|
# Mapping between template types and provided object model
|
10
10
|
def object_map
|
11
|
-
@config
|
11
|
+
@config[:output][:object_map]
|
12
12
|
end
|
13
13
|
|
14
14
|
# Mapping between template types and provided output templates.
|
15
15
|
def template_map
|
16
|
-
|
16
|
+
|
17
17
|
end
|
18
18
|
|
19
19
|
# Template converter used by this generator instance.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Output
|
3
|
+
module Web
|
4
|
+
class IndexedFile
|
5
|
+
WRITE_MODE = 'w+:UTF-8'.freeze
|
6
|
+
|
7
|
+
# @return [String] Basename reflecting the server convention (usually: index.html)
|
8
|
+
def index_name
|
9
|
+
@index_name ||= config.index_name || 'index.html'
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String] Docroot of the output target
|
13
|
+
def docroot
|
14
|
+
@docroot ||= config.output_dir || 'public'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Write an output file to the specified path under the docroot.
|
18
|
+
#
|
19
|
+
# @param path [String]
|
20
|
+
# @param content [String]
|
21
|
+
def write(path, content)
|
22
|
+
# If the target path is a directory,
|
23
|
+
# generate a default index filename.
|
24
|
+
if path[path.length-1] == '/'
|
25
|
+
path = "#{path}#{index_name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
target_path = Pathname.new("#{docroot}#{path}")
|
29
|
+
|
30
|
+
FileUtils.mkdir_p(target_path.dirname)
|
31
|
+
|
32
|
+
File.open(target_path.to_s, WRITE_MODE) do |file|
|
33
|
+
file.puts(content)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Process
|
3
|
+
class ProjectManifest < StepProcessor
|
4
|
+
accepts String
|
5
|
+
provides String
|
6
|
+
|
7
|
+
def before_step(content)
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def step(content)
|
12
|
+
"#{content} | ProjectManifest::Result"
|
13
|
+
end
|
14
|
+
|
15
|
+
def after_step(content)
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Process
|
3
|
+
class StepProcessor
|
4
|
+
attr_reader :source
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :accepted_input, :provided_output
|
8
|
+
|
9
|
+
def accepts(input_const)
|
10
|
+
@accepted_input = input_const.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def provides(output_const)
|
14
|
+
@provided_output = output_const.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@source = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def accepts
|
23
|
+
self.class.accepted_input
|
24
|
+
end
|
25
|
+
|
26
|
+
def provides
|
27
|
+
self.class.provided_output
|
28
|
+
end
|
29
|
+
|
30
|
+
def can_accept?(provided)
|
31
|
+
accepts == provided
|
32
|
+
end
|
33
|
+
|
34
|
+
def process(source)
|
35
|
+
# begin
|
36
|
+
result = step(source)
|
37
|
+
# log.info("<Result source=#{result}>")
|
38
|
+
# rescue
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Process
|
3
|
+
class Workflow
|
4
|
+
def initialize(input)
|
5
|
+
@input = input
|
6
|
+
@processors = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def connect(processor)
|
10
|
+
provided_input = if @processors.any?
|
11
|
+
@processors.last.provides
|
12
|
+
else
|
13
|
+
@input.class.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
if processor.can_accept?(provided_input)
|
17
|
+
@processors << processor
|
18
|
+
else
|
19
|
+
raise ArgumentError.new(
|
20
|
+
"`#{processor.class}` accepts `#{processor.accepts}` but was connected to `#{provided_input}`"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process(&block)
|
26
|
+
result = @input
|
27
|
+
|
28
|
+
@processors.each do |processor|
|
29
|
+
result = processor.process(result)
|
30
|
+
end
|
31
|
+
|
32
|
+
block.call(result)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Yarrow
|
2
|
+
module Schema
|
3
|
+
module Type
|
4
|
+
class Any
|
5
|
+
end
|
6
|
+
end
|
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
|
71
|
+
end
|
72
|
+
|
73
|
+
validator.check(attr_values)
|
74
|
+
# TODO: type coercion or mapping decision goes here
|
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"
|
119
|
+
end
|
120
|
+
|
121
|
+
instance_variable_set("@#{key}", value)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_h
|
127
|
+
dictionary.keys.reduce({}) do |h, name|
|
128
|
+
h[name] = instance_variable_get("@#{name}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|