jekyll-sheafy 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +61 -4
- data/lib/jekyll/sheafy/dependencies.rb +138 -0
- data/lib/jekyll/sheafy/references.rb +70 -0
- data/lib/jekyll/sheafy/taxa.rb +18 -0
- data/lib/jekyll/sheafy/version.rb +1 -1
- data/lib/jekyll/sheafy.rb +28 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3388e74b27cfab3e3fbdd846805e3c03c5326a7e91a30e6d1eb807f9d5619212
|
4
|
+
data.tar.gz: 0c9299c8e5cf165f0b8c84a66b5f7d9b51680c4b00453d999367aa5ea4d914b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1b71ad0af49028612d9e60fe0544e13d941dfae46e81e135023da8f7004baeceff1b7d32571ffe05e0008a1d026446d2bc036ec653fc7b703ad432751e05322
|
7
|
+
data.tar.gz: 7b42013f49e76759e2fb80e5477bb562c28d9ebc88815afca2afe4c72968050c28163f2f9abe5b9002ac74d8a14e97947f455200cb350789816a79a45e553637
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -21,21 +21,78 @@
|
|
21
21
|
|
22
22
|
## Getting started
|
23
23
|
|
24
|
-
|
24
|
+
Currently, the state of the art in using `jekyll-sheafy` is represented by [The Nursery][math-url]. Until I write a minimal guide, the bes way to get started is forking it and playing around with it.
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
-
TODO
|
28
|
+
> TODO: general usage notes.
|
29
|
+
|
30
|
+
### Architecture
|
31
|
+
|
32
|
+
> TODO: explain the Directed Rooted Forest structure and the taxa mechanism.
|
33
|
+
|
34
|
+
### Configuration
|
35
|
+
|
36
|
+
> TODO: fill in details for each parameter.
|
37
|
+
|
38
|
+
```yaml
|
39
|
+
sheafy:
|
40
|
+
references:
|
41
|
+
matchers: [] # ...
|
42
|
+
taxa: {} # ...
|
43
|
+
```
|
44
|
+
|
45
|
+
### Node variables
|
46
|
+
|
47
|
+
> TODO: fill in details for each variable.
|
48
|
+
|
49
|
+
#### General
|
50
|
+
|
51
|
+
- `taxon`
|
52
|
+
|
53
|
+
#### Layouting
|
54
|
+
|
55
|
+
- `layout`
|
56
|
+
- `sublayout`
|
57
|
+
|
58
|
+
#### Dependencies
|
59
|
+
|
60
|
+
- `ancestors`
|
61
|
+
- `parent`
|
62
|
+
- `subroot`
|
63
|
+
- `children`
|
64
|
+
|
65
|
+
#### References
|
66
|
+
|
67
|
+
- `referrers`
|
68
|
+
|
69
|
+
#### Numbering
|
70
|
+
|
71
|
+
- `clicker`
|
72
|
+
- `clicks`
|
29
73
|
|
30
74
|
## Roadmap
|
31
75
|
|
32
|
-
|
76
|
+
These are the features you can expect in the future:
|
77
|
+
|
78
|
+
- Enable `referents` variable w/ nodes references by the current one
|
79
|
+
- `root` variable w/ root node of the tree
|
80
|
+
- `siblings` variable or some variant of it to enable navigation between adjacent nodes at the same depth
|
81
|
+
- Prev/next node navigation
|
82
|
+
- Variable inheritance from parent/root node
|
83
|
+
- Search feature
|
84
|
+
|
85
|
+
Of course any feedback is welcome!
|
33
86
|
|
34
87
|
## Acknowledgements
|
35
88
|
|
36
|
-
- Thanks to [@jonsterling](https://github.com/jonsterling) for
|
89
|
+
- Thanks to [@jonsterling](https://github.com/jonsterling) for
|
90
|
+
- using [`krater`][krater-url] to setup [The Nursery][math-url],
|
91
|
+
- having the "brew your own Kerodon" idea, and
|
92
|
+
- letting me collaborate to spin it off into `jekyll-sheafy`.
|
37
93
|
|
38
94
|
[jekyll-url]: https://jekyllrb.com/
|
95
|
+
[krater-url]: https://github.com/paolobrasolin/krater/
|
39
96
|
[math-url]: https://github.com/jonsterling/math
|
40
97
|
[gerby-url]: https://gerby-project.github.io/
|
41
98
|
[stacks-url]: https://stacks.math.columbia.edu/
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "jekyll/sheafy/directed_graph"
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Sheafy
|
5
|
+
module Dependencies
|
6
|
+
RE_INCLUDE_TAG = /^@include{(?<slug>.+?)}$/
|
7
|
+
SUBLAYOUT_KEY = "sublayout"
|
8
|
+
SUBLAYOUT_DEFAULT_VALUE = "sheafy/node/default"
|
9
|
+
SUBROOT_KEY = "subroot"
|
10
|
+
|
11
|
+
def self.process(nodes)
|
12
|
+
adjacency_list = build_adjacency_list(nodes)
|
13
|
+
graph = build_rooted_forest!(adjacency_list)
|
14
|
+
denormalize_adjacency_list!(graph, nodes)
|
15
|
+
attribute_neighbors!(graph)
|
16
|
+
|
17
|
+
# NOTE: top.ord. is good to denormalize from leaves up to roots, while
|
18
|
+
# rev.top.ord is good to denormalize data from roots down to leaves.
|
19
|
+
# I.e.: for destructive procedures which need the altered children use
|
20
|
+
# tsorted_nodes.each { |node| ... }
|
21
|
+
# and for destructive procedures which need the original children use
|
22
|
+
# tsorted_nodes.reverse.each { |node| ... }
|
23
|
+
tsorted_nodes = graph.topologically_sorted
|
24
|
+
|
25
|
+
tsorted_nodes.reverse.each do |node|
|
26
|
+
attribute_ancestors!(node)
|
27
|
+
attribute_depth!(node)
|
28
|
+
attribute_clicks!(node)
|
29
|
+
end
|
30
|
+
|
31
|
+
# NOTE: this pass is separate to have data available in layouts.
|
32
|
+
tsorted_nodes.reverse.each do |node|
|
33
|
+
flatten_subtree!(node, nodes)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.scan_includes(node)
|
38
|
+
node.content.scan(RE_INCLUDE_TAG).flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.build_adjacency_list(nodes_index)
|
42
|
+
nodes_index.transform_values(&method(:scan_includes))
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.denormalize_adjacency_list!(list, index)
|
46
|
+
# TODO: handle missing nodes
|
47
|
+
list.transform_keys!(&index)
|
48
|
+
list.values.each { |children| children.map!(&index) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.attribute_neighbors!(list)
|
52
|
+
list.each do |parent, children|
|
53
|
+
parent.data["children"] = children
|
54
|
+
children.each { |child| child.data["parent"] ||= parent }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.attribute_ancestors!(node)
|
59
|
+
node.data["ancestors"] = []
|
60
|
+
parent = node.data["parent"]
|
61
|
+
if parent
|
62
|
+
ancestors = [*parent.data["ancestors"], parent]
|
63
|
+
node.data["ancestors"] = ancestors
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.attribute_depth!(node)
|
68
|
+
parent = node.data["parent"]
|
69
|
+
node.data["depth"] = 1 + (parent&.data&.[]("depth") || -1)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.attribute_clicks!(node)
|
73
|
+
node.data["clicks"] ||= [
|
74
|
+
{ "clicker" => node.data["clicker"], "value" => 0 },
|
75
|
+
]
|
76
|
+
node.data["children"].
|
77
|
+
group_by { |child| child.data["clicker"] }.
|
78
|
+
each do |clicker, children|
|
79
|
+
children.each_with_index do |child, index|
|
80
|
+
clicks = node.data["clicks"].dup
|
81
|
+
clicks << { "clicker" => clicker, "value" => index }
|
82
|
+
child.data["clicks"] = clicks
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.build_rooted_forest!(adjacency_list)
|
88
|
+
Sheafy::DirectedGraph[adjacency_list].
|
89
|
+
tap(&:ensure_rooted_forest!)
|
90
|
+
rescue Sheafy::DirectedGraph::PayloadError => error
|
91
|
+
message = case error
|
92
|
+
when Sheafy::DirectedGraph::MultipleEdgesError then "node reuse"
|
93
|
+
when Sheafy::DirectedGraph::LoopsError then "self reference"
|
94
|
+
when Sheafy::DirectedGraph::CyclesError then "cyclic reference"
|
95
|
+
when Sheafy::DirectedGraph::IndegreeError then "node reuse"
|
96
|
+
else raise StandardError.new("Malformed dependency graph!")
|
97
|
+
end
|
98
|
+
raise StandardError.new(<<~MESSAGE)
|
99
|
+
Error in dependency graph topology, #{message} detected: #{error.payload}
|
100
|
+
MESSAGE
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.apply_sublayout(resource, content, subroot)
|
104
|
+
sublayout = resource.data.fetch(SUBLAYOUT_KEY, SUBLAYOUT_DEFAULT_VALUE)
|
105
|
+
# NOTE: all this mess is just to adhere to Jekyll's internals
|
106
|
+
site = resource.site
|
107
|
+
payload = site.site_payload
|
108
|
+
payload["page"] = resource.to_liquid
|
109
|
+
payload["page"].merge!(SUBROOT_KEY => subroot)
|
110
|
+
payload["content"] = content
|
111
|
+
info = {
|
112
|
+
:registers => { :site => site, :page => payload["page"] },
|
113
|
+
:strict_filters => site.config["liquid"]["strict_filters"],
|
114
|
+
:strict_variables => site.config["liquid"]["strict_variables"],
|
115
|
+
}
|
116
|
+
layout = site.layouts[sublayout]
|
117
|
+
# TODO add_regenerator_dependencies(layout)
|
118
|
+
template = site.liquid_renderer.file(layout.path).parse(layout.content)
|
119
|
+
# TODO: handle warnings like https://github.com/jekyll/jekyll/blob/0b12fd26aed1038f69169b665818f5245e4f4b6d/lib/jekyll/renderer.rb#L126
|
120
|
+
template.render!(payload, info)
|
121
|
+
# TODO: handle exceptions like https://github.com/jekyll/jekyll/blob/0b12fd26aed1038f69169b665818f5245e4f4b6d/lib/jekyll/renderer.rb#L131
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.flatten_subtree!(node, nodes)
|
125
|
+
node.content = flatten_subtree(node, nodes)
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.flatten_subtree(resource, resources, subroot = resource)
|
129
|
+
content = resource.content.gsub(RE_INCLUDE_TAG) do
|
130
|
+
doc = resources[Regexp.last_match[:slug]]
|
131
|
+
# TODO: handle missing references
|
132
|
+
flatten_subtree(doc, resources, subroot)
|
133
|
+
end
|
134
|
+
apply_sublayout(resource, content, subroot)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "safe_yaml"
|
2
|
+
# NOTE: allows for rexexp entries in mathers array in config
|
3
|
+
SafeYAML::OPTIONS[:whitelisted_tags].push("!ruby/regexp")
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
module Sheafy
|
7
|
+
module References
|
8
|
+
class Error < StandardError; end
|
9
|
+
class InvalidMatcher < Error; end
|
10
|
+
|
11
|
+
CONFIG_KEY = "references"
|
12
|
+
DEFAULT_CONFIG = {
|
13
|
+
"matchers" => [
|
14
|
+
/{%\s*ref (?<slug>.+?)\s*%}/,
|
15
|
+
],
|
16
|
+
}
|
17
|
+
@@config = DEFAULT_CONFIG
|
18
|
+
REFERRERS_KEY = "referrers"
|
19
|
+
|
20
|
+
def self.validate_matcher!(matcher)
|
21
|
+
return if matcher.names.include?("slug")
|
22
|
+
raise InvalidMatcher.new(<<~ERROR)
|
23
|
+
Sheafy configuration error: the matcher #{matcher} is missing a capturing group named "slug".
|
24
|
+
ERROR
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load_config(config)
|
28
|
+
overrides = config.fetch(CONFIG_KEY, {})
|
29
|
+
overrides["matchers"]&.each(&method(:validate_matcher!))
|
30
|
+
@@config = Jekyll::Utils.deep_merge_hashes(DEFAULT_CONFIG, overrides)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.process(nodes_index, config = {})
|
34
|
+
load_config(config)
|
35
|
+
adjacency_list = build_adjacency_list(nodes_index)
|
36
|
+
denormalize_adjacency_list!(adjacency_list, nodes_index)
|
37
|
+
# NOTE: topology is arbitrary so no single pass technique is possible.
|
38
|
+
nodes_index.values.each { |node| node.data[REFERRERS_KEY] = [] }
|
39
|
+
attribute_neighbors!(adjacency_list)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.scan_references(node)
|
43
|
+
@@config["matchers"].flat_map do |matcher|
|
44
|
+
node.content.scan(matcher).flatten
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.build_adjacency_list(nodes_index)
|
49
|
+
nodes_index.transform_values(&method(:scan_references))
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.denormalize_adjacency_list!(list, index)
|
53
|
+
# TODO: handle missing nodes
|
54
|
+
list.transform_keys!(&index)
|
55
|
+
list.values.each { |children| children.map!(&index) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.attribute_neighbors!(list)
|
59
|
+
list.each do |referrer, referents|
|
60
|
+
# TODO: can referents be useful?
|
61
|
+
# referrer.data["referents"] = referents.uniq
|
62
|
+
referents.each do |referent|
|
63
|
+
next if referent.data[REFERRERS_KEY].include?(referrer)
|
64
|
+
referent.data[REFERRERS_KEY] << referrer
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Jekyll
|
2
|
+
module Sheafy
|
3
|
+
module Taxa
|
4
|
+
TAXON_KEY = "taxon"
|
5
|
+
|
6
|
+
def self.process(nodes_index)
|
7
|
+
nodes_index.values.each(&method(:apply_taxon!))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.apply_taxon!(node)
|
11
|
+
taxon_name = node.data[TAXON_KEY]
|
12
|
+
taxon_data = node.site.config.dig("sheafy", "taxa", taxon_name) || {}
|
13
|
+
# TODO: emit warning on undefined taxon
|
14
|
+
node.data.merge!(taxon_data) { |key, left, right| left }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/jekyll/sheafy.rb
CHANGED
@@ -1 +1,29 @@
|
|
1
|
+
require "jekyll"
|
2
|
+
require "jekyll/sheafy/dependencies"
|
3
|
+
require "jekyll/sheafy/references"
|
4
|
+
require "jekyll/sheafy/taxa"
|
1
5
|
require "jekyll/sheafy/version"
|
6
|
+
|
7
|
+
module Jekyll
|
8
|
+
module Sheafy
|
9
|
+
CONFIG_KEY = "sheafy"
|
10
|
+
|
11
|
+
def self.process(site)
|
12
|
+
config = site.config.fetch(CONFIG_KEY, {})
|
13
|
+
nodes_index = build_nodes_index(site)
|
14
|
+
Jekyll::Sheafy::Taxa.process(nodes_index)
|
15
|
+
Jekyll::Sheafy::References.process(nodes_index, config)
|
16
|
+
Jekyll::Sheafy::Dependencies.process(nodes_index)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build_nodes_index(site)
|
20
|
+
site.collections.values.flat_map(&:docs).
|
21
|
+
filter { |doc| doc.data.key?(Jekyll::Sheafy::Taxa::TAXON_KEY) }.
|
22
|
+
map { |doc| [doc.data["slug"], doc] }.to_h
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Jekyll::Hooks.register :site, :post_read, priority: 30 do |site|
|
28
|
+
Jekyll::Sheafy.process(site)
|
29
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-sheafy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paolo Brasolin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
@@ -99,7 +99,10 @@ files:
|
|
99
99
|
- LICENSE
|
100
100
|
- README.md
|
101
101
|
- lib/jekyll/sheafy.rb
|
102
|
+
- lib/jekyll/sheafy/dependencies.rb
|
102
103
|
- lib/jekyll/sheafy/directed_graph.rb
|
104
|
+
- lib/jekyll/sheafy/references.rb
|
105
|
+
- lib/jekyll/sheafy/taxa.rb
|
103
106
|
- lib/jekyll/sheafy/version.rb
|
104
107
|
homepage: https://github.com/paolobrasolin/jekyll-sheafy
|
105
108
|
licenses:
|
@@ -125,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
128
|
- !ruby/object:Gem::Version
|
126
129
|
version: '0'
|
127
130
|
requirements: []
|
128
|
-
rubygems_version: 3.1.
|
131
|
+
rubygems_version: 3.1.6
|
129
132
|
signing_key:
|
130
133
|
specification_version: 4
|
131
134
|
summary: Brew your own Stacks Project with Jekyll!
|