obsidian-parser 0.3.0 → 0.5.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 +12 -2
- data/Gemfile.lock +1 -1
- data/lib/obsidian/parser/obsidian_flavored_markdown.rb +1 -1
- data/lib/obsidian/parser/page.rb +131 -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 +20 -89
- 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: 10a282bce512c7d763d89eed0aedff250e6721cad6e0849af3821dd897e8d819
|
4
|
+
data.tar.gz: d3271c30ab0f7094b6273b0e8f86b33ec2280729fe757958b61459cc0bca5447
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69cdd0c2c4c6ceb085c9e861d18be91ce3f6b237445f26720bf1787da8448c7ab459c24b4a717d51d1001c7ab9f09b143cb296d28dfee075a869dad9aed10467
|
7
|
+
data.tar.gz: 9511deca5d2844548218b5a7841912b89469b036db63ad3c879c0e246453bf527cb37c732fc3df4e22dfcba4ea5f24e53340dc8572ac0c9372146ee14fb95805
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2023-07-30
|
4
|
+
- Fix ordering of `Page#children` so that index pages come first.
|
5
|
+
- Fix handling of `index.md` documents so that the slug reflects the directory path.
|
6
|
+
|
7
|
+
## [0.4.0] - 2023-07-30
|
8
|
+
- 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.
|
9
|
+
- Remove `Parser#table_of_contents` and `Parser#walk_tree`.
|
10
|
+
- Add `Page#find_in_tree` to recursively search for a page with a matching slug.
|
11
|
+
- Rename `Obsidian::MarkdownConverter` to `Obsidian::ParsedMarkdownDocument`
|
12
|
+
|
3
13
|
## [0.3.0] - 2023-07-27
|
4
14
|
|
5
|
-
- Note objects have a `parent` attribute.
|
15
|
+
- `Note` objects have a `parent` attribute.
|
6
16
|
|
7
17
|
## [0.2.0] - 2023-07-24
|
8
18
|
|
9
|
-
- Note objects have a `content` attribute. Call `content.generate_html` to generate HTML on demand.
|
19
|
+
- `Note` objects have a `content` attribute. Call `content.generate_html` to generate HTML on demand.
|
10
20
|
|
11
21
|
## [0.1.0] - 2023-07-10
|
12
22
|
|
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,131 @@
|
|
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
|
+
).tap do |page|
|
65
|
+
page.update_content(content: content, last_modified: last_modified)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_or_create_child(title:, slug:, last_modified: nil, content: nil)
|
70
|
+
# TODO: validate slug matches the current page slug
|
71
|
+
|
72
|
+
@children[title] ||= Page.new(
|
73
|
+
slug: slug,
|
74
|
+
title: title,
|
75
|
+
last_modified: last_modified,
|
76
|
+
content: content,
|
77
|
+
parent: self
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_content(content:, last_modified:)
|
82
|
+
@content ||= content
|
83
|
+
@last_modified ||= last_modified
|
84
|
+
end
|
85
|
+
|
86
|
+
def children
|
87
|
+
@children.values.sort_by { |c| [c.is_index? ? 0 : 1, c.title] }
|
88
|
+
end
|
89
|
+
|
90
|
+
def walk_tree(&block)
|
91
|
+
children.each do |page|
|
92
|
+
block.call(page)
|
93
|
+
page.walk_tree(&block)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return the page that matches a slug.
|
98
|
+
# If there is an exact match, we should always return that
|
99
|
+
# Otherwise, if we can skip over some anscestors and get a
|
100
|
+
# match, then return the first, shortest match.
|
101
|
+
def find_in_tree(query_slug)
|
102
|
+
# Exact match
|
103
|
+
return self if slug == query_slug
|
104
|
+
|
105
|
+
# Partial match
|
106
|
+
query_parts = query_slug.split("/")
|
107
|
+
length = query_parts.size
|
108
|
+
slug_parts = slug.split("/")
|
109
|
+
|
110
|
+
if slug_parts.length > length
|
111
|
+
if slug_parts.slice(-length, length) == query_parts
|
112
|
+
return self
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Recurse
|
117
|
+
children.each do |child|
|
118
|
+
result = child.find_in_tree(query_slug)
|
119
|
+
return result unless result.nil?
|
120
|
+
end
|
121
|
+
|
122
|
+
nil
|
123
|
+
end
|
124
|
+
|
125
|
+
attr_reader :title
|
126
|
+
attr_reader :slug
|
127
|
+
attr_reader :last_modified
|
128
|
+
attr_reader :content
|
129
|
+
attr_reader :parent
|
130
|
+
end
|
131
|
+
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,112 +25,40 @@ 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
|
98
36
|
|
99
37
|
next if basename == "."
|
100
38
|
|
101
|
-
#
|
102
|
-
|
39
|
+
# Remove the path component "." from the start of the dirname
|
40
|
+
parent_slug = dirname.to_s.gsub(/\A\.\/?/, "")
|
41
|
+
|
42
|
+
if basename.to_s == "index.md"
|
43
|
+
slug = parent_slug.to_s.gsub(/\.md\z/, "")
|
44
|
+
else
|
103
45
|
title = basename.to_s.gsub(/\.md\z/, "")
|
104
|
-
|
105
|
-
content = MarkdownContent.new(path)
|
106
|
-
@index.add_note(title, parent_slug, path.mtime, content: content)
|
46
|
+
slug = Obsidian.build_slug(title, parent_slug)
|
107
47
|
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# TODO: capture links between notes
|
111
|
-
end
|
112
48
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
index.directories.sort_by(&:title).each do |note|
|
119
|
-
block.call(note, level)
|
120
|
-
walk_tree(note, level + 1, &block)
|
49
|
+
@index.add_page(
|
50
|
+
slug,
|
51
|
+
last_modified: path.mtime,
|
52
|
+
content: MarkdownContent.new(path)
|
53
|
+
)
|
121
54
|
end
|
122
55
|
|
123
|
-
|
124
|
-
block.call(note, level)
|
125
|
-
end
|
56
|
+
# TODO: capture links between notes
|
126
57
|
end
|
127
58
|
|
128
|
-
def
|
59
|
+
def pages
|
129
60
|
result = []
|
130
|
-
walk_tree
|
61
|
+
index.walk_tree { |page| result << page }
|
131
62
|
result
|
132
63
|
end
|
133
64
|
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.5.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:
|