jekyll_ghost_importer 1.0.0 → 1.1.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
- 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