typepad_to_jekyll 0.0.1
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.
- 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: []
|