obsidian-parser 0.2.0 → 0.4.0
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 +4 -4
- data/CHANGELOG.md +11 -1
- data/Gemfile.lock +1 -1
- data/lib/obsidian/parser/obsidian_flavored_markdown.rb +1 -1
- data/lib/obsidian/parser/page.rb +124 -0
- data/lib/obsidian/parser/{markdown_converter.rb → parsed_markdown_document.rb} +1 -1
- data/lib/obsidian/parser/version.rb +1 -1
- data/lib/obsidian/parser.rb +14 -81
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 041aa2c11a3972e24477c8601a880276de98b0108ef7628d9c30b623a1b14cb9
|
|
4
|
+
data.tar.gz: 1843bb05271c17a14658c956b7418472f19a5c460d95708f9f3e3fbe7d115d09
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c21ac0b8b64e487cfbdf7f35c15b486c4094a69dd4d068a90e5d7135159efd148f5df7aaa3ac2a52f327849cb30c5bd698938f0bca9ae4d45e558ab9cb142f2
|
|
7
|
+
data.tar.gz: e7f08dda0226319f82135e7bf7374c0f9b6448497f9e7e89a6b1e29a5d0fd0a281a0415d4fc246aae645a91b9cb8a2fb2ac024c941ec2ded63f5089a5ff51dec
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2023-07-30
|
|
4
|
+
- Unify `Note` and `Index` classes into `Page`. This is a breaking API change. `Parser#notes is replaced by Parse#pages`. Call `Page#is_index?`to distinguish between directory derived pages and documents.
|
|
5
|
+
- Remove `Parser#table_of_contents` and `Parser#walk_tree`.
|
|
6
|
+
- Add `Page#find_in_tree` to recursively search for a page with a matching slug.
|
|
7
|
+
- Rename `Obsidian::MarkdownConverter` to `Obsidian::ParsedMarkdownDocument`
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2023-07-27
|
|
10
|
+
|
|
11
|
+
- `Note` objects have a `parent` attribute.
|
|
12
|
+
|
|
3
13
|
## [0.2.0] - 2023-07-24
|
|
4
14
|
|
|
5
|
-
- Note objects have a `content` attribute. Call `content.generate_html` to generate HTML on demand.
|
|
15
|
+
- `Note` objects have a `content` attribute. Call `content.generate_html` to generate HTML on demand.
|
|
6
16
|
|
|
7
17
|
## [0.1.0] - 2023-07-10
|
|
8
18
|
|
data/Gemfile.lock
CHANGED
|
@@ -34,7 +34,7 @@ module Obsidian
|
|
|
34
34
|
# Parse links from obsidian-flavored-markdown text
|
|
35
35
|
def self.parse(markdown_text)
|
|
36
36
|
document = Kramdown::Document.new(normalize(markdown_text), input: "GFM")
|
|
37
|
-
Obsidian::
|
|
37
|
+
Obsidian::ParsedMarkdownDocument.new(document)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Obsidian
|
|
4
|
+
# A page in the vault corresponding to either a markdown document,
|
|
5
|
+
# or a directory containing other documents.
|
|
6
|
+
#
|
|
7
|
+
# If a directory contains an index.md, that is used as the content of
|
|
8
|
+
# the directory page; otherwise content will be nil.
|
|
9
|
+
class Page
|
|
10
|
+
def self.create_root
|
|
11
|
+
Page.new(title: "", slug: "")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(title:, slug:, last_modified: nil, content: nil, parent: nil)
|
|
15
|
+
# TODO: check frontmatter for titles as well
|
|
16
|
+
@title = title
|
|
17
|
+
@slug = slug
|
|
18
|
+
@last_modified = last_modified
|
|
19
|
+
@content = content
|
|
20
|
+
@parent = parent
|
|
21
|
+
@children = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def is_index?
|
|
25
|
+
!children.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def inspect
|
|
29
|
+
"Page(title: #{title.inspect}, slug: #{slug.inspect})"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ==(other)
|
|
33
|
+
self.class == other.class &&
|
|
34
|
+
!slug.nil? &&
|
|
35
|
+
slug == other&.slug
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
alias_method :eql?, :==
|
|
39
|
+
|
|
40
|
+
def hash
|
|
41
|
+
slug.hash
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Add a note to the tree based on its slug.
|
|
45
|
+
# Call this method on the root page.
|
|
46
|
+
# When calling this method, you must ensure that anscestor pages
|
|
47
|
+
# are added before their descendents.
|
|
48
|
+
def add_page(slug, last_modified: nil, content: nil)
|
|
49
|
+
path_components = slug.split("/")
|
|
50
|
+
raise ArgumentError, "Expecting non-empty slug" if path_components.empty?
|
|
51
|
+
|
|
52
|
+
title = path_components.pop
|
|
53
|
+
|
|
54
|
+
parent = path_components.reduce(self) do |index, anscestor_title|
|
|
55
|
+
anscestor_slug = Obsidian.build_slug(anscestor_title, index.slug)
|
|
56
|
+
index.get_or_create_child(slug: anscestor_slug, title: anscestor_title)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
parent.get_or_create_child(
|
|
60
|
+
title: title,
|
|
61
|
+
slug: slug,
|
|
62
|
+
last_modified: last_modified,
|
|
63
|
+
content: content
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_or_create_child(title:, slug:, last_modified: nil, content: nil)
|
|
68
|
+
# TODO: validate slug matches the current page slug
|
|
69
|
+
|
|
70
|
+
@children[title] ||= Page.new(
|
|
71
|
+
slug: slug,
|
|
72
|
+
title: title,
|
|
73
|
+
last_modified: last_modified,
|
|
74
|
+
content: content,
|
|
75
|
+
parent: self
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def children
|
|
80
|
+
@children.values.sort_by { |c| [c.is_index? ? 1 : 0, c.title] }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def walk_tree(&block)
|
|
84
|
+
children.each do |page|
|
|
85
|
+
block.call(page)
|
|
86
|
+
page.walk_tree(&block)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Return the page that matches a slug.
|
|
91
|
+
# If there is an exact match, we should always return that
|
|
92
|
+
# Otherwise, if we can skip over some anscestors and get a
|
|
93
|
+
# match, then return the first, shortest match.
|
|
94
|
+
def find_in_tree(query_slug)
|
|
95
|
+
# Exact match
|
|
96
|
+
return self if slug == query_slug
|
|
97
|
+
|
|
98
|
+
# Partial match
|
|
99
|
+
query_parts = query_slug.split("/")
|
|
100
|
+
length = query_parts.size
|
|
101
|
+
slug_parts = slug.split("/")
|
|
102
|
+
|
|
103
|
+
if slug_parts.length > length
|
|
104
|
+
if slug_parts.slice(-length, length) == query_parts
|
|
105
|
+
return self
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Recurse
|
|
110
|
+
children.each do |child|
|
|
111
|
+
result = child.find_in_tree(query_slug)
|
|
112
|
+
return result unless result.nil?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
attr_reader :title
|
|
119
|
+
attr_reader :slug
|
|
120
|
+
attr_reader :last_modified
|
|
121
|
+
attr_reader :content
|
|
122
|
+
attr_reader :parent
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/obsidian/parser.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "parser/version"
|
|
4
|
-
require_relative "parser/
|
|
4
|
+
require_relative "parser/parsed_markdown_document"
|
|
5
5
|
require_relative "parser/obsidian_flavored_markdown"
|
|
6
|
+
require_relative "parser/page"
|
|
7
|
+
|
|
8
|
+
require "forwardable"
|
|
6
9
|
|
|
7
10
|
module Obsidian
|
|
8
11
|
class Error < StandardError; end
|
|
@@ -22,72 +25,11 @@ module Obsidian
|
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
27
|
|
|
25
|
-
class Note
|
|
26
|
-
def initialize(title, slug, last_modified, content: nil)
|
|
27
|
-
# TODO: check frontmatter for titles as well
|
|
28
|
-
@title = title
|
|
29
|
-
@slug = slug
|
|
30
|
-
@last_modified = last_modified
|
|
31
|
-
@content = content
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def inspect
|
|
35
|
-
"Note(title: #{title.inspect}, slug: #{slug.inspect})"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
attr_reader :title
|
|
39
|
-
attr_reader :slug
|
|
40
|
-
attr_reader :last_modified
|
|
41
|
-
attr_reader :content
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
class Index
|
|
45
|
-
def initialize(title = "", slug = "", content: nil)
|
|
46
|
-
@title = title
|
|
47
|
-
@slug = slug
|
|
48
|
-
@notes = []
|
|
49
|
-
@directories = {}
|
|
50
|
-
@content = content
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def add_directory(title)
|
|
54
|
-
new_slug = Obsidian.build_slug(title, slug)
|
|
55
|
-
@directories[title] ||= Index.new(title, new_slug)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def add_note(title, parent_slug, last_modified, content: nil)
|
|
59
|
-
slug = Obsidian.build_slug(title, parent_slug)
|
|
60
|
-
directory = nested_directory(parent_slug.split("/"))
|
|
61
|
-
note = Note.new(title, slug, last_modified, content: content)
|
|
62
|
-
|
|
63
|
-
directory.notes << note
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def inspect
|
|
67
|
-
"Index(title: #{title.inspect}, slug: #{slug.inspect})"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def directories
|
|
71
|
-
@directories.values
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
attr_reader :notes
|
|
75
|
-
attr_reader :title
|
|
76
|
-
attr_reader :slug
|
|
77
|
-
attr_reader :content
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
def nested_directory(path_components)
|
|
82
|
-
path_components.reduce(self) { |index, subdirectory| index.add_directory(subdirectory) }
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
28
|
class Parser
|
|
87
29
|
attr_reader :index
|
|
88
30
|
|
|
89
31
|
def initialize(vault_directory)
|
|
90
|
-
@index =
|
|
32
|
+
@index = Obsidian::Page.create_root
|
|
91
33
|
|
|
92
34
|
vault_directory.glob("**/*.md").each do |path|
|
|
93
35
|
dirname, basename = path.relative_path_from(vault_directory).split
|
|
@@ -98,32 +40,23 @@ module Obsidian
|
|
|
98
40
|
if basename != "index.md"
|
|
99
41
|
title = basename.to_s.gsub(/\.md\z/, "")
|
|
100
42
|
parent_slug = dirname.to_s.gsub(/\A\.\/?/, "")
|
|
43
|
+
slug = Obsidian.build_slug(title, parent_slug)
|
|
101
44
|
content = MarkdownContent.new(path)
|
|
102
|
-
|
|
45
|
+
|
|
46
|
+
@index.add_page(
|
|
47
|
+
slug,
|
|
48
|
+
last_modified: path.mtime,
|
|
49
|
+
content: content
|
|
50
|
+
)
|
|
103
51
|
end
|
|
104
52
|
end
|
|
105
53
|
|
|
106
54
|
# TODO: capture links between notes
|
|
107
55
|
end
|
|
108
56
|
|
|
109
|
-
def
|
|
110
|
-
table_of_contents.map(&:first)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def walk_tree(index, level = 0, &block)
|
|
114
|
-
index.directories.sort_by(&:title).each do |note|
|
|
115
|
-
block.call(note, level)
|
|
116
|
-
walk_tree(note, level + 1, &block)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
index.notes.sort_by(&:title).each do |note|
|
|
120
|
-
block.call(note, level)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def table_of_contents
|
|
57
|
+
def pages
|
|
125
58
|
result = []
|
|
126
|
-
walk_tree
|
|
59
|
+
index.walk_tree { |page| result << page }
|
|
127
60
|
result
|
|
128
61
|
end
|
|
129
62
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: obsidian-parser
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mat Moore
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-07-
|
|
11
|
+
date: 2023-07-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: kramdown
|
|
@@ -56,8 +56,9 @@ files:
|
|
|
56
56
|
- README.md
|
|
57
57
|
- Rakefile
|
|
58
58
|
- lib/obsidian/parser.rb
|
|
59
|
-
- lib/obsidian/parser/markdown_converter.rb
|
|
60
59
|
- lib/obsidian/parser/obsidian_flavored_markdown.rb
|
|
60
|
+
- lib/obsidian/parser/page.rb
|
|
61
|
+
- lib/obsidian/parser/parsed_markdown_document.rb
|
|
61
62
|
- lib/obsidian/parser/version.rb
|
|
62
63
|
- sig/obsidian/parser.rbs
|
|
63
64
|
homepage:
|