notion_to_md 0.0.3 → 0.2.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: 5e11590c90c4539c151d8c290f1175a76c331bcd4027832c055158ae11b3e396
4
- data.tar.gz: 410e1b78ddb8f287b9b4a92b7e583910cccf6474d612b42c4a4933dad0d154b5
3
+ metadata.gz: ce6b39ba9cc39e1428bc8a654532d16b9ea99054e3fbd96cf30edd56cbed28a8
4
+ data.tar.gz: 6e38131568bb150a9ca7904f383fd8ade5721ccc5affe2949672b597b948ffe3
5
5
  SHA512:
6
- metadata.gz: deeb77d21a3be57cb713b1c765b008b502d8c100bb103be99a88b50311c3895d3557f8c2909db1d8334616e223862fd6bf6e0b21c233237ab99698f7d008c466
7
- data.tar.gz: 594bc21094683131274a3eb254ea9efad95a6d98839d219862bfd38b867aba43fb833ef3c9adb47dce55aa9a089ee16d806e049a356fedb1b5aee177746be8a1
6
+ metadata.gz: 3ccfa65a6877fd1bcdc9a36f72094cc8374934a2713abfc10441b23348b919818539c286469afad699f69594dc854a12f884299d2c335ff6cd9bc17d920bbcb6
7
+ data.tar.gz: fd638c9a53e8c6f6cf644e2e471fc7ecf8150bb1ef371d9f6f41215e4d1bd953d1929113c25dc703dd66647165adf84bbf9f7bddcb6494d8b0bcea5bd06f2741
data/README.md CHANGED
@@ -40,7 +40,60 @@ md = notion_converter.convert
40
40
 
41
41
  And that's all. The `md` is a string variable containing the notion page formatted in markdown.
42
42
 
43
+
44
+ ### Front matter
45
+
46
+ From version 0.2.0, notion_to_md supports front matter in markdown files.
47
+
48
+ By default, the front matter section is not included to the document. To do so, provide the `:frontmatter` option set to `true` to `convert` method.
49
+
50
+ ```ruby
51
+ NotionToMd::Converter.new(page_id: 'b91d5...').convert(frontmatter: tue)
52
+ ```
53
+
54
+ Default notion properties are page `id`, `title`, `created_time`, `last_edited_time`, `icon`, `archived` and `cover`.
55
+
56
+ ```yml
57
+ ---
58
+ id: e42383cd-4975-4897-b967-ce453760499f
59
+ title: An amazing post
60
+ cover: https://img.bank.sh/an_image.jpg
61
+ created_time: 2022-01-23T12:31:00.000Z
62
+ last_edited_time: 2022-01-23T12:31:00.000Z
63
+ icon: 💥
64
+ archived: false
65
+ ---
66
+ ```
67
+
68
+ In addition to default properties, custom properties are also supported.
69
+ Custom properties name is [parameterized](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize) and [underscorized](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore) before added to front matter.
70
+ For example, two properties named `Multiple Options` and `Tags` will be transformed to `multiple_options` and `tags`, respectively.
71
+
72
+ ```yml
73
+ ---
74
+ tags: tag1, tag2, tag3
75
+ multiple_options: option1, option2
76
+ ---
77
+ ```
78
+
79
+ The supported property types are:
80
+
81
+ * `number`
82
+ * `select`
83
+ * `multi_select`
84
+ * `date`
85
+ * `people`
86
+ * `files`
87
+ * `checkbox`
88
+ * `url`
89
+ * `email`
90
+ * `phone_number`
91
+
92
+ `rich_text` as advanced types like `formula`, `relation` and `rollup` are not supported.
93
+
94
+ Check notion documentation about [property values](https://developers.notion.com/reference/property-value-object#all-property-values) to know more.
95
+
43
96
  ## Test
44
97
  ```bash
45
- $ ruby -Ilib:test test/notion_to_md/converter_spec.rb
98
+ rspec
46
99
  ```
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ class Block
5
+ class << self
6
+ def paragraph(block)
7
+ convert_text(block)
8
+ end
9
+
10
+ def heading_1(block)
11
+ "# #{convert_text(block)}"
12
+ end
13
+
14
+ def heading_2(block)
15
+ "## #{convert_text(block)}"
16
+ end
17
+
18
+ def heading_3(block)
19
+ "### #{convert_text(block)}"
20
+ end
21
+
22
+ def callout(block)
23
+ icon = get_icon(block[:icon])
24
+ text = convert_text(block)
25
+ "#{icon} #{text}"
26
+ end
27
+
28
+ def quote(block)
29
+ "> #{convert_text(block)}"
30
+ end
31
+
32
+ def bulleted_list_item(block)
33
+ "- #{convert_text(block)}"
34
+ end
35
+
36
+ # TODO: numbered_list_item
37
+ def numbered_list_item(block)
38
+ Logger.info('numbered_list_item type not supported. Shown as bulleted_list_item.')
39
+ bulleted_list_item(block)
40
+ end
41
+
42
+ def to_do(block)
43
+ checked = block[:checked]
44
+ text = convert_text(block)
45
+
46
+ "- #{checked ? '[x]' : '[ ]'} #{text}"
47
+ end
48
+
49
+ def code(block)
50
+ language = block[:language]
51
+ text = convert_text(block)
52
+
53
+ "```#{language}\n\t#{text}\n```"
54
+ end
55
+
56
+ def embed(block)
57
+ url = block[:url]
58
+
59
+ "[#{url}](#{url})"
60
+ end
61
+
62
+ def image(block)
63
+ type = block[:type].to_sym
64
+ url = block.dig(type, :url)
65
+ caption = convert_caption(block)
66
+
67
+ "![](#{url})\n\n#{caption}"
68
+ end
69
+
70
+ def bookmark(block)
71
+ url = block[:url]
72
+ "[#{url}](#{url})"
73
+ end
74
+
75
+ def divider(_block)
76
+ '---'
77
+ end
78
+
79
+ def blank
80
+ '<br />'
81
+ end
82
+
83
+ def convert_text(block)
84
+ block[:text].map do |text|
85
+ content = Text.send(text[:type], text)
86
+ enrich_text_content(text, content)
87
+ end.join
88
+ end
89
+
90
+ def convert_caption(block)
91
+ convert_text(text: block[:caption])
92
+ end
93
+
94
+ def get_icon(block)
95
+ type = block[:type].to_sym
96
+ block[type]
97
+ end
98
+
99
+ def enrich_text_content(text, content)
100
+ enriched_content = add_link(text, content)
101
+ add_annotations(text, enriched_content)
102
+ end
103
+
104
+ def add_link(text, content)
105
+ href = text[:href]
106
+ return content if href.nil?
107
+
108
+ "[#{content}](#{href})"
109
+ end
110
+
111
+ def add_annotations(text, content)
112
+ annotations = text[:annotations].select { |_key, value| !!value }
113
+ annotations.keys.inject(content) do |enriched_content, annotation|
114
+ TextAnnotation.send(annotation.to_sym, enriched_content)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'notion'
4
- require_relative './logger'
5
- require_relative './text_annotation'
6
-
7
3
  module NotionToMd
8
4
  class Converter
9
5
  attr_reader :page_id
@@ -13,14 +9,23 @@ module NotionToMd
13
9
  @page_id = page_id
14
10
  end
15
11
 
16
- def convert
12
+ def convert(frontmatter: false)
13
+ <<~MD
14
+ #{parse_frontmatter if frontmatter}
15
+ #{parse_content}
16
+ MD
17
+ end
18
+
19
+ private
20
+
21
+ def parse_content
17
22
  md = page_blocks[:results].map do |block|
18
- next blank if block[:type] == 'paragraph' && block.dig(:paragraph, :text).empty?
23
+ next Block.blank if block[:type] == 'paragraph' && block.dig(:paragraph, :text).empty?
19
24
 
20
25
  block_type = block[:type].to_sym
21
26
 
22
27
  begin
23
- send(block_type, block[block_type])
28
+ Block.send(block_type, block[block_type])
24
29
  rescue StandardError
25
30
  Logger.info("Unsupported block type: #{block_type}")
26
31
  next nil
@@ -30,127 +35,24 @@ module NotionToMd
30
35
  md.compact.join("\n\n")
31
36
  end
32
37
 
33
- private
34
-
35
- def page_blocks
36
- @page_blocks ||= @notion.block_children(id: page_id)
37
- end
38
-
39
- def paragraph(block)
40
- convert_text(block)
41
- end
42
-
43
- def heading_1(block)
44
- "# #{convert_text(block)}"
45
- end
46
-
47
- def heading_2(block)
48
- "## #{convert_text(block)}"
38
+ def parse_frontmatter
39
+ notion_page = Page.new(page: page)
40
+ frontmatter = notion_page.props.to_a.map do |k, v|
41
+ "#{k}: #{v}"
42
+ end.join("\n")
43
+ <<~CONTENT
44
+ ---
45
+ #{frontmatter}
46
+ ---
47
+ CONTENT
49
48
  end
50
49
 
51
- def heading_3(block)
52
- "### #{convert_text(block)}"
53
- end
54
-
55
- def callout(block)
56
- icon = get_icon(block[:icon])
57
- text = convert_text(block)
58
- "#{icon} #{text}"
59
- end
60
-
61
- def quote(block)
62
- "> #{convert_text(block)}"
63
- end
64
-
65
- def bulleted_list_item(block)
66
- "- #{convert_text(block)}"
67
- end
68
-
69
- # TODO: numbered_list_item
70
- def numbered_list_item(block)
71
- Logger.info('numbered_list_item type not supported. Shown as bulleted_list_item.')
72
- bulleted_list_item(block)
73
- end
74
-
75
- def to_do(block)
76
- checked = block[:checked]
77
- text = convert_text(block)
78
-
79
- "- #{checked ? '[x]' : '[ ]'} #{text}"
80
- end
81
-
82
- def code(block)
83
- language = block[:language]
84
- text = convert_text(block)
85
-
86
- "```#{language}\n\t#{text}\n```"
50
+ def page
51
+ @page ||= @notion.page(id: page_id)
87
52
  end
88
53
 
89
- def embed(block)
90
- url = block[:url]
91
-
92
- "[#{url}](#{url})"
93
- end
94
-
95
- def image(block)
96
- type = block[:type].to_sym
97
- url = block.dig(type, :url)
98
- caption = convert_caption(block)
99
-
100
- "![](#{url})\n\n#{caption}"
101
- end
102
-
103
- def bookmark(block)
104
- url = block[:url]
105
- "[#{url}](#{url})"
106
- end
107
-
108
- def divider(_block)
109
- '---'
110
- end
111
-
112
- def equation(block)
113
- equ = convert_text(block)
114
- "$$ #{equ} $$"
115
- end
116
-
117
- def blank
118
- '<br />'
119
- end
120
-
121
- def convert_text(block)
122
- block[:text].map do |text|
123
- content = text[:plain_text]
124
- enrich_text_content(text, content)
125
- end.join
126
- end
127
-
128
- def convert_caption(block)
129
- convert_text(text: block[:caption])
130
- end
131
-
132
- def get_icon(block)
133
- type = block[:type].to_sym
134
- block[type]
135
- end
136
-
137
- def enrich_text_content(text, content)
138
- enriched_content = add_link(text, content)
139
- add_annotations(text, enriched_content)
140
- end
141
-
142
- def add_link(text, content)
143
- href = text[:href]
144
- return content if href.nil?
145
-
146
- "[#{content}](#{href})"
147
- end
148
-
149
- def add_annotations(text, content)
150
- annotations = text[:annotations].select { |_key, value| !!value }
151
- annotations.keys.inject(content) do |enriched_content, annotation|
152
- TextAnnotation.send(annotation.to_sym, enriched_content)
153
- end
54
+ def page_blocks
55
+ @page_blocks ||= @notion.block_children(id: page_id)
154
56
  end
155
57
  end
156
58
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
4
-
5
3
  module NotionToMd
6
4
  class Logger
7
5
  @logger = ::Logger.new($stdout)
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NotionToMd
4
+ class Page
5
+ attr_reader :page
6
+
7
+ def initialize(page:)
8
+ @page = page
9
+ end
10
+
11
+ def title
12
+ page.dig(:properties, :Name, :title).inject('') do |acc, slug|
13
+ acc + slug[:plain_text]
14
+ end
15
+ end
16
+
17
+ def cover
18
+ page.dig(:cover, :external, :url)
19
+ end
20
+
21
+ def icon
22
+ page.dig(:icon, :emoji)
23
+ end
24
+
25
+ def id
26
+ page[:id]
27
+ end
28
+
29
+ def created_time
30
+ DateTime.parse(page['created_time'])
31
+ end
32
+
33
+ def last_edited_time
34
+ DateTime.parse(page['last_edited_time'])
35
+ end
36
+
37
+ def url
38
+ page[:url]
39
+ end
40
+
41
+ def archived
42
+ page[:archived]
43
+ end
44
+
45
+ def props
46
+ @props ||= custom_props.deep_merge(default_props)
47
+ end
48
+
49
+ def custom_props
50
+ @custom_props ||= page.properties.each_with_object({}) do |prop, memo|
51
+ name = prop.first
52
+ value = prop.last # Notion::Messages::Message
53
+ type = value.type
54
+
55
+ next memo unless CustomProperty.respond_to?(type.to_sym)
56
+
57
+ memo[name.parameterize.underscore] = CustomProperty.send(type, value)
58
+ end.reject { |_k, v| v.presence.nil? }
59
+ end
60
+
61
+ def default_props
62
+ @default_props ||= {
63
+ 'id' => id,
64
+ 'title' => title,
65
+ 'created_time' => created_time,
66
+ 'cover' => cover,
67
+ 'icon' => icon,
68
+ 'last_edited_time' => last_edited_time,
69
+ 'archived' => archived
70
+ }
71
+ end
72
+
73
+ class CustomProperty
74
+ class << self
75
+ def multi_select(prop)
76
+ prop.multi_select.map(&:name)
77
+ end
78
+
79
+ def select(prop)
80
+ prop['select'].name
81
+ end
82
+
83
+ def people(prop)
84
+ prop.people.map(&:name)
85
+ end
86
+
87
+ def files(prop)
88
+ prop.files.map { |f| f.file.url }
89
+ end
90
+
91
+ def phone_number(prop)
92
+ prop.phone_number
93
+ end
94
+
95
+ def number(prop)
96
+ prop.number
97
+ end
98
+
99
+ def email(prop)
100
+ prop.email
101
+ end
102
+
103
+ def checkbox(prop)
104
+ prop.checkbox.to_s
105
+ end
106
+
107
+ # date type properties not supported:
108
+ # - end
109
+ # - time_zone
110
+ def date(prop)
111
+ prop.date.start
112
+ end
113
+
114
+ def url(prop)
115
+ prop.url
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,13 @@
1
+ module NotionToMd
2
+ class Text
3
+ class << self
4
+ def text(text)
5
+ text[:plain_text]
6
+ end
7
+
8
+ def equation(text)
9
+ "$$ #{text[:plain_text]} $$"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -18,12 +18,12 @@ module NotionToMd
18
18
  "**#{text}**"
19
19
  end
20
20
 
21
- def striketrough(text)
21
+ def strikethrough(text)
22
22
  "~~#{text}~~"
23
23
  end
24
24
 
25
25
  def underline(text)
26
- text
26
+ "<u>#{text}</u>"
27
27
  end
28
28
 
29
29
  def code(text)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NotionToMd
4
- VERSION = '0.0.3'
4
+ VERSION = '0.2.1'
5
5
  end
data/lib/notion_to_md.rb CHANGED
@@ -1,4 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'notion'
4
+ require 'logger'
5
+ require 'active_support/inflector'
6
+ require 'active_support/core_ext/object/blank'
3
7
  require_relative './notion_to_md/version'
4
8
  require_relative './notion_to_md/converter'
9
+ require_relative './notion_to_md/logger'
10
+ require_relative './notion_to_md/page'
11
+ require_relative './notion_to_md/block'
12
+ require_relative './notion_to_md/text'
13
+ require_relative './notion_to_md/text_annotation'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: notion_to_md
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.2.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: 2021-12-31 00:00:00.000000000 Z
11
+ date: 2022-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: notion-ruby-client
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,20 @@ dependencies:
38
52
  - - ">="
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
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'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: rubocop
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -60,8 +88,11 @@ extra_rdoc_files: []
60
88
  files:
61
89
  - README.md
62
90
  - lib/notion_to_md.rb
91
+ - lib/notion_to_md/block.rb
63
92
  - lib/notion_to_md/converter.rb
64
93
  - lib/notion_to_md/logger.rb
94
+ - lib/notion_to_md/page.rb
95
+ - lib/notion_to_md/text.rb
65
96
  - lib/notion_to_md/text_annotation.rb
66
97
  - lib/notion_to_md/version.rb
67
98
  homepage: https://github.com/emoriarty/notion_to_md