jekyll_ghost_importer 1.0.0 → 1.1.0

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
- SHA1:
3
- metadata.gz: 6efbcdcc0c92d880bb0109f84ccee340dd54cce8
4
- data.tar.gz: 349ae207909497df123de057efe2624b030fda85
2
+ SHA256:
3
+ metadata.gz: 793f7694612eae2a10c876897f4213b77f1211e80cab15086733d9d3457c6e47
4
+ data.tar.gz: d8a413bf79bfcadfada7399ae93b3152e9e06129d955bf960512f7e85885a576
5
5
  SHA512:
6
- metadata.gz: 3365d9b8c06daed3c14017a49c8caee73e1c1227da7ece77d0afab2a05da4f9670e5c06b2033de749bd1292a0ffddd22caad4a607e1488fb46373abab2e3667d
7
- data.tar.gz: d28969705c02a254503accfc8409a86d70f976de9b4d6d141ba33f70f89311b69dd0f46018908b1bf09b60f4f748b809f8073864347df507a0c4bba717702b4d
6
+ metadata.gz: e65416ad56707d31d1e8bf850dc7de6d61301885bd7a92bcea2dd9934568a819d55a95e687c6442204f773b266a0ae11843d2f1dfe3844374cac171144939d01
7
+ data.tar.gz: f0ee9fa32ba49407cb4a7152db3512ebf17add35c5290f67ad02e934cfa4b18b4e6e0181669a0140a36655c524f5c2d404736a21a651d0ebae7e7e697d1e934b
@@ -0,0 +1,22 @@
1
+ Changelog
2
+ =========
3
+
4
+ All notable changes to this project will be documented in this file.
5
+
6
+ The format is based on [Keep a Changelog], and this project adheres to
7
+ [Semantic Versioning].
8
+
9
+ [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
10
+ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html
11
+
12
+ ## [Unreleased]
13
+
14
+ ## [1.1.0] - 2018-02-01
15
+
16
+ ### Added
17
+
18
+ - Add support for exports with mobiledoc without any markdown card.
19
+
20
+ ### Changed
21
+
22
+ - Add new dependency on `reverse_markdown`.
data/README.md CHANGED
@@ -9,7 +9,7 @@ This program let you import your post on [ghost][1] to [jekyll][2]. It uses a
9
9
 
10
10
  [1]: https://ghost.org/about/
11
11
  [2]: http://jekyllrb.com/
12
- [3]: http://support.ghost.org/import-and-export-my-ghost-blog-settings-and-data/
12
+ [3]: https://help.ghost.org/hc/en-us/articles/224112927-Import-Export-Data
13
13
 
14
14
  ## Installation
15
15
 
@@ -20,42 +20,21 @@ require 'fileutils'
20
20
  require 'json'
21
21
  require 'date'
22
22
  require 'yaml'
23
+ require 'ostruct'
24
+ require 'reverse_markdown'
23
25
 
24
- FileUtils.mkdir_p("_posts")
25
- FileUtils.mkdir_p("_drafts")
26
-
27
- $json = JSON.parse File.read(ARGV.pop), symbolize_names: true
28
-
29
- unless $json[:data].nil?
30
- posts = $json[:data][:posts]
31
- else
32
- posts = $json[:db].first[:data][:posts]
33
- end
34
-
35
- def there_are_tags?
36
- @tags ||=
37
- $json[:db] &&
38
- $json[:db].first[:data][:posts_tags] &&
39
- $json[:db].first[:data][:tags]
40
- end
41
-
42
- @imported = Hash.new(0)
43
-
44
- class Post
45
- # the source of the post
46
- attr_reader :post
47
-
48
- def initialize post
49
- @post = post
50
- end
51
-
26
+ class Post < OpenStruct
52
27
  def import
53
28
  puts "Importing #{ filename }"
54
29
  File.write filename, full_body
55
30
  end
56
31
 
57
32
  def draft?
58
- post[:status] == 'draft'
33
+ status == 'draft'
34
+ end
35
+
36
+ def tags
37
+ @tags ||= []
59
38
  end
60
39
 
61
40
  private
@@ -69,63 +48,48 @@ class Post
69
48
  end
70
49
 
71
50
  def date
72
- case post[:published_at]
51
+ case published_at
73
52
  when String
74
- DateTime.parse(post[:published_at])
53
+ DateTime.parse(published_at)
75
54
  when Integer
76
- Time.at(post[:published_at] / 1000).utc.to_datetime
55
+ Time.at(published_at / 1000).utc.to_datetime
77
56
  end
78
57
  end
79
58
 
80
59
  def front_matter
81
60
  front_matter_hash = {
82
61
  'layout' => "post",
83
- 'title' => post[:title]
62
+ 'title' => title
84
63
  }
85
-
86
- front_matter_hash['featured'] = true if post[:featured] == 1
87
- front_matter_hash['image'] = post[:image] if post[:image] != nil
88
-
89
- unless draft?
90
- front_matter_hash['date'] = date.strftime('%Y-%m-%d %H:%M:%S') if date
91
- end
92
- front_matter_hash['tags'] = tags if tags && !tags.empty?
93
- front_matter_hash
94
- end
95
64
 
96
- def slug
97
- post[:slug]
98
- end
65
+ front_matter_hash['featured'] = true if featured == 1
66
+ front_matter_hash['image'] = image if image != nil
99
67
 
100
- def tags
101
- if there_are_tags?
102
- post_tags = $json[:db].first[:data][:posts_tags].select do |pt|
103
- pt[:post_id] == post[:id]
104
- end
105
- tags_ids = post_tags.map { |pt| pt[:tag_id] }
106
- tags = $json[:db].first[:data][:tags].select do |t|
107
- tags_ids.include? t[:id]
108
- end
109
- tags.map { |t| t[:slug] }
110
- end
68
+ front_matter_hash['date'] = date.strftime('%Y-%m-%d %H:%M:%S') if !draft? && date
69
+ front_matter_hash['tags'] = tags if tags.any?
70
+ front_matter_hash
111
71
  end
112
72
 
113
73
  def full_body
114
- front_matter.to_yaml + "---\n\n" + markdown
74
+ front_matter.to_yaml + "---\n\n" + markdown_body
115
75
  end
116
76
 
117
- def markdown
118
- if post[:markdown]
119
- post[:markdown]
120
- elsif post[:mobiledoc]
121
- mobiledoc = JSON.parse post[:mobiledoc], symbolize_names: true
122
- card = mobiledoc[:cards].find { |c| c.first == "card-markdown" }
123
- card.last[:markdown]
77
+ def markdown_body
78
+ if markdown
79
+ markdown
80
+ elsif mobiledoc
81
+ mobiledoc_data = JSON.parse mobiledoc, symbolize_names: true
82
+ markdown_card = mobiledoc_data[:cards].find { |c| c.first == "card-markdown" }
83
+ markdown_card ? markdown_card.last[:markdown] : markdown_from_html
124
84
  else
125
85
  ""
126
86
  end
127
87
  end
128
88
 
89
+ def markdown_from_html
90
+ ReverseMarkdown.convert html
91
+ end
92
+
129
93
  def basename
130
94
  if draft?
131
95
  "#{ slug }.markdown"
@@ -135,11 +99,50 @@ class Post
135
99
  end
136
100
  end
137
101
 
138
- posts.each do |post|
139
- post = Post.new post
140
- post.import
141
- @imported[:posts] += 1
142
- @imported[:drafts] += 1 if post.draft?
102
+ class PostRepository
103
+ attr_reader :posts
104
+
105
+ def initialize json
106
+ @json = json
107
+ @posts = (data[:posts] || []).map { |post_json| Post.new(post_json) }
108
+ @tags = data[:tags] || []
109
+ @posts_tags = data[:posts_tags] || []
110
+ assign_tags_to_posts
111
+ end
112
+
113
+ def drafts
114
+ @posts.select &:draft?
115
+ end
116
+
117
+ private
118
+
119
+ def assign_tags_to_posts
120
+ @posts_tags.each do |post_tag|
121
+ post = get_post(post_tag[:post_id])
122
+ tag = get_tag(post_tag[:tag_id])
123
+ post && tag && post.tags << tag[:slug]
124
+ end
125
+ end
126
+
127
+ def get_post id
128
+ @posts.detect { |post| post.id == id }
129
+ end
130
+
131
+ def get_tag id
132
+ @tags.detect { |tag| tag[:id] == id }
133
+ end
134
+
135
+ def data
136
+ @json[:data] ? @json[:data] : @json[:db].first[:data]
137
+ end
143
138
  end
144
139
 
145
- puts "#{ @imported[:posts] } posts imported ( #{ @imported[:drafts] } draft )"
140
+ FileUtils.mkdir_p("_posts")
141
+ FileUtils.mkdir_p("_drafts")
142
+
143
+ json = JSON.parse File.read(ARGV.pop), symbolize_names: true
144
+
145
+ repository = PostRepository.new(json)
146
+ repository.posts.map &:import
147
+
148
+ puts "#{ repository.posts.count } posts imported ( #{ repository.drafts.count } draft )"
@@ -0,0 +1,88 @@
1
+ {
2
+ "db": [
3
+ {
4
+ "meta": {
5
+ "version": "2.6.0"
6
+ },
7
+ "data": {
8
+ "posts": [
9
+ {
10
+ "id": "5c424716e9286b0021583e8d",
11
+ "uuid": "dc097038-8688-4825-90d9-5c438580c233",
12
+ "title": "The Maker's Guide to the Zombie Apocalypse",
13
+ "slug": "the-makers-guide-to-the-zombie-apocalypse",
14
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[],\"markups\":[[\"a\",[\"href\",\"http://amzn.to/1spRddk\"]],[\"a\",[\"href\",\"http://amzn.to/1spRqwW\"]]],\"sections\":[]}",
15
+ "html": "<p><a href=\"http://amzn.to/1spRddk\">The Maker's Guide to the Zombie Apocalypse</a>, a book written by Simon Monk (who I gather is quite well known within the maker community, having <a href=\"http://amzn.to/1spRqwW\">authored various books</a> on the Arduino and Raspberry Pi)</p>",
16
+ "comment_id": "2",
17
+ "plaintext": "The Maker's Guide to the Zombie Apocalypse",
18
+ "feature_image": "//d1a0j00khen1nw.cloudfront.net/2018/02/IMG_0695-1-.jpg",
19
+ "featured": 0,
20
+ "page": 0,
21
+ "status": "published",
22
+ "locale": null,
23
+ "visibility": "public",
24
+ "meta_title": null,
25
+ "meta_description": null,
26
+ "author_id": "1",
27
+ "created_at": "2016-05-31T19:54:06.000Z",
28
+ "created_by": "1",
29
+ "updated_at": "2018-11-22T12:47:51.000Z",
30
+ "updated_by": "1",
31
+ "published_at": "2016-05-31T20:03:23.000Z",
32
+ "published_by": "1",
33
+ "custom_excerpt": "The Maker's Guide to the Zombie Apocalypse, a book written by Simon Monk is clearly something that knows how to motivate me...",
34
+ "codeinjection_head": null,
35
+ "codeinjection_foot": null,
36
+ "og_image": null,
37
+ "og_title": null,
38
+ "og_description": null,
39
+ "twitter_image": null,
40
+ "twitter_title": null,
41
+ "twitter_description": null,
42
+ "custom_template": ""
43
+ }
44
+ ],
45
+ "posts_authors": [
46
+ {
47
+ "id": "5c424716e9286b0021583eb3",
48
+ "post_id": "5c424716e9286b0021583e8d",
49
+ "author_id": "1",
50
+ "sort_order": 0
51
+ }
52
+ ],
53
+ "posts_tags": [
54
+ {
55
+ "id": "5c424716e9286b0021583eaf",
56
+ "post_id": "5c424716e9286b0021583e8d",
57
+ "tag_id": "5c42470be9286b0021583aec",
58
+ "sort_order": 0
59
+ }
60
+ ],
61
+ "tags": [
62
+ {
63
+ "id": "5c42470be9286b0021583aec",
64
+ "name": "The Maker's Guide to the Zombie Apocalypse",
65
+ "slug": "the-makers-guide-to-the-zombie-apocalypse",
66
+ "description": null,
67
+ "feature_image": "//d1a0j00khen1nw.cloudfront.net/2018/02/IMG_0695-1-.jpg",
68
+ "parent_id": null,
69
+ "visibility": "public",
70
+ "meta_title": null,
71
+ "meta_description": null,
72
+ "created_at": "2018-02-14T10:44:20.000Z",
73
+ "created_by": "1",
74
+ "updated_at": "2018-10-11T21:35:26.000Z",
75
+ "updated_by": "1"
76
+ }
77
+ ],
78
+ "users": [
79
+ {
80
+ "id": "1",
81
+ "name": "DH",
82
+ "slug": "daniel"
83
+ }
84
+ ]
85
+ }
86
+ }
87
+ ]
88
+ }
@@ -295,6 +295,30 @@ Feature: Import posts
295
295
  Something here
296
296
  """
297
297
 
298
+ Scenario: Import a backup file with version 004
299
+ Given a ghost backup file version 004 with some sample posts
300
+ When I run `jekyll_ghost_importer GhostBackup.json`
301
+ Then it should pass with:
302
+ """
303
+ Importing _posts/2016-05-31-the-makers-guide-to-the-zombie-apocalypse.markdown
304
+ 1 posts imported ( 0 draft )
305
+ """
306
+ And a directory named "_posts" should exist
307
+ And the following files should exist:
308
+ | _posts/2016-05-31-the-makers-guide-to-the-zombie-apocalypse.markdown |
309
+ And the file "_posts/2016-05-31-the-makers-guide-to-the-zombie-apocalypse.markdown" should contain:
310
+ """
311
+ ---
312
+ layout: post
313
+ title: The Maker's Guide to the Zombie Apocalypse
314
+ date: '2016-05-31 20:03:23'
315
+ tags:
316
+ - the-makers-guide-to-the-zombie-apocalypse
317
+ ---
318
+
319
+ [The Maker's Guide to the Zombie Apocalypse](http://amzn.to/1spRddk), a book written by Simon Monk (who I gather is quite well known within the maker community, having [authored various books](http://amzn.to/1spRqwW) on the Arduino and Raspberry Pi)
320
+ """
321
+
298
322
  Scenario: Import a draft with an invalid published_at date
299
323
  Given a file named "draft_with_invalid_date.json" with:
300
324
  """
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
  Gem::Specification.new do |spec|
3
3
  spec.name = "jekyll_ghost_importer"
4
- spec.version = "1.0.0"
4
+ spec.version = "1.1.0"
5
5
  spec.authors = ["Eloy Espinaco"]
6
6
  spec.email = ["eloyesp@gmail.com"]
7
7
  spec.summary = %q{Import your posts from a ghost backup file.}
@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
14
  spec.require_paths = ["lib"]
15
15
 
16
+ spec.add_dependency "reverse_markdown", '~> 1.1'
16
17
  spec.add_development_dependency "cucumber", '~> 1.3'
17
18
  spec.add_development_dependency "aruba", '~> 0'
18
19
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll_ghost_importer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Espinaco
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-02 00:00:00.000000000 Z
11
+ date: 2019-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: reverse_markdown
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: cucumber
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -48,6 +62,7 @@ extra_rdoc_files: []
48
62
  files:
49
63
  - ".gitignore"
50
64
  - ".travis.yml"
65
+ - CHANGELOG.md
51
66
  - COPYING
52
67
  - Gemfile
53
68
  - Guardfile
@@ -55,6 +70,7 @@ files:
55
70
  - Rakefile
56
71
  - bin/jekyll_ghost_importer
57
72
  - features/fixtures/version-003.json
73
+ - features/fixtures/version-004.json
58
74
  - features/import_posts.feature
59
75
  - features/step_definitions/backup_files.rb
60
76
  - features/support/requires.rb
@@ -80,12 +96,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
96
  version: '0'
81
97
  requirements: []
82
98
  rubyforge_project:
83
- rubygems_version: 2.5.2.1
99
+ rubygems_version: 2.7.6
84
100
  signing_key:
85
101
  specification_version: 4
86
102
  summary: Import your posts from a ghost backup file.
87
103
  test_files:
88
104
  - features/fixtures/version-003.json
105
+ - features/fixtures/version-004.json
89
106
  - features/import_posts.feature
90
107
  - features/step_definitions/backup_files.rb
91
108
  - features/support/requires.rb