jekyll-graph 0.0.1
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 +7 -0
- data/.gitignore +27 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +16 -0
- data/LICENSE +676 -0
- data/README.md +192 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/jekyll-graph.gemspec +39 -0
- data/lib/jekyll-graph/config.rb +82 -0
- data/lib/jekyll-graph/context.rb +13 -0
- data/lib/jekyll-graph/jekyll-graph.js +330 -0
- data/lib/jekyll-graph/page.rb +16 -0
- data/lib/jekyll-graph/tags.rb +33 -0
- data/lib/jekyll-graph/version.rb +9 -0
- data/lib/jekyll-graph.rb +295 -0
- metadata +92 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
module Jekyll
|
5
|
+
module Graph
|
6
|
+
|
7
|
+
class PageWithoutAFile < Page
|
8
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
9
|
+
def read_yaml(*)
|
10
|
+
@data ||= {}
|
11
|
+
end
|
12
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jekyll
|
4
|
+
module Graph
|
5
|
+
|
6
|
+
class ForceGraphTag < Liquid::Tag
|
7
|
+
def render(context)
|
8
|
+
[
|
9
|
+
"<script src=\"//unpkg.com/element-resize-detector/dist/element-resize-detector.min.js\"></script>",
|
10
|
+
"<script src=\"//unpkg.com/force-graph\"></script>",
|
11
|
+
"<script src=\"https://d3js.org/d3.v6.min.js\"></script>",
|
12
|
+
].join("\n").gsub!("\n", "") # for long-string legibility
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# from: https://github.com/jekyll/jekyll-feed/blob/6d4913fe5017c685d2437f328ab4a9138cea07a8/lib/jekyll-feed/meta-tag.rb
|
17
|
+
class GraphScriptTag < Liquid::Tag
|
18
|
+
# TODO: this tag is actually not being used right now --
|
19
|
+
# but it's still here in case it is desirable to
|
20
|
+
# allow users to access each graph via their own
|
21
|
+
# div and skip scripting entirely
|
22
|
+
|
23
|
+
# Use Jekyll's native relative_url filter
|
24
|
+
include Jekyll::Filters::URLFilters
|
25
|
+
|
26
|
+
def render(context)
|
27
|
+
@context = context
|
28
|
+
"<script type=\"module\" src=\"#{$graph_conf.baseurl}#{$graph_conf.path_scripts}/jekyll-graph.js\" /></script>"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
data/lib/jekyll-graph.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jekyll"
|
3
|
+
|
4
|
+
require_relative "jekyll-graph/context"
|
5
|
+
require_relative "jekyll-graph/page"
|
6
|
+
require_relative "jekyll-graph/version"
|
7
|
+
|
8
|
+
# setup config
|
9
|
+
require_relative "jekyll-graph/config"
|
10
|
+
Jekyll::Hooks.register :site, :after_init do |site|
|
11
|
+
# global '$graph_conf' to ensure that all local jekyll plugins
|
12
|
+
# are reading from the same configuration with the same helper methods
|
13
|
+
# (global var is not ideal, but is DRY)
|
14
|
+
$graph_conf = Jekyll::Graph::PluginConfig.new(site.config)
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative "jekyll-graph/tags"
|
18
|
+
Liquid::Template.register_tag "force_graph", Jekyll::Graph::ForceGraphTag
|
19
|
+
Liquid::Template.register_tag "graph_scripts", Jekyll::Graph::GraphScriptTag
|
20
|
+
|
21
|
+
module Jekyll
|
22
|
+
module Graph
|
23
|
+
|
24
|
+
class Generator < Jekyll::Generator
|
25
|
+
priority :lowest
|
26
|
+
|
27
|
+
# Use Jekyll's native relative_url filter
|
28
|
+
include Jekyll::Filters::URLFilters
|
29
|
+
|
30
|
+
CONVERTER_CLASS = Jekyll::Converters::Markdown
|
31
|
+
|
32
|
+
def generate(site)
|
33
|
+
return if $graph_conf.disabled?
|
34
|
+
if !$graph_conf.disabled_net_web? && site.link_index.nil?
|
35
|
+
Jekyll.logger.error("To generate the net-web graph, please add and enable the 'jekyll-wikilinks' plugin")
|
36
|
+
return
|
37
|
+
end
|
38
|
+
if !$graph_conf.disabled_tree? && site.tree.nil?
|
39
|
+
Jekyll.logger.error("To generate the tree graph, please add and enable the 'jekyll-namespaces' plugin")
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
# setup site
|
44
|
+
@site = site
|
45
|
+
@context ||= Context.new(site)
|
46
|
+
|
47
|
+
# setup markdown docs
|
48
|
+
docs = []
|
49
|
+
docs += @site.pages if !$graph_conf.excluded?(:pages)
|
50
|
+
docs += @site.docs_to_write.filter { |d| !$graph_conf.excluded?(d.type) }
|
51
|
+
@md_docs = docs.filter { |doc| markdown_extension?(doc.extname) }
|
52
|
+
if @md_docs.empty?
|
53
|
+
Jekyll.logger.debug("No documents to process.")
|
54
|
+
end
|
55
|
+
|
56
|
+
# write graph
|
57
|
+
if !$graph_conf.disabled_net_web?
|
58
|
+
# generate json data
|
59
|
+
json_net_web_nodes, json_net_web_links = self.generate_json_net_web()
|
60
|
+
self.set_neighbors(json_net_web_nodes, json_net_web_links)
|
61
|
+
net_web_graph_content = JSON.dump(
|
62
|
+
nodes: json_net_web_nodes,
|
63
|
+
links: json_net_web_links,
|
64
|
+
)
|
65
|
+
# create json file
|
66
|
+
json_net_web_graph_file = self.new_page($graph_conf.path_assets, "graph-net-web.json", net_web_graph_content)
|
67
|
+
self.register_static_file(json_net_web_graph_file)
|
68
|
+
end
|
69
|
+
if !$graph_conf.disabled_tree?
|
70
|
+
# generate json data
|
71
|
+
json_tree_nodes, json_tree_links = self.generate_json_tree(@site.tree.root)
|
72
|
+
self.set_relatives(json_tree_nodes, json_tree_links)
|
73
|
+
tree_graph_content = JSON.dump(
|
74
|
+
nodes: json_tree_nodes,
|
75
|
+
links: json_tree_links,
|
76
|
+
)
|
77
|
+
# create json file
|
78
|
+
json_tree_graph_file = self.new_page($graph_conf.path_assets, "graph-tree.json", tree_graph_content)
|
79
|
+
self.register_static_file(json_tree_graph_file)
|
80
|
+
end
|
81
|
+
# add graph drawing scripts
|
82
|
+
script_filename = "jekyll-graph.js"
|
83
|
+
graph_script_content = File.read(source_path(script_filename))
|
84
|
+
static_file = self.new_page($graph_conf.path_scripts, script_filename, graph_script_content)
|
85
|
+
self.register_static_file(static_file)
|
86
|
+
end
|
87
|
+
|
88
|
+
# helpers
|
89
|
+
|
90
|
+
# from: https://github.com/jekyll/jekyll-sitemap/blob/master/lib/jekyll/jekyll-sitemap.rb#L39
|
91
|
+
def source_path(file)
|
92
|
+
File.expand_path "jekyll-graph/#{file}", __dir__
|
93
|
+
end
|
94
|
+
|
95
|
+
# Checks if a file already exists in the site source
|
96
|
+
def file_exists?(file_path)
|
97
|
+
@site.static_files.any? { |p| p.url == "/#{file_path}" }
|
98
|
+
end
|
99
|
+
|
100
|
+
def markdown_extension?(extension)
|
101
|
+
markdown_converter.matches(extension)
|
102
|
+
end
|
103
|
+
|
104
|
+
def markdown_converter
|
105
|
+
@markdown_converter ||= @site.find_converter_instance(CONVERTER_CLASS)
|
106
|
+
end
|
107
|
+
|
108
|
+
# generator helpers
|
109
|
+
|
110
|
+
def new_page(path, filename, content)
|
111
|
+
new_file = PageWithoutAFile.new(@site, __dir__, "", filename)
|
112
|
+
new_file.content = content
|
113
|
+
new_file.data["layout"] = nil
|
114
|
+
new_file.data["permalink"] = File.join(path, filename)
|
115
|
+
@site.pages << new_file unless file_exists?(filename)
|
116
|
+
return new_file
|
117
|
+
end
|
118
|
+
|
119
|
+
def register_static_file(static_file)
|
120
|
+
# tests fail without manually adding the static file, but actual site builds seem to do ok
|
121
|
+
# ...although there does seem to be a race condition which causes a rebuild to be necessary in order to detect the graph data file
|
122
|
+
if $graph_conf.testing
|
123
|
+
@site.static_files << static_file if !@site.static_files.include?(static_file)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# json population helpers
|
128
|
+
# set ids here, full javascript objects are populated in client-side javascript.
|
129
|
+
|
130
|
+
def set_neighbors(json_nodes, json_links)
|
131
|
+
json_links.each do |json_link|
|
132
|
+
source_node = json_nodes.detect { |n| n[:id] == json_link[:source] }
|
133
|
+
target_node = json_nodes.detect { |n| n[:id] == json_link[:target] }
|
134
|
+
|
135
|
+
source_node[:neighbors][:nodes] << target_node[:id]
|
136
|
+
target_node[:neighbors][:nodes] << source_node[:id]
|
137
|
+
|
138
|
+
source_node[:neighbors][:links] << json_link
|
139
|
+
target_node[:neighbors][:links] << json_link
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def set_relatives(json_nodes, json_links)
|
144
|
+
# TODO: json nodes have relative_url, but node.id's/urls are doc urls.
|
145
|
+
json_nodes.each do |json_node|
|
146
|
+
ancestor_node_ids, descendent_node_ids = @site.tree.get_all_relative_ids(json_node[:id])
|
147
|
+
relative_node_ids = ancestor_node_ids.concat(descendent_node_ids)
|
148
|
+
json_node[:relatives][:nodes] = relative_node_ids if !relative_node_ids.nil?
|
149
|
+
|
150
|
+
# include current node when filtering for links along entire relative lineage
|
151
|
+
lineage_ids = relative_node_ids.concat([json_node[:id]])
|
152
|
+
|
153
|
+
json_relative_links = json_links.select { |l| lineage_ids.include?(l[:source]) && lineage_ids.include?(l[:target]) }
|
154
|
+
json_node[:relatives][:links] = json_relative_links if !json_relative_links.nil?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# json generation helpers
|
159
|
+
|
160
|
+
def generate_json_net_web()
|
161
|
+
net_web_nodes, net_web_links = [], []
|
162
|
+
|
163
|
+
@md_docs.each do |doc|
|
164
|
+
if !$graph_conf.excluded?(doc.type)
|
165
|
+
|
166
|
+
Jekyll.logger.debug "Processing graph nodes for doc: ", doc.data['title']
|
167
|
+
#
|
168
|
+
# missing nodes
|
169
|
+
#
|
170
|
+
@site.link_index.index[doc.url].missing.each do |missing_link_name|
|
171
|
+
if net_web_nodes.none? { |node| node[:id] == missing_link_name }
|
172
|
+
Jekyll.logger.warn "Net-Web node missing: ", missing_link_name
|
173
|
+
Jekyll.logger.warn " in: ", doc.data['title']
|
174
|
+
net_web_nodes << {
|
175
|
+
id: missing_link_name, # an id is necessary for link targets
|
176
|
+
url: '',
|
177
|
+
label: missing_link_name,
|
178
|
+
neighbors: {
|
179
|
+
nodes: [],
|
180
|
+
links: [],
|
181
|
+
},
|
182
|
+
}
|
183
|
+
net_web_links << {
|
184
|
+
source: doc.url,
|
185
|
+
target: missing_link_name,
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
#
|
190
|
+
# existing nodes
|
191
|
+
#
|
192
|
+
net_web_nodes << {
|
193
|
+
# TODO: when using real ids, be sure to convert id to string (to_s)
|
194
|
+
id: doc.url,
|
195
|
+
url: relative_url(doc.url),
|
196
|
+
label: doc.data['title'],
|
197
|
+
neighbors: {
|
198
|
+
nodes: [],
|
199
|
+
links: [],
|
200
|
+
},
|
201
|
+
}
|
202
|
+
# TODO: this link calculation ends up with duplicates -- re-visit this later.
|
203
|
+
@site.link_index.index[doc.url].attributes.each do |link| # link = { 'type' => str, 'urls' => [str, str, ...] }
|
204
|
+
# TODO: Header + Block-level wikilinks
|
205
|
+
link['urls'].each do |lu|
|
206
|
+
link_no_anchor = lu.match(/([^#]+)/i)[0]
|
207
|
+
link_no_baseurl = @site.baseurl.nil? ? link_no_anchor : link_no_anchor.gsub(@site.baseurl, "")
|
208
|
+
linked_doc = @md_docs.select{ |d| d.url == link_no_baseurl }
|
209
|
+
if !linked_doc.nil? && linked_doc.size == 1 && !$graph_conf.excluded?(linked_doc.first.type)
|
210
|
+
# TODO: add link['type'] to d3 graph
|
211
|
+
net_web_links << {
|
212
|
+
source: doc.url,
|
213
|
+
target: linked_doc.first.url,
|
214
|
+
}
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
@site.link_index.index[doc.url].forelinks.each do |link| # link = { 'type' => str, 'url' => str }
|
219
|
+
# TODO: Header + Block-level wikilinks
|
220
|
+
link_no_anchor = link['url'].match(/([^#]+)/i)[0]
|
221
|
+
link_no_baseurl = @site.baseurl.nil? ? link_no_anchor : link_no_anchor.gsub(@site.baseurl, "")
|
222
|
+
linked_doc = @md_docs.select{ |d| d.url == link_no_baseurl }
|
223
|
+
if !linked_doc.nil? && linked_doc.size == 1 && !$graph_conf.excluded?(linked_doc.first.type)
|
224
|
+
# TODO: add link['type'] to d3 graph
|
225
|
+
net_web_links << {
|
226
|
+
source: doc.url,
|
227
|
+
target: linked_doc.first.url,
|
228
|
+
}
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
return net_web_nodes, net_web_links
|
236
|
+
end
|
237
|
+
|
238
|
+
def generate_json_tree(node, json_parent="", tree_nodes=[], tree_links=[])
|
239
|
+
#
|
240
|
+
# missing nodes
|
241
|
+
#
|
242
|
+
if node.missing
|
243
|
+
Jekyll.logger.warn("Document for tree node missing: ", node.namespace)
|
244
|
+
|
245
|
+
leaf = node.namespace.split('.').pop()
|
246
|
+
missing_node = {
|
247
|
+
id: node.namespace,
|
248
|
+
label: leaf.gsub('-', ' '),
|
249
|
+
namespace: node.namespace,
|
250
|
+
url: "",
|
251
|
+
relatives: {
|
252
|
+
nodes: [],
|
253
|
+
links: [],
|
254
|
+
},
|
255
|
+
}
|
256
|
+
tree_nodes << missing_node
|
257
|
+
if !json_parent.empty?
|
258
|
+
tree_links << {
|
259
|
+
source: json_parent[:id],
|
260
|
+
target: node.namespace,
|
261
|
+
}
|
262
|
+
end
|
263
|
+
json_parent = missing_node
|
264
|
+
#
|
265
|
+
# existing nodes
|
266
|
+
#
|
267
|
+
else
|
268
|
+
existing_node = {
|
269
|
+
id: node.url,
|
270
|
+
label: node.title,
|
271
|
+
namespace: node.namespace,
|
272
|
+
url: relative_url(node.url),
|
273
|
+
relatives: {
|
274
|
+
nodes: [],
|
275
|
+
links: [],
|
276
|
+
},
|
277
|
+
}
|
278
|
+
tree_nodes << existing_node
|
279
|
+
if !json_parent.empty?
|
280
|
+
tree_links << {
|
281
|
+
source: json_parent[:id],
|
282
|
+
target: node.url,
|
283
|
+
}
|
284
|
+
end
|
285
|
+
json_parent = existing_node
|
286
|
+
end
|
287
|
+
node.children.each do |child|
|
288
|
+
self.generate_json_tree(child, json_parent, tree_nodes, tree_links)
|
289
|
+
end
|
290
|
+
return tree_nodes, tree_links
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jekyll-graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- manunamz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jekyll-namespaces
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jekyll-wikilinks
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.6
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.0.6
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- manunamz@pm.me
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".rspec"
|
50
|
+
- ".rubocop.yml"
|
51
|
+
- ".travis.yml"
|
52
|
+
- CHANGELOG.md
|
53
|
+
- Gemfile
|
54
|
+
- LICENSE
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- bin/console
|
58
|
+
- bin/setup
|
59
|
+
- jekyll-graph.gemspec
|
60
|
+
- lib/jekyll-graph.rb
|
61
|
+
- lib/jekyll-graph/config.rb
|
62
|
+
- lib/jekyll-graph/context.rb
|
63
|
+
- lib/jekyll-graph/jekyll-graph.js
|
64
|
+
- lib/jekyll-graph/page.rb
|
65
|
+
- lib/jekyll-graph/tags.rb
|
66
|
+
- lib/jekyll-graph/version.rb
|
67
|
+
homepage: https://github.com/manunamz/jekyll-graph
|
68
|
+
licenses: []
|
69
|
+
metadata:
|
70
|
+
homepage_uri: https://github.com/manunamz/jekyll-graph
|
71
|
+
source_code_uri: https://github.com/manunamz/jekyll-graph
|
72
|
+
changelog_uri: https://github.com/manunamz/jekyll-graph/blob/main/CHANGELOG.md
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 2.4.0
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubygems_version: 3.2.17
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Add d3 graph generation to jekyll.
|
92
|
+
test_files: []
|