jekyll-graph 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|