notion_to_md 0.0.3 → 0.2.1

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: 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