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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d4d015eba5703cb9cc20923e78d52aa6e34581793348d3283e2652f375947d4
4
- data.tar.gz: 87eb2cde0952ff0887a711d6c00d93101f3b67072953946cdcdbfaa196f8a58a
3
+ metadata.gz: 3388e74b27cfab3e3fbdd846805e3c03c5326a7e91a30e6d1eb807f9d5619212
4
+ data.tar.gz: 0c9299c8e5cf165f0b8c84a66b5f7d9b51680c4b00453d999367aa5ea4d914b5
5
5
  SHA512:
6
- metadata.gz: e193c3aefb25940bb7d905bca9cb6090a9b2c64614f589eeaf7366299768ef79895dff96966c52f18abe3fb281f2404e207f89582eb05da173a30207d0ca0795
7
- data.tar.gz: 3f8a4517fd4c1e5dcc7fbb3a6cbe4c56e3b9512607756ca66f2f7d951a63185d4152266ec6c274eb69c11e935dd5a5e7aefac5e0d73c4b8ec6b0be9ddf9d044c
6
+ metadata.gz: b1b71ad0af49028612d9e60fe0544e13d941dfae46e81e135023da8f7004baeceff1b7d32571ffe05e0008a1d026446d2bc036ec653fc7b703ad432751e05322
7
+ data.tar.gz: 7b42013f49e76759e2fb80e5477bb562c28d9ebc88815afca2afe4c72968050c28163f2f9abe5b9002ac74d8a14e97947f455200cb350789816a79a45e553637
data/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2022-01-29
11
+
12
+ ### Added
13
+
14
+ - Configurable matchers for reference detection.
15
+
10
16
  ## [0.1.0] - 2022-01-28
11
17
 
12
18
  ### Added
data/README.md CHANGED
@@ -21,21 +21,78 @@
21
21
 
22
22
  ## Getting started
23
23
 
24
- TODO
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
- TODO
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 [having the idea][math-url] and letting me collaborate and spin it off.
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
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module Sheafy
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  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.1.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-28 00:00:00.000000000 Z
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.4
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!