obsidian-parser 0.3.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 +8 -2
- 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 -85
- 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,12 +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
|
+
|
3
9
|
## [0.3.0] - 2023-07-27
|
4
10
|
|
5
|
-
- Note objects have a `parent` attribute.
|
11
|
+
- `Note` objects have a `parent` attribute.
|
6
12
|
|
7
13
|
## [0.2.0] - 2023-07-24
|
8
14
|
|
9
|
-
- 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.
|
10
16
|
|
11
17
|
## [0.1.0] - 2023-07-10
|
12
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,76 +25,11 @@ module Obsidian
|
|
22
25
|
end
|
23
26
|
end
|
24
27
|
|
25
|
-
class Note
|
26
|
-
def initialize(title, slug, last_modified, content: nil, parent: nil)
|
27
|
-
# TODO: check frontmatter for titles as well
|
28
|
-
@title = title
|
29
|
-
@slug = slug
|
30
|
-
@last_modified = last_modified
|
31
|
-
@content = content
|
32
|
-
@parent = parent
|
33
|
-
end
|
34
|
-
|
35
|
-
def inspect
|
36
|
-
"Note(title: #{title.inspect}, slug: #{slug.inspect})"
|
37
|
-
end
|
38
|
-
|
39
|
-
attr_reader :title
|
40
|
-
attr_reader :slug
|
41
|
-
attr_reader :last_modified
|
42
|
-
attr_reader :content
|
43
|
-
attr_reader :parent
|
44
|
-
end
|
45
|
-
|
46
|
-
class Index
|
47
|
-
def initialize(title = "", slug = "", content: nil)
|
48
|
-
@title = title
|
49
|
-
@slug = slug
|
50
|
-
@notes = []
|
51
|
-
@directories = {}
|
52
|
-
@content = content
|
53
|
-
end
|
54
|
-
|
55
|
-
def add_directory(title)
|
56
|
-
new_slug = Obsidian.build_slug(title, slug)
|
57
|
-
@directories[title] ||= Index.new(title, new_slug)
|
58
|
-
end
|
59
|
-
|
60
|
-
def add_note(title, parent_slug, last_modified, content: nil)
|
61
|
-
slug = Obsidian.build_slug(title, parent_slug)
|
62
|
-
directory = nested_directory(parent_slug.split("/"))
|
63
|
-
note = Note.new(title, slug, last_modified, content: content, parent: directory)
|
64
|
-
|
65
|
-
directory.notes << note
|
66
|
-
|
67
|
-
note
|
68
|
-
end
|
69
|
-
|
70
|
-
def inspect
|
71
|
-
"Index(title: #{title.inspect}, slug: #{slug.inspect})"
|
72
|
-
end
|
73
|
-
|
74
|
-
def directories
|
75
|
-
@directories.values
|
76
|
-
end
|
77
|
-
|
78
|
-
attr_reader :notes
|
79
|
-
attr_reader :title
|
80
|
-
attr_reader :slug
|
81
|
-
attr_reader :content
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
def nested_directory(path_components)
|
86
|
-
path_components.reduce(self) { |index, subdirectory| index.add_directory(subdirectory) }
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
28
|
class Parser
|
91
29
|
attr_reader :index
|
92
30
|
|
93
31
|
def initialize(vault_directory)
|
94
|
-
@index =
|
32
|
+
@index = Obsidian::Page.create_root
|
95
33
|
|
96
34
|
vault_directory.glob("**/*.md").each do |path|
|
97
35
|
dirname, basename = path.relative_path_from(vault_directory).split
|
@@ -102,32 +40,23 @@ module Obsidian
|
|
102
40
|
if basename != "index.md"
|
103
41
|
title = basename.to_s.gsub(/\.md\z/, "")
|
104
42
|
parent_slug = dirname.to_s.gsub(/\A\.\/?/, "")
|
43
|
+
slug = Obsidian.build_slug(title, parent_slug)
|
105
44
|
content = MarkdownContent.new(path)
|
106
|
-
|
45
|
+
|
46
|
+
@index.add_page(
|
47
|
+
slug,
|
48
|
+
last_modified: path.mtime,
|
49
|
+
content: content
|
50
|
+
)
|
107
51
|
end
|
108
52
|
end
|
109
53
|
|
110
54
|
# TODO: capture links between notes
|
111
55
|
end
|
112
56
|
|
113
|
-
def
|
114
|
-
table_of_contents.map(&:first)
|
115
|
-
end
|
116
|
-
|
117
|
-
def walk_tree(index, level = 0, &block)
|
118
|
-
index.directories.sort_by(&:title).each do |note|
|
119
|
-
block.call(note, level)
|
120
|
-
walk_tree(note, level + 1, &block)
|
121
|
-
end
|
122
|
-
|
123
|
-
index.notes.sort_by(&:title).each do |note|
|
124
|
-
block.call(note, level)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def table_of_contents
|
57
|
+
def pages
|
129
58
|
result = []
|
130
|
-
walk_tree
|
59
|
+
index.walk_tree { |page| result << page }
|
131
60
|
result
|
132
61
|
end
|
133
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:
|