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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 347ae1e2c1152ccae4ae2ad6a19c4477ae07a8dca77dcfd73d1e31da823cf5f6
4
- data.tar.gz: 4d2257fd34be583429baf260e3136ca6cdf6cdb4d66fbd592abc396e81a32dfa
3
+ metadata.gz: 041aa2c11a3972e24477c8601a880276de98b0108ef7628d9c30b623a1b14cb9
4
+ data.tar.gz: 1843bb05271c17a14658c956b7418472f19a5c460d95708f9f3e3fbe7d115d09
5
5
  SHA512:
6
- metadata.gz: 99cfb58610e351574d4ca83faa28b90117ae63a2d219151f005d87e365ee80704d44759c2610cd6bf9b8301fec0ffeb4245d112231fa9ace90202283ad094758
7
- data.tar.gz: a032cb2063e781db38c5ecafbe13f047d36e16cb5d71cf4c6b8558e40a6867d20b510a25aabbfe2201f5ce45a80a0ca7c729f1c5bd3e78f8030f01be812f61ac
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- obsidian-parser (0.3.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.3.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,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 = Index.new("", "")
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
- @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
+ )
107
51
  end
108
52
  end
109
53
 
110
54
  # TODO: capture links between notes
111
55
  end
112
56
 
113
- def notes
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(index) { |note, level| result << [note, level] }
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.3.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-27 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: