hatenadiary-legacy 0.0.7

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: df2818a88d8b430ede88c6706fd43055826fd849
4
+ data.tar.gz: 7ef4f5ab24b6e970ecfe47957478d2fabf4ec691
5
+ SHA512:
6
+ metadata.gz: d69a727c148d1c214e19dda0100bcb4aef383f20bcda4490a86341ad7659309c6a9ee9122cd0bf0e9739e5cebfa6b11932f6ec08824ad00a1743be0263056bb2
7
+ data.tar.gz: 0a6ca4c9a5b04e55577fef1c1b823207a09e4c418bbb86572881397909c5ff1a90504736ba44d2b957900d7298fbda94c10b892f076c66a76ca4316d60f24091
data/ChangeLog ADDED
@@ -0,0 +1,53 @@
1
+ 2015-02-11 arikui <arikui.ruby@gmail.com>
2
+
3
+ * rename to `hatenadiary-legacy'.
4
+
5
+ * test/test_hatenadiary.rb: use `flexmock' instead of `mocha'
6
+
7
+ * Rakefile: version up tp 0.0.7
8
+
9
+
10
+ 2011-02-24 arikui <arikui.ruby@gmail.com>
11
+
12
+ * lib/hatenadiary.rb: use constant Mechanize instead of WWW::Mechanize because
13
+ it is going to be deprecated.
14
+
15
+ * test/test_hatenadiary.rb: add #test_default_mechanizer to touch a constant Mechanize.
16
+
17
+ * Rakefile: version up to 0.0.6
18
+
19
+ 2009-12-18 arikui <arikui.ruby@gmail.com>
20
+
21
+ * lib/hatenadiary.rb: remove gem() version requirement.
22
+
23
+ * Rakefile: version up to 0.0.5
24
+ (gemspec) fix dependence about nokogiri.
25
+
26
+ 2009-12-04 arikui <arikui.ruby@gmail.com>
27
+
28
+ * Rakefile: define common settings as constant.
29
+ include gemspec.
30
+ (task 'gemspec') added. this task updates `hatenadiary.gemspec' along to spec written in Rakefile.
31
+ It means, don't have to correct `hatenadiary.gemspec' by hand.
32
+ (task 'gem') added.
33
+ (task 'cutter') added. this task uploads gem to GemCutter.
34
+
35
+ * hatenadiary.gemspec: version up to 0.0.4
36
+
37
+ 2009-11-29 arikui <arikui.ruby@gmail.com>
38
+
39
+ * test/test_hatenadiary.rb: replace test by mocha used version.
40
+
41
+ * lib/hatenadiary.rb: use hpricot -> nokogiri 1.3.3
42
+ respond to hatena group diary.
43
+ (HatenaDiary::Client#post) change interface.
44
+ (HatenaDiary::Client#initialize): interface extended for test.
45
+
46
+ 2009-07-03 arikui <arikui.ruby@gmail.com>
47
+
48
+ * lib/hatenadiary.rb: use nokogiri -> hpricot
49
+
50
+ 2009-06-22 arikui <arikui.ruby@gmail.com>
51
+
52
+ * lib/hatenadiary.rb: first version.
53
+
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ Copyright (c) 2009 arikui <arikui.ruby@gmail.com>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above
11
+ copyright notice, this list of conditions and the following
12
+ disclaimer in the documentation and/or other materials provided
13
+ with the distribution.
14
+ * Neither the name of the arikui-hatenadiary nor the names of its
15
+ contributors may be used to endorse or promote products derived
16
+ from this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README ADDED
@@ -0,0 +1,23 @@
1
+ = hatenadiary-legacy
2
+
3
+ It is a library provides
4
+ a client for Hatena Diary to post and delete blog entries.
5
+
6
+ No longer this library is only maintained.
7
+
8
+ Developed on:
9
+ - ruby 2.1.3p242 (2014-09-19 revision 47630) [i386-mingw32]
10
+ - Gem mechanize 2.7.3
11
+
12
+ This list means these environments are enable to make all of /test pass.
13
+
14
+
15
+ == Dependences
16
+
17
+ - mechanize
18
+
19
+
20
+ == Signature
21
+
22
+ arikui (arikui.ruby _at_ gmail.com)
23
+
@@ -0,0 +1,187 @@
1
+ #
2
+ # Distributes under The modified BSD license.
3
+ #
4
+ # Copyright (c) 2009 arikui <http://d.hatena.ne.jp/arikui1911/>
5
+ # All rights reserved.
6
+ #
7
+
8
+ require 'rubygems'
9
+ require 'nokogiri'
10
+ require 'mechanize'
11
+
12
+ # depend on Ruby version
13
+ module HatenaDiary
14
+ module Util
15
+ if RUBY_VERSION >= '1.9'
16
+ def encode_to_utf8(str)
17
+ str.encode(Encoding::UTF_8)
18
+ end
19
+ else
20
+ require 'kconv'
21
+
22
+ def encode_to_utf8(str)
23
+ Kconv.toutf8(str)
24
+ end
25
+ end
26
+
27
+ module_function :encode_to_utf8
28
+ end
29
+ end
30
+
31
+
32
+ module HatenaDiary
33
+ #
34
+ # Allocates Client object and makes it login, execute a received block,
35
+ # and then logout.
36
+ #
37
+ # :call-seq:
38
+ # login(username, password, proxy = nil){|client| ... }
39
+ #
40
+ def login(*args, &block)
41
+ Client.login(*args, &block)
42
+ end
43
+ module_function :login
44
+
45
+ class LoginError < RuntimeError
46
+ def set_account(username, password)
47
+ @username = username
48
+ @password = password
49
+ self
50
+ end
51
+
52
+ attr_reader :username
53
+ attr_reader :password
54
+ end
55
+
56
+ class Client
57
+ def self.mechanizer
58
+ @mechanizer ||= Mechanize
59
+ end
60
+
61
+ def self.mechanizer=(klass)
62
+ @mechanizer = klass
63
+ end
64
+
65
+ # Allocates Client object.
66
+ #
67
+ # If block given, login and execute a received block, and then logout ensurely.
68
+ #
69
+ # [username] Hatena ID
70
+ # [password] Password for _username_
71
+ # [proxy] Proxy configuration; [proxy_url, port_no] | nil
72
+ #
73
+ def self.login(username, password, proxy = nil, &block)
74
+ client = new(username, password)
75
+ client.set_proxy(*proxy) if proxy
76
+ return client unless block_given?
77
+ client.transaction(&block)
78
+ end
79
+
80
+ # Allocates Client object.
81
+ #
82
+ # [username] Hatena ID
83
+ # [password] Password for _username_
84
+ def initialize(username, password, agent = self.class.mechanizer.new)
85
+ @agent = agent
86
+ @username = username
87
+ @password = password
88
+ @current_account = nil
89
+ end
90
+
91
+ # Configure proxy.
92
+ def set_proxy(url, port)
93
+ @agent.set_proxy(url, port)
94
+ end
95
+
96
+ # Login and execute a received block, and then logout ensurely.
97
+ def transaction(username = nil, password = nil)
98
+ raise LocalJumpError, "no block given" unless block_given?
99
+ login(username, password)
100
+ begin
101
+ yield(self)
102
+ ensure
103
+ logout
104
+ end
105
+ end
106
+
107
+ # Returns a client itself was logined or not.
108
+ #
109
+ # -> true | false
110
+ def login?
111
+ @current_account ? true : false
112
+ end
113
+
114
+ # Does login.
115
+ #
116
+ # If _username_ or _password_ are invalid, raises HatenaDiary::LoginError .
117
+ def login(username = nil, password = nil)
118
+ username ||= @username
119
+ password ||= @password
120
+ form = @agent.get("https://www.hatena.ne.jp/login").forms.first
121
+ form["name"] = username
122
+ form["password"] = password
123
+ form["persistent"] = "true"
124
+ response = form.submit
125
+ @current_account = [username, password]
126
+ case response.title
127
+ when "Hatena" then response
128
+ when "Login - Hatena" then raise LoginError.new("login failure").set_account(username, password)
129
+ else raise Exception, '[BUG] must not happen (maybe cannot follow hatena spec)'
130
+ end
131
+ end
132
+
133
+ # Does logout if already logined.
134
+ def logout
135
+ return unless login?
136
+ @agent.get("https://www.hatena.ne.jp/logout")
137
+ account = @current_account
138
+ @current_account = nil
139
+ account
140
+ end
141
+
142
+ # Posts an entry to Hatena diary service.
143
+ #
144
+ # Raises HatenaDiary::LoginError unless logined.
145
+ #
146
+ # options
147
+ # [:trivial] check a checkbox of trivial updating.
148
+ # [:group] assign hatena-group name. edit group diary.
149
+ #
150
+ # Invalid options were ignored.
151
+ def post(yyyy, mm, dd, title, body, options = {})
152
+ title = Util.encode_to_utf8(title)
153
+ body = Util.encode_to_utf8(body)
154
+ form = get_form(yyyy, mm, dd, options[:group]){|r| r.form_with(:name => 'edit') }
155
+ form["year"] = "%04d" % yyyy
156
+ form["month"] = "%02d" % mm
157
+ form["day"] = "%02d" % dd
158
+ form["title"] = title
159
+ form["body"] = body
160
+ form["trivial"] = "true" if options[:trivial]
161
+ @agent.submit form, form.button_with(:name => 'edit')
162
+ end
163
+
164
+ # Deletes an entry from Hatena diary service.
165
+ #
166
+ # Raises HatenaDiary::LoginError unless logined.
167
+ #
168
+ # options
169
+ # [:group] assign hatena-group name. edit group diary.
170
+ #
171
+ # Invalid options were ignored.
172
+ def delete(yyyy, mm, dd, options = {})
173
+ get_form(yyyy, mm, dd, options[:group]){|r| r.forms.last }.submit
174
+ end
175
+
176
+ private
177
+
178
+ def get_form(yyyy, mm, dd, group = nil)
179
+ raise LoginError, "not login yet" unless login?
180
+ vals = [group ? "#{group}.g" : "d",
181
+ @current_account[0],
182
+ yyyy, mm, dd]
183
+ yield @agent.get("http://%s.hatena.ne.jp/%s/edit?date=%04d%02d%02d" % vals)
184
+ end
185
+ end
186
+ end
187
+
@@ -0,0 +1,266 @@
1
+ require 'test/unit'
2
+ require 'flexmock'
3
+ require 'hatenadiary'
4
+
5
+
6
+ class TestHatenaDiaryAPI < Test::Unit::TestCase
7
+ include FlexMock::TestCase
8
+
9
+ def test_default_mechanizer
10
+ HatenaDiary::Client.mechanizer = nil
11
+ assert_equal Mechanize, HatenaDiary::Client.mechanizer
12
+ end
13
+
14
+ def setup
15
+ @username = 'USERNAME'
16
+ @password = 'PASSWORD'
17
+ @proxy_url = "PROXY_URL"
18
+ @proxy_port = "PROXY_PORT"
19
+ end
20
+
21
+ def test_api_login
22
+ flexmock HatenaDiary::Client
23
+ HatenaDiary::Client.should_receive(:login)
24
+ HatenaDiary.login
25
+ end
26
+
27
+ def setup_mocks
28
+ flexmock HatenaDiary::Client
29
+ @client = flexmock("client")
30
+ end
31
+
32
+ def test_login
33
+ setup_mocks
34
+ HatenaDiary::Client.should_receive(:new).with(@username, @password).and_return(@client)
35
+ @client.should_receive(:transaction).and_yield('block delegate check')
36
+ HatenaDiary::Client.login(@username, @password) do |str|
37
+ assert_equal 'block delegate check', str
38
+ end
39
+ end
40
+
41
+ def test_login_with_proxy
42
+ setup_mocks
43
+ HatenaDiary::Client.should_receive(:new).with(@username, @password).and_return(@client)
44
+ @client.should_receive(:set_proxy).with(@proxy_url, @proxy_port)
45
+ @client.should_receive(:transaction).yields('block delegate check')
46
+ HatenaDiary::Client.login(@username, @password, [@proxy_url, @proxy_port]) do |str|
47
+ assert_equal 'block delegate check', str
48
+ end
49
+ end
50
+
51
+ def test_login_without_block
52
+ setup_mocks
53
+ HatenaDiary::Client.should_receive(:new).with(@username, @password).and_return(@client)
54
+ assert_equal @client, HatenaDiary::Client.login(@username, @password)
55
+ end
56
+
57
+ def test_login_without_block_with_proxy
58
+ setup_mocks
59
+ HatenaDiary::Client.should_receive(:new).with(@username, @password).and_return(@client)
60
+ @client.should_receive(:set_proxy).with(@proxy_url, @proxy_port)
61
+ assert_equal @client, HatenaDiary::Client.login(@username, @password, [@proxy_url, @proxy_port])
62
+ end
63
+ end
64
+
65
+
66
+ class TestHatenaDiary < Test::Unit::TestCase
67
+ include FlexMock::TestCase
68
+
69
+ def setup
70
+ @username = 'USERNAME'
71
+ @password = 'PASSWORD'
72
+ @agent = flexmock("agent")
73
+ @client = HatenaDiary::Client.new(@username, @password, @agent)
74
+ end
75
+
76
+ def test_set_proxy
77
+ proxy_url = 'PROXY_URL'
78
+ proxy_port = 'PROXY_PORT'
79
+ @agent.should_receive(:set_proxy).with(proxy_url, proxy_port)
80
+ @client.set_proxy(proxy_url, proxy_port)
81
+ end
82
+
83
+ def test_logout_without_login
84
+ @client.logout
85
+ end
86
+
87
+ module MockHash
88
+ def [](k)
89
+ (@_table ||= {})[k]
90
+ end
91
+
92
+ def []=(k, v)
93
+ (@_table ||= {})[k] = v
94
+ end
95
+
96
+ def to_h
97
+ @_table ||= {}
98
+ end
99
+ end
100
+
101
+ def login_mocking(submit_response_page_title)
102
+ login_page = flexmock("login_page")
103
+ form = flexmock("form").extend(MockHash)
104
+ forms = [form]
105
+ response = flexmock("response")
106
+ @agent.should_receive(:get).with("https://www.hatena.ne.jp/login").and_return(login_page)
107
+ login_page.should_receive(:forms).and_return(forms)
108
+ form.should_receive(:submit).and_return(response)
109
+ response.should_receive(:title).and_return(submit_response_page_title)
110
+ form
111
+ end
112
+
113
+ def logout_mocking
114
+ @agent.should_receive(:get).with("https://www.hatena.ne.jp/logout")
115
+ end
116
+
117
+ def test_login_and_logout
118
+ # before login
119
+ assert !@client.login?
120
+ # login
121
+ form = login_mocking("Hatena")
122
+ @client.login
123
+ assert @client.login?
124
+ assert_equal form["name"], @username
125
+ assert_equal form["password"], @password
126
+ assert_equal form["persistent"], "true"
127
+ # logout
128
+ logout_mocking
129
+ @client.logout
130
+ assert !@client.login?
131
+ end
132
+
133
+ def test_login_failure
134
+ login_mocking "Login - Hatena"
135
+ begin
136
+ @client.login
137
+ rescue HatenaDiary::LoginError => ex
138
+ assert_equal @username, ex.username
139
+ assert_equal @password, ex.password
140
+ else
141
+ flunk "login error must be raised."
142
+ end
143
+ end
144
+
145
+ def test_login_if_hatena_changed
146
+ login_mocking "*jumbled pagetitle*"
147
+ begin
148
+ @client.login
149
+ rescue Exception => ex
150
+ assert /must not happen/ =~ ex.message
151
+ else
152
+ flunk "exception must be raised"
153
+ end
154
+ end
155
+
156
+ def test_transaction
157
+ assert !@client.login?
158
+ login_mocking "Hatena"
159
+ logout_mocking
160
+ @client.transaction do |client|
161
+ assert @client.equal?(client)
162
+ assert @client.login?
163
+ end
164
+ assert !@client.login?
165
+ end
166
+
167
+ def test_transaction_without_block
168
+ assert !@client.login?
169
+ assert_raise LocalJumpError do
170
+ @client.transaction
171
+ end
172
+ assert !@client.login?
173
+ end
174
+
175
+ def post_mocking(host, date_str)
176
+ edit_page = flexmock("edit_page")
177
+ form = flexmock("form").extend(MockHash)
178
+ button = Object.new
179
+ login_mocking "Hatena"
180
+ logout_mocking
181
+ @agent.should_receive(:get).with("http://#{host}.hatena.ne.jp/#{@username}/edit?date=#{date_str}").and_return(edit_page)
182
+ edit_page.should_receive(:form_with).with(:name => 'edit').and_return(form)
183
+ form.should_receive(:button_with).with(:name => 'edit').and_return(button)
184
+ @agent.should_receive(:submit).with(form, button)
185
+ form
186
+ end
187
+
188
+ def test_post
189
+ form = post_mocking("d", "12340506")
190
+ @client.transaction do |client|
191
+ client.post 1234, 5, 6, 'TITLE', 'BODY'
192
+ end
193
+ expected = {
194
+ "year" => "1234",
195
+ "month" => "05",
196
+ "day" => "06",
197
+ "title" => "TITLE",
198
+ "body" => "BODY",
199
+ }
200
+ assert_equal expected, form.to_h
201
+ assert !form["trivial"]
202
+ end
203
+
204
+ def test_post_trivial
205
+ form = post_mocking("d", "20071108")
206
+ @client.transaction do |client|
207
+ client.post 2007, 11, 8, 'TITLE', 'BODY', :trivial => true
208
+ end
209
+ assert_equal "true", form["trivial"]
210
+ end
211
+
212
+ def test_post_group
213
+ post_mocking "hoge.g", "12340506"
214
+ @client.transaction do |client|
215
+ client.post 1234, 5, 6, 'TITLE', 'BODY', :group => 'hoge'
216
+ end
217
+ end
218
+
219
+ def test_post_group_trivial
220
+ form = post_mocking("hoge.g", "12340506")
221
+ @client.transaction do |client|
222
+ client.post 1234, 5, 6, 'TITLE', 'BODY', :group => 'hoge', :trivial => true
223
+ end
224
+ assert_equal "true", form["trivial"]
225
+ end
226
+
227
+ def test_post_without_login
228
+ assert_raise HatenaDiary::LoginError do
229
+ @client.post 1999, 5, 26, "TITLE", "BODY\n"
230
+ end
231
+ end
232
+
233
+ def delete_mocking(host, date_str)
234
+ edit_page = flexmock("edit_page")
235
+ form = flexmock("form").extend(MockHash)
236
+ forms = [form]
237
+ button = Object.new
238
+ login_mocking "Hatena"
239
+ logout_mocking
240
+ @agent.should_receive(:get).with("http://#{host}.hatena.ne.jp/#{@username}/edit?date=#{date_str}").and_return(edit_page)
241
+ edit_page.should_receive(:forms).returns(forms)
242
+ form.should_receive(:submit)
243
+ form
244
+ end
245
+
246
+ def test_delete
247
+ delete_mocking "d", "12340506"
248
+ @client.transaction do |client|
249
+ client.delete 1234, 5, 6
250
+ end
251
+ end
252
+
253
+ def test_delete_group
254
+ delete_mocking "piyo.g", "12340506"
255
+ @client.transaction do |client|
256
+ client.delete 1234, 5, 6, :group => 'piyo'
257
+ end
258
+ end
259
+
260
+ def test_delete_without_login
261
+ assert_raise HatenaDiary::LoginError do
262
+ @client.delete 2009, 8, 30
263
+ end
264
+ end
265
+ end
266
+
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hatenadiary-legacy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - arikui
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: flexmock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A client for Hatena Diary to post and delete blog entries.
56
+ email: arikui.ruby@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files:
60
+ - README
61
+ - LICENSE
62
+ - ChangeLog
63
+ files:
64
+ - ChangeLog
65
+ - LICENSE
66
+ - README
67
+ - lib/hatenadiary.rb
68
+ - test/test_hatenadiary.rb
69
+ homepage: http://wiki.github.com/arikui1911/hatenadiary-legacy
70
+ licenses: []
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options:
74
+ - "--title"
75
+ - hatenadiary-legacy documentation
76
+ - "--opname"
77
+ - index.html
78
+ - "--line-numbers"
79
+ - "--main"
80
+ - README
81
+ - "--inline-source"
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.4.5
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: It is a library provides a client for Hatena Diary to post and delete blog
100
+ entries.
101
+ test_files:
102
+ - test/test_hatenadiary.rb