obsidian-parser 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 347ae1e2c1152ccae4ae2ad6a19c4477ae07a8dca77dcfd73d1e31da823cf5f6
4
- data.tar.gz: 4d2257fd34be583429baf260e3136ca6cdf6cdb4d66fbd592abc396e81a32dfa
3
+ metadata.gz: 10a282bce512c7d763d89eed0aedff250e6721cad6e0849af3821dd897e8d819
4
+ data.tar.gz: d3271c30ab0f7094b6273b0e8f86b33ec2280729fe757958b61459cc0bca5447
5
5
  SHA512:
6
- metadata.gz: 99cfb58610e351574d4ca83faa28b90117ae63a2d219151f005d87e365ee80704d44759c2610cd6bf9b8301fec0ffeb4245d112231fa9ace90202283ad094758
7
- data.tar.gz: a032cb2063e781db38c5ecafbe13f047d36e16cb5d71cf4c6b8558e40a6867d20b510a25aabbfe2201f5ce45a80a0ca7c729f1c5bd3e78f8030f01be812f61ac
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- obsidian-parser (0.3.0)
4
+ obsidian-parser (0.5.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,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
@@ -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.5.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,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 = 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
98
36
 
99
37
  next if basename == "."
100
38
 
101
- # TODO: handle index.md files
102
- if basename != "index.md"
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
- parent_slug = dirname.to_s.gsub(/\A\.\/?/, "")
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
- 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)
49
+ @index.add_page(
50
+ slug,
51
+ last_modified: path.mtime,
52
+ content: MarkdownContent.new(path)
53
+ )
121
54
  end
122
55
 
123
- index.notes.sort_by(&:title).each do |note|
124
- block.call(note, level)
125
- end
56
+ # TODO: capture links between notes
126
57
  end
127
58
 
128
- def table_of_contents
59
+ def pages
129
60
  result = []
130
- walk_tree(index) { |note, level| result << [note, level] }
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.3.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-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: