notion_to_md 2.5.1 → 3.0.0.beta2

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.
data/lib/notion_to_md.rb CHANGED
@@ -4,49 +4,76 @@ require 'notion'
4
4
  require 'logger'
5
5
  require 'active_support/inflector'
6
6
  require 'active_support/core_ext/object/blank'
7
- require 'callee'
8
- require_relative './notion_to_md/helpers'
9
- require_relative './notion_to_md/version'
10
- require_relative './notion_to_md/converter'
11
- require_relative './notion_to_md/logger'
12
- require_relative './notion_to_md/page_property'
13
- require_relative './notion_to_md/page'
14
- require_relative './notion_to_md/blocks'
15
- require_relative './notion_to_md/text'
16
- require_relative './notion_to_md/text_annotation'
17
-
18
- # The NotionToMd class allows to transform notion pages to markdown documents.
19
- class NotionToMd
20
- include Callee
21
-
22
- attr_reader :page_id, :token, :frontmatter
7
+ require 'zeitwerk'
23
8
 
24
- def initialize(page_id:, token: nil, frontmatter: false)
25
- @page_id = page_id
26
- @token = token || ENV['NOTION_TOKEN']
27
- @frontmatter = frontmatter
28
- end
9
+ # Load the NotionToMd classes using Zeitwerk
10
+ loader = Zeitwerk::Loader.for_gem
11
+ loader.setup
29
12
 
30
- # === Parameters
31
- # page_id::
32
- # A string representing the notion page id.
33
- # token::
34
- # The notion API secret token. The token can replaced by the environment variable NOTION_TOKEN.
35
- # frontmatter::
36
- # A boolean indicating whether to include the page properties as frontmatter.
37
- #
38
- # === Returns
39
- # The string that represent the markdown document.
13
+ ##
14
+ # The {NotionToMd} class is the main entry point for converting
15
+ # Notion pages and databases into Markdown documents.
16
+ #
17
+ # It provides a single class method {.call} (aliased as {.convert})
18
+ # that accepts a Notion resource type (`:page` or `:database`), an ID,
19
+ # and options to control conversion.
20
+ #
21
+ # @example Convert a single Notion page to Markdown
22
+ # markdown = NotionToMd.call(:page, id: "xxxx-xxxx", token: "secret_token")
23
+ #
24
+ # @example Convert a Notion database to Markdown and yield the result
25
+ # NotionToMd.convert(:database, id: "xxxx-xxxx").each_with_index do |md, index|
26
+ # File.write("output_#{index}.md", md)
27
+ # end
28
+ #
29
+ class NotionToMd
30
+ ##
31
+ # Supported resource types for conversion.
40
32
  #
41
- def self.convert(page_id:, token: nil, frontmatter: false)
42
- Converter.new(page_id: page_id, token: token).convert(frontmatter: frontmatter)
43
- end
33
+ # @return [Hash{Symbol => Symbol}] mapping of friendly keys to types
34
+ TYPES = { database: :database, page: :page }.freeze
35
+
36
+ class << self
37
+ ##
38
+ # Convert a Notion resource (page or database) to Markdown.
39
+ #
40
+ # @param type [Symbol] the type of Notion resource (`:page` or `:database`).
41
+ # @param id [String] the Notion page or database ID.
42
+ # @param token [String, nil] the Notion API token.
43
+ # If omitted, defaults to `ENV['NOTION_TOKEN']`.
44
+ # @param frontmatter [Boolean] whether to include YAML frontmatter
45
+ # in the generated Markdown.
46
+ #
47
+ # @yield [md] optional block to handle the generated Markdown.
48
+ # @yieldparam md [String] the Markdown output.
49
+ #
50
+ # @return [String] the Markdown representation of the Notion resource.
51
+ #
52
+ # @raise [RuntimeError] if the given +type+ is not supported.
53
+ #
54
+ def call(type, id:, token: nil, frontmatter: false)
55
+ raise "#{type} is not supported. Use :database or :page" unless TYPES.values.include?(type)
56
+
57
+ notion_client = Notion::Client.new(token: token || ENV.fetch('NOTION_TOKEN', nil))
58
+ md = case type
59
+ when TYPES[:database]
60
+ Database.call(id: id, notion_client: notion_client, frontmatter: frontmatter).to_md
61
+ when TYPES[:page]
62
+ Page.call(id: id, notion_client: notion_client, frontmatter: frontmatter).to_md
63
+ end
44
64
 
45
- def call
46
- md = self.class.convert(page_id: page_id, token: token, frontmatter: frontmatter)
65
+ yield md if block_given?
47
66
 
48
- yield md if block_given?
67
+ md
68
+ end
49
69
 
50
- md
70
+ ##
71
+ # Alias for {.call}.
72
+ #
73
+ # @see .call
74
+ #
75
+ def convert(type, id:, token: nil, frontmatter: false, &block)
76
+ call(type, id: id, token: token, frontmatter: frontmatter, &block)
77
+ end
51
78
  end
52
79
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: notion_to_md
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.1
4
+ version: 3.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Enrique Arias
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-19 00:00:00.000000000 Z
11
+ date: 2025-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7'
27
- - !ruby/object:Gem::Dependency
28
- name: callee
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.3.6
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.3.6
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: notion-ruby-client
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -53,89 +39,19 @@ dependencies:
53
39
  - !ruby/object:Gem::Version
54
40
  version: '1'
55
41
  - !ruby/object:Gem::Dependency
56
- name: hashie
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rspec
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop
42
+ name: zeitwerk
85
43
  requirement: !ruby/object:Gem::Requirement
86
44
  requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop-rspec
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: simplecov
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: vcr
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
45
+ - - "~>"
130
46
  - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
47
+ version: '2.6'
48
+ type: :runtime
133
49
  prerelease: false
134
50
  version_requirements: !ruby/object:Gem::Requirement
135
51
  requirements:
136
- - - ">="
52
+ - - "~>"
137
53
  - !ruby/object:Gem::Version
138
- version: '0'
54
+ version: '2.6'
139
55
  description: Notion Markdown Exporter in Ruby
140
56
  email: emoriarty81@gmail.com
141
57
  executables: []
@@ -144,33 +60,38 @@ extra_rdoc_files: []
144
60
  files:
145
61
  - README.md
146
62
  - lib/notion_to_md.rb
147
- - lib/notion_to_md/blocks.rb
148
63
  - lib/notion_to_md/blocks/block.rb
149
- - lib/notion_to_md/blocks/builder.rb
150
64
  - lib/notion_to_md/blocks/bulleted_list_block.rb
151
65
  - lib/notion_to_md/blocks/bulleted_list_item_block.rb
152
66
  - lib/notion_to_md/blocks/factory.rb
153
67
  - lib/notion_to_md/blocks/normalizer.rb
154
68
  - lib/notion_to_md/blocks/numbered_list_block.rb
155
69
  - lib/notion_to_md/blocks/numbered_list_item_block.rb
70
+ - lib/notion_to_md/blocks/renderer.rb
156
71
  - lib/notion_to_md/blocks/table_block.rb
157
72
  - lib/notion_to_md/blocks/table_row_block.rb
158
73
  - lib/notion_to_md/blocks/to_do_list_block.rb
159
74
  - lib/notion_to_md/blocks/to_do_list_item_block.rb
160
- - lib/notion_to_md/blocks/types.rb
161
- - lib/notion_to_md/converter.rb
162
- - lib/notion_to_md/helpers.rb
163
- - lib/notion_to_md/helpers/yaml_sanitizer.rb
75
+ - lib/notion_to_md/database.rb
76
+ - lib/notion_to_md/database/builder.rb
164
77
  - lib/notion_to_md/logger.rb
78
+ - lib/notion_to_md/metadata_type.rb
165
79
  - lib/notion_to_md/page.rb
166
- - lib/notion_to_md/page_property.rb
80
+ - lib/notion_to_md/page/builder.rb
81
+ - lib/notion_to_md/support/frontmatter.rb
82
+ - lib/notion_to_md/support/metadata_properties.rb
83
+ - lib/notion_to_md/support/pagination.rb
84
+ - lib/notion_to_md/support/yaml_sanitizer.rb
167
85
  - lib/notion_to_md/text.rb
168
86
  - lib/notion_to_md/text_annotation.rb
169
87
  - lib/notion_to_md/version.rb
170
88
  homepage: https://github.com/emoriarty/notion_to_md
171
89
  licenses:
172
90
  - MIT
173
- metadata: {}
91
+ metadata:
92
+ rubygems_mfa_required: 'true'
93
+ status: beta
94
+ source_code_uri: https://github.com/emoriarty/notion_to_md
174
95
  post_install_message:
175
96
  rdoc_options: []
176
97
  require_paths:
@@ -179,12 +100,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
100
  requirements:
180
101
  - - ">="
181
102
  - !ruby/object:Gem::Version
182
- version: '0'
103
+ version: 2.7.0
183
104
  required_rubygems_version: !ruby/object:Gem::Requirement
184
105
  requirements:
185
- - - ">="
106
+ - - ">"
186
107
  - !ruby/object:Gem::Version
187
- version: '0'
108
+ version: 1.3.1
188
109
  requirements: []
189
110
  rubygems_version: 3.1.6
190
111
  signing_key:
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class NotionToMd
4
- module Blocks
5
- # === NotionToMd::Blocks::Builder
6
- #
7
- # This class is responsible for building a tree of blocks from the Notion API.
8
- class Builder
9
- ##
10
- # Array containing the block types allowed to have nested blocks (children).
11
- BLOCKS_WITH_PERMITTED_CHILDREN = %i[
12
- bulleted_list_item
13
- numbered_list_item
14
- paragraph
15
- to_do
16
- table
17
- ].freeze
18
-
19
- # === Parameters
20
- # block::
21
- # A {Notion::Messages::Message}[https://github.com/orbit-love/notion-ruby-client/blob/main/lib/notion/messages/message.rb] object.
22
- #
23
- # === Returns
24
- # A boolean indicating if the blocked passed in
25
- # is permitted to have children based on its type.
26
- #
27
- def self.permitted_children_for?(block:)
28
- BLOCKS_WITH_PERMITTED_CHILDREN.include?(block.type.to_sym) && block.has_children
29
- end
30
-
31
- attr_reader :block_id, :fetch_blocks
32
-
33
- # === Parameters
34
- # block_id::
35
- # A string representing a notion block id .
36
- # fetch_blocks::
37
- # A block that fetches the blocks from the Notion API.
38
- #
39
- # === Returns
40
- # An array of NotionToMd::Blocks::Block.
41
- #
42
- def initialize(block_id:, &fetch_blocks)
43
- @block_id = block_id
44
- @fetch_blocks = fetch_blocks
45
- end
46
-
47
- # === Parameters
48
- #
49
- # === Returns
50
- # An array of NotionToMd::Blocks::Block.
51
- #
52
- def build
53
- notion_messages = fetch_blocks.call(block_id)
54
- blocks = notion_messages.results.map do |block|
55
- children = if Builder.permitted_children_for?(block: block)
56
- Builder.new(block_id: block.id, &fetch_blocks).build
57
- else
58
- []
59
- end
60
- Factory.build(block: block, children: children)
61
- end
62
-
63
- Normalizer.normalize(blocks: blocks)
64
- end
65
- end
66
- end
67
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './blocks/builder'
4
- require_relative './blocks/normalizer'
5
- require_relative './blocks/factory'
6
- require_relative './blocks/types'
7
- require_relative './blocks/block'
8
- require_relative './blocks/table_block'
9
- require_relative './blocks/table_row_block'
10
- require_relative './blocks/bulleted_list_block'
11
- require_relative './blocks/bulleted_list_item_block'
12
- require_relative './blocks/numbered_list_block'
13
- require_relative './blocks/numbered_list_item_block'
14
- require_relative './blocks/to_do_list_block'
15
- require_relative './blocks/to_do_list_item_block'
16
-
17
- class NotionToMd
18
- # === NotionToMd::Blocks
19
- #
20
- # This module is responsible for building Notion blocks.
21
- module Blocks
22
- # === Parameters
23
- # block_id::
24
- # A string representing a notion block id .
25
- # fetch_blocks::
26
- # A block that fetches the blocks from the Notion API.
27
- #
28
- # === Returns
29
- # An array of NotionToMd::Blocks::Block.
30
- #
31
- def self.build(block_id:, &fetch_blocks)
32
- Builder.new(block_id: block_id, &fetch_blocks).build
33
- end
34
- end
35
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class NotionToMd
4
- # The Converter class allows to transform notion pages to markdown documents.
5
- # Just create a new Converter instance by providing the page_id:
6
- # page_converter = NotionToMd::Converter.new(page_id: '9dc17c9c9d2e469dbbf0f9648f3288d3')
7
- # Then, call for convert to obtain the markdown document:
8
- # page_converter.convert
9
- class Converter
10
- include Callee
11
-
12
- attr_reader :page_id, :frontmatter
13
-
14
- # === Parameters
15
- # page_id::
16
- # A string representing the notion page id.
17
- # token::
18
- # The notion API secret token. The token can replaced by the environment variable NOTION_TOKEN.
19
- #
20
- # === Returns
21
- # A NotionToMd::Converter object.
22
- #
23
- def initialize(page_id:, token: nil, frontmatter: false)
24
- @notion = Notion::Client.new(token: token || ENV['NOTION_TOKEN'])
25
- @page_id = page_id
26
- @frontmatter = frontmatter
27
- end
28
-
29
- # === Parameters
30
- # frontmatter::
31
- # A boolean value that indicates whether the front matter block is included in the markdown document.
32
- #
33
- # === Returns
34
- # The string that represent the markdown document.
35
- #
36
- def convert(frontmatter: false)
37
- md_page = Page.new(page: page, blocks: page_blocks)
38
- <<~MD
39
- #{md_page.frontmatter if frontmatter}
40
- #{md_page.body}
41
- MD
42
- end
43
-
44
- def call
45
- md = convert frontmatter: frontmatter
46
-
47
- yield md if block_given?
48
-
49
- md
50
- end
51
-
52
- private
53
-
54
- def page
55
- @page ||= @notion.page(page_id: page_id)
56
- end
57
-
58
- def page_blocks
59
- @page_blocks ||= build_blocks(block_id: page_id)
60
- end
61
-
62
- def build_blocks(block_id:)
63
- Blocks.build(block_id: block_id) do |nested_block_id|
64
- fetch_blocks(block_id: nested_block_id)
65
- end
66
- end
67
-
68
- def fetch_blocks(block_id:)
69
- @notion.block_children(block_id: block_id)
70
- end
71
- end
72
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class NotionToMd
4
- module Helpers
5
- module YamlSanitizer
6
- # Escape the frontmatter value if it contains a colon or a dash followed by a space
7
- # @param value [String] the value to escape
8
- # @return [String] the escaped value
9
- def escape_frontmatter_value(value)
10
- if value.match?(/: |-\s/)
11
- # Escape the double quotes inside the string
12
- "\"#{value.gsub('"', '\"')}\""
13
- else
14
- value
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative './helpers/yaml_sanitizer'
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class NotionToMd
4
- class PageProperty
5
- class << self
6
- include Helpers::YamlSanitizer
7
-
8
- def file(prop)
9
- prop.dig(:file, :url)
10
- rescue NoMethodError
11
- nil
12
- end
13
-
14
- def external(prop)
15
- prop.dig(:external, :url)
16
- rescue NoMethodError
17
- nil
18
- end
19
-
20
- def emoji(prop)
21
- prop[:emoji]
22
- rescue NoMethodError
23
- nil
24
- end
25
-
26
- def multi_select(prop)
27
- prop[:multi_select].map { |sel| sel[:name] }
28
- rescue NoMethodError
29
- nil
30
- end
31
-
32
- def select(prop)
33
- escape_frontmatter_value(prop.dig(:select, :name))
34
- rescue NoMethodError
35
- nil
36
- end
37
-
38
- def people(prop)
39
- prop[:people].map { |sel| sel[:name] }
40
- rescue NoMethodError
41
- nil
42
- end
43
-
44
- def files(prop)
45
- prop[:files].map { |f| file(f) || external(f) }
46
- rescue NoMethodError
47
- nil
48
- end
49
-
50
- def phone_number(prop)
51
- prop[:phone_number]
52
- rescue NoMethodError
53
- nil
54
- end
55
-
56
- def number(prop)
57
- prop[:number]
58
- rescue NoMethodError
59
- nil
60
- end
61
-
62
- def email(prop)
63
- prop[:email]
64
- rescue NoMethodError
65
- nil
66
- end
67
-
68
- def checkbox(prop)
69
- prop[:checkbox]&.to_s
70
- rescue NoMethodError
71
- nil
72
- end
73
-
74
- # date type properties not supported:
75
- # - end
76
- # - time_zone
77
- def date(prop)
78
- date = prop.dig(:date, :start)
79
-
80
- case date
81
- when Date
82
- date.to_time
83
- when String
84
- Time.parse(date)
85
- else
86
- date # Time or nil
87
- end
88
- rescue NoMethodError
89
- nil
90
- end
91
-
92
- def url(prop)
93
- prop[:url]
94
- rescue NoMethodError
95
- nil
96
- end
97
-
98
- def rich_text(prop)
99
- text = prop[:rich_text].map { |text| text[:plain_text] }.join
100
- text.blank? ? nil : escape_frontmatter_value(text)
101
- rescue NoMethodError
102
- nil
103
- end
104
- end
105
- end
106
- end