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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54b2b8b2c841d2c15bf739cc6e2c20a6dea2e29261719104ad1903adbd1a3337
4
- data.tar.gz: c51dd21ba256fd8c5f03eb646aee741d45167f0cad79eb7ba715c4c776335205
3
+ metadata.gz: 041aa2c11a3972e24477c8601a880276de98b0108ef7628d9c30b623a1b14cb9
4
+ data.tar.gz: 1843bb05271c17a14658c956b7418472f19a5c460d95708f9f3e3fbe7d115d09
5
5
  SHA512:
6
- metadata.gz: 633e3ccd369bc760f316d977aedf485cef4d76bbe28af3f29079a92d5bcb2b92a6a2d529ea0dcf60c09a85bac2c598838ea70ff51b50b60e372ae33652523cae
7
- data.tar.gz: 8cac449aa204d036b5e72444afa1235e9dab3cb6b46cd1992d104d6a165775a70171f921c3b3a4064752c5daf08075c20d415a800bc1b6ca6f9c9fcd7c6037b1
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- obsidian-parser (0.2.0)
4
+ obsidian-parser (0.4.0)
5
5
  kramdown (~> 2.4)
6
6
  kramdown-parser-gfm (~> 1.1)
7
7
 
@@ -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::MarkdownConverter.new(document)
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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Obsidian
4
- class MarkdownConverter
4
+ class ParsedMarkdownDocument
5
5
  def initialize(document)
6
6
  @document = document
7
7
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Obsidian
4
4
  class Parser
5
- VERSION = "0.2.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "parser/version"
4
- require_relative "parser/markdown_converter"
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 = Index.new("", "")
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
- @index.add_note(title, parent_slug, path.mtime, content: content)
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 notes
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(index) { |note, level| result << [note, level] }
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.2.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-24 00:00:00.000000000 Z
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: