hateda2md 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hateda2md.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 kyoendo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # Hateda2md
2
+
3
+ This is a converter that build separated markdown files using for Jekyll from a Hatena-Diary XML file, which written with Hatena notations. You can set several pre-defined filters and/or can define your original filters.
4
+
5
+ `Hateda2md`は、はてな記法で書かれたXMLファイルから、Jekyll用のMarkdownファイルを生成するコンバータです。定義済みフィルタを使って、または自身でフィルタを定義して変換を行うことができます。
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'hateda2md'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install hateda2md
20
+
21
+ ## Usage
22
+
23
+ require "Hateda2md"
24
+
25
+ mdb = HateDa::MdBuilder.new('hatena-diary.xml')
26
+
27
+ # set several pre-defined filters
28
+ # 幾つかの定義済みフィルタをセットする
29
+ mdb.set :title
30
+ mdb.set :subtitle
31
+ mdb.set :link
32
+ mdb.set :amazon
33
+
34
+ # run converter
35
+ # 変換を実行する
36
+ mdb.run
37
+
38
+ # save converted data to separated markdown files correspond to each entry
39
+ # 変換後のデータを各エントリーに対応した複数のMarkdownファイルに保存する
40
+ mdb.save_to_files
41
+
42
+ This process create several markdown files under `md` directory.
43
+
44
+ 本処理により`md`ディレクトリ以下に、複数のmarkdownファイルが生成されます。
45
+
46
+ To set all pre-defined filters, you can call `MdBuilder#pre_defined_filters` or `HateDa::Converter.pre_defined_filters` method.
47
+
48
+ すべての定義済みフィルタをセットするには、`MdBuilder#pre_defined_filters`または`HateDa::Converter.pre_defined_filters`メソッドを呼びます。
49
+
50
+ # read all pre-defined filters
51
+ # すべての定義済みフィルタを呼ぶ
52
+ filters = mdb.pre_defined_filters
53
+ # => [:title, :subtitle, :subsubtitle, :order_list, :unorder_list, :blockquote, :pre, :super_pre, :footnote, :br, :link, :hatebu, :amazon, :youtube, :image, :gist]
54
+
55
+ # set all the pre-defined filters
56
+ # すべての定義済みフィルタをセットする
57
+ filters.each { |f| mdb.set f }
58
+
59
+ You can define filters.
60
+
61
+ 独自フィルタを定義できます。
62
+
63
+ # define a filter to convert wikipedia hatena tag to a correspond liquid tag
64
+ # はてな記法によるwikipediaタグをliquid tagに変換するフィルタを定義する
65
+ mdb.filter(/\[wikipedia:(.*?)\]/) do |md, st|
66
+ st[:wikipedias] << md[1]
67
+ "{% wikipedia #{md[1]} %}"
68
+ end
69
+
70
+ `MdBuilder#run` can take parameters for selecting entries to be converted.
71
+
72
+ `MdBuilder#run`に引数を渡すことで、特定のエントリだけを変換することができます。
73
+
74
+ # convert only #20 entry
75
+ # 20番目のエントリだけを変換
76
+ mdb.run(20)
77
+
78
+ # convert #100 to last entries
79
+ # 100番から最後のエントリを変換
80
+ mdb.run(100..-1)
81
+
82
+ # convert 20 entries from #10
83
+ # 10番から20件を変換
84
+ mdb.run(10,20)
85
+
86
+
87
+ ## Contributing
88
+
89
+ 1. Fork it
90
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
91
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
92
+ 4. Push to the branch (`git push origin my-new-feature`)
93
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ # encoding: UTF-8
2
+ require "../lib/hateda2md"
3
+
4
+ mdb = HateDa::MdBuilder.new('example.xml')
5
+
6
+ filters = mdb.pre_defined_filters
7
+ filters.each { |f| mdb.set f }
8
+ mdb.run(0..200)
9
+ mdb.save_to_files
data/hateda2md.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hateda2md/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["kyoendo"]
6
+ gem.email = ["postagie@gmail.com"]
7
+ gem.description = %q{Convert Hatena-Diary XML file to Markdown files for Jekyll}
8
+ gem.summary = %q{
9
+ This is a converter that build separated markdown files using for Jekyll from a Hatena-Diary XML file, which written with Hatena notations. You can set several pre-defined filters or can define your original filters.
10
+ }.strip
11
+ gem.homepage = "https://github.com/melborne/hateda2md"
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "hateda2md"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = Hateda2md::VERSION
18
+ gem.required_ruby_version = '>=1.9.2'
19
+ gem.add_development_dependency 'rspec'
20
+ gem.add_dependency 'nokogiri'
21
+ gem.add_dependency 'gsub_filter'
22
+ end
@@ -0,0 +1,176 @@
1
+ # encoding: UTF-8
2
+ require "gsub_filter"
3
+ require "uri"
4
+
5
+ module HateDa::Converter
6
+ class NoFilterError < StandardError; end
7
+
8
+ def set(item, *args)
9
+ unless HateDa::Converter.pre_defined_filters(true).include?(item)
10
+ raise NoFilterError, "#{item} does not pre-defined."
11
+ end
12
+ send item, *args
13
+ end
14
+
15
+ def to_md(hdtext)
16
+ (@gf ||= GsubFilter.new).run(hdtext)
17
+ end
18
+
19
+ def clear_filter
20
+ @gf.filters.clear if @gf
21
+ end
22
+
23
+ def stocks
24
+ @gf.stocks if @gf
25
+ end
26
+
27
+ def filter(pattern, opt={}, &replace)
28
+ @gf ||= GsubFilter.new
29
+ @gf.filter(pattern, opt, &replace)
30
+ end
31
+
32
+ def self.pre_defined_filters(aliases=false)
33
+ als = aliases ? [] : [:header, :subheader, :subsubheader, :ul, :ol]
34
+ private_instance_methods(false) - [:SYM] - als
35
+ end
36
+
37
+ private
38
+ def SYM(key)
39
+ {h1:'#',h2:'##',h3:'###',h4:'####',h5:'#####'}[key]
40
+ end
41
+
42
+ def title(h=:h1)
43
+ filter(/\*p?\d+\*(.*)$/) do |md, st|
44
+ st[:titles] << md[1]
45
+ "#{SYM(h)}#{md[1]}"
46
+ end
47
+ filter(/^#{SYM(h)}.*$/, global:false) do |md, st|
48
+ st[:titles].size == 1 ? '' : md.to_s
49
+ end
50
+ end
51
+ alias :header :title
52
+
53
+ def subtitle(h=:h2)
54
+ filter(/^\*\*((?!\*).*)$/) do |md, st|
55
+ st[:subtitles] << md[1]
56
+ "#{SYM(h)}#{md[1]}"
57
+ end
58
+ end
59
+ alias :subheader :subtitle
60
+
61
+ def subsubtitle(h=:h3)
62
+ filter(/^\*\*\*((?!\*).*)$/) do |md, st|
63
+ st[:subsubtitles] << md[1]
64
+ "#{SYM(h)}#{md[1]}"
65
+ end
66
+ end
67
+ alias :subsubheader :subsubtitle
68
+
69
+ def order_list
70
+ filter(/^(\++)\s*(.*?)$/) do |md, st|
71
+ st[:order_lists] << md[2]
72
+ shift = (" " * 4) * (md[1].size-1)
73
+ "#{shift}1. #{md[2]}"
74
+ end
75
+ end
76
+ alias :ol :order_list
77
+
78
+ def unorder_list
79
+ filter(/^(\-+)\s*(.*?)$/) do |md, st|
80
+ st[:unorder_lists] << md[2]
81
+ shift = (" " * 4) * (md[1].size-1)
82
+ "#{shift}- #{md[2]}"
83
+ end
84
+ end
85
+ alias :ul :unorder_list
86
+
87
+ def blockquote
88
+ filter(/^>>\n(.*?)^<<$/m) do |md|
89
+ "\n" + md[1].lines.map { |line| "> #{line}" }.join
90
+ end
91
+ end
92
+
93
+ def pre
94
+ filter(/^>\|\n(.*?)^\|<$/m) do |md|
95
+ "\n" + md[1].lines.map { |line| " #{line}" }.join
96
+ end
97
+ end
98
+
99
+ def super_pre
100
+ filter(/^>\|(\w+)?\|/) do |md|
101
+ lang = md[1] || "bash"
102
+ "{% highlight #{lang} %}"
103
+ end
104
+
105
+ filter(/^\|\|</) { "{% endhighlight %}" }
106
+ end
107
+
108
+ def footnote
109
+ filter(/\(\((.*?)\)\)/) do |md, st|
110
+ "{% fn_ref #{st[:footnotes].size+1} %}"
111
+ .tap { st[:footnotes] << "{% fn #{md[1]} %}" }
112
+ end
113
+ end
114
+
115
+ def br
116
+ filter("\n\n") { "\n" }
117
+ end
118
+
119
+ def link
120
+ url_r = URI.regexp(['http', 'https'])
121
+
122
+ filter(/(?:(?<=[ \(])|^)#{url_r}(?!\s%})(?:(?=[ \)])|$)/) do |md|
123
+ "[#{md}](#{md})"
124
+ end
125
+
126
+ filter(/\[(#{url_r})(?::title=?(.*?))\]/) do |md|
127
+ t = md.captures.last
128
+ title = t.empty? ? ((st = stocks[:titles]) ? st.first : 'link') : t
129
+ "[#{title}](#{md[1]})"
130
+ end
131
+ end
132
+
133
+ def hatebu
134
+ url_r = URI.regexp(['http', 'https'])
135
+ filter(/\[(#{url_r}):bookmark\]/) do |md|
136
+ "{% hatebu #{md[1]} %}"
137
+ end
138
+ end
139
+
140
+ def amazon
141
+ filter(/\[?(?:isbn|asin):(\w+)(?::(title|detail|image))?\]?/i) do |md|
142
+ case md[2]
143
+ when 'title'
144
+ "{{ '#{md[1]}' | amazon_link }}"
145
+ when 'image'
146
+ "{{ '#{md[1]}' | amazon_medium_image }}"
147
+ when 'detail'
148
+ "{{ '#{md[1]}' | amazon_medium_image }}\n{{ '#{md[1]}' | amazon_link }} by {{ '#{md[1]}' | amazon_authors }}"
149
+ else
150
+ "{{ '#{md[1]}' | amazon_medium_image }}"
151
+ end
152
+ end
153
+ end
154
+
155
+ def youtube
156
+ filter(/[\(\[]?\s*https?:\/\/.*?youtube.*?\?v=([a-zA-Z0-9_-]+):movie\s*[\]\)]?/) do |md|
157
+ "{% youtube #{md[1]} %}"
158
+ end
159
+ end
160
+
161
+ def image
162
+ filter(/\[?f:id:(.*?):(\d+)(\w):image.*?\]?/) do |md|
163
+ m1, m2, m3 = md.captures
164
+ ft = %w(png jpg bmp gif).detect { |e| e[/^#{m3}/] }
165
+ host = "http://img.f.hatena.ne.jp/images/fotolife"
166
+ %{\n![image](#{host}/#{m1[0]}/#{m1}/#{m2[0,8]}/#{m2}.#{ft})\n}
167
+ end
168
+ end
169
+
170
+ def gist
171
+ host = %r{https?://gist.github.com/}
172
+ filter(/<script src=\"#{host}(\d+)\.js\?file=(.*?)\"><\/script>/) do |md|
173
+ "{% gist #{md[1]} #{md[2]} %}"
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+
3
+ class HateDa::Entry < Struct.new(:ent_date, :ent_body, :ent_mdbody, :ent_title)
4
+ end
5
+
@@ -0,0 +1,105 @@
1
+ # encoding: UTF-8
2
+ require "nokogiri"
3
+ class String
4
+ def ~
5
+ mergin = scan(/^ +/).map(&:size).min
6
+ gsub(/^ {#{mergin}}/, '')
7
+ end
8
+
9
+ def to_nil
10
+ self.empty? ? nil : self
11
+ end
12
+ end
13
+
14
+ class HateDa::MdBuilder
15
+ attr_reader :entries
16
+ def initialize(path)
17
+ @entries = build_entries(path)
18
+ end
19
+
20
+ def build_entries(filepath)
21
+ xml = Nokogiri::XML(open filepath)
22
+ xml.search('day').map do |ent|
23
+ date = ent.attributes['date'].value
24
+ body = ent.css('body').text.strip
25
+ mdbody = nil
26
+ title = ent.attributes['title'].value
27
+ HateDa::Entry[date, body, mdbody, title]
28
+ end
29
+ end
30
+
31
+ def set(item, *args)
32
+ entries.each { |ent| ent.set item, *args }
33
+ end
34
+
35
+ def filter(pattern, opt={}, &replace)
36
+ entries.each { |ent| ent.filter(pattern, opt, &replace) }
37
+ end
38
+
39
+ def pre_defined_filters(alias_flag=false)
40
+ HateDa::Converter.pre_defined_filters(alias_flag)
41
+ end
42
+
43
+ def run(*range)
44
+ range = [0..-1] if range.empty?
45
+ entries[*range].map do |entry|
46
+ md = entry.to_md(entry.ent_body)
47
+ entry.ent_title = get_title(entry) if entry.ent_title.empty?
48
+ entry.ent_mdbody = md
49
+ entry
50
+ end
51
+ end
52
+
53
+ def save_to_files(dir='md', ext='md')
54
+ md_entries = entries.select { |ent| ent.ent_mdbody }
55
+ unless md_entries.empty?
56
+ Dir.mkdir(dir) unless Dir.exist?(dir)
57
+ md_entries.each do |ent|
58
+ path = "#{dir}/#{ent.ent_date}-#{title_for_file(ent.ent_title)}.#{ext}"
59
+ File.open(path, 'w') do |f|
60
+ f.puts header(ent.ent_title, ent.ent_date)
61
+ f.puts ent.ent_mdbody
62
+ f.puts footnotes(ent.stocks[:footnotes]) unless ent.stocks[:footnotes].empty?
63
+ end
64
+ end
65
+ end
66
+ rescue => e
67
+ print "class => #{e.class}\nmessage => #{e.message}\nbacktrace => #{e.backtrace}\n"
68
+ end
69
+
70
+ private
71
+ def get_title(entry)
72
+ entry.stocks[:titles].first || 'notitle'
73
+ end
74
+
75
+ def title_for_file(title)
76
+ title.scan(/\w+/).join('-').to_nil || 'notitle'
77
+ end
78
+
79
+ def header(title, date)
80
+ title = title.gsub(/['"`]/, '')
81
+ ~<<-EOS
82
+ ---
83
+ layout: post
84
+ title: "#{title}"
85
+ date: #{date}
86
+ comments: true
87
+ categories:
88
+ tags:
89
+ published: true
90
+ ---
91
+
92
+ EOS
93
+ end
94
+
95
+ def footnotes(fnotes)
96
+ notes = fnotes.map { |note| "#{note}" }.join("\n ")
97
+ ~<<-EOS
98
+
99
+ {% footnotes %}
100
+ #{notes}
101
+ {% endfootnotes %}
102
+ EOS
103
+ end
104
+ end
105
+
@@ -0,0 +1,3 @@
1
+ module Hateda2md
2
+ VERSION = "0.0.1"
3
+ end
data/lib/hateda2md.rb ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ module HateDa
4
+ require_relative 'hateda2md/entry'
5
+ require_relative 'hateda2md/mdbuilder'
6
+ require_relative 'hateda2md/converter'
7
+
8
+ Entry.send(:include, Converter)
9
+ end
10
+
@@ -0,0 +1,369 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative "spec_helper"
4
+
5
+ include HateDa::Converter
6
+
7
+ describe HateDa::Converter do
8
+ context "pass a text to to_md method" do
9
+ before(:each) do
10
+ @hdtext = <<EOS
11
+ *p1*title1
12
+ content1
13
+ content2
14
+ **subtitle1
15
+ content3
16
+ content4
17
+ **subtitle2
18
+ contetn5
19
+ *123*title2
20
+
21
+ EOS
22
+ end
23
+
24
+ after(:each) do
25
+ clear_filter
26
+ end
27
+
28
+ it "should not change the text when filter is empty" do
29
+ hdtext = "*p1*title1\ncontent"
30
+ to_md(hdtext).should eql hdtext
31
+ end
32
+
33
+ it "should raise error when set non-defined tags" do
34
+ ->{ set(:hello) }.should raise_error(HateDa::Converter::NoFilterError)
35
+ end
36
+
37
+ context "header" do
38
+ it "change a header" do
39
+ hdtext = "*p1*title1\ncontent\n*p2*title2"
40
+ md = "#title1\ncontent\n#title2"
41
+ set :header
42
+ to_md(hdtext).should eql md
43
+ stocks[:titles].should eql ['title1', 'title2']
44
+ end
45
+
46
+ it "change a header by h3" do
47
+ hdtext = "*p1*title1\ncontent\n*p2*title2\ncontent"
48
+ md = "###title1\ncontent\n###title2\ncontent"
49
+ set :header, :h3
50
+ to_md(hdtext).should eql md
51
+ end
52
+
53
+ it "change several headers with :title(alias of :header)" do
54
+ hdtext = "*123*title1\ncontent1\n*p3*title2\ncontent2"
55
+ md = "#title1\ncontent1\n#title2\ncontent2"
56
+ set :title
57
+ to_md(hdtext).should eql md
58
+ stocks[:titles].should eql ['title1', 'title2']
59
+ end
60
+
61
+ it "change a subheader" do
62
+ hdtext = "**subtitle1\ncontent**2"
63
+ md = "##subtitle1\ncontent**2"
64
+ set :subheader
65
+ to_md(hdtext).should eql md
66
+ end
67
+
68
+ it "change subheaders with :subtitle(alias of :subheader)" do
69
+ hdtext = "**subtitle1\ncontent1\n**subtitle2\ncontent**2"
70
+ md = "##subtitle1\ncontent1\n##subtitle2\ncontent**2"
71
+ set :subtitle
72
+ to_md(hdtext).should eql md
73
+ end
74
+
75
+ it "change a subsubheader" do
76
+ hdtext = "***subsubtitle1\ncontent***2"
77
+ md = "###subsubtitle1\ncontent***2"
78
+ set :subsubheader
79
+ to_md(hdtext).should eql md
80
+ end
81
+
82
+ it "change mixed title cases1" do
83
+ hdtext = "*p1*title\n***sstitle\ncontent\n**stit***le\nconten**t\n***sstitle"
84
+ set :subheader
85
+ md = "*p1*title\n***sstitle\ncontent\n##stit***le\nconten**t\n***sstitle"
86
+ to_md(hdtext).should eql md
87
+ end
88
+
89
+ it "change mixed title cases2" do
90
+ hdtext = "*p1*title\n***sstitle\ncontent\n**stit***le\nconten**t\n***sstitle"
91
+ set :subsubtitle
92
+ md = "*p1*title\n###sstitle\ncontent\n**stit***le\nconten**t\n###sstitle"
93
+ to_md(hdtext).should eql md
94
+ end
95
+
96
+ it "change mixed title cases3" do
97
+ hdtext = "*p1*title\n***sstitle\ncontent\n**stit***le\n*p2*title2\nconten**t\n***sstitle"
98
+ set :title
99
+ set :subtitle
100
+ md = "#title\n***sstitle\ncontent\n##stit***le\n#title2\nconten**t\n***sstitle"
101
+ to_md(hdtext).should eql md
102
+ end
103
+
104
+ it "change mixed title cases4" do
105
+ hdtext = "*p1*title\n***sstitle1\ncontent\n**stit***le\nconten**t\n***sstitle2"
106
+ set :subtitle
107
+ set :subsubtitle
108
+ md = "*p1*title\n###sstitle1\ncontent\n##stit***le\nconten**t\n###sstitle2"
109
+ to_md(hdtext).should eql md
110
+ end
111
+ end
112
+
113
+ context "list" do
114
+ it "change ordered list" do
115
+ hdtext = "+item1\n+item2\n+item3"
116
+ set :order_list
117
+ md = "1. item1\n1. item2\n1. item3"
118
+ to_md(hdtext).should eql md
119
+ end
120
+
121
+ it "change nested ordered list" do
122
+ hdtext = <<EOS
123
+ +item1
124
+ ++item1-1
125
+ ++item1-2
126
+ +item2
127
+ +item3
128
+ ++item3-1
129
+ +++item3-1-1
130
+ +++item3-1-2
131
+ ++item3-2
132
+ EOS
133
+
134
+ md = <<EOS
135
+ 1. item1
136
+ 1. item1-1
137
+ 1. item1-2
138
+ 1. item2
139
+ 1. item3
140
+ 1. item3-1
141
+ 1. item3-1-1
142
+ 1. item3-1-2
143
+ 1. item3-2
144
+ EOS
145
+ set :order_list
146
+ to_md(hdtext).should eql md
147
+ end
148
+
149
+ it "change unordered list" do
150
+ hdtext = "-item1\n-item2\n-item3"
151
+ set :unorder_list
152
+ md = "- item1\n- item2\n- item3"
153
+ to_md(hdtext).should eql md
154
+ end
155
+
156
+ it "change nested unordered list" do
157
+ hdtext = <<EOS
158
+ -item1
159
+ --item1-1
160
+ --item1-2
161
+ -item2
162
+ -item3
163
+ --item3-1
164
+ ---item3-1-1
165
+ ---item3-1-2
166
+ --item3-2
167
+ EOS
168
+
169
+ md = <<EOS
170
+ - item1
171
+ - item1-1
172
+ - item1-2
173
+ - item2
174
+ - item3
175
+ - item3-1
176
+ - item3-1-1
177
+ - item3-1-2
178
+ - item3-2
179
+ EOS
180
+ set :unorder_list
181
+ to_md(hdtext).should eql md
182
+ end
183
+
184
+
185
+ end
186
+
187
+ context "pre" do
188
+ it "change blockquote '>> <<' to '>'" do
189
+ hdtext = <<EOS
190
+ >>
191
+ blockquoted
192
+ blockquoted
193
+ blockquoted
194
+ <<
195
+ EOS
196
+ md = <<EOS
197
+
198
+ > blockquoted
199
+ > blockquoted
200
+ > blockquoted
201
+
202
+ EOS
203
+ set :blockquote
204
+ to_md(hdtext).should eql md
205
+ end
206
+
207
+ it "change pre '>|' to 4 spaces" do
208
+ hdtext = <<EOS
209
+ >|
210
+ blockquoted
211
+ blockquoted
212
+ blockquoted
213
+ |<
214
+ EOS
215
+ md = <<EOS
216
+
217
+ blockquoted
218
+ blockquoted
219
+ blockquoted
220
+
221
+ EOS
222
+ set :pre
223
+ to_md(hdtext).should eql md
224
+ end
225
+
226
+ it "change super_pre '>|type|' to highlight tag" do
227
+ hdtext = <<EOS
228
+ >|ruby|
229
+ def hello(name)
230
+ "hello, \#{name}!"
231
+ end
232
+ ||<
233
+ EOS
234
+ md = <<EOS
235
+ {% highlight ruby %}
236
+ def hello(name)
237
+ "hello, \#{name}!"
238
+ end
239
+ {% endhighlight %}
240
+ EOS
241
+ set :super_pre
242
+ to_md(hdtext).should eql md
243
+ end
244
+ end
245
+
246
+ context "footnote" do
247
+ it "change '(())' to footnote tag" do
248
+ hdtext = "sentence((aaa)), sentence\nsentence((bbb))"
249
+ md = "sentence{% fn_ref 1 %}, sentence\nsentence{% fn_ref 2 %}"
250
+ set :footnote
251
+ to_md(hdtext).should eql md
252
+ stocks[:footnotes].should eql ["{% fn aaa %}", "{% fn bbb %}"]
253
+ end
254
+ end
255
+
256
+ context "br" do
257
+ it "change '\n\n' to <br/>" do
258
+ hdtext = "content\n\ncontent\ncontent\n\ncontent"
259
+ md = "content\ncontent\ncontent\ncontent"
260
+ set :br
261
+ to_md(hdtext).should eql md
262
+ end
263
+ end
264
+
265
+ context "link" do
266
+ it "change a url to a link" do
267
+ hdtext = <<EOS
268
+ sentence
269
+ http://www.abc.com
270
+ sentence http://www.efg.com/123_456 sentence
271
+ sentence(https://www.xyz.co.jp),sen..
272
+ EOS
273
+ md = <<EOS
274
+ sentence
275
+ [http://www.abc.com](http://www.abc.com)
276
+ sentence [http://www.efg.com/123_456](http://www.efg.com/123_456) sentence
277
+ sentence([https://www.xyz.co.jp](https://www.xyz.co.jp)),sen..
278
+ EOS
279
+ set :link
280
+ to_md(hdtext).should eql md
281
+ end
282
+
283
+ it "change a url with title to a link" do
284
+ hdtext = <<EOS
285
+ *p1*Title X
286
+ sentence
287
+ [http://www.abc.com/:title]
288
+ sentence [http://www.efg.com/123_456:title=Title1] sentence
289
+ *p3*Title Y
290
+ sentence([https://www.xyz.co.jp/:title=Title no.2]),sen..
291
+ http://mmm.ff.co.jp/
292
+ EOS
293
+ md = <<EOS
294
+ #Title X
295
+ sentence
296
+ [Title X](http://www.abc.com/)
297
+ sentence [Title1](http://www.efg.com/123_456) sentence
298
+ #Title Y
299
+ sentence([Title no.2](https://www.xyz.co.jp/)),sen..
300
+ [http://mmm.ff.co.jp/](http://mmm.ff.co.jp/)
301
+ EOS
302
+ set :title
303
+ set :link
304
+ to_md(hdtext).should eql md
305
+ end
306
+ end
307
+
308
+ context "amazon" do
309
+ it "change amazon detail link to amazon tag" do
310
+ hdtext = "[asin:4797356014:detail]"
311
+ md = "{{ '4797356014' | amazon_medium_image }}\n{{ '4797356014' | amazon_link }} by {{ '4797356014' | amazon_authors }}"
312
+ set :amazon
313
+ to_md(hdtext).should eql md
314
+ end
315
+
316
+ it "change amazon image link to amazon tag" do
317
+ hdtext = "[asin:4797356014:image]"
318
+ md = "{{ '4797356014' | amazon_medium_image }}"
319
+ set :amazon
320
+ to_md(hdtext).should eql md
321
+ end
322
+ end
323
+
324
+ context "youtube" do
325
+ it "change youtube link to youtube tag" do
326
+ hdtext = "[http://www.youtube.com/watch?v=oDSigzI6YKw:movie]"
327
+ md = "{% youtube oDSigzI6YKw %}"
328
+ set :youtube
329
+ to_md(hdtext).should eql md
330
+ end
331
+ end
332
+
333
+ context "fotolife" do
334
+ it "change fotolife link to image tag" do
335
+ hdtext = "[f:id:keyesberry:20110209105103p:image]"
336
+ md = "\n![image](http://img.f.hatena.ne.jp/images/fotolife/k/keyesberry/20110209/20110209105103.png)\n"
337
+ set :image
338
+ to_md(hdtext).should eql md
339
+ end
340
+ end
341
+
342
+ context "gist" do
343
+ it "change gist link to gist tag" do
344
+ hdtext = %{<script src="https://gist.github.com/2177656.js?file=gsub_filter.rb"></script>}
345
+ md = "{% gist 2177656 gsub_filter.rb %}"
346
+ set :gist
347
+ to_md(hdtext).should eql md
348
+ end
349
+ end
350
+
351
+ context "hatebu" do
352
+ it "change hatena bookmark link to gist tag" do
353
+ hdtext = %{[http://d.hatena.ne.jp/keyesberry/20090318/p1:bookmark]}
354
+ md = "{% hatebu http://d.hatena.ne.jp/keyesberry/20090318/p1 %}"
355
+ set :hatebu
356
+ to_md(hdtext).should eql md
357
+ end
358
+
359
+ it "change mixed case for hatebu link and regular link" do
360
+ hdtext = %{[http://d.hatena.ne.jp/keyesberry/20090318/p1:title='hello'][http://d.hatena.ne.jp/keyesberry/20090318/p1:bookmark]}
361
+ md = "['hello'](http://d.hatena.ne.jp/keyesberry/20090318/p1){% hatebu http://d.hatena.ne.jp/keyesberry/20090318/p1 %}"
362
+ set :hatebu
363
+ set :link
364
+ to_md(hdtext).should eql md
365
+ end
366
+ end
367
+ end
368
+ end
369
+
@@ -0,0 +1,34 @@
1
+ # encoding: UTF-8
2
+ require_relative 'spec_helper'
3
+
4
+ describe HateDa::Entry do
5
+ context "when created" do
6
+ context "without argument" do
7
+ before(:each) do
8
+ @entry = HateDa::Entry.new
9
+ end
10
+
11
+ it "should have ent_date, ent_title, ent_body, ent_mdbody property" do
12
+ ->{ @entry.ent_date }.should_not raise_error
13
+ ->{ @entry.ent_title }.should_not raise_error
14
+ ->{ @entry.ent_body }.should_not raise_error
15
+ ->{ @entry.ent_mdbody }.should_not raise_error
16
+ end
17
+ end
18
+
19
+ context "with data" do
20
+ before(:each) do
21
+ @date, @body, @mdbody, @title = '2012-04-21', "hello\nfriend!", "", "hello"
22
+ @entry = HateDa::Entry.new(@date, @body, @mdbody, @title)
23
+ end
24
+
25
+ it "should return data" do
26
+ @entry.ent_date.should eql @date
27
+ @entry.ent_body.should eql @body
28
+ @entry.ent_mdbody.should eql @mdbody
29
+ @entry.ent_title.should eql @title
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,111 @@
1
+ # encoding: UTF-8
2
+ require_relative "spec_helper"
3
+ require "fileutils"
4
+
5
+ describe HateDa::MdBuilder do
6
+ before(:each) do
7
+ @mdb = HateDa::MdBuilder.new('spec/test1.xml')
8
+ end
9
+
10
+ context "when initialized with a sample xml" do
11
+ it "create entries property" do
12
+ ->{ @mdb.entries }.should_not raise_error
13
+ end
14
+
15
+ it "create entries which are Entry classes" do
16
+ @mdb.entries.each { |entry| entry.class.should eql HateDa::Entry }
17
+ end
18
+
19
+ it "set date, body, mdbody, titlefor each entry" do
20
+ body = "*p1*Title\nline1\nline2\nline3\n**SubTitle\nline4"
21
+ a_entry = @mdb.entries.first
22
+ a_entry.ent_date.should eql '2012-03-01'
23
+ a_entry.ent_body.should eql body
24
+ a_entry.ent_mdbody.should eql nil
25
+ a_entry.ent_title.should eql 'hello'
26
+ end
27
+ end
28
+
29
+ context "when run for building markdown data for each entry" do
30
+ it "set original body to mdbody of entry with no filter" do
31
+ @mdb.run
32
+ a_entry = @mdb.entries.first
33
+ a_entry.ent_mdbody.should eql a_entry.ent_body
34
+ end
35
+
36
+ it "set markdowned body to mdbody of entry" do
37
+ md = "\nline1\nline2\nline3\n##SubTitle\nline4"
38
+ a_entry = @mdb.entries.first
39
+ a_entry.set :title
40
+ a_entry.set :subtitle
41
+ @mdb.run
42
+ a_entry.ent_mdbody.should eql md
43
+ end
44
+
45
+ it "set title of entry when it is empty" do
46
+ body = "*p1*Title1\nline1\nline2\nline3\n\n*p2*Title2\nline4\nline5"
47
+ a_entry, b_entry = @mdb.entries.take(2)
48
+ a_entry.set :title
49
+ b_entry.set :title
50
+ @mdb.run
51
+ a_entry.ent_title.should eql 'hello'
52
+ b_entry.ent_title.should eql 'Title1'
53
+ end
54
+ end
55
+
56
+ context "set filter to all entries at once" do
57
+ before(:each) do
58
+ @mdb2 = HateDa::MdBuilder.new('spec/test2.xml')
59
+ end
60
+
61
+ it "set title with set method" do
62
+ md1 = "\nline1\nline2\nline3\n**SubTitle\nline4"
63
+ md2 = "#Title1\nline1\nline2\nline3\n\n#Title2\nline4\nline5"
64
+ @mdb2.set :title
65
+ @mdb2.run
66
+ entries = @mdb2.entries.zip([md1, md2])
67
+ entries.each do |ent, md|
68
+ ent.ent_mdbody.should eql md
69
+ end
70
+ end
71
+
72
+ it "set title & subtitle with filter method" do
73
+ md1 = "#Title\nline1\nline2\nline3\n##SubTitle\nline4"
74
+ md2 = "#Title1\nline1\nline2\nline3\n\n#Title2\nline4\nline5"
75
+ @mdb2.filter(/\*p?\d+\*(.*)$/) do |md, st|
76
+ st[:titles] << md[1]
77
+ "##{md[1]}"
78
+ end
79
+ @mdb2.filter(/^\*\*((?!\*).*)$/) do |md, st|
80
+ st[:subtitles] << md[1]
81
+ "###{md[1]}"
82
+ end
83
+ @mdb2.run
84
+ entries = @mdb2.entries.zip([md1, md2])
85
+ entries.each do |ent, md|
86
+ ent.ent_mdbody.should eql md
87
+ end
88
+ end
89
+ end
90
+
91
+ context "run option" do
92
+ it "run for top 2 entries" do
93
+ nilness = [false, false, true, true]
94
+ @mdb.run(0,2)
95
+ @mdb.entries.take(4).map { |ent| ent.ent_mdbody.nil? }.should eql nilness
96
+ end
97
+ end
98
+
99
+ context "save md data to files" do
100
+ it "save md to separate files" do
101
+ files = Dir['md/*']
102
+ FileUtils.rm(Dir['md/*']) unless files.empty?
103
+ num = 5
104
+ filters = @mdb.pre_defined_filters
105
+ filters.each { |f| @mdb.set f }
106
+ @mdb.run(0...num)
107
+ @mdb.save_to_files
108
+ Dir['md/*'].size.should eql num
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,4 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require "rspec"
4
+ require "hateda2md"
data/spec/test1.xml ADDED
@@ -0,0 +1,127 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <diary>
3
+ <day date="2012-03-01" title="hello">
4
+ <body>
5
+ *p1*Title
6
+ line1
7
+ line2
8
+ line3
9
+ **SubTitle
10
+ line4
11
+
12
+ </body>
13
+ </day>
14
+ <day date="2012-04-01" title="">
15
+ <body>
16
+ *p1*Title1
17
+ line1
18
+ line2
19
+ line3
20
+
21
+ *p2*Title2
22
+ line4
23
+ line5
24
+
25
+ *123*タイトル3
26
+ ライン1
27
+ ライン2
28
+ ライン3
29
+ </body>
30
+ </day>
31
+ <day date="2012-04-04" title="">
32
+ <body>
33
+ *1135046820*Title1
34
+
35
+ content
36
+ **SubTitle1
37
+ line1
38
+ line2
39
+ **SubTitle2
40
+ line3
41
+ +list1
42
+ +list2
43
+ ++list2-1
44
+ ++list2-2
45
+
46
+ ***SubSubTitle
47
+ line4
48
+ -list1
49
+ -list2
50
+ -list3
51
+
52
+ http://arena.nikkeibp.co.jp/rev/20051124/114391/index.shtml
53
+
54
+ asin:B000BGEAR2:image:small
55
+ asin:B000BGEAR2:title
56
+
57
+ [http://local.google.co.jp/maps?q=%E6%98%AD%E6%96%87%E7%A4%BE&amp;near=%E5%8D%83%E4%BB%A3%E7%94%B0%E5%8C%BA%E9%BA%B9%E7%94%BA%EF%BC%93&amp;btnG=%E6%A4%9C%E7%B4%A2&amp;sll=36.562600,136.362305&amp;sspn=23.257402,31.684570&amp;t=&amp;hl=ja&amp;cid=35684108,139738742,6343705222926448571&amp;li=lmd&amp;z=0:title]
58
+ Google local
59
+
60
+
61
+ [http://earth.google.com/:title]
62
+
63
+ </body>
64
+ </day>
65
+ <day date="2012-05-16" title="">
66
+ <body>
67
+ *p1*RubyでText Dollarを解く-CodeEval
68
+ なんか想像以上に手こずったよ^^;
69
+ case式内が見苦しい..
70
+ もっと簡単なやり方あるんだろうな
71
+ Integer#dollarizeを定義してみた
72
+
73
+
74
+ 数字を英語のドル表記に変換
75
+ &lt;script src=&quot;https://gist.github.com/1697463.js?file=print_check.rb&quot;&gt;&lt;/script&gt;
76
+
77
+ </body>
78
+ </day>
79
+ <day date="2012-05-18" title="">
80
+ <body>
81
+ *p1*RubyでLevenshtein Distanceを解く-CodeEval
82
+ できません..
83
+ アルゴリズム的にはできてるんだけど(('causes'の解答が一致した))
84
+ 答えを得るのに1時間とかorz..
85
+ 5秒で答えなきゃいけないのに
86
+ あとグローバル変数を使ってしまった
87
+
88
+
89
+ どうも高速化は苦手です
90
+ そこに注力する気がなかなか起きない..
91
+
92
+ [http://d.hatena.ne.jp/keyesberry/20120211/p1:title=RubyのEnumerable#mapをもっと便利にしたいよ - hp12c]
93
+
94
+ &gt;|ruby|
95
+ module Enumerable
96
+ def mapp(op=nil, *args, &amp;blk)
97
+ op ? map { |e| op.intern.to_proc[e, *args]} : map(&amp;blk)
98
+ end
99
+ end
100
+
101
+ langs = [&quot;Ruby&quot;, &quot;Python&quot;, &quot;Lisp&quot;, &quot;Haskell&quot;]
102
+ langs.mapp(:+, 'ist') # =&gt; [&quot;Rubyist&quot;, &quot;Pythonist&quot;, &quot;Lispist&quot;, &quot;Haskellist&quot;]
103
+
104
+ [1, 2, 3].mapp(:+, 10) # =&gt; [11, 12, 13]
105
+
106
+ (1..5).mapp(:**, 2) # =&gt; [1, 4, 9, 16, 25]
107
+
108
+ [[1,2,3,4], [5,6,7,8], [9,10,11,12]].mapp(:last, 2) # =&gt; [[3, 4], [7, 8], [11, 12]]
109
+
110
+ [&quot;ruby&quot;, &quot;python&quot;, &quot;lisp&quot;, &quot;haskell&quot;].mapp(:[], -2, 2) # =&gt; [&quot;by&quot;, &quot;on&quot;, &quot;sp&quot;, &quot;ll&quot;]
111
+ ||&lt;
112
+
113
+ レーベンシュタイン距離が1の語同士をfriendとして
114
+ 与えられた辞書におけるhelloの語から始まる
115
+ friendの輪に含まれるすべての語の数を答える
116
+ &lt;script src=&quot;https://gist.github.com/1697463.js?file=levenshtein_distance.rb&quot;&gt;&lt;/script&gt;
117
+
118
+ &gt;&gt;
119
+ それはここまで来たらもうmappは
120
+ 要らないんじゃないかってことなんだ
121
+ &lt;&lt;
122
+ </body>
123
+ </day>
124
+
125
+
126
+
127
+ </diary>
data/spec/test2.xml ADDED
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <diary>
3
+ <day date="2012-03-01" title="hello">
4
+ <body>
5
+ *p1*Title
6
+ line1
7
+ line2
8
+ line3
9
+ **SubTitle
10
+ line4
11
+
12
+ </body>
13
+ </day>
14
+ <day date="2012-04-01" title="">
15
+ <body>
16
+ *p1*Title1
17
+ line1
18
+ line2
19
+ line3
20
+
21
+ *p2*Title2
22
+ line4
23
+ line5
24
+ </body>
25
+ </day>
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hateda2md
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - kyoendo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2153771040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2153771040
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ requirement: &2153770580 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2153770580
36
+ - !ruby/object:Gem::Dependency
37
+ name: gsub_filter
38
+ requirement: &2153770160 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2153770160
47
+ description: Convert Hatena-Diary XML file to Markdown files for Jekyll
48
+ email:
49
+ - postagie@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .DS_Store
55
+ - .gitignore
56
+ - Gemfile
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - example/example.rb
61
+ - hateda2md.gemspec
62
+ - lib/hateda2md.rb
63
+ - lib/hateda2md/converter.rb
64
+ - lib/hateda2md/entry.rb
65
+ - lib/hateda2md/mdbuilder.rb
66
+ - lib/hateda2md/version.rb
67
+ - spec/hateda_converter_spec.rb
68
+ - spec/hateda_entry_spec.rb
69
+ - spec/hateda_mdbuilder_spec.rb
70
+ - spec/spec_helper.rb
71
+ - spec/test1.xml
72
+ - spec/test2.xml
73
+ homepage: https://github.com/melborne/hateda2md
74
+ licenses: []
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: 1.9.2
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 1.8.11
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: This is a converter that build separated markdown files using for Jekyll
97
+ from a Hatena-Diary XML file, which written with Hatena notations. You can set several
98
+ pre-defined filters or can define your original filters.
99
+ test_files:
100
+ - spec/hateda_converter_spec.rb
101
+ - spec/hateda_entry_spec.rb
102
+ - spec/hateda_mdbuilder_spec.rb
103
+ - spec/spec_helper.rb
104
+ - spec/test1.xml
105
+ - spec/test2.xml
106
+ has_rdoc: