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.
@@ -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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Graph
5
+
6
+ VERSION = "0.0.1"
7
+
8
+ end
9
+ end
@@ -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: []