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 +5 -5
- data/CHANGELOG.md +22 -0
- data/README.md +1 -1
- data/bin/jekyll_ghost_importer +74 -71
- data/features/fixtures/version-004.json +88 -0
- data/features/import_posts.feature +24 -0
- data/jekyll_ghost_importer.gemspec +2 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 793f7694612eae2a10c876897f4213b77f1211e80cab15086733d9d3457c6e47
|
4
|
+
data.tar.gz: d8a413bf79bfcadfada7399ae93b3152e9e06129d955bf960512f7e85885a576
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e65416ad56707d31d1e8bf850dc7de6d61301885bd7a92bcea2dd9934568a819d55a95e687c6442204f773b266a0ae11843d2f1dfe3844374cac171144939d01
|
7
|
+
data.tar.gz: f0ee9fa32ba49407cb4a7152db3512ebf17add35c5290f67ad02e934cfa4b18b4e6e0181669a0140a36655c524f5c2d404736a21a651d0ebae7e7e697d1e934b
|
data/CHANGELOG.md
ADDED
@@ -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]:
|
12
|
+
[3]: https://help.ghost.org/hc/en-us/articles/224112927-Import-Export-Data
|
13
13
|
|
14
14
|
## Installation
|
15
15
|
|
data/bin/jekyll_ghost_importer
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
51
|
+
case published_at
|
73
52
|
when String
|
74
|
-
DateTime.parse(
|
53
|
+
DateTime.parse(published_at)
|
75
54
|
when Integer
|
76
|
-
Time.at(
|
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' =>
|
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
|
-
|
97
|
-
|
98
|
-
end
|
65
|
+
front_matter_hash['featured'] = true if featured == 1
|
66
|
+
front_matter_hash['image'] = image if image != nil
|
99
67
|
|
100
|
-
|
101
|
-
if
|
102
|
-
|
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" +
|
74
|
+
front_matter.to_yaml + "---\n\n" + markdown_body
|
115
75
|
end
|
116
76
|
|
117
|
-
def
|
118
|
-
if
|
119
|
-
|
120
|
-
elsif
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
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.
|
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.
|
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:
|
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.
|
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
|