notion_to_md 2.2.4 → 2.3.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: 1fb7426b97e6a8fdd9cd27cd1254939c5ccd3cb3455920ff35071d5c3429a4f1
4
- data.tar.gz: 523b5ff29517de54208a7506970115f11d087c9bb314e4b0a626650760ec89f3
3
+ metadata.gz: bba453442621ce806b36625a5febfd14a2f75d8051c49923d845435535e11574
4
+ data.tar.gz: bc8a6b8a8ba768728cbb88b88a5d4c4069aa173ac9275d5c6a9cb95a37827a43
5
5
  SHA512:
6
- metadata.gz: f9f181ba6bdbece59d9f8f5ad81c05afe86e942fcca913674ca42f04876a158091e8dc21b4eb392d9226c8839a8eea021c223a9d40c681093c9a16f153ec9793
7
- data.tar.gz: 4999b3781d9bf53357f8e5f8585beef8c4c3b501c6b35d360f2593b8596ae5b615979bb20246b2aee911342f5d97824a0c1da882c8d54a544fc14c1e0e61dbb1
6
+ metadata.gz: d7005a6bdfd26d3322a92ee49274ec8006549e90f850cc0d4fbc5907a0e0a9ecc526578b7c8964462563395e7980269a87d43d705fd6a2ccd8aaf26b240b6006
7
+ data.tar.gz: 9c52fe4feff24a090c19c6b736d1af5daaefc8dd5ce0fbe994acf6b41c2c1bbf8631846d71c59b5757219dc72583d50d6c316fc73bb81a453e8e3a02270407d6
data/README.md CHANGED
@@ -25,6 +25,14 @@ notion_converter = NotionToMd::Converter.new(page_id: 'b91d5...', token: 'secret
25
25
  md = notion_converter.convert
26
26
  ```
27
27
 
28
+ Since v2.3 you can also use the convenient `convert` method from the root module.
29
+
30
+ ```ruby
31
+ require 'notion_to_md'
32
+
33
+ md = NotionToMd.convert(page_id: 'b91d5...', token: 'secret_...')
34
+ ```
35
+
28
36
  If the secret token is provided as an environment variable —`NOTION_TOKEN`—, there's no need to pass it as an argument to the constructor.
29
37
 
30
38
  ```bash
@@ -36,6 +44,8 @@ require 'notion_to_md'
36
44
 
37
45
  notion_converter = NotionToMd::Converter.new(page_id: 'b91d5...')
38
46
  md = notion_converter.convert
47
+ # or
48
+ md = NotionToMd.convert(page_id: 'b91d5...')
39
49
  ```
40
50
 
41
51
  And that's all. The `md` is a string variable containing the notion page formatted in markdown.
@@ -49,7 +59,7 @@ Everything in a notion page body is a [block object](https://developers.notion.c
49
59
  * `heading_2`
50
60
  * `heading_3`
51
61
  * `bulleted_list_item`
52
- * `numbered_list_item` as `bulleted_list_item`
62
+ * `numbered_list_item` (supported since v2.3, in previous versions is displayed as `bulleted_list_item`)
53
63
  * `to_do`
54
64
  * `image`
55
65
  * `bookmark`
@@ -77,6 +87,8 @@ By default, the front matter section is not included to the document. To do so,
77
87
 
78
88
  ```ruby
79
89
  NotionToMd::Converter.new(page_id: 'b91d5...').convert(frontmatter: tue)
90
+ # or
91
+ NotionToMd.convert(page_id: 'b91d5...', frontmatter: true) # Since v2.3
80
92
  ```
81
93
 
82
94
  Default notion [properties](https://developers.notion.com/reference/page#all-pages) are page `id`, `title`, `created_time`, `last_edited_time`, `icon`, `archived` and `cover`.
@@ -9,6 +9,8 @@ module NotionToMd
9
9
 
10
10
  attr_reader :block, :children
11
11
 
12
+ def_delegators :block, :type
13
+
12
14
  # === Parameters:
13
15
  # block::
14
16
  # A {Notion::Messages::Message}[https://github.com/orbit-love/notion-ruby-client/blob/main/lib/notion/messages/message.rb] object.
@@ -32,14 +34,18 @@ module NotionToMd
32
34
  #
33
35
  def to_md(tab_width: 0)
34
36
  block_type = block.type.to_sym
35
- md = Types.send(block_type, block[block_type])
37
+ md = Types.send(block_type, block[block_type]) + newline
36
38
  md + build_nested_blocks(tab_width + 1)
37
39
  rescue NoMethodError
38
40
  Logger.info("Unsupported block type: #{block_type}")
39
41
  nil
40
42
  end
41
43
 
42
- private
44
+ protected
45
+
46
+ def newline
47
+ "\n\n"
48
+ end
43
49
 
44
50
  def build_nested_blocks(tab_width)
45
51
  mds = markdownify_children(tab_width).compact
@@ -54,7 +60,7 @@ module NotionToMd
54
60
 
55
61
  def indent_children(mds, tab_width)
56
62
  mds.map do |md|
57
- "\n\n#{"\t" * tab_width}#{md}"
63
+ "#{"\t" * tab_width}#{md}"
58
64
  end
59
65
  end
60
66
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class Builder
6
+ ##
7
+ # Array containing the block types allowed to have nested blocks (children).
8
+ BLOCKS_WITH_PERMITTED_CHILDREN = %i[
9
+ bulleted_list_item
10
+ numbered_list_item
11
+ paragraph
12
+ to_do
13
+ table
14
+ ].freeze
15
+
16
+ # === Parameters
17
+ # block::
18
+ # A {Notion::Messages::Message}[https://github.com/orbit-love/notion-ruby-client/blob/main/lib/notion/messages/message.rb] object.
19
+ #
20
+ # === Returns
21
+ # A boolean indicating if the blocked passed in
22
+ # is permitted to have children based on its type.
23
+ #
24
+ def self.permitted_children_for?(block:)
25
+ BLOCKS_WITH_PERMITTED_CHILDREN.include?(block.type.to_sym) && block.has_children
26
+ end
27
+
28
+ attr_reader :block_id, :fetch_blocks
29
+
30
+ # === Parameters
31
+ # block_id::
32
+ # A string representing a notion block id .
33
+ # fetch_blocks::
34
+ # A block that fetches the blocks from the Notion API.
35
+ #
36
+ # === Returns
37
+ # An array of NotionToMd::Blocks::Block.
38
+ #
39
+ def initialize(block_id:, &fetch_blocks)
40
+ @block_id = block_id
41
+ @fetch_blocks = fetch_blocks
42
+ end
43
+
44
+ # === Parameters
45
+ #
46
+ # === Returns
47
+ # An array of NotionToMd::Blocks::Block.
48
+ #
49
+ def build
50
+ notion_messages = fetch_blocks.call(block_id)
51
+ blocks = notion_messages.results.map do |block|
52
+ children = if Builder.permitted_children_for?(block: block)
53
+ Builder.new(block_id: block.id, &fetch_blocks).build
54
+ else
55
+ []
56
+ end
57
+ Factory.build(block: block, children: children)
58
+ end
59
+
60
+ Normalizer.normalize(blocks: blocks)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class BulletedListBlock < Block
6
+ def initialize(children: [])
7
+ @children = children
8
+ end
9
+
10
+ # === Parameters:
11
+ # tab_width::
12
+ # The number of tabs used to indent the block.
13
+ #
14
+ # === Returns
15
+ # The current block (and its children) converted to a markdown string.
16
+ #
17
+ def to_md(tab_width: 0)
18
+ if tab_width.zero?
19
+ build_nested_blocks(tab_width) + newline
20
+ else
21
+ build_nested_blocks(tab_width)
22
+ end
23
+ end
24
+
25
+ def type
26
+ 'bulleted_list'
27
+ end
28
+
29
+ def newline
30
+ "\n"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class BulletedListItemBlock < Block
6
+ def newline
7
+ "\n"
8
+ end
9
+
10
+ def indent_children(mds, _tab_width)
11
+ mds
12
+ end
13
+ end
14
+ end
15
+ end
@@ -7,6 +7,14 @@ module NotionToMd
7
7
  case block.type.to_sym
8
8
  when :table
9
9
  TableBlock.new(block: block, children: children)
10
+ when :table_row
11
+ TableRowBlock.new(block: block, children: children)
12
+ when :bulleted_list_item
13
+ BulletedListItemBlock.new(block: block, children: children)
14
+ when :numbered_list_item
15
+ NumberedListItemBlock.new(block: block, children: children)
16
+ when :to_do
17
+ ToDoListItemBlock.new(block: block, children: children)
10
18
  else
11
19
  Blocks::Block.new(block: block, children: children)
12
20
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class Normalizer
6
+ # === Parameters
7
+ # blocks::
8
+ # An array of NotionToMd::Blocks::Block.
9
+ #
10
+ def self.normalize(blocks:)
11
+ new(blocks: blocks).normalize
12
+ end
13
+
14
+ attr_reader :blocks, :normalized_blocks
15
+
16
+ def initialize(blocks:)
17
+ @blocks = blocks
18
+ @normalized_blocks = blocks
19
+ end
20
+
21
+ def normalize
22
+ normalize_for :bulleted_list_item
23
+ normalize_for :numbered_list_item
24
+ normalize_for :to_do
25
+ end
26
+
27
+ def normalize_for(type)
28
+ new_blocks = []
29
+
30
+ normalized_blocks.each do |block|
31
+ if block.type.to_sym == type
32
+ blocks_to_normalize << block
33
+ else
34
+ # When we encounter a block that is not of the provided type,
35
+ # we need to normalize the blocks we've collected so far.
36
+ # Then we add the current block to the new blocks array.
37
+ # This is because we want to keep the order of the blocks.
38
+ new_blocks << new_block_and_reset(type, blocks_to_normalize) unless blocks_to_normalize.empty?
39
+ new_blocks << block
40
+ end
41
+ end
42
+
43
+ # If the last block is of the provided type, it won't be added to the new blocks array.
44
+ # So, we need to normalize the blocks we've collected so far.
45
+ new_blocks << new_block_and_reset(type, blocks_to_normalize) unless blocks_to_normalize.empty?
46
+
47
+ normalized_blocks.replace(new_blocks)
48
+ end
49
+
50
+ private
51
+
52
+ def new_block_and_reset(type, children)
53
+ new_block = new_block_for(type, children)
54
+ reset_blocks_to_normalize
55
+ new_block
56
+ end
57
+
58
+ def blocks_to_normalize
59
+ @blocks_to_normalize ||= []
60
+ end
61
+
62
+ def reset_blocks_to_normalize
63
+ @blocks_to_normalize = []
64
+ end
65
+
66
+ def new_block_for(type, children)
67
+ case type
68
+ when :bulleted_list_item
69
+ BulletedListBlock.new(children: children)
70
+ when :numbered_list_item
71
+ NumberedListBlock.new(children: children)
72
+ when :to_do
73
+ ToDoListBlock.new(children: children)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class NumberedListBlock < BulletedListBlock
6
+ def type
7
+ 'numbered_list'
8
+ end
9
+
10
+ protected
11
+
12
+ def markdownify_children(tab_width)
13
+ children.map.with_index do |nested_block, index|
14
+ nested_block.to_md(tab_width: tab_width, index: index + 1)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './bulleted_list_item_block'
4
+
5
+ module NotionToMd
6
+ module Blocks
7
+ class NumberedListItemBlock < BulletedListItemBlock
8
+ # === Parameters:
9
+ # tab_width::
10
+ # The number of tabs used to indent the block.
11
+ # index::
12
+ # The index of the block in its parent's children array used to number the item.
13
+ #
14
+ # === Returns
15
+ # The current block (and its children) converted to a markdown string.
16
+ #
17
+ def to_md(tab_width: 0, index: nil)
18
+ md = Types.numbered_list_item(block[block.type.to_sym], index) + newline
19
+ md + build_nested_blocks(tab_width + 1)
20
+ rescue NoMethodError
21
+ Logger.info("Unsupported block type: #{block.type}")
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -10,17 +10,21 @@ module NotionToMd
10
10
  table_aligment = markdownify_aligment
11
11
  table_body = table[1..table.size]
12
12
 
13
- [table_header, table_aligment, table_body.join("\n")].compact.join("\n")
13
+ [table_header, table_aligment, table_body].compact.join + newline
14
14
  end
15
15
 
16
- private
16
+ protected
17
17
 
18
18
  def row_size
19
19
  @row_size ||= children.first.block.table_row.cells.size
20
20
  end
21
21
 
22
22
  def markdownify_aligment
23
- "|#{row_size.times.map { '---' }.join('|')}|" if block.table.has_column_header
23
+ "|#{row_size.times.map { '---' }.join('|')}|\n" if block.table.has_column_header
24
+ end
25
+
26
+ def newline
27
+ "\n"
24
28
  end
25
29
  end
26
30
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class TableRowBlock < Block
6
+ def newline
7
+ "\n"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ module Blocks
5
+ class ToDoListBlock < BulletedListBlock
6
+ def type
7
+ 'to_do_list'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './bulleted_list_item_block'
4
+
5
+ module NotionToMd
6
+ module Blocks
7
+ class ToDoListItemBlock < BulletedListItemBlock
8
+ def newline
9
+ "\n"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -36,9 +36,10 @@ module NotionToMd
36
36
  "- #{convert_text(block)}"
37
37
  end
38
38
 
39
- def numbered_list_item(block)
40
- Logger.info('numbered_list_item type not supported. Shown as bulleted_list_item.')
41
- bulleted_list_item(block)
39
+ def numbered_list_item(block, index = nil)
40
+ return bulleted_list_item(block) if index.nil?
41
+
42
+ "#{index}. #{convert_text(block)}"
42
43
  end
43
44
 
44
45
  def to_do(block)
@@ -1,51 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './blocks/block'
3
+ require_relative './blocks/builder'
4
+ require_relative './blocks/normalizer'
4
5
  require_relative './blocks/factory'
5
- require_relative './blocks/table_block'
6
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'
7
16
 
8
17
  module NotionToMd
9
18
  module Blocks
10
- ##
11
- # Array containing the block types allowed to have nested blocks (children).
12
- PERMITTED_CHILDREN = [
13
- Types.method(:bulleted_list_item).name,
14
- Types.method(:numbered_list_item).name,
15
- Types.method(:paragraph).name,
16
- Types.method(:to_do).name,
17
- :table
18
- ].freeze
19
-
20
- # === Parameters
21
- # block::
22
- # A {Notion::Messages::Message}[https://github.com/orbit-love/notion-ruby-client/blob/main/lib/notion/messages/message.rb] object.
23
- #
24
- # === Returns
25
- # A boolean indicating if the blocked passed in
26
- # is permitted to have children based on its type.
27
- #
28
- def self.permitted_children?(block:)
29
- PERMITTED_CHILDREN.include?(block.type.to_sym) && block.has_children
30
- end
31
-
32
19
  # === Parameters
33
20
  # block_id::
34
21
  # A string representing a notion block id .
22
+ # fetch_blocks::
23
+ # A block that fetches the blocks from the Notion API.
35
24
  #
36
25
  # === Returns
37
26
  # An array of NotionToMd::Blocks::Block.
38
27
  #
39
28
  def self.build(block_id:, &fetch_blocks)
40
- blocks = fetch_blocks.call(block_id)
41
- blocks.results.map do |block|
42
- children = if permitted_children?(block: block)
43
- build(block_id: block.id, &fetch_blocks)
44
- else
45
- []
46
- end
47
- Factory.build(block: block, children: children)
48
- end
29
+ Builder.new(block_id: block_id, &fetch_blocks).build
49
30
  end
50
31
  end
51
32
  end
@@ -45,7 +45,7 @@ module NotionToMd
45
45
  end
46
46
 
47
47
  def body
48
- @body ||= blocks.map(&:to_md).compact.join("\n\n")
48
+ @body ||= blocks.map(&:to_md).compact.join
49
49
  end
50
50
 
51
51
  def frontmatter
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NotionToMd
4
- VERSION = '2.2.4'
4
+ VERSION = '2.3.0'
5
5
  end
data/lib/notion_to_md.rb CHANGED
@@ -12,3 +12,20 @@ require_relative './notion_to_md/page'
12
12
  require_relative './notion_to_md/blocks'
13
13
  require_relative './notion_to_md/text'
14
14
  require_relative './notion_to_md/text_annotation'
15
+
16
+ module NotionToMd
17
+ # === Parameters
18
+ # page_id::
19
+ # A string representing the notion page id.
20
+ # token::
21
+ # The notion API secret token. The token can replaced by the environment variable NOTION_TOKEN.
22
+ # frontmatter::
23
+ # A boolean indicating whether to include the page properties as frontmatter.
24
+ #
25
+ # === Returns
26
+ # The string that represent the markdown document.
27
+ #
28
+ def self.convert(page_id:, token: nil, frontmatter: false)
29
+ Converter.new(page_id: page_id, token: token).convert(frontmatter: frontmatter)
30
+ end
31
+ 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.2.4
4
+ version: 2.3.0
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-10-22 00:00:00.000000000 Z
11
+ date: 2023-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
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'
111
125
  description: Notion Markdown Exporter in Ruby
112
126
  email: emoriarty81@gmail.com
113
127
  executables: []
@@ -118,8 +132,17 @@ files:
118
132
  - lib/notion_to_md.rb
119
133
  - lib/notion_to_md/blocks.rb
120
134
  - lib/notion_to_md/blocks/block.rb
135
+ - lib/notion_to_md/blocks/builder.rb
136
+ - lib/notion_to_md/blocks/bulleted_list_block.rb
137
+ - lib/notion_to_md/blocks/bulleted_list_item_block.rb
121
138
  - lib/notion_to_md/blocks/factory.rb
139
+ - lib/notion_to_md/blocks/normalizer.rb
140
+ - lib/notion_to_md/blocks/numbered_list_block.rb
141
+ - lib/notion_to_md/blocks/numbered_list_item_block.rb
122
142
  - lib/notion_to_md/blocks/table_block.rb
143
+ - lib/notion_to_md/blocks/table_row_block.rb
144
+ - lib/notion_to_md/blocks/to_do_list_block.rb
145
+ - lib/notion_to_md/blocks/to_do_list_item_block.rb
123
146
  - lib/notion_to_md/blocks/types.rb
124
147
  - lib/notion_to_md/converter.rb
125
148
  - lib/notion_to_md/logger.rb