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