jekyll-sheafy 0.1.0 → 0.2.0
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/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!
|