jekyll-obsidian 1.1.2

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,250 @@
1
+ # lib/jekyll/obsidian.rb
2
+ # frozen_string_literal: true
3
+
4
+ require "jekyll"
5
+ require "json"
6
+ require "fileutils"
7
+
8
+ require_relative "obsidian/version"
9
+
10
+ module Jekyll
11
+ module Obsidian
12
+ # Jekyll::Hooks.register :site, :post_write do |site|
13
+ # vault = site.config["obsidian_vault"]
14
+ # vault_path = File.join(site.dest, vault)
15
+ # Dir.glob(File.join(vault_path, "**", "*.md")).each do |md_file|
16
+ # new_file_path = md_file.sub(/\.md$/, ".mdnote")
17
+ # File.rename(md_file, new_file_path)
18
+ # end
19
+ # end
20
+ class FileTreeGenerator < Jekyll::Generator
21
+ safe true
22
+ priority :lowest
23
+
24
+ def generate(site)
25
+ # -------------------------------- site config ------------------------------- #
26
+ vault = site.config["obsidian_vault"] || site.source
27
+ if vault.nil?
28
+ puts "Error: obsidian_vault is not set in config.yml"
29
+ exit(1)
30
+ end
31
+ enable_backlinks = site.config["obsidian_backlinks"]
32
+ enable_embeds = site.config["obsidian_embeds"]
33
+
34
+ # --------------------------------- site data -------------------------------- #
35
+ data_dir = File.join(File.dirname(site.dest), "_data", "obsidian")
36
+ FileUtils.mkdir_p(data_dir) unless File.directory?(data_dir)
37
+
38
+ site.data["obsidian"] = {} unless site.data["obsidian"]
39
+
40
+ counts = {dirs: 0, files: 0, size: 0}
41
+ obsidian_files = collect_files(vault, "", counts)
42
+ vault_data_json = File.join(data_dir, "vault_data.json")
43
+ File.write(vault_data_json, JSON.pretty_generate(counts.to_json))
44
+
45
+ vault_files_json = File.join(data_dir, "vault_files.json")
46
+ File.write(vault_files_json, JSON.pretty_generate(obsidian_files.to_json))
47
+
48
+ backlinks, embeds = build_links(vault, obsidian_files, obsidian_files)
49
+
50
+ if enable_backlinks || enable_backlinks.nil?
51
+ backlinks_json = File.join(data_dir, "backlinks.json")
52
+ File.write(backlinks_json, escape_backlinks(backlinks))
53
+ puts "Backlinks built."
54
+ else
55
+ puts "Backlinks disabled"
56
+ end
57
+
58
+ if enable_embeds || enable_embeds.nil?
59
+ embeds_json = File.join(data_dir, "embeds.json")
60
+ File.write(embeds_json, escape_embeds(embeds))
61
+ puts "Embeds built."
62
+ else
63
+ puts "Embeds disabled"
64
+ end
65
+
66
+ site.config["obsidian_homepage"]
67
+
68
+ obsidian_dir = File.join(File.dirname(site.dest), "_includes", "obsidian")
69
+ FileUtils.mkdir_p(obsidian_dir) unless File.directory?(obsidian_dir)
70
+
71
+ layouts_dir = File.join(File.dirname(site.dest), "_layouts")
72
+ FileUtils.mkdir_p(layouts_dir) unless File.directory?(layouts_dir)
73
+
74
+ scss_dir = File.join(File.dirname(site.dest), "assets", "obsidian")
75
+ FileUtils.mkdir_p(scss_dir) unless File.directory?(scss_dir)
76
+
77
+ partials_dir = File.join(File.dirname(site.dest), "_sass", "obsidian")
78
+ FileUtils.mkdir_p(partials_dir) unless File.directory?(partials_dir)
79
+
80
+ project_root = File.expand_path("../..", File.dirname(__FILE__))
81
+ plugin_dir = File.join(project_root, "assets")
82
+
83
+ main_scss = File.join(plugin_dir, "css", "obsidian.scss")
84
+ copy_file_to_dir(main_scss, scss_dir)
85
+
86
+ copy_files_from_dir(File.join(plugin_dir, "css", "partials"), partials_dir)
87
+
88
+ layout = File.join(plugin_dir, "layouts", "obsidian.html")
89
+ copy_file_to_dir(layout, layouts_dir, true)
90
+
91
+ copy_files_from_dir(File.join(plugin_dir, "includes"), obsidian_dir, true)
92
+ end
93
+
94
+ private
95
+
96
+ def copy_file_to_dir(file, dir, overwrite = false)
97
+ if File.exist?(file)
98
+ destination_file = File.join(dir, File.basename(file))
99
+
100
+ if !overwrite && File.exist?(destination_file)
101
+ puts "#{File.basename(file)} currently exists"
102
+ else
103
+ FileUtils.cp(file, dir)
104
+ puts "#{File.basename(file)} copied over"
105
+ end
106
+ else
107
+ puts "Error: #{file} does not exist"
108
+ exit
109
+ end
110
+ end
111
+
112
+ def copy_files_from_dir(source_dir, destination_dir, overwrite = false)
113
+ Dir.glob(File.join(source_dir, "*")).each do |file_path|
114
+ next if File.directory?(file_path)
115
+ copy_file_to_dir(file_path, destination_dir, overwrite)
116
+ end
117
+ end
118
+
119
+ def excluded_file_exts(filename)
120
+ extensions = [".exe", ".bat", ".sh", ".zip", ".7z", ".stl", ".fbx"]
121
+ is_excluded = extensions.any? { |ext| filename.end_with?(ext) }
122
+ if is_excluded
123
+ puts "Excluded file: #{filename}"
124
+ end
125
+ is_excluded
126
+ end
127
+
128
+ # ------------------------ Ruby Hash object generators ----------------------- #
129
+ def collect_files(rootdir, path = "", counts = {dirs: 0, files: 0, size: 0})
130
+ root_files_ = []
131
+ Dir.entries(rootdir).each do |entry|
132
+ next if entry.start_with?(".", "_")
133
+ entry_path = File.join(rootdir, entry)
134
+ root_files_ << if File.directory?(entry_path)
135
+ next if entry.start_with?(".obsidian")
136
+ counts[:dirs] += 1
137
+ {name: entry, type: "dir", path: File.join(path, entry),
138
+ children: collect_files(entry_path, File.join(path, entry), counts)}
139
+ else
140
+ next if File.zero?(entry_path) || File.empty?(entry_path)
141
+
142
+ if File.extname(entry) == ".md"
143
+ new_name = entry.sub(".md", ".mdnote")
144
+ new_path = File.join(rootdir, new_name)
145
+ File.rename(entry_path, new_path)
146
+ entry_path = new_path
147
+ entry = new_name
148
+ end
149
+
150
+ counts[:files] += 1
151
+ counts[:size] += File.size(entry_path)
152
+ {name: entry, type: "file", path: File.join(path, entry), size: File.size(entry_path)}
153
+ end
154
+ end
155
+ root_files_
156
+ end
157
+
158
+ def build_links(rootdir, root_files_, root_files, backlinks = {}, embeds = {})
159
+ root_files_.each do |file|
160
+ if file[:type] == "dir"
161
+ build_links(rootdir, file[:children], root_files, backlinks, embeds)
162
+ elsif file[:type] == "file"
163
+ entry_path = File.join(rootdir, file[:path])
164
+ next if File.zero?(entry_path) || excluded_file_exts(file[:name])
165
+ if file[:name].end_with?(".mdnote", ".canvas")
166
+ begin
167
+ content = File.read(entry_path)
168
+ rescue Errno::ENOENT
169
+ puts "Error reading file: #{entry_path} - No such file"
170
+ next
171
+ rescue Errno::EACCES
172
+ puts "Error reading file: #{entry_path} - Permission denied"
173
+ next
174
+ end
175
+
176
+ links = content.scan(/\[\[(.*?)\]\]/).flatten
177
+
178
+ backlinks[file[:path]] ||= {"backlink_paths" => []}
179
+
180
+ links.each do |link|
181
+ lowercase_link = link.downcase
182
+ matched_entry = find_matching_entry(root_files, lowercase_link)
183
+ if matched_entry
184
+ unless matched_entry[:path] == file[:path] ||
185
+ backlinks[file[:path]]["backlink_paths"].include?(matched_entry[:path])
186
+ backlinks[file[:path]]["backlink_paths"] << matched_entry[:path]
187
+ end
188
+ end
189
+ end
190
+ elsif !file[:name].end_with?(".mdnote", ".canvas")
191
+ if embeds[file[:path]].nil? || embeds[file[:path]]["embed_paths"].nil?
192
+ embeds[file[:path]] = {"embed_paths" => [entry_path]}
193
+ else
194
+ unless embeds[file[:path]]["embed_paths"].include?(entry_path)
195
+ embeds[file[:path]]["embed_paths"] << entry_path
196
+ end
197
+ end
198
+ end
199
+ else
200
+ puts "Skipping non-markdown file: #{file[:name]}"
201
+ end
202
+ end
203
+ [backlinks, embeds]
204
+ end
205
+
206
+ def find_matching_entry(files, lowercase_link)
207
+ stripped_link = lowercase_link.sub(/\|.*$/, "").sub(/#.*$/, "")
208
+ files.each do |file|
209
+ if file[:type] == "dir"
210
+ result = find_matching_entry(file[:children], lowercase_link)
211
+ return result if result
212
+ elsif file[:type] == "file" && file[:name].end_with?(".mdnote", ".canvas")
213
+ file_name_without_extension = file[:name].sub(/\.\w+$/, "").downcase
214
+ return file if file_name_without_extension == stripped_link
215
+ end
216
+ end
217
+ nil
218
+ end
219
+
220
+ # ------------------------ Ruby Hash object formatters ----------------------- #
221
+ def escape_backlinks(backlinks)
222
+ escaped_backlinks = {}
223
+ backlinks.each do |path, data|
224
+ escaped_path = escape_path(path)
225
+ escaped_data = {
226
+ "backlink_paths" => data["backlink_paths"].map do |path|
227
+ escape_path(path)
228
+ end
229
+ }
230
+ escaped_backlinks[escaped_path] = escaped_data
231
+ end
232
+ JSON.pretty_generate(escaped_backlinks.to_json)
233
+ end
234
+
235
+ def escape_embeds(embeds)
236
+ escaped_embeds = {}
237
+ embeds.each do |path, _|
238
+ escaped_path = escape_path(path)
239
+ escaped_embeds[escaped_path] = {}
240
+ end
241
+ JSON.pretty_generate(escaped_embeds.to_json)
242
+ end
243
+
244
+ def escape_path(path)
245
+ escaped_path = path.gsub("'", "/:|").gsub('"', "/:|")
246
+ (escaped_path[0] == "/") ? escaped_path.slice(1..-1) : escaped_path
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,36 @@
1
+ require "google/apis/drive_v3"
2
+ require "googleauth"
3
+ require "googleauth/stores/file_token_store"
4
+ require "fileutils"
5
+ # ------------------- Isn't actually implemented right now ------------------- #
6
+ module Remote
7
+ OOB_URI = "urn:ietf:wg:oauth:2.0:oob"
8
+ APPLICATION_NAME = "Drive API Ruby Quickstart"
9
+ CREDENTIALS_PATH = ".json"
10
+ TOKEN_PATH = "token.yaml"
11
+ SCOPE = Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY
12
+
13
+ def self.authorize
14
+ client_id = Google::Auth::ClientId.from_file(CREDENTIALS_PATH)
15
+ token_store = Google::Auth::Stores::FileTokenStore.new(file: TOKEN_PATH)
16
+ authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, token_store)
17
+ user_id = "default"
18
+ credentials = authorizer.get_credentials(user_id)
19
+ if credentials.nil?
20
+ url = authorizer.get_authorization_url(base_url: OOB_URI)
21
+ puts "Open the following URL in the browser and enter the " \
22
+ "resulting code after authorization:\n" + url
23
+ code = gets
24
+ credentials = authorizer.get_credentials_from_code(user_id: user_id, code: code, base_url: OOB_URI)
25
+ end
26
+ credentials
27
+ end
28
+
29
+ def self.list_files_in_drive(directory_id)
30
+ drive_service = Google::Apis::DriveV3::DriveService.new
31
+ drive_service.client_options.application_name = APPLICATION_NAME
32
+ drive_service.authorization = authorize
33
+ response = drive_service.list_files(q: "'#{directory_id}' in parents")
34
+ response.files
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ # lib/jekyll-obsidian.rb
2
+ require "jekyll/obsidian"
3
+ require "jekyll/obsidian/version"
Binary file
@@ -0,0 +1,6 @@
1
+ module Jekyll
2
+ module Obsidian
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-obsidian
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Khiem Luong
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description: Jekyll site implementation of obsidian as close to how it looks in the
42
+ app
43
+ email:
44
+ - khiemgluong@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".rspec"
50
+ - ".standard.yml"
51
+ - CHANGELOG.md
52
+ - CODE_OF_CONDUCT.md
53
+ - LICENSE
54
+ - README.md
55
+ - Rakefile
56
+ - assets/css/obsidian.scss
57
+ - assets/css/partials/_canvas.scss
58
+ - assets/css/partials/_explorer.scss
59
+ - assets/css/partials/_fileread.scss
60
+ - assets/css/partials/_loading.scss
61
+ - assets/css/partials/_modals.scss
62
+ - assets/css/partials/_note.scss
63
+ - assets/css/partials/_sidebar.scss
64
+ - assets/includes/canvas.html
65
+ - assets/includes/explorer.html
66
+ - assets/includes/fileread.html
67
+ - assets/includes/modals.html
68
+ - assets/includes/note.html
69
+ - assets/includes/sidebar.html
70
+ - assets/layouts/obsidian.html
71
+ - lib/jekyll-obsidian.rb
72
+ - lib/jekyll/obsidian.rb
73
+ - lib/jekyll/obsidian/version.rb
74
+ - lib/jekyll/remote.rb
75
+ - screenshots/jekyll-obsidian.png
76
+ - sig/jekyll/obsidian.rbs
77
+ homepage: https://khiemgluong.github.io/blade-ballad/
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ homepage_uri: https://khiemgluong.github.io/blade-ballad/
82
+ source_code_uri: https://github.com/khiemgluong/jekyll-obsidian/
83
+ changelog_uri: https://github.com/khiemgluong/jekyll-obsidian/blob/main/CHANGELOG.md
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 3.0.0
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubygems_version: 3.5.18
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Obsidian integration for Jekyll
103
+ test_files: []