notion_to_md 2.4.1 → 2.5.1

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: d2c563b14bee2d927bd6f37cbbc18f59f52545bdc02bcb74e865657231a8d926
4
- data.tar.gz: e99d9b5e01e3c042e26818ea5eb8b7652701fca9d3a1d94f1c4b5cc41efdca8b
3
+ metadata.gz: bb8852f9013936bcb404e0779e4930e84831e2db4235b2f0bca8268a9e4edaf0
4
+ data.tar.gz: d3b9fe50d279cfc14abf20d7f8468d1534b043b73e00ee8123248556c03b43bf
5
5
  SHA512:
6
- metadata.gz: 0f523319dad22eeae015ab2e52bbd24f04d34acf9f654e4d92a9dcebed1e3a0255f08d8492af371347d2fc81d3acf24d0ef0e51d3a0708a2d143485056e783b9
7
- data.tar.gz: 8d01b8e6d570da473aa38eb32391f26841ec49e75809ea5733f94974a37a495d731200f3c24a2e3909579ee9ffc1d7d3fcb8b1523fb3070a9d87a711af2be8d9
6
+ metadata.gz: e49f3a1d040fb7381e8369c774cff8a31a510909c3e7813c4ba8016085075d613d01808a23a8075d9d9246335eadd1e08347225d287c702c07753df44f3f3af7
7
+ data.tar.gz: 52806a867c8d24e96e993c4a07741ff13322d14151e0285351c341237a772579f7084913764112f703435e963d598754e326e0c047c29f2d74f4ce6b58f68951
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # notion_to_md
2
2
  Notion Markdown Exporter in Ruby.
3
3
 
4
+ The generated document is compliant with the [GitHub Flavored Markdown specification](https://github.github.com/gfm/).
5
+
4
6
  ## Installation
5
7
  Use gem to install.
6
8
  ```bash
@@ -28,8 +30,6 @@ md = notion_converter.convert
28
30
  Since v2.3 you can also use the convenient `convert` method from the root module.
29
31
 
30
32
  ```ruby
31
- require 'notion_to_md'
32
-
33
33
  md = NotionToMd.convert(page_id: 'b91d5...', token: 'secret_...')
34
34
  ```
35
35
 
@@ -40,15 +40,27 @@ $ export NOTION_TOKEN=<secret_...>
40
40
  ```
41
41
 
42
42
  ```ruby
43
- require 'notion_to_md'
44
-
45
43
  notion_converter = NotionToMd::Converter.new(page_id: 'b91d5...')
46
44
  md = notion_converter.convert
47
45
  # or
48
46
  md = NotionToMd.convert(page_id: 'b91d5...')
49
47
  ```
50
48
 
51
- And that's all. The `md` is a string variable containing the notion page formatted in markdown.
49
+ And that's all. The `md` is a string variable containing the notion document formatted in markdown.
50
+
51
+ ### Callable
52
+
53
+ From version 2.5.0, the `call` method is also available. It's an alias for `convert`.
54
+
55
+ ```ruby
56
+ md = NotionToMd.call(page_id: 'b91d5...')
57
+ ```
58
+
59
+ The `call` method also supports the passing of a block.
60
+
61
+ ```ruby
62
+ NotionToMd.call(page_id: 'b91d5...') { puts _1 }
63
+ ```
52
64
 
53
65
  ## Blocks
54
66
 
@@ -69,6 +81,11 @@ Everything in a notion page body is a [block object](https://developers.notion.c
69
81
  * `table`
70
82
  * `embed`
71
83
  * `code`
84
+ * `link_preview`
85
+ * `file`
86
+ * `pdf`
87
+ * `video`
88
+ * `equation`
72
89
 
73
90
  ### Nested blocks
74
91
 
@@ -2,8 +2,11 @@
2
2
 
3
3
  require 'forwardable'
4
4
 
5
- module NotionToMd
5
+ class NotionToMd
6
6
  module Blocks
7
+ # === NotionToMd::Blocks::Block
8
+ #
9
+ # This class is responsible for converting Notion blocks to markdown.
7
10
  class Block
8
11
  extend Forwardable
9
12
 
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
+ # === NotionToMd::Blocks::Builder
6
+ #
7
+ # This class is responsible for building a tree of blocks from the Notion API.
5
8
  class Builder
6
9
  ##
7
10
  # Array containing the block types allowed to have nested blocks (children).
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class BulletedListBlock < Block
6
6
  def initialize(children: [])
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class BulletedListItemBlock < Block
6
6
  def newline
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
+ # === NotionToMd::Blocks::Factory
6
+ #
7
+ # Factory class to create block instances based on the block type
5
8
  class Factory
9
+ # rubocop:disable Metrics/MethodLength
6
10
  def self.build(block:, children: [])
7
11
  case block.type.to_sym
8
12
  when :table
@@ -19,6 +23,7 @@ module NotionToMd
19
23
  Blocks::Block.new(block: block, children: children)
20
24
  end
21
25
  end
26
+ # rubocop:enable Metrics/MethodLength
22
27
  end
23
28
  end
24
29
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
+ # === NotionToMd::Blocks::Normalizer
6
+ #
7
+ # This class is responsible for normalizing blocks of the same type
5
8
  class Normalizer
6
9
  # === Parameters
7
10
  # blocks::
@@ -27,27 +30,37 @@ module NotionToMd
27
30
  new_blocks = []
28
31
 
29
32
  normalized_blocks.each do |block|
30
- if block.type.to_sym == type
33
+ if block_of_type?(block, type)
31
34
  blocks_to_normalize << block
32
35
  else
33
36
  # When we encounter a block that is not of the provided type,
34
37
  # we need to normalize the blocks we've collected so far.
35
38
  # Then we add the current block to the new blocks array.
36
- # This is because we want to keep the order of the blocks.
37
- new_blocks << new_block_and_reset_blocks_to_normalize(type) unless blocks_to_normalize.empty?
39
+ # This is to keep the order of the blocks as they are in the original array.
40
+ flush_blocks_to_normalize_into(new_blocks, type)
38
41
  new_blocks << block
39
42
  end
40
43
  end
41
44
 
42
45
  # If the last block is the provided type, it won't be added to the new blocks array.
43
46
  # So, we need to normalize the blocks we've collected so far.
44
- new_blocks << new_block_and_reset_blocks_to_normalize(type) unless blocks_to_normalize.empty?
47
+ flush_blocks_to_normalize_into(new_blocks, type)
45
48
 
46
49
  normalized_blocks.replace(new_blocks)
47
50
  end
48
51
 
49
52
  private
50
53
 
54
+ def block_of_type?(block, type)
55
+ block.type.to_sym == type
56
+ end
57
+
58
+ def flush_blocks_to_normalize_into(new_blocks, type)
59
+ return if blocks_to_normalize.empty?
60
+
61
+ new_blocks << new_block_and_reset_blocks_to_normalize(type)
62
+ end
63
+
51
64
  def new_block_and_reset_blocks_to_normalize(type)
52
65
  new_block = new_block_for(type, blocks_to_normalize)
53
66
  @blocks_to_normalize = []
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class NumberedListBlock < BulletedListBlock
6
6
  def type
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './bulleted_list_item_block'
4
4
 
5
- module NotionToMd
5
+ class NotionToMd
6
6
  module Blocks
7
7
  class NumberedListItemBlock < BulletedListItemBlock
8
8
  # === Parameters:
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class TableBlock < Block
6
6
  def to_md
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class TableRowBlock < Block
6
6
  def newline
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class ToDoListBlock < BulletedListBlock
6
6
  def type
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './bulleted_list_item_block'
4
4
 
5
- module NotionToMd
5
+ class NotionToMd
6
6
  module Blocks
7
7
  class ToDoListItemBlock < BulletedListItemBlock
8
8
  def newline
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  module Blocks
5
5
  class Types
6
6
  class << self
@@ -89,6 +89,32 @@ module NotionToMd
89
89
  "|#{block[:cells].map(&method(:convert_table_row)).join('|')}|"
90
90
  end
91
91
 
92
+ def link_preview(block)
93
+ url = block[:url]
94
+
95
+ "[#{url}](#{url})"
96
+ end
97
+
98
+ def file(block)
99
+ type = block[:type].to_sym
100
+ url = block.dig(type, :url)
101
+ caption = convert_caption(block)
102
+
103
+ "[#{url}](#{url})\n\n#{caption}"
104
+ end
105
+
106
+ def pdf(block)
107
+ file(block)
108
+ end
109
+
110
+ def video(block)
111
+ file(block)
112
+ end
113
+
114
+ def equation(block)
115
+ "$$#{block['expression']}$$"
116
+ end
117
+
92
118
  private
93
119
 
94
120
  def convert_table_row(cells)
@@ -14,7 +14,10 @@ require_relative './blocks/numbered_list_item_block'
14
14
  require_relative './blocks/to_do_list_block'
15
15
  require_relative './blocks/to_do_list_item_block'
16
16
 
17
- module NotionToMd
17
+ class NotionToMd
18
+ # === NotionToMd::Blocks
19
+ #
20
+ # This module is responsible for building Notion blocks.
18
21
  module Blocks
19
22
  # === Parameters
20
23
  # block_id::
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
4
- ##
3
+ class NotionToMd
5
4
  # The Converter class allows to transform notion pages to markdown documents.
6
5
  # Just create a new Converter instance by providing the page_id:
7
6
  # page_converter = NotionToMd::Converter.new(page_id: '9dc17c9c9d2e469dbbf0f9648f3288d3')
8
7
  # Then, call for convert to obtain the markdown document:
9
8
  # page_converter.convert
10
-
11
9
  class Converter
12
- attr_reader :page_id
10
+ include Callee
11
+
12
+ attr_reader :page_id, :frontmatter
13
13
 
14
14
  # === Parameters
15
15
  # page_id::
@@ -20,9 +20,10 @@ module NotionToMd
20
20
  # === Returns
21
21
  # A NotionToMd::Converter object.
22
22
  #
23
- def initialize(page_id:, token: nil)
23
+ def initialize(page_id:, token: nil, frontmatter: false)
24
24
  @notion = Notion::Client.new(token: token || ENV['NOTION_TOKEN'])
25
25
  @page_id = page_id
26
+ @frontmatter = frontmatter
26
27
  end
27
28
 
28
29
  # === Parameters
@@ -40,6 +41,14 @@ module NotionToMd
40
41
  MD
41
42
  end
42
43
 
44
+ def call
45
+ md = convert frontmatter: frontmatter
46
+
47
+ yield md if block_given?
48
+
49
+ md
50
+ end
51
+
43
52
  private
44
53
 
45
54
  def page
@@ -1,4 +1,6 @@
1
- module NotionToMd
1
+ # frozen_string_literal: true
2
+
3
+ class NotionToMd
2
4
  module Helpers
3
5
  module YamlSanitizer
4
6
  # Escape the frontmatter value if it contains a colon or a dash followed by a space
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative './helpers/yaml_sanitizer'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  class Logger
5
5
  @logger = ::Logger.new($stdout)
6
6
 
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
+ # === NotionToMd::Page
5
+ #
6
+ # This class is responsible for representing a Notion page.
4
7
  class Page
5
8
  include Helpers::YamlSanitizer
6
9
 
@@ -81,17 +84,10 @@ module NotionToMd
81
84
  end
82
85
 
83
86
  def custom_props
84
- @custom_props ||= page.properties.each_with_object({}) do |prop, memo|
85
- name = prop.first
86
- value = prop.last # Notion::Messages::Message
87
- type = value.type
88
-
89
- next memo unless CustomProperty.respond_to?(type.to_sym)
90
-
91
- memo[name.parameterize.underscore] = CustomProperty.send(type, value)
92
- end.reject { |_k, v| v.presence.nil? }
87
+ @custom_props ||= filtered_custom_properties
93
88
  end
94
89
 
90
+ # rubocop:disable Metrics/MethodLength
95
91
  def default_props
96
92
  @default_props ||= {
97
93
  'id' => id,
@@ -107,10 +103,35 @@ module NotionToMd
107
103
  'last_edited_by_id' => last_edited_by_id
108
104
  }
109
105
  end
106
+ # rubocop:enable Metrics/MethodLength
110
107
 
111
108
  # This class is kept for retro compatibility reasons.
112
109
  # Use instead the PageProperty class.
113
110
  class CustomProperty < PageProperty
114
111
  end
112
+
113
+ private
114
+
115
+ def filtered_custom_properties
116
+ build_custom_properties.reject { |_k, v| v.presence.nil? }
117
+ end
118
+
119
+ def build_custom_properties
120
+ page.properties.each_with_object({}) do |(name, value), memo|
121
+ type = value.type
122
+ next unless valid_custom_property_type?(type)
123
+
124
+ key = name.parameterize.underscore
125
+ memo[key] = build_custom_property(type, value)
126
+ end
127
+ end
128
+
129
+ def valid_custom_property_type?(type)
130
+ CustomProperty.respond_to?(type.to_sym)
131
+ end
132
+
133
+ def build_custom_property(type, value)
134
+ CustomProperty.send(type, value)
135
+ end
115
136
  end
116
137
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  class PageProperty
5
5
  class << self
6
6
  include Helpers::YamlSanitizer
@@ -66,7 +66,7 @@ module NotionToMd
66
66
  end
67
67
 
68
68
  def checkbox(prop)
69
- prop[:checkbox].nil? ? nil : prop[:checkbox].to_s
69
+ prop[:checkbox]&.to_s
70
70
  rescue NoMethodError
71
71
  nil
72
72
  end
@@ -1,4 +1,6 @@
1
- module NotionToMd
1
+ # frozen_string_literal: true
2
+
3
+ class NotionToMd
2
4
  class Text
3
5
  class << self
4
6
  def text(text)
@@ -6,7 +8,7 @@ module NotionToMd
6
8
  end
7
9
 
8
10
  def equation(text)
9
- "$$ #{text[:plain_text]} $$"
11
+ "$`#{text[:plain_text]}`$"
10
12
  end
11
13
  end
12
14
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
3
+ class NotionToMd
4
4
  ##
5
5
  # Append the text type:
6
6
  # * italic: boolean,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NotionToMd
4
- VERSION = '2.4.1'
3
+ class NotionToMd
4
+ VERSION = '2.5.1'
5
5
  end
data/lib/notion_to_md.rb CHANGED
@@ -4,6 +4,7 @@ require 'notion'
4
4
  require 'logger'
5
5
  require 'active_support/inflector'
6
6
  require 'active_support/core_ext/object/blank'
7
+ require 'callee'
7
8
  require_relative './notion_to_md/helpers'
8
9
  require_relative './notion_to_md/version'
9
10
  require_relative './notion_to_md/converter'
@@ -14,7 +15,18 @@ require_relative './notion_to_md/blocks'
14
15
  require_relative './notion_to_md/text'
15
16
  require_relative './notion_to_md/text_annotation'
16
17
 
17
- module NotionToMd
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
23
+
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
29
+
18
30
  # === Parameters
19
31
  # page_id::
20
32
  # A string representing the notion page id.
@@ -29,4 +41,12 @@ module NotionToMd
29
41
  def self.convert(page_id:, token: nil, frontmatter: false)
30
42
  Converter.new(page_id: page_id, token: token).convert(frontmatter: frontmatter)
31
43
  end
44
+
45
+ def call
46
+ md = self.class.convert(page_id: page_id, token: token, frontmatter: frontmatter)
47
+
48
+ yield md if block_given?
49
+
50
+ md
51
+ end
32
52
  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.4.1
4
+ version: 2.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Enrique Arias
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-20 00:00:00.000000000 Z
11
+ date: 2025-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,6 +24,20 @@ 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
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: notion-ruby-client
29
43
  requirement: !ruby/object:Gem::Requirement