hateda2md 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/.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: