typepad_to_jekyll 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +8 -0
- data/README.md +47 -0
- data/Rakefile +19 -0
- data/bin/typepad_to +34 -0
- data/lib/typepad_to_jekyll.rb +12 -0
- data/lib/typepad_to_jekyll/converter.rb +58 -0
- data/lib/typepad_to_jekyll/parser.rb +191 -0
- data/lib/typepad_to_jekyll/post.rb +68 -0
- data/test/data/typepad_multiple_posts.txt +113 -0
- data/test/data/typepad_one_post.txt +43 -0
- data/test/data/typepad_posts_yaml.txt +103 -0
- data/test/test_converter.rb +123 -0
- metadata +61 -0
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
License Terms
|
2
|
+
=============
|
3
|
+
|
4
|
+
Distributed under the user's choice of the [GPL Version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) (see COPYING for details) or the
|
5
|
+
[Ruby software license](http://www.ruby-lang.org/en/LICENSE.txt) by
|
6
|
+
Chris Stansbury.
|
7
|
+
|
8
|
+
Feel free to email [Chris Stansbury](mailto:chris@koozie.org) with any questions.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
Typepad to Jekyll
|
2
|
+
=================
|
3
|
+
|
4
|
+
by Chris Stansbury
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
TypepadToJekyll is a Ruby command line application to convert a MoveableType or Typepad blog
|
10
|
+
backup file, [MTIF format], into Jekyll blog posts. Post files will be stored in the _posts
|
11
|
+
and the _drafts sub directories under your jekyll directory.
|
12
|
+
|
13
|
+
|
14
|
+
Install
|
15
|
+
-------
|
16
|
+
|
17
|
+
|
18
|
+
gem install typepad_to_jekyll
|
19
|
+
gem install thor
|
20
|
+
|
21
|
+
|
22
|
+
Examples
|
23
|
+
--------
|
24
|
+
|
25
|
+
$ typepad_to jekyll <typepad_backup_filename> <jekyll_directory>
|
26
|
+
|
27
|
+
|
28
|
+
Requirements
|
29
|
+
------------
|
30
|
+
|
31
|
+
Ruby
|
32
|
+
Ruby Gems: Thor
|
33
|
+
|
34
|
+
|
35
|
+
Supported Platforms
|
36
|
+
-------------------
|
37
|
+
|
38
|
+
Known to work on
|
39
|
+
|
40
|
+
1.9.3
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
Questions and/or Comments
|
45
|
+
-------------------------
|
46
|
+
|
47
|
+
Feel free to email [Chris Stansbury](mailto:chris@koozie.org) with any questions.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rake/clean"
|
4
|
+
|
5
|
+
CLEAN.include('*.gem')
|
6
|
+
CLOBBER.include(FileList['.yardoc/*','doc/*'].exclude('.gitignore'))
|
7
|
+
|
8
|
+
task :default => [:test]
|
9
|
+
|
10
|
+
Rake::TestTask.new do |test|
|
11
|
+
test.libs << "test"
|
12
|
+
test.test_files = Dir[ "test/test_*.rb" ]
|
13
|
+
test.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Build Gem"
|
17
|
+
task :build do
|
18
|
+
sh "gem build typepad_to_jekyll.gemspec"
|
19
|
+
end
|
data/bin/typepad_to
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
|
4
|
+
|
5
|
+
require 'typepad_to_jekyll'
|
6
|
+
require 'thor'
|
7
|
+
|
8
|
+
|
9
|
+
class Application < Thor
|
10
|
+
|
11
|
+
attr_reader :jekyll_dir, :posts_dir, :drafts_dir
|
12
|
+
|
13
|
+
desc "jekyll BACKUP DIRECTORY", "Convert Typepad Backup file to Jekyll blog posts and store in _posts sub directory"
|
14
|
+
long_desc <<-LONGDESC
|
15
|
+
`typepad_to jekyll` will parse a TypePad backup file in the MTIF file format and genereate blog posts
|
16
|
+
which will be stored in the jekyll sub directory of _posts of _drafts.
|
17
|
+
|
18
|
+
typepad_to jekyll BACKUP JEKYLL-DIRECTORY
|
19
|
+
\x5 BACKUP: the filename of your TypePad backup file.
|
20
|
+
\x5 JEKYLL-DIRECTORY: path to your Jekyll directory.
|
21
|
+
LONGDESC
|
22
|
+
def jekyll(typepad_backup_filename, jekyll_directory)
|
23
|
+
abort "ERROR: [#{jekyll_directory}] is not a directory. " unless File.directory?(jekyll_directory)
|
24
|
+
app = TypepadToJekyll::Converter.new(typepad_backup_filename, jekyll_directory)
|
25
|
+
app.process
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
end
|
30
|
+
|
31
|
+
Application.start(ARGV)
|
32
|
+
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module TypepadToJekyll
|
4
|
+
|
5
|
+
# Convert Typepad Backup (MTIF Format) to
|
6
|
+
# Jekyll posts. MTIF -> Jekyll _posts
|
7
|
+
class Converter
|
8
|
+
attr_reader :jekyll_base_dir, :posts_dir, :drafts_dir
|
9
|
+
attr_reader :typepad_backup_filename
|
10
|
+
attr_accessor :posts
|
11
|
+
|
12
|
+
def initialize(backup_filename, jekyll_base_directory)
|
13
|
+
@typepad_backup_filename = backup_filename
|
14
|
+
@jekyll_base_dir = jekyll_base_directory
|
15
|
+
@posts_dir = File.join(jekyll_base_dir, '_posts')
|
16
|
+
@drafts_dir = File.join(jekyll_base_dir, '_drafts')
|
17
|
+
@posts = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def process
|
21
|
+
setup_sub_dirs
|
22
|
+
parse_backup_file
|
23
|
+
write_posts
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse_backup_file
|
29
|
+
app = TypepadToJekyll::Parser.new
|
30
|
+
app.source_filename = typepad_backup_filename
|
31
|
+
app.process
|
32
|
+
@posts = app.posts
|
33
|
+
end
|
34
|
+
|
35
|
+
#write post to _posts directory in jekyll format
|
36
|
+
def write_posts
|
37
|
+
posts.each do |post|
|
38
|
+
if post.status == :publish
|
39
|
+
dir = posts_dir
|
40
|
+
else
|
41
|
+
dir = drafts_dir
|
42
|
+
end
|
43
|
+
fname = File.join(dir, post.filename)
|
44
|
+
File.open(fname, 'w') do |file|
|
45
|
+
file.puts post.to_jekyll
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def setup_sub_dirs
|
51
|
+
dirs = [posts_dir, drafts_dir]
|
52
|
+
dirs.each do |dir|
|
53
|
+
FileUtils.mkdir_p(dir) if not File.directory?(dir)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end # class
|
58
|
+
end # module
|
@@ -0,0 +1,191 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
module TypepadToJekyll
|
7
|
+
|
8
|
+
# Convert Typepad Backup (MTIF Format) to
|
9
|
+
# Jekyll posts. MTIF -> Jekyll _posts
|
10
|
+
class Parser
|
11
|
+
|
12
|
+
attr_accessor :source_filename
|
13
|
+
attr_reader :import_status, :current_line, :last_line
|
14
|
+
attr_reader :current_post, :posts
|
15
|
+
attr_reader :current_comment
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
setup
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
setup
|
23
|
+
process_typepad_file
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def process_typepad_file
|
29
|
+
new_post
|
30
|
+
File.open(source_filename,'r') do |file|
|
31
|
+
file.each do |line|
|
32
|
+
@last_line = current_line
|
33
|
+
@current_line = line
|
34
|
+
process_line
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_post
|
40
|
+
@import_status = :post_header #:post_header, :body, :extended_body, :excerpt, :keywords, :boundary_5
|
41
|
+
@current_post = Post.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_boundary
|
45
|
+
@import_status = :boundary_5 if current_line =~ /^-----/
|
46
|
+
@import_status = :boundary_7 if current_line =~ /^-------/
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_line
|
50
|
+
check_boundary
|
51
|
+
case import_status
|
52
|
+
when :post_header
|
53
|
+
process_header_line
|
54
|
+
when :comment_header
|
55
|
+
process_comment_line
|
56
|
+
when :comment_body
|
57
|
+
process_comment_body
|
58
|
+
when :body
|
59
|
+
process_body
|
60
|
+
when :extended_body
|
61
|
+
process_extended_body
|
62
|
+
when :excerpt
|
63
|
+
process_excerpt
|
64
|
+
when :boundary_7
|
65
|
+
#write_post
|
66
|
+
posts << current_post
|
67
|
+
new_post
|
68
|
+
when :boundary_5
|
69
|
+
process_boundary5_item
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_boundary5_item
|
74
|
+
case current_line
|
75
|
+
when /^BODY/
|
76
|
+
@import_status = :body
|
77
|
+
when /^EXTENDED BODY/
|
78
|
+
@import_status = :extended_body
|
79
|
+
when /^EXCERPT/
|
80
|
+
@import_status = :excerpt
|
81
|
+
when /^KEYWORDS/
|
82
|
+
@import_status = :keywords
|
83
|
+
when /^COMMENT/
|
84
|
+
@import_status = :comment_header
|
85
|
+
@current_comment = current_post.new_comment
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def process_body
|
90
|
+
current_post.body += current_line
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_extended_body
|
94
|
+
current_post.extended_body += current_line
|
95
|
+
end
|
96
|
+
|
97
|
+
def process_excerpt
|
98
|
+
current_post.excerpt += current_line if current_line.strip.chomp.size > 0
|
99
|
+
end
|
100
|
+
|
101
|
+
def process_comment_body
|
102
|
+
current_comment.body += current_line
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_comment_line
|
106
|
+
return if current_line.strip == ""
|
107
|
+
case current_line
|
108
|
+
when /^AUTHOR/
|
109
|
+
current_comment.author = current_line.split('AUTHOR:').last.strip
|
110
|
+
when /^EMAIL/
|
111
|
+
current_comment.email = current_line.split('EMAIL:').last.strip
|
112
|
+
when /^IP/
|
113
|
+
current_comment.ip = current_line.split('IP:').last.strip
|
114
|
+
when /^URL/
|
115
|
+
current_comment.url = current_line.split('URL:').last.strip
|
116
|
+
when /^DATE/
|
117
|
+
d_str = current_line.split('DATE:').last.strip
|
118
|
+
current_comment.date = DateTime.strptime(d_str, "%m/%d/%Y %I:%M:%S %p")
|
119
|
+
@import_status = :comment_body #comment body follows directly after comment date
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def process_header_line
|
124
|
+
return if current_line.strip == ""
|
125
|
+
case current_line
|
126
|
+
when /^AUTHOR/
|
127
|
+
current_post.author = current_line.split('AUTHOR:').last.strip
|
128
|
+
when /^TITLE/
|
129
|
+
current_post.title = current_line.split('TITLE:').last.strip
|
130
|
+
when /^STATUS/
|
131
|
+
current_post.status = current_line.split('STATUS:').last.strip.downcase.to_sym
|
132
|
+
when /^ALLOW COMMENTS/
|
133
|
+
current_post.allow_comments = current_line.split('ALLOW COMMENTS:').last.strip == '1' ? true : false
|
134
|
+
when /^CONVERT BREAKS/
|
135
|
+
current_post.convert_breaks = current_line.split('CONVERT BREAKS:').last.strip
|
136
|
+
when /^ALLOW PINGS/
|
137
|
+
current_post.allow_pings = current_line.split('ALLOW PINGS:').last.strip == '1' ? true : false
|
138
|
+
when /^BASENAME/
|
139
|
+
current_post.basename = current_line.split('BASENAME:').last.strip
|
140
|
+
when /^CATEGORY/
|
141
|
+
category_name = current_line.split('CATEGORY:').last.strip
|
142
|
+
current_post.categories << clean_category_name(category_name)
|
143
|
+
when /^UNIQUE URL/
|
144
|
+
current_post.unique_url = current_line.split('UNIQUE URL:').last.strip
|
145
|
+
when /^DATE/
|
146
|
+
d_str = current_line.split('DATE:').last.strip
|
147
|
+
current_post.date = DateTime.strptime(d_str, "%m/%d/%Y %I:%M:%S %p")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def clean_category_name(cat)
|
152
|
+
cat.downcase.gsub(/ /,'-')
|
153
|
+
end
|
154
|
+
|
155
|
+
def setup
|
156
|
+
@posts = []
|
157
|
+
@current_line = ''
|
158
|
+
@last_line = ''
|
159
|
+
end
|
160
|
+
|
161
|
+
end # class
|
162
|
+
end # module
|
163
|
+
|
164
|
+
__END__
|
165
|
+
|
166
|
+
|
167
|
+
dirs = [destination_dir, posts_dir, drafts_dir]
|
168
|
+
dirs.each do |dir|
|
169
|
+
if not File.directory?(dir)
|
170
|
+
puts "Not a directory [#{dir}]"
|
171
|
+
exit 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
#write post to _posts directory in jekyll format
|
180
|
+
def write_post
|
181
|
+
if current_post.status == :publish
|
182
|
+
dir = posts_dir
|
183
|
+
else
|
184
|
+
dir = drafts_dir
|
185
|
+
end
|
186
|
+
fname = File.join(dir, current_post.filename)
|
187
|
+
File.open(fname, 'w') do |file|
|
188
|
+
file.puts current_post.to_yaml
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module TypepadToJekyll
|
2
|
+
class Comment
|
3
|
+
attr_accessor :author, :email, :ip, :url, :date, :body
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@body = ''
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
module TypepadToJekyll
|
13
|
+
class Post
|
14
|
+
attr_accessor :author, :title, :status, :allow_comments, :convert_breaks
|
15
|
+
attr_accessor :allow_pings, :basename, :unique_url, :date, :body
|
16
|
+
attr_accessor :extended_body, :excerpt
|
17
|
+
|
18
|
+
attr_reader :categories, :keywords, :comments
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@categories = []
|
22
|
+
@keywords = []
|
23
|
+
@comments = []
|
24
|
+
@body = ''
|
25
|
+
@extended_body = ''
|
26
|
+
@excerpt = ''
|
27
|
+
end
|
28
|
+
|
29
|
+
def filename
|
30
|
+
"#{date.strftime("%Y-%m-%d")}-#{basename}.html"
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_jekyll
|
34
|
+
str = []
|
35
|
+
str << '---'
|
36
|
+
str << 'layout: post'
|
37
|
+
str << 'title: ' + clean_yaml(title)
|
38
|
+
str << "date: #{date.strftime("%Y-%m-%d %H:%M:%S")}"
|
39
|
+
if categories.size == 1
|
40
|
+
str << "category: #{categories.first}"
|
41
|
+
elsif categories.size > 1
|
42
|
+
str << "categories: #{categories.join(' ')}"
|
43
|
+
end
|
44
|
+
str << '---'
|
45
|
+
str << body
|
46
|
+
str << extended_body
|
47
|
+
|
48
|
+
return str.join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
#handle colons, quotes, and double quotes in string
|
52
|
+
def clean_yaml(str)
|
53
|
+
if str.include?(':') or str.include?("'") or str.include?('"')
|
54
|
+
#return "'" + str.gsub(/"/, '\"').gsub(/'/, "''") + "'"
|
55
|
+
return "'" + str.gsub(/'/, "''") + "'"
|
56
|
+
else
|
57
|
+
return str
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def new_comment
|
62
|
+
c = Comment.new
|
63
|
+
comments << c
|
64
|
+
return c
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
AUTHOR: Chris
|
2
|
+
TITLE: First Post
|
3
|
+
STATUS: Draft
|
4
|
+
ALLOW COMMENTS: 1
|
5
|
+
CONVERT BREAKS: __default__
|
6
|
+
ALLOW PINGS: 0
|
7
|
+
BASENAME: first_post
|
8
|
+
|
9
|
+
UNIQUE URL: http://www.example.org/2003/05/first_post.html
|
10
|
+
DATE: 05/29/2003 01:02:06 AM
|
11
|
+
-----
|
12
|
+
BODY:
|
13
|
+
<p>This should be my first post to Moveable Type. I'm still trying to figure this system out. </p>
|
14
|
+
|
15
|
+
<p>Jerry</p>
|
16
|
+
-----
|
17
|
+
EXTENDED BODY:
|
18
|
+
<p>IDEAS:</p>
|
19
|
+
|
20
|
+
<p>crayons<br />
|
21
|
+
Good Technology<br />
|
22
|
+
ONIX File Format<br />
|
23
|
+
419 Scams<br />
|
24
|
+
Blat Utility<br />
|
25
|
+
Digital Camera Stuff</p>
|
26
|
+
-----
|
27
|
+
EXCERPT:
|
28
|
+
|
29
|
+
-----
|
30
|
+
KEYWORDS:
|
31
|
+
|
32
|
+
-----
|
33
|
+
COMMENT:
|
34
|
+
AUTHOR: Troy Aikman
|
35
|
+
EMAIL: troy@example.com
|
36
|
+
IP: 208.201.230.101
|
37
|
+
URL: http://troy.example.com/
|
38
|
+
DATE: 05/30/2003 01:32:14 PM
|
39
|
+
Easy, ain't it? :)
|
40
|
+
laters
|
41
|
+
-----
|
42
|
+
--------
|
43
|
+
AUTHOR: John Franklin McEnroe
|
44
|
+
TITLE: Home Wi-Fi
|
45
|
+
STATUS: Publish
|
46
|
+
ALLOW COMMENTS: 1
|
47
|
+
CONVERT BREAKS: __default__
|
48
|
+
ALLOW PINGS: 1
|
49
|
+
BASENAME: home_wifi
|
50
|
+
CATEGORY: Hardware
|
51
|
+
|
52
|
+
UNIQUE URL: http://blog.example.org/2003/01/home_wifi.html
|
53
|
+
DATE: 01/10/2003 01:39:35 PM
|
54
|
+
-----
|
55
|
+
BODY:
|
56
|
+
<p>I put in <a href="http://www.linksys.com/Products/product.asp?grid=33&scid=35&prid=508">Linksys wireless router/access point</a> on my home network one week ago. I quickly configured the router using its web based configuration tool. I've also been able to get a descent signal ALL over the house.</p>
|
57
|
+
|
58
|
+
<p>Wireless is just plain cool. I can't believe I waited so long to use it. </p>
|
59
|
+
|
60
|
+
<p>-Chris</p>
|
61
|
+
-----
|
62
|
+
EXTENDED BODY:
|
63
|
+
|
64
|
+
-----
|
65
|
+
EXCERPT:
|
66
|
+
|
67
|
+
-----
|
68
|
+
KEYWORDS:
|
69
|
+
|
70
|
+
-----
|
71
|
+
COMMENT:
|
72
|
+
AUTHOR: Fred Mike Jones
|
73
|
+
EMAIL: fmj@example.com
|
74
|
+
IP: 68.166.91.157
|
75
|
+
URL: http://www.example.com/blog
|
76
|
+
DATE: 08/10/2003 02:54:44 PM
|
77
|
+
Yeah, wireless is the way to go. Mrs. Noded has here own laptop. It makes it so much easier. Here, there and everywhere.
|
78
|
+
|
79
|
+
You posting from the backyard?
|
80
|
+
-----
|
81
|
+
COMMENT:
|
82
|
+
AUTHOR: mike
|
83
|
+
EMAIL: mike@example.org
|
84
|
+
IP: 65.70.200.35
|
85
|
+
URL: http://www.example.org
|
86
|
+
DATE: 08/10/2003 03:17:23 PM
|
87
|
+
It's to freaking HOT here to be outside longer than 5 minutes. We had tempeartures up to 108 degrees. Maybe I'll try the outside thing this fall.
|
88
|
+
-----
|
89
|
+
COMMENT:
|
90
|
+
AUTHOR: mike
|
91
|
+
EMAIL: mike@example.net
|
92
|
+
IP: 193.195.87.146
|
93
|
+
URL: http://example.net
|
94
|
+
DATE: 08/12/2003 08:58:00 AM
|
95
|
+
Just wait until your wireless access point dies (just like mine did this week) and then you REALLY realise how much you thought of it. I got good coverage all over the house and the garden. Now I'm stuck to sitting in the office tied by a long piece of cat5 cable :-(
|
96
|
+
-----
|
97
|
+
COMMENT:
|
98
|
+
AUTHOR: Mike
|
99
|
+
EMAIL: mike@example.org
|
100
|
+
IP: 207.8.15.210
|
101
|
+
URL: http://www.example.org
|
102
|
+
DATE: 08/12/2003 12:07:13 PM
|
103
|
+
Before I had wireless, I connected two 25' cat5 cables together so I could work in the living room instead of the office. :-)
|
104
|
+
-----
|
105
|
+
COMMENT:
|
106
|
+
AUTHOR: Jerry McGuire
|
107
|
+
EMAIL: blog@example.net
|
108
|
+
IP: 81.86.172.239
|
109
|
+
URL: http://example.net
|
110
|
+
DATE: 08/13/2003 04:00:09 AM
|
111
|
+
I had something similar - a LONG piece of cable through a hole drilled in the ceiling from the upstairs office into the living room and then coiled in a big loop so it would reach anywhere downstairs if stretched across the room....that changed when I got married :-)
|
112
|
+
-----
|
113
|
+
--------
|
@@ -0,0 +1,43 @@
|
|
1
|
+
AUTHOR: Chris
|
2
|
+
TITLE: First Post
|
3
|
+
STATUS: Draft
|
4
|
+
ALLOW COMMENTS: 1
|
5
|
+
CONVERT BREAKS: __default__
|
6
|
+
ALLOW PINGS: 0
|
7
|
+
BASENAME: first_post
|
8
|
+
|
9
|
+
UNIQUE URL: http://www.example.org/2003/05/first_post.html
|
10
|
+
DATE: 05/29/2003 01:02:06 AM
|
11
|
+
-----
|
12
|
+
BODY:
|
13
|
+
<p>This should be my first post to Moveable Type. I'm still trying to figure this system out. </p>
|
14
|
+
|
15
|
+
<p>Jerry</p>
|
16
|
+
-----
|
17
|
+
EXTENDED BODY:
|
18
|
+
<p>IDEAS:</p>
|
19
|
+
|
20
|
+
<p>crayons<br />
|
21
|
+
Good Technology<br />
|
22
|
+
ONIX File Format<br />
|
23
|
+
419 Scams<br />
|
24
|
+
Blat Utility<br />
|
25
|
+
Digital Camera Stuff</p>
|
26
|
+
-----
|
27
|
+
EXCERPT:
|
28
|
+
|
29
|
+
-----
|
30
|
+
KEYWORDS:
|
31
|
+
|
32
|
+
-----
|
33
|
+
COMMENT:
|
34
|
+
AUTHOR: Troy Aikman
|
35
|
+
EMAIL: troy@example.com
|
36
|
+
IP: 208.201.230.101
|
37
|
+
URL: http://troy.example.com/
|
38
|
+
DATE: 05/30/2003 01:32:14 PM
|
39
|
+
Easy, ain't it? :)
|
40
|
+
laters
|
41
|
+
-----
|
42
|
+
--------
|
43
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
AUTHOR: Frank
|
2
|
+
TITLE: Junk Faxes: Redux
|
3
|
+
STATUS: Draft
|
4
|
+
ALLOW COMMENTS: 1
|
5
|
+
CONVERT BREAKS: __default__
|
6
|
+
ALLOW PINGS: 1
|
7
|
+
BASENAME: junk_faxes
|
8
|
+
CATEGORY: SPAM
|
9
|
+
|
10
|
+
UNIQUE URL: http://www.koozie.org/2003/11/junk_faxes.html
|
11
|
+
DATE: 11/06/2003 05:04:49 PM
|
12
|
+
-----
|
13
|
+
BODY:
|
14
|
+
<p>I found some interesting sites today about the piles and piles of junk faxes I seemed to be collecting these days.</p>
|
15
|
+
|
16
|
+
<p>800-390-1403 is the so-called removal number.</p>
|
17
|
+
|
18
|
+
<p>1st ad 800-891-2682 Medical Insurance<br />
|
19
|
+
2nd ad pumps some stocks, by OTC Analysis.</p>
|
20
|
+
|
21
|
+
<p>http://www.junkfaxes.org/</p>
|
22
|
+
-----
|
23
|
+
EXTENDED BODY:
|
24
|
+
|
25
|
+
-----
|
26
|
+
EXCERPT:
|
27
|
+
|
28
|
+
-----
|
29
|
+
KEYWORDS:
|
30
|
+
|
31
|
+
-----
|
32
|
+
--------
|
33
|
+
AUTHOR: Chris
|
34
|
+
TITLE: All about Frank's "MIDI" Files
|
35
|
+
STATUS: Publish
|
36
|
+
ALLOW COMMENTS: 1
|
37
|
+
CONVERT BREAKS: __default__
|
38
|
+
ALLOW PINGS: 1
|
39
|
+
BASENAME: midi_files
|
40
|
+
CATEGORY: General IT
|
41
|
+
CATEGORY: Programming
|
42
|
+
|
43
|
+
UNIQUE URL: http://www.koozie.org/2003/11/midi_files.html
|
44
|
+
DATE: 11/04/2003 12:07:24 PM
|
45
|
+
-----
|
46
|
+
BODY:
|
47
|
+
<p>I've been busy at work and at home with two daughters, wife and dog. I have managed to squeeze in some playing time on the new piano. I found a couple of articles to help me understand midi files, which is how music is stored in my digital piano. The best analogy I found on describing midi was midi files are like music paper rolls for player pianos.</p>
|
48
|
+
|
49
|
+
<p>-Chris</p>
|
50
|
+
|
51
|
+
<p><a href="http://mp3.about.com/library/weekly/aa021797.htm">What is MIDI, Anyway?</a></p>
|
52
|
+
|
53
|
+
<p><a href="http://mp3.about.com/library/weekly/aa072699.htm">MIDI and MP3: What's the Difference?</a></p>
|
54
|
+
|
55
|
+
<p><a href="http://mp3.about.com/b/a/037484.htm">How to Convert MIDI to MP3 or CD</a></p>
|
56
|
+
-----
|
57
|
+
EXTENDED BODY:
|
58
|
+
|
59
|
+
-----
|
60
|
+
EXCERPT:
|
61
|
+
|
62
|
+
-----
|
63
|
+
KEYWORDS:
|
64
|
+
|
65
|
+
-----
|
66
|
+
--------
|
67
|
+
AUTHOR: Chris
|
68
|
+
TITLE: Comment Spam: Here it comes again, Sue's List of "Crazy" Items
|
69
|
+
STATUS: Publish
|
70
|
+
ALLOW COMMENTS: 1
|
71
|
+
CONVERT BREAKS: __default__
|
72
|
+
ALLOW PINGS: 1
|
73
|
+
BASENAME: first_comment_s
|
74
|
+
CATEGORY: General IT
|
75
|
+
|
76
|
+
UNIQUE URL: http://www.koozie.org/2003/11/first_comment_s.html
|
77
|
+
DATE: 11/04/2003 11:54:18 AM
|
78
|
+
-----
|
79
|
+
BODY:
|
80
|
+
<p>I finally received my first comment spam on MT.</p>
|
81
|
+
|
82
|
+
<blockquote>
|
83
|
+
IP Address: 209.208.9.254
|
84
|
+
Name: debt-consolidation
|
85
|
+
Email Address: gangastrotagati[at]yahoo.com
|
86
|
+
URL: http://www.karmicdebtconsolidation[dot]com/
|
87
|
+
|
88
|
+
<p>Comments:</p>
|
89
|
+
|
90
|
+
<p>Marvelous<br />
|
91
|
+
|
92
|
+
|
93
|
+
-----
|
94
|
+
EXTENDED BODY:
|
95
|
+
|
96
|
+
-----
|
97
|
+
EXCERPT:
|
98
|
+
|
99
|
+
-----
|
100
|
+
KEYWORDS:
|
101
|
+
|
102
|
+
-----
|
103
|
+
--------
|
@@ -0,0 +1,123 @@
|
|
1
|
+
gem 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'typepad_to_jekyll'
|
4
|
+
|
5
|
+
class ParserTestOne < Minitest::Test
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@fn = File.join(File.dirname(__FILE__),'data','typepad_one_post.txt')
|
9
|
+
@app = TypepadToJekyll::Parser.new
|
10
|
+
@app.source_filename = @fn
|
11
|
+
@app.process
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_one_post_front_matter
|
15
|
+
assert_equal @fn, @app.source_filename
|
16
|
+
|
17
|
+
post = @app.posts.first
|
18
|
+
assert_equal 'Chris', post.author
|
19
|
+
assert_equal 'First Post', post.title
|
20
|
+
assert_equal :draft, post.status
|
21
|
+
assert_equal 'http://www.example.org/2003/05/first_post.html', post.unique_url
|
22
|
+
assert_equal '05/29/2003 01:02:06 AM', post.date.strftime("%m/%d/%Y %I:%M:%S %p")
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_comment_one_post
|
26
|
+
post = @app.posts.first
|
27
|
+
assert_equal 1, post.comments.size
|
28
|
+
comment = post.comments.first
|
29
|
+
assert_equal 'Troy Aikman', comment.author
|
30
|
+
assert_equal 'troy@example.com', comment.email
|
31
|
+
assert_equal '208.201.230.101', comment.ip
|
32
|
+
assert_equal '05/30/2003 01:32:14 PM', comment.date.strftime("%m/%d/%Y %I:%M:%S %p")
|
33
|
+
assert_equal "Easy, ain't it? :)\nlaters\n", comment.body
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ParserTestMultilePosts < Minitest::Test
|
38
|
+
def setup
|
39
|
+
@fn = File.join(File.dirname(__FILE__),'data','typepad_multiple_posts.txt')
|
40
|
+
@app = TypepadToJekyll::Parser.new
|
41
|
+
@app.source_filename = @fn
|
42
|
+
@app.process
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_last_post_front_matter
|
46
|
+
post = @app.posts.last
|
47
|
+
assert_equal 'John Franklin McEnroe', post.author
|
48
|
+
assert_equal 'Home Wi-Fi', post.title
|
49
|
+
assert_equal :publish, post.status
|
50
|
+
assert_equal 'http://blog.example.org/2003/01/home_wifi.html', post.unique_url
|
51
|
+
assert_equal '01/10/2003 01:39:35 PM', post.date.strftime("%m/%d/%Y %I:%M:%S %p")
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_all_comments_parse_last_post
|
55
|
+
post = @app.posts.last
|
56
|
+
assert_equal 5, post.comments.size
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_last_comment_from_last_post
|
60
|
+
post = @app.posts.last
|
61
|
+
comment = post.comments.last
|
62
|
+
assert_equal 'Jerry McGuire', comment.author
|
63
|
+
assert_equal 'blog@example.net', comment.email
|
64
|
+
assert_equal '81.86.172.239', comment.ip
|
65
|
+
assert_equal '08/13/2003 04:00:09 AM', comment.date.strftime("%m/%d/%Y %I:%M:%S %p")
|
66
|
+
assert_equal "I had something similar - a LONG piece of cable through a hole drilled in the ceiling from the upstairs office into the living room and then coiled in a big loop so it would reach anywhere downstairs if stretched across the room....that changed when I got married :-)\n", comment.body
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Tests for checking blog post title where ' or " or : appears in title
|
72
|
+
require 'yaml'
|
73
|
+
class ParserTestPostTileYamlIssues < Minitest::Test
|
74
|
+
def setup
|
75
|
+
@fn = File.join(File.dirname(__FILE__),'data','typepad_posts_yaml.txt')
|
76
|
+
@app = TypepadToJekyll::Parser.new
|
77
|
+
@app.source_filename = @fn
|
78
|
+
@app.process
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_first_post_front_matter
|
82
|
+
post = @app.posts.first
|
83
|
+
assert_equal 'Junk Faxes: Redux', post.title
|
84
|
+
post_yaml = YAML.load(post.to_jekyll)
|
85
|
+
assert_equal post_yaml['title'], post.title
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_second_post_front_matter
|
89
|
+
post = @app.posts[1]
|
90
|
+
assert_equal 'All about Frank\'s "MIDI" Files', post.title
|
91
|
+
post_yaml = YAML.load(post.to_jekyll)
|
92
|
+
#assert_equal 'All about Frank\'s \"MIDI\" Files' ,post_yaml['title']
|
93
|
+
assert_equal 'All about Frank\'s "MIDI" Files' ,post_yaml['title']
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Tests for checking category single or categories multiple
|
99
|
+
require 'yaml'
|
100
|
+
class ParserTestCategories < Minitest::Test
|
101
|
+
def setup
|
102
|
+
@fn = File.join(File.dirname(__FILE__),'data','typepad_posts_yaml.txt')
|
103
|
+
@app = TypepadToJekyll::Parser.new
|
104
|
+
@app.source_filename = @fn
|
105
|
+
@app.process
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_first_post_category_single
|
109
|
+
post = @app.posts.first
|
110
|
+
post_yaml = YAML.load(post.to_jekyll)
|
111
|
+
key = 'category'
|
112
|
+
assert post_yaml.key?(key)
|
113
|
+
assert post_yaml[key].split(' ').size == 1
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_first_post_category_single
|
117
|
+
post = @app.posts[1]
|
118
|
+
post_yaml = YAML.load(post.to_jekyll)
|
119
|
+
key = 'categories'
|
120
|
+
assert post_yaml.key?(key)
|
121
|
+
assert post_yaml[key].split(' ').size == 2
|
122
|
+
end
|
123
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typepad_to_jekyll
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Stansbury
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-07-04 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A commandline application to convert Typepad and Moveabletype backup
|
15
|
+
files into Jekyll posts. Posts to be stored in either the _posts or _drafts directory
|
16
|
+
email: Chris@koozie.org
|
17
|
+
executables:
|
18
|
+
- typepad_to
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- Rakefile
|
23
|
+
- bin/typepad_to
|
24
|
+
- lib/typepad_to_jekyll/converter.rb
|
25
|
+
- lib/typepad_to_jekyll/parser.rb
|
26
|
+
- lib/typepad_to_jekyll/post.rb
|
27
|
+
- lib/typepad_to_jekyll.rb
|
28
|
+
- test/data/typepad_multiple_posts.txt
|
29
|
+
- test/data/typepad_one_post.txt
|
30
|
+
- test/data/typepad_posts_yaml.txt
|
31
|
+
- test/test_converter.rb
|
32
|
+
- README.md
|
33
|
+
- LICENSE
|
34
|
+
homepage: https://github.com/koozie/typepad_to_jekyll
|
35
|
+
licenses:
|
36
|
+
- GPL-2
|
37
|
+
- Ruby Software License
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.8.23.2
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: ! '''Typepad to Jekyll'' is a ruby library and commandline app that converts
|
60
|
+
a Typepad backup file (MTIF format) into Jekyll post files'
|
61
|
+
test_files: []
|