hatenadiary 0.0.4

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.
Files changed (6) hide show
  1. data/ChangeLog +26 -0
  2. data/LICENSE +28 -0
  3. data/README +25 -0
  4. data/lib/hatenadiary.rb +191 -0
  5. data/test/test_hatenadiary.rb +238 -0
  6. metadata +87 -0
data/ChangeLog ADDED
@@ -0,0 +1,26 @@
1
+ 2009-12-04 arikui <arikui.ruby@gmail.com>
2
+
3
+ * Rakefile: define common settings as constant.
4
+ include gemspec.
5
+ (task 'gemspec') added. this task updates `hatenadiary.gemspec' along to spec written in Rakefile.
6
+ It means, don't have to correct `hatenadiary.gemspec' by hand.
7
+
8
+ * hatenadiary.gemspec: version up to 0.0.4
9
+
10
+ 2009-11-29 arikui <arikui.ruby@gmail.com>
11
+
12
+ * test/test_hatenadiary.rb: replace test by mocha used version.
13
+
14
+ * lib/hatenadiary.rb: use hpricot -> nokogiri 1.3.3
15
+ respond to hatena group diary.
16
+ (HatenaDiary::Client#post) change interface.
17
+ (HatenaDiary::Client#initialize): interface extended for test.
18
+
19
+ 2009-07-03 arikui <arikui.ruby@gmail.com>
20
+
21
+ * lib/hatenadiary.rb: use nokogiri -> hpricot
22
+
23
+ 2009-06-22 arikui <arikui.ruby@gmail.com>
24
+
25
+ * lib/hatenadiary.rb: first version.
26
+
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,25 @@
1
+ = hatenadiary
2
+
3
+ It is a library provides
4
+ a client for Hatena Diary to post and delete blog entries.
5
+
6
+ Developed on:
7
+ - ruby 1.9.1p243 (2009-07-16 revision 24175) [i386-mswin32]
8
+ - ruby 1.8.7 (2009-06-12 patchlevel 174) [i386-mswin32]
9
+
10
+ - Gem mechanize 0.9.3
11
+ - Gem nokogiri 1.3.3
12
+
13
+ This list means these environments are enable to make all of /test pass.
14
+
15
+
16
+ == Dependences
17
+
18
+ - www/mechanize
19
+ - nokogiri
20
+
21
+
22
+ == Signature
23
+
24
+ arikui (arikui.ruby@gmail.com)
25
+
@@ -0,0 +1,191 @@
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
+
10
+ # Gem Version
11
+ gem 'nokogiri', '1.3.3'
12
+
13
+ require 'nokogiri'
14
+ require 'mechanize'
15
+
16
+ # depend on Ruby version
17
+ module HatenaDiary
18
+ module Util
19
+ if RUBY_VERSION >= '1.9'
20
+ def encode_to_utf8(str)
21
+ str.encode(Encoding::UTF_8)
22
+ end
23
+ else
24
+ require 'kconv'
25
+
26
+ def encode_to_utf8(str)
27
+ Kconv.toutf8(str)
28
+ end
29
+ end
30
+
31
+ module_function :encode_to_utf8
32
+ end
33
+ end
34
+
35
+
36
+ module HatenaDiary
37
+ #
38
+ # Allocates Client object and makes it login, execute a received block,
39
+ # and then logout.
40
+ #
41
+ # :call-seq:
42
+ # login(username, password, proxy = nil){|client| ... }
43
+ #
44
+ def login(*args, &block)
45
+ Client.login(*args, &block)
46
+ end
47
+ module_function :login
48
+
49
+ class LoginError < RuntimeError
50
+ def set_account(username, password)
51
+ @username = username
52
+ @password = password
53
+ self
54
+ end
55
+
56
+ attr_reader :username
57
+ attr_reader :password
58
+ end
59
+
60
+ class Client
61
+ def self.mechanizer
62
+ @mechanizer ||= WWW::Mechanize
63
+ end
64
+
65
+ def self.mechanizer=(klass)
66
+ @mechanizer = klass
67
+ end
68
+
69
+ # Allocates Client object.
70
+ #
71
+ # If block given, login and execute a received block, and then logout ensurely.
72
+ #
73
+ # [username] Hatena ID
74
+ # [password] Password for _username_
75
+ # [proxy] Proxy configuration; [proxy_url, port_no] | nil
76
+ #
77
+ def self.login(username, password, proxy = nil, &block)
78
+ client = new(username, password)
79
+ client.set_proxy(*proxy) if proxy
80
+ return client unless block_given?
81
+ client.transaction(&block)
82
+ end
83
+
84
+ # Allocates Client object.
85
+ #
86
+ # [username] Hatena ID
87
+ # [password] Password for _username_
88
+ def initialize(username, password, agent = self.class.mechanizer.new)
89
+ @agent = agent
90
+ @username = username
91
+ @password = password
92
+ @current_account = nil
93
+ end
94
+
95
+ # Configure proxy.
96
+ def set_proxy(url, port)
97
+ @agent.set_proxy(url, port)
98
+ end
99
+
100
+ # Login and execute a received block, and then logout ensurely.
101
+ def transaction(username = nil, password = nil)
102
+ raise LocalJumpError, "no block given" unless block_given?
103
+ login(username, password)
104
+ begin
105
+ yield(self)
106
+ ensure
107
+ logout
108
+ end
109
+ end
110
+
111
+ # Returns a client itself was logined or not.
112
+ #
113
+ # -> true | false
114
+ def login?
115
+ @current_account ? true : false
116
+ end
117
+
118
+ # Does login.
119
+ #
120
+ # If _username_ or _password_ are invalid, raises HatenaDiary::LoginError .
121
+ def login(username = nil, password = nil)
122
+ username ||= @username
123
+ password ||= @password
124
+ form = @agent.get("https://www.hatena.ne.jp/login").forms.first
125
+ form["name"] = username
126
+ form["password"] = password
127
+ form["persistent"] = "true"
128
+ response = form.submit
129
+ @current_account = [username, password]
130
+ case response.title
131
+ when "Hatena" then response
132
+ when "Login - Hatena" then raise LoginError.new("login failure").set_account(username, password)
133
+ else raise Exception, '[BUG] must not happen (maybe cannot follow hatena spec)'
134
+ end
135
+ end
136
+
137
+ # Does logout if already logined.
138
+ def logout
139
+ return unless login?
140
+ @agent.get("https://www.hatena.ne.jp/logout")
141
+ account = @current_account
142
+ @current_account = nil
143
+ account
144
+ end
145
+
146
+ # Posts an entry to Hatena diary service.
147
+ #
148
+ # Raises HatenaDiary::LoginError unless logined.
149
+ #
150
+ # options
151
+ # [:trivial] check a checkbox of trivial updating.
152
+ # [:group] assign hatena-group name. edit group diary.
153
+ #
154
+ # Invalid options were ignored.
155
+ def post(yyyy, mm, dd, title, body, options = {})
156
+ title = Util.encode_to_utf8(title)
157
+ body = Util.encode_to_utf8(body)
158
+ form = get_form(yyyy, mm, dd, options[:group]){|r| r.form_with(:name => 'edit') }
159
+ form["year"] = "%04d" % yyyy
160
+ form["month"] = "%02d" % mm
161
+ form["day"] = "%02d" % dd
162
+ form["title"] = title
163
+ form["body"] = body
164
+ form["trivial"] = "true" if options[:trivial]
165
+ @agent.submit form, form.button_with(:name => 'edit')
166
+ end
167
+
168
+ # Deletes an entry from Hatena diary service.
169
+ #
170
+ # Raises HatenaDiary::LoginError unless logined.
171
+ #
172
+ # options
173
+ # [:group] assign hatena-group name. edit group diary.
174
+ #
175
+ # Invalid options were ignored.
176
+ def delete(yyyy, mm, dd, options = {})
177
+ get_form(yyyy, mm, dd, options[:group]){|r| r.forms.last }.submit
178
+ end
179
+
180
+ private
181
+
182
+ def get_form(yyyy, mm, dd, group = nil)
183
+ raise LoginError, "not login yet" unless login?
184
+ vals = [group ? "#{group}.g" : "d",
185
+ @current_account[0],
186
+ yyyy, mm, dd]
187
+ yield @agent.get("http://%s.hatena.ne.jp/%s/edit?date=%04d%02d%02d" % vals)
188
+ end
189
+ end
190
+ end
191
+
@@ -0,0 +1,238 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'test/unit'
3
+ require 'mocha'
4
+ require 'hatenadiary'
5
+
6
+
7
+ class TestHatenaDiaryAPI < Test::Unit::TestCase
8
+ def setup
9
+ @username = 'USERNAME'
10
+ @password = 'PASSWORD'
11
+ @proxy_url = "PROXY_URL"
12
+ @proxy_port = "PROXY_PORT"
13
+ end
14
+
15
+ def test_api_login
16
+ HatenaDiary::Client.expects(:login)
17
+ HatenaDiary.login
18
+ end
19
+
20
+ def test_login
21
+ client = mock()
22
+ HatenaDiary::Client.expects(:new).with(@username, @password).returns(client)
23
+ client.expects(:transaction).yields('block delegate check')
24
+ HatenaDiary::Client.login(@username, @password) do |str|
25
+ assert_equal 'block delegate check', str
26
+ end
27
+ end
28
+
29
+ def test_login_with_proxy
30
+ client = mock()
31
+ HatenaDiary::Client.expects(:new).with(@username, @password).returns(client)
32
+ client.expects(:set_proxy).with(@proxy_url, @proxy_port)
33
+ client.expects(:transaction).yields('block delegate check')
34
+ HatenaDiary::Client.login(@username, @password, [@proxy_url, @proxy_port]) do |str|
35
+ assert_equal 'block delegate check', str
36
+ end
37
+ end
38
+
39
+ def test_login_without_block
40
+ client = Object.new
41
+ HatenaDiary::Client.expects(:new).with(@username, @password).returns(client)
42
+ assert_equal client, HatenaDiary::Client.login(@username, @password)
43
+ end
44
+
45
+ def test_login_without_block_with_proxy
46
+ client = mock()
47
+ HatenaDiary::Client.expects(:new).with(@username, @password).returns(client)
48
+ client.expects(:set_proxy).with(@proxy_url, @proxy_port)
49
+ assert_equal client, HatenaDiary::Client.login(@username, @password, [@proxy_url, @proxy_port])
50
+ end
51
+ end
52
+
53
+
54
+ class TestHatenaDiary < Test::Unit::TestCase
55
+ def setup
56
+ @username = 'USERNAME'
57
+ @password = 'PASSWORD'
58
+ @agent = mock()
59
+ @client = HatenaDiary::Client.new(@username, @password, @agent)
60
+ end
61
+
62
+ def test_set_proxy
63
+ proxy_url = 'PROXY_URL'
64
+ proxy_port = 'PROXY_PORT'
65
+ @agent.expects(:set_proxy).with(proxy_url, proxy_port)
66
+ @client.set_proxy(proxy_url, proxy_port)
67
+ end
68
+
69
+ def test_logout_without_login
70
+ @client.logout
71
+ end
72
+
73
+ def login_mocking(submit_response_page_title)
74
+ login_page = mock()
75
+ form = {}
76
+ forms = [form]
77
+ response = mock()
78
+ @agent.expects(:get).with("https://www.hatena.ne.jp/login").returns(login_page)
79
+ login_page.expects(:forms).returns(forms)
80
+ form.expects(:submit).returns(response)
81
+ response.expects(:title).returns(submit_response_page_title)
82
+ form
83
+ end
84
+
85
+ def logout_mocking
86
+ @agent.expects(:get).with("https://www.hatena.ne.jp/logout")
87
+ end
88
+
89
+ def test_login_and_logout
90
+ # before login
91
+ assert !@client.login?
92
+ # login
93
+ form = login_mocking("Hatena")
94
+ @client.login
95
+ assert @client.login?
96
+ assert_equal form["name"], @username
97
+ assert_equal form["password"], @password
98
+ assert_equal form["persistent"], "true"
99
+ # logout
100
+ logout_mocking
101
+ @client.logout
102
+ assert !@client.login?
103
+ end
104
+
105
+ def test_login_failure
106
+ login_mocking "Login - Hatena"
107
+ begin
108
+ @client.login
109
+ rescue HatenaDiary::LoginError => ex
110
+ assert_equal @username, ex.username
111
+ assert_equal @password, ex.password
112
+ else
113
+ flunk "login error must be raised."
114
+ end
115
+ end
116
+
117
+ def test_login_if_hatena_changed
118
+ login_mocking "*jumbled pagetitle*"
119
+ begin
120
+ @client.login
121
+ rescue Exception => ex
122
+ assert /must not happen/ =~ ex.message
123
+ else
124
+ flunk "exception must be raised"
125
+ end
126
+ end
127
+
128
+ def test_transaction
129
+ assert !@client.login?
130
+ login_mocking "Hatena"
131
+ logout_mocking
132
+ @client.transaction do |client|
133
+ assert_same @client, client
134
+ assert @client.login?
135
+ end
136
+ assert !@client.login?
137
+ end
138
+
139
+ def test_transaction_without_block
140
+ assert !@client.login?
141
+ assert_raises LocalJumpError do
142
+ @client.transaction
143
+ end
144
+ assert !@client.login?
145
+ end
146
+
147
+ def post_mocking(host, date_str)
148
+ edit_page = mock()
149
+ form = {}
150
+ button = Object.new
151
+ login_mocking "Hatena"
152
+ logout_mocking
153
+ @agent.expects(:get).with("http://#{host}.hatena.ne.jp/#{@username}/edit?date=#{date_str}").returns(edit_page)
154
+ edit_page.expects(:form_with).with(:name => 'edit').returns(form)
155
+ form.expects(:button_with).with(:name => 'edit').returns(button)
156
+ @agent.expects(:submit).with(form, button)
157
+ form
158
+ end
159
+
160
+ def test_post
161
+ form = post_mocking("d", "12340506")
162
+ @client.transaction do |client|
163
+ client.post 1234, 5, 6, 'TITLE', 'BODY'
164
+ end
165
+ expected = {
166
+ "year" => "1234",
167
+ "month" => "05",
168
+ "day" => "06",
169
+ "title" => "TITLE",
170
+ "body" => "BODY",
171
+ }
172
+ assert_equal expected, form
173
+ assert !form["trivial"]
174
+ end
175
+
176
+ def test_post_trivial
177
+ form = post_mocking("d", "20071108")
178
+ @client.transaction do |client|
179
+ client.post 2007, 11, 8, 'TITLE', 'BODY', :trivial => true
180
+ end
181
+ assert_equal "true", form["trivial"]
182
+ end
183
+
184
+ def test_post_group
185
+ post_mocking "hoge.g", "12340506"
186
+ @client.transaction do |client|
187
+ client.post 1234, 5, 6, 'TITLE', 'BODY', :group => 'hoge'
188
+ end
189
+ end
190
+
191
+ def test_post_group_trivial
192
+ form = post_mocking("hoge.g", "12340506")
193
+ @client.transaction do |client|
194
+ client.post 1234, 5, 6, 'TITLE', 'BODY', :group => 'hoge', :trivial => true
195
+ end
196
+ assert_equal "true", form["trivial"]
197
+ end
198
+
199
+ def test_post_without_login
200
+ assert_raises HatenaDiary::LoginError do
201
+ @client.post 1999, 5, 26, "TITLE", "BODY\n"
202
+ end
203
+ end
204
+
205
+ def delete_mocking(host, date_str)
206
+ edit_page = mock()
207
+ form = {}
208
+ forms = [form]
209
+ button = Object.new
210
+ login_mocking "Hatena"
211
+ logout_mocking
212
+ @agent.expects(:get).with("http://#{host}.hatena.ne.jp/#{@username}/edit?date=#{date_str}").returns(edit_page)
213
+ edit_page.expects(:forms).returns(forms)
214
+ form.expects(:submit)
215
+ form
216
+ end
217
+
218
+ def test_delete
219
+ delete_mocking "d", "12340506"
220
+ @client.transaction do |client|
221
+ client.delete 1234, 5, 6
222
+ end
223
+ end
224
+
225
+ def test_delete_group
226
+ delete_mocking "piyo.g", "12340506"
227
+ @client.transaction do |client|
228
+ client.delete 1234, 5, 6, :group => 'piyo'
229
+ end
230
+ end
231
+
232
+ def test_delete_without_login
233
+ assert_raises HatenaDiary::LoginError do
234
+ @client.delete 2009, 8, 30
235
+ end
236
+ end
237
+ end
238
+
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hatenadiary
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - arikui
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-04 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mechanize
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.3
34
+ version:
35
+ description: A client for Hatena Diary to post and delete blog entries.
36
+ email: arikui.ruby@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ - LICENSE
44
+ - ChangeLog
45
+ files:
46
+ - lib/hatenadiary.rb
47
+ - test/test_hatenadiary.rb
48
+ - README
49
+ - LICENSE
50
+ - ChangeLog
51
+ has_rdoc: true
52
+ homepage: http://wiki.github.com/arikui1911/hatenadiary
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --title
58
+ - hatenadiary documentation
59
+ - --opname
60
+ - index.html
61
+ - --line-numbers
62
+ - --main
63
+ - README
64
+ - --inline-source
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project: hatenadiary
82
+ rubygems_version: 1.3.5
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: It is a library provides a client for Hatena Diary to post and delete blog entries.
86
+ test_files:
87
+ - test/test_hatenadiary.rb