hatenablog 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b71f66f0d8e827aa61f9c9298682f0a1a9c5de8
4
- data.tar.gz: 2d52e581da4e418ddae1877409b8826e46491e10
3
+ metadata.gz: cf376e1c7bc165c96b04d31cdca1c7ad519eee6d
4
+ data.tar.gz: 47dbbe4a2e6e7efff7208a418424a224e74a2ee0
5
5
  SHA512:
6
- metadata.gz: 0f4dbffdfb7822e0ccd725a9a3813638b69fb01037bb098bd839a95c304d40987ec731172f7adb59d020f9e57e5152573a9dd36bcfa81a87c3d248b4ca04aa6a
7
- data.tar.gz: f18c984318d3d9991ac17dc1c2ede1b44beb42d87638ebecda60a92faefee249b8a14a4e1785245e42ef5d0993884bd3957b0f1d803cd230ac992e5fd97300fe
6
+ metadata.gz: 1a20f773e2c91639daa1f3c446edcaae4206f83f547ae552381afbd3d6912565b75d87f2dd8cdae3ee7bed4bd773b81f4c97f2319e5041d924a263fc6d8e4d2a
7
+ data.tar.gz: a839ae31b99284ea6b40e9d736d1375afa90ccd66fcabb872decdb5ad727fd838ee46daaabb507ca702840b2a35b024e0f8cc1d561f18db94b0bf0039051b1a9
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - 2.2.0
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Hatenablog
2
2
 
3
3
  A library for Hatenablog AtomPub API.
4
- This library supports following operations through OAuth authorization:
4
+ This gem supports following operations using OAuth authorization:
5
5
 
6
6
  - Get blog feeds, entries and categories
7
7
  - Post blog entries
@@ -26,7 +26,7 @@ Or install it yourself as:
26
26
 
27
27
  ### Get OAuth keys and tokens
28
28
 
29
- You need to set up OAuth 1.0a keys and tokens before using the gem.
29
+ You need to set up OAuth 1.0a keys and tokens before using this gem.
30
30
 
31
31
  #### 1. Get consumer key and consumer key secret
32
32
 
@@ -44,7 +44,7 @@ Execute this command:
44
44
 
45
45
  #### 3. Set up the YAML configuration file
46
46
 
47
- The default file name is `conf.yml`:
47
+ The default configuration file name is `conf.yml`:
48
48
 
49
49
  ```yml
50
50
  consumer_key: <Hatena application consumer key>
@@ -61,7 +61,7 @@ blog_id: <Hatenablog ID>
61
61
  require 'hatenablog'
62
62
 
63
63
  # Read the OAuth configuration from 'conf.yml'
64
- Hatenablog.create do |blog|
64
+ Hatenablog::Client.create do |blog|
65
65
  # Get each entry's content
66
66
  blog.entries.each do |entry|
67
67
  puts entry.content
@@ -73,12 +73,12 @@ Hatenablog.create do |blog|
73
73
  ['Test', 'Programming']) # categories
74
74
 
75
75
  # Update entry
76
- posted_entry = blog.update_entry(posted_entry.id,
77
- 'Revised Entry Title',
78
- posted_entry.content,
79
- posted_entry.categories)
76
+ updated_entry = blog.update_entry(posted_entry.id,
77
+ 'Revised Entry Title',
78
+ posted_entry.content,
79
+ posted_entry.categories)
80
80
 
81
81
  # Delete entry
82
- blog.delete_entry(posted_entry.id)
82
+ blog.delete_entry(updated_entry.id)
83
83
  end
84
84
  ```
data/Rakefile CHANGED
@@ -2,9 +2,18 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rake/testtask'
5
+ require 'yard'
6
+ require 'yard/rake/yardoc_task'
7
+
8
+ task :default => :test
5
9
 
6
10
  Rake::TestTask.new do |t|
7
11
  t.libs << "test"
8
- t.test_files = Dir["test/*_test.rb"]
12
+ t.test_files = Dir["test/hatenablog/*_test.rb"]
9
13
  t.verbose = true
10
14
  end
15
+
16
+ YARD::Rake::YardocTask.new do |t|
17
+ t.files = Dir["lib/*.rb"]
18
+ t.options = %w(--debug --verbose) if $trace
19
+ end
@@ -19,8 +19,15 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ["lib"]
21
21
 
22
+ spec.required_ruby_version = '>= 2.0'
23
+
22
24
  spec.add_development_dependency "bundler", "~> 1.10"
23
25
  spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rr", "~> 1.1.2"
27
+ spec.add_development_dependency "test-unit", "~> 3.1.4"
28
+ spec.add_development_dependency "test-unit-rr", "~> 1.0.3"
29
+ spec.add_development_dependency "yard", "~> 0.8.7"
24
30
 
25
- spec.add_dependency "oauth"
31
+ spec.add_dependency "nokogiri", "~> 1.6.6"
32
+ spec.add_dependency "oauth", "~> 0.4.7"
26
33
  end
@@ -1,272 +1,5 @@
1
- require 'rexml/document'
2
- require 'oauth'
3
-
4
- require 'blog_entry'
5
- require 'blog_feed'
6
- require 'configuration'
7
-
8
- class Hatenablog
9
- DEFAULT_CONFIG_PATH = './config.yml'
10
-
11
- COLLECTION_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry"
12
- MEMBER_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry/%s"
13
- CATEGORY_URI = "https://blog.hatena.ne.jp/%s/%s/atom/category"
14
-
15
- attr_writer :access_token
16
-
17
- # Create a new hatenablog AtomPub client from a configuration file.
18
- # @param [String] config_file configuration file path
19
- # @return [Hatenablog] created hatenablog client
20
- def self.create(config_file = DEFAULT_CONFIG_PATH)
21
- config = Configuration.new(config_file)
22
- blog = Hatenablog.new(config.consumer_key, config.consumer_secret,
23
- config.access_token, config.access_token_secret,
24
- config.user_id, config.blog_id)
25
- return blog unless block_given?
26
- yield blog
27
- end
28
-
29
- # Get a blog title.
30
- # @return [String] blog title
31
- def title
32
- feed = BlogFeed.load_xml(get_collection(collection_uri).body)
33
- feed.title
34
- end
35
-
36
- # Get a author name.
37
- # @return [String] blog author name
38
- def author_name
39
- feed = BlogFeed.load_xml(get_collection(collection_uri).body)
40
- feed.author_name
41
- end
42
-
43
- # Get blog entries array.
44
- # @param [Fixnum] page page number to get
45
- # @return [Array] blog entries
46
- def entries(page = 0)
47
- next_page_uri = collection_uri
48
- current_page = 0
49
- entries = []
50
- while current_page <= page
51
- feed = BlogFeed.load_xml(get_collection(next_page_uri).body)
52
- entries += feed.entries
53
-
54
- break unless feed.has_next?
55
- next_page_uri = feed.next_uri
56
- current_page += 1
57
- end
58
- entries
59
- end
60
-
61
- # Get blog categories array.
62
- # @return [Array] blog categories
63
- def categories
64
- categories_doc = REXML::Document.new(get_category_doc.body)
65
- categories_list = []
66
- categories_doc.elements.each('//atom:category') do |cat|
67
- categories_list << cat.attribute('term').to_s
68
- end
69
- categories_list
70
- end
71
-
72
- # Get a blog entry specified by its ID.
73
- # @param [String] entry_id entry ID
74
- # @return [BlogEntry] entry
75
- def get_entry(entry_id)
76
- response = get(member_uri(entry_id))
77
- BlogEntry.load_xml(response.body)
78
- end
79
-
80
- # Post a blog entry.
81
- # @param [String] title entry title
82
- # @param [String] content entry content
83
- # @param [Array] categories entry categories
84
- # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
85
- # @return [BlogEntry] posted entry
86
- def post_entry(title = '', content = '', categories = [], draft = 'no')
87
- entry_xml = entry_xml(title, content, categories, draft)
88
- response = post(entry_xml)
89
- BlogEntry.load_xml(response.body)
90
- end
91
-
92
- # Update a blog entry specified by its ID.
93
- # @param [String] entry_id updated entry ID
94
- # @param [String] title entry title
95
- # @param [String] content entry content
96
- # @param [Array] categories entry categories
97
- # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
98
- # @return [BlogEntry] updated entry
99
- def update_entry(entry_id, title = '', content = '', categories = [], draft = 'no')
100
- entry_xml = entry_xml(title, content, categories, draft)
101
- response = put(entry_xml, member_uri(entry_id))
102
- BlogEntry.load_xml(response.body)
103
- end
104
-
105
- # Delete a blog entry specified by its ID.
106
- # @param [String] entry_id deleted entry ID
107
- def delete_entry(entry_id)
108
- delete(member_uri(entry_id))
109
- end
110
-
111
- # Get Hatenablog AtomPub collection URI.
112
- # @param [String] user_id Hatena user ID
113
- # @param [String] blog_id Hatenablog ID
114
- # @return [String] Hatenablog AtomPub collection URI
115
- def collection_uri(user_id = @user_id, blog_id = @blog_id)
116
- COLLECTION_URI % [user_id, blog_id]
117
- end
118
-
119
- # Get Hatenablog AtomPub member URI.
120
- # @param [String] entry_id entry ID
121
- # @param [String] user_id Hatena user ID
122
- # @param [String] blog_id Hatenablog ID
123
- # @return [String] Hatenablog AtomPub member URI
124
- def member_uri(entry_id, user_id = @user_id, blog_id = @blog_id)
125
- MEMBER_URI % [user_id, blog_id, entry_id]
126
- end
127
-
128
- # Get Hatenablog AtomPub category document URI.
129
- # @param [String] user_id Hatena user ID
130
- # @param [String] blog_id Hatenablog ID
131
- # @return [String] Hatenablog AtomPub category document URI
132
- def category_doc_uri(user_id = @user_id, blog_id = @blog_id)
133
- CATEGORY_URI % [user_id, blog_id]
134
- end
135
-
136
- # Build a entry XML from arguments.
137
- # @param [String] title entry title
138
- # @param [String] content entry content
139
- # @param [Array] categories entry categories
140
- # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
141
- # @param [String] author_name entry author name
142
- # @return [String] XML string
143
- def entry_xml(title = '', content = '', categories = [], draft = 'no', author_name = @user_id)
144
- xml = <<XML
145
- <?xml version="1.0" encoding="utf-8"?>
146
- <entry xmlns="http://www.w3.org/2005/Atom"
147
- xmlns:app="http://www.w3.org/2007/app">
148
- <title>%s</title>
149
- <author><name>%s</name></author>
150
- <content type="text/x-markdown">%s</content>
151
- %s
152
- <app:control>
153
- <app:draft>%s</app:draft>
154
- </app:control>
155
- </entry>
156
- XML
157
-
158
- categories_tag = categories.inject('') do |s, c|
159
- s + "<category term=\"#{c}\" />\n"
160
- end
161
- xml % [title, author_name, content, categories_tag, draft]
162
- end
163
-
164
-
165
- private
166
-
167
- def initialize(consumer_key, consumer_secret, access_token, access_token_secret,
168
- user_id, blog_id)
169
- consumer = OAuth::Consumer.new(consumer_key, consumer_secret)
170
- @access_token = OAuthAccessToken.new(OAuth::AccessToken.new(consumer,
171
- access_token,
172
- access_token_secret))
173
-
174
- @user_id = user_id
175
- @blog_id = blog_id
176
- end
177
-
178
-
179
- def get(uri)
180
- @access_token.get(uri)
181
- end
182
-
183
- def get_collection(uri = collection_uri)
184
- unless uri.include?(collection_uri)
185
- raise ArgumentError.new('Invalid collection URI: ' + uri)
186
- end
187
- get(uri)
188
- end
189
-
190
- def get_category_doc
191
- get(category_doc_uri)
192
- end
193
-
194
- def post(entry_xml, uri = collection_uri)
195
- @access_token.post(uri, entry_xml)
196
- end
197
-
198
- def put(entry_xml, uri)
199
- @access_token.put(uri, entry_xml)
200
- end
201
-
202
- def delete(uri)
203
- @access_token.delete(uri)
204
- end
205
- end
206
-
207
- class OAuthAccessToken
208
-
209
- # Create a new OAuth 1.0a access token.
210
- # @param [OAuth::AccessToken] access_token access token object
211
- def initialize(access_token)
212
- @access_token = access_token
213
- end
214
-
215
- # HTTP GET method
216
- # @param [string] uri target URI
217
- # @return [Net::HTTPResponse] HTTP response
218
- def get(uri)
219
- begin
220
- response = @access_token.get(uri)
221
- rescue => problem
222
- raise 'Fail to GET: ' + problem.to_s
223
- end
224
- response
225
- end
226
-
227
- # HTTP POST method
228
- # @param [string] uri target URI
229
- # @param [string] body HTTP request body
230
- # @param [string] headers HTTP request headers
231
- # @return [Net::HTTPResponse] HTTP response
232
- def post(uri,
233
- body = '',
234
- headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
235
- begin
236
- response = @access_token.post(uri, body, headers)
237
- rescue => problem
238
- raise 'Fail to POST: ' + problem.to_s
239
- end
240
- response
241
- end
242
-
243
- # HTTP PUT method
244
- # @param [string] uri target URI
245
- # @param [string] body HTTP request body
246
- # @param [string] headers HTTP request headers
247
- # @return [Net::HTTPResponse] HTTP response
248
- def put(uri,
249
- body = '',
250
- headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
251
- begin
252
- response = @access_token.put(uri, body, headers)
253
- rescue => problem
254
- raise 'Fail to PUT: ' + problem.to_s
255
- end
256
- response
257
- end
258
-
259
- # HTTP DELETE method
260
- # @param [string] uri target URI
261
- # @param [string] headers HTTP request headers
262
- # @return [Net::HTTPResponse] HTTP response
263
- def delete(uri,
264
- headers = { 'Content-Type' => 'application/atom+xml; type=entry' })
265
- begin
266
- response = @access_token.delete(uri, headers)
267
- rescue => problem
268
- raise 'Fail to DELETE: ' + problem.to_s
269
- end
270
- response
271
- end
272
- end
1
+ require 'hatenablog/category'
2
+ require 'hatenablog/client'
3
+ require 'hatenablog/configuration'
4
+ require 'hatenablog/entry'
5
+ require 'hatenablog/feed'
@@ -0,0 +1,47 @@
1
+ require 'nokogiri'
2
+
3
+ module Hatenablog
4
+ class Category
5
+
6
+ # Create a new blog categories from a XML string.
7
+ # @param [String] xml XML string representation
8
+ # @return [Hatenablog::Category]
9
+ def self.load_xml(xml)
10
+ Hatenablog::Category.new(xml)
11
+ end
12
+
13
+ # @return [Array]
14
+ def categories
15
+ @categories.dup
16
+ end
17
+
18
+ def each
19
+ @categories.each do |category|
20
+ yield category
21
+ end
22
+ end
23
+
24
+ # If fixed, only categories in this categories can be used for a blog entry.
25
+ # @return [Boolean]
26
+ def fixed?
27
+ @fixed == 'yes'
28
+ end
29
+
30
+
31
+ private
32
+
33
+ def initialize(xml)
34
+ @document = Nokogiri::XML(xml)
35
+ parse_document
36
+ end
37
+
38
+ def parse_document
39
+ @categories = @document.css('atom|category').inject([]) do |categories, category|
40
+ categories << category['term'].to_s
41
+ end
42
+
43
+ @fixed = @document.at_css('app|categories')['fixed'].to_s
44
+ @fixed = 'no' if @fixed.nil?
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,272 @@
1
+ require 'oauth'
2
+
3
+ require 'hatenablog/category'
4
+ require 'hatenablog/entry'
5
+ require 'hatenablog/feed'
6
+ require 'hatenablog/configuration'
7
+
8
+ module Hatenablog
9
+ class Client
10
+ DEFAULT_CONFIG_PATH = './config.yml'.freeze
11
+
12
+ COLLECTION_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry".freeze
13
+ MEMBER_URI = "https://blog.hatena.ne.jp/%s/%s/atom/entry/%s".freeze
14
+ CATEGORY_URI = "https://blog.hatena.ne.jp/%s/%s/atom/category".freeze
15
+
16
+ attr_writer :access_token
17
+
18
+ # Create a new hatenablog AtomPub client from a configuration file.
19
+ # @param [String] config_file configuration file path
20
+ # @return [Hatenablog::Client] created hatenablog client
21
+ def self.create(config_file = DEFAULT_CONFIG_PATH)
22
+ config = Configuration.new(config_file)
23
+ blog = Hatenablog::Client.new(config.consumer_key, config.consumer_secret,
24
+ config.access_token, config.access_token_secret,
25
+ config.user_id, config.blog_id)
26
+ return blog unless block_given?
27
+ yield blog
28
+ end
29
+
30
+ # Get a blog title.
31
+ # @return [String] blog title
32
+ def title
33
+ feed = Feed.load_xml(get_collection(collection_uri).body)
34
+ feed.title
35
+ end
36
+
37
+ # Get a author name.
38
+ # @return [String] blog author name
39
+ def author_name
40
+ feed = Feed.load_xml(get_collection(collection_uri).body)
41
+ feed.author_name
42
+ end
43
+
44
+ # Get blog entries array.
45
+ # @param [Fixnum] page page number to get
46
+ # @return [Array] blog entries
47
+ def entries(page = 0)
48
+ next_page_uri = collection_uri
49
+ current_page = 0
50
+ entries = []
51
+ while current_page <= page
52
+ feed = Feed.load_xml(get_collection(next_page_uri).body)
53
+ entries += feed.entries
54
+
55
+ break unless feed.has_next?
56
+ next_page_uri = feed.next_uri
57
+ current_page += 1
58
+ end
59
+ entries
60
+ end
61
+
62
+ # Get blog categories array.
63
+ # @return [Array] blog categories
64
+ def categories
65
+ categories_doc = Category.new(get_category_doc.body)
66
+ categories_doc.categories
67
+ end
68
+
69
+ # Get a blog entry specified by its ID.
70
+ # @param [String] entry_id entry ID
71
+ # @return [Hatenablog::BlogEntry] entry
72
+ def get_entry(entry_id)
73
+ response = get(member_uri(entry_id))
74
+ Entry.load_xml(response.body)
75
+ end
76
+
77
+ # Post a blog entry.
78
+ # @param [String] title entry title
79
+ # @param [String] content entry content
80
+ # @param [Array] categories entry categories
81
+ # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
82
+ # @return [Hatenablog::BlogEntry] posted entry
83
+ def post_entry(title = '', content = '', categories = [], draft = 'no')
84
+ entry_xml = entry_xml(title, content, categories, draft)
85
+ response = post(entry_xml)
86
+ Entry.load_xml(response.body)
87
+ end
88
+
89
+ # Update a blog entry specified by its ID.
90
+ # @param [String] entry_id updated entry ID
91
+ # @param [String] title entry title
92
+ # @param [String] content entry content
93
+ # @param [Array] categories entry categories
94
+ # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
95
+ # @return [Hatenablog::BlogEntry] updated entry
96
+ def update_entry(entry_id, title = '', content = '', categories = [], draft = 'no')
97
+ entry_xml = entry_xml(title, content, categories, draft)
98
+ response = put(entry_xml, member_uri(entry_id))
99
+ Entry.load_xml(response.body)
100
+ end
101
+
102
+ # Delete a blog entry specified by its ID.
103
+ # @param [String] entry_id deleted entry ID
104
+ def delete_entry(entry_id)
105
+ delete(member_uri(entry_id))
106
+ end
107
+
108
+ # Get Hatenablog AtomPub collection URI.
109
+ # @param [String] user_id Hatena user ID
110
+ # @param [String] blog_id Hatenablog ID
111
+ # @return [String] Hatenablog AtomPub collection URI
112
+ def collection_uri(user_id = @user_id, blog_id = @blog_id)
113
+ COLLECTION_URI % [user_id, blog_id]
114
+ end
115
+
116
+ # Get Hatenablog AtomPub member URI.
117
+ # @param [String] entry_id entry ID
118
+ # @param [String] user_id Hatena user ID
119
+ # @param [String] blog_id Hatenablog ID
120
+ # @return [String] Hatenablog AtomPub member URI
121
+ def member_uri(entry_id, user_id = @user_id, blog_id = @blog_id)
122
+ MEMBER_URI % [user_id, blog_id, entry_id]
123
+ end
124
+
125
+ # Get Hatenablog AtomPub category document URI.
126
+ # @param [String] user_id Hatena user ID
127
+ # @param [String] blog_id Hatenablog ID
128
+ # @return [String] Hatenablog AtomPub category document URI
129
+ def category_doc_uri(user_id = @user_id, blog_id = @blog_id)
130
+ CATEGORY_URI % [user_id, blog_id]
131
+ end
132
+
133
+ # Build a entry XML from arguments.
134
+ # @param [String] title entry title
135
+ # @param [String] content entry content
136
+ # @param [Array] categories entry categories
137
+ # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
138
+ # @param [String] author_name entry author name
139
+ # @return [String] XML string
140
+ def entry_xml(title = '', content = '', categories = [], draft = 'no', author_name = @user_id)
141
+ xml = <<XML
142
+ <?xml version="1.0" encoding="utf-8"?>
143
+ <entry xmlns="http://www.w3.org/2005/Atom"
144
+ xmlns:app="http://www.w3.org/2007/app">
145
+ <title>%s</title>
146
+ <author><name>%s</name></author>
147
+ <content type="text/x-markdown">%s</content>
148
+ %s
149
+ <app:control>
150
+ <app:draft>%s</app:draft>
151
+ </app:control>
152
+ </entry>
153
+ XML
154
+
155
+ categories_tag = categories.inject('') do |s, c|
156
+ s + "<category term=\"#{c}\" />\n"
157
+ end
158
+ xml % [title, author_name, content, categories_tag, draft]
159
+ end
160
+
161
+
162
+ private
163
+
164
+ def initialize(consumer_key, consumer_secret, access_token, access_token_secret,
165
+ user_id, blog_id)
166
+ consumer = OAuth::Consumer.new(consumer_key, consumer_secret)
167
+ @access_token = OAuthAccessToken.new(OAuth::AccessToken.new(consumer,
168
+ access_token,
169
+ access_token_secret))
170
+
171
+ @user_id = user_id
172
+ @blog_id = blog_id
173
+ end
174
+
175
+
176
+ def get(uri)
177
+ @access_token.get(uri)
178
+ end
179
+
180
+ def get_collection(uri = collection_uri)
181
+ unless uri.include?(collection_uri)
182
+ raise ArgumentError.new('Invalid collection URI: ' + uri)
183
+ end
184
+ get(uri)
185
+ end
186
+
187
+ def get_category_doc
188
+ get(category_doc_uri)
189
+ end
190
+
191
+ def post(entry_xml, uri = collection_uri)
192
+ @access_token.post(uri, entry_xml)
193
+ end
194
+
195
+ def put(entry_xml, uri)
196
+ @access_token.put(uri, entry_xml)
197
+ end
198
+
199
+ def delete(uri)
200
+ @access_token.delete(uri)
201
+ end
202
+ end
203
+
204
+ class OAuthAccessToken
205
+
206
+ # Create a new OAuth 1.0a access token.
207
+ # @param [OAuth::AccessToken] access_token access token object
208
+ def initialize(access_token)
209
+ @access_token = access_token
210
+ end
211
+
212
+ # HTTP GET method
213
+ # @param [string] uri target URI
214
+ # @return [Net::HTTPResponse] HTTP response
215
+ def get(uri)
216
+ begin
217
+ response = @access_token.get(uri)
218
+ rescue => problem
219
+ raise OAuthError, 'Fail to GET: ' + problem.to_s
220
+ end
221
+ response
222
+ end
223
+
224
+ # HTTP POST method
225
+ # @param [string] uri target URI
226
+ # @param [string] body HTTP request body
227
+ # @param [string] headers HTTP request headers
228
+ # @return [Net::HTTPResponse] HTTP response
229
+ def post(uri,
230
+ body = '',
231
+ headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
232
+ begin
233
+ response = @access_token.post(uri, body, headers)
234
+ rescue => problem
235
+ raise OAuthError, 'Fail to POST: ' + problem.to_s
236
+ end
237
+ response
238
+ end
239
+
240
+ # HTTP PUT method
241
+ # @param [string] uri target URI
242
+ # @param [string] body HTTP request body
243
+ # @param [string] headers HTTP request headers
244
+ # @return [Net::HTTPResponse] HTTP response
245
+ def put(uri,
246
+ body = '',
247
+ headers = { 'Content-Type' => 'application/atom+xml; type=entry' } )
248
+ begin
249
+ response = @access_token.put(uri, body, headers)
250
+ rescue => problem
251
+ raise OAuthError, 'Fail to PUT: ' + problem.to_s
252
+ end
253
+ response
254
+ end
255
+
256
+ # HTTP DELETE method
257
+ # @param [string] uri target URI
258
+ # @param [string] headers HTTP request headers
259
+ # @return [Net::HTTPResponse] HTTP response
260
+ def delete(uri,
261
+ headers = { 'Content-Type' => 'application/atom+xml; type=entry' })
262
+ begin
263
+ response = @access_token.delete(uri, headers)
264
+ rescue => problem
265
+ raise OAuthError, 'Fail to DELETE: ' + problem.to_s
266
+ end
267
+ response
268
+ end
269
+ end
270
+
271
+ class OAuthError < StandardError; end
272
+ end
@@ -0,0 +1,31 @@
1
+ require 'yaml'
2
+
3
+ module Hatenablog
4
+ class Configuration
5
+ # For OAuth authorization.
6
+ attr_reader :consumer_key, :consumer_secret, :access_token, :access_token_secret
7
+
8
+ attr_reader :user_id, :blog_id
9
+
10
+ # Create a new configuration.
11
+ # @param [String] config_file configuration file path
12
+ # @return [Hatenablog::Configuration]
13
+ def initialize(config_file)
14
+ config = YAML.load_file(config_file)
15
+ unless config.has_key?('consumer_key') && config.has_key?('consumer_secret') &&
16
+ config.has_key?('access_token') && config.has_key?('access_token_secret') &&
17
+ config.has_key?('user_id') && config.has_key?('blog_id')
18
+ raise ConfigurationError, 'the configure file is incorrect'
19
+ end
20
+
21
+ @consumer_key = config['consumer_key']
22
+ @consumer_secret = config['consumer_secret']
23
+ @access_token = config['access_token']
24
+ @access_token_secret = config['access_token_secret']
25
+ @user_id = config['user_id']
26
+ @blog_id = config['blog_id']
27
+ end
28
+ end
29
+
30
+ class ConfigurationError < StandardError; end
31
+ end
@@ -0,0 +1,98 @@
1
+ require 'nokogiri'
2
+
3
+ module Hatenablog
4
+ class Entry
5
+ attr_reader :uri, :edit_uri, :id, :author_name, :title, :content
6
+
7
+ # Create a new blog entry from a XML string.
8
+ # @param [String] xml XML string representation
9
+ # @return [Hatenablog::Entry]
10
+ def self.load_xml(xml)
11
+ Hatenablog::Entry.new(xml)
12
+ end
13
+
14
+ # Create a new blog entry from arguments.
15
+ # @param [String] uri entry URI
16
+ # @param [String] edit_uri entry URI for editing
17
+ # @param [String] author_name entry author name
18
+ # @param [String] title entry title
19
+ # @param [String] content entry content
20
+ # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
21
+ # @param [Array] categories categories array
22
+ # @return [Hatenablog::Entry]
23
+ def self.create(uri: '', edit_uri: '', author_name: '', title: '',
24
+ content: '', draft: 'no', categories: [])
25
+ Hatenablog::Entry.new(self.build_xml(uri, edit_uri, author_name, title,
26
+ content, draft, categories))
27
+ end
28
+
29
+ # @return [Boolean]
30
+ def draft?
31
+ @draft == 'yes'
32
+ end
33
+
34
+ # @return [Array]
35
+ def categories
36
+ @categories.dup
37
+ end
38
+
39
+ def each_category
40
+ @categories.each do |category|
41
+ yield category
42
+ end
43
+ end
44
+
45
+ # @return [String]
46
+ def to_xml
47
+ @document.to_s.gsub(/\"/, "'")
48
+ end
49
+
50
+
51
+ private
52
+
53
+ def self.build_xml(uri, edit_uri, author_name, title, content, draft, categories)
54
+ xml = <<XML
55
+ <?xml version='1.0' encoding='UTF-8'?>
56
+ <entry xmlns:app='http://www.w3.org/2007/app' xmlns='http://www.w3.org/2005/Atom'>
57
+ <link href='%s' rel='edit'/>
58
+ <link href='%s' rel='alternate' type='text/html'/>
59
+ <author><name>%s</name></author>
60
+ <title>%s</title>
61
+ <content type='text/x-markdown'>%s</content>
62
+ %s
63
+ <app:control>
64
+ <app:draft>%s</app:draft>
65
+ </app:control>
66
+ </entry>
67
+ XML
68
+
69
+ categories_tag = categories.inject('') do |s, c|
70
+ s + "<category term=\"#{c}\" />\n"
71
+ end
72
+ xml % [edit_uri, uri, author_name, title, content, categories_tag, draft]
73
+ end
74
+
75
+ def initialize(xml)
76
+ @document = Nokogiri::XML(xml)
77
+ parse_document
78
+ end
79
+
80
+ def parse_document
81
+ @uri = @document.at_css('link[@rel="alternate"]')['href'].to_s
82
+ @edit_uri = @document.at_css('link[@rel="edit"]')['href'].to_s
83
+ @id = @edit_uri.split('/').last
84
+ @author_name = @document.at_css('author name').content
85
+ @title = @document.at_css('title').content
86
+ @content = @document.at_css('content').content
87
+ @draft = @document.at_css('entry app|control app|draft').content
88
+ @categories = parse_categories
89
+ end
90
+
91
+ def parse_categories
92
+ categories = @document.css('category').inject([]) do |categories, category|
93
+ categories << category['term'].to_s
94
+ end
95
+ categories
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,63 @@
1
+ require 'nokogiri'
2
+ require 'time'
3
+
4
+ require 'hatenablog/entry'
5
+
6
+ module Hatenablog
7
+ class Feed
8
+ attr_reader :uri, :next_uri, :title, :author_name, :updated
9
+
10
+ # Create a new blog feed from a XML string.
11
+ # @param [String] xml XML string representation
12
+ # @return [Hatenablog::Feed]
13
+ def self.load_xml(xml)
14
+ Hatenablog::Feed.new(xml)
15
+ end
16
+
17
+ # @return [Array]
18
+ def entries
19
+ @entries.dup
20
+ end
21
+
22
+ def each_entry
23
+ @entries.each do |entry|
24
+ yield entry
25
+ end
26
+ end
27
+
28
+ # Return true if this feed has next feed.
29
+ # @return [Boolean]
30
+ def has_next?
31
+ @next_uri != ''
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def initialize(xml)
38
+ @document = Nokogiri::XML(xml)
39
+ parse_document
40
+ end
41
+
42
+ def parse_document
43
+ @uri = @document.at_css("feed link[@rel='alternate']")['href'].to_s
44
+ @next_uri = if @document.css("feed link[@rel='next']").empty?
45
+ ''
46
+ else
47
+ @document.at_css("feed link[@rel='next']")['href'].to_s
48
+ end
49
+ @title = @document.at_css('feed title').content
50
+ @author_name = @document.at_css('author name').content
51
+ @updated = Time.parse(@document.at_css('feed updated').content)
52
+ parse_entry
53
+ end
54
+
55
+ def parse_entry
56
+ @entries = @document.css('feed > entry').inject([]) do |entries, entry|
57
+ # add namespace 'app' to recognize XML correctly
58
+ entry['xmlns:app'] = 'http://www.w3.org/2007/app'
59
+ entries << Hatenablog::Entry.load_xml(entry.to_s)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,3 +1,3 @@
1
1
  module Hatenablog
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hatenablog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kohei Yamamoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-06 00:00:00.000000000 Z
11
+ date: 2015-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,20 +38,90 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rr
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.1.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.1.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: test-unit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.1.4
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.1.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit-rr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: yard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.8.7
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.8.7
97
+ - !ruby/object:Gem::Dependency
98
+ name: nokogiri
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.6.6
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.6.6
41
111
  - !ruby/object:Gem::Dependency
42
112
  name: oauth
43
113
  requirement: !ruby/object:Gem::Requirement
44
114
  requirements:
45
- - - ">="
115
+ - - "~>"
46
116
  - !ruby/object:Gem::Version
47
- version: '0'
117
+ version: 0.4.7
48
118
  type: :runtime
49
119
  prerelease: false
50
120
  version_requirements: !ruby/object:Gem::Requirement
51
121
  requirements:
52
- - - ">="
122
+ - - "~>"
53
123
  - !ruby/object:Gem::Version
54
- version: '0'
124
+ version: 0.4.7
55
125
  description: Hatenablog AtomPub API library
56
126
  email:
57
127
  - kymmt90@gmail.com
@@ -61,6 +131,7 @@ extensions: []
61
131
  extra_rdoc_files: []
62
132
  files:
63
133
  - ".gitignore"
134
+ - ".travis.yml"
64
135
  - Gemfile
65
136
  - LICENSE.txt
66
137
  - README.md
@@ -69,11 +140,12 @@ files:
69
140
  - bin/setup
70
141
  - exe/get_access_token
71
142
  - hatenablog.gemspec
72
- - lib/blog_category.rb
73
- - lib/blog_entry.rb
74
- - lib/blog_feed.rb
75
- - lib/configuration.rb
76
143
  - lib/hatenablog.rb
144
+ - lib/hatenablog/category.rb
145
+ - lib/hatenablog/client.rb
146
+ - lib/hatenablog/configuration.rb
147
+ - lib/hatenablog/entry.rb
148
+ - lib/hatenablog/feed.rb
77
149
  - lib/hatenablog/version.rb
78
150
  homepage: https://github.com/kymmt90/hatenablog
79
151
  licenses:
@@ -87,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
159
  requirements:
88
160
  - - ">="
89
161
  - !ruby/object:Gem::Version
90
- version: '0'
162
+ version: '2.0'
91
163
  required_rubygems_version: !ruby/object:Gem::Requirement
92
164
  requirements:
93
165
  - - ">="
@@ -100,3 +172,4 @@ signing_key:
100
172
  specification_version: 4
101
173
  summary: Hatenablog AtomPub API library
102
174
  test_files: []
175
+ has_rdoc:
@@ -1,46 +0,0 @@
1
- require 'rexml/document'
2
-
3
- class BlogCategory
4
-
5
- # Create a new blog categories from a XML string.
6
- # @param [String] xml XML string representation
7
- # @return [BlogCategory]
8
- def self.load_xml(xml)
9
- BlogCategory.new(xml)
10
- end
11
-
12
- # @return [Array]
13
- def categories
14
- @categories.dup
15
- end
16
-
17
- def each
18
- @categories.each do |category|
19
- yield category
20
- end
21
- end
22
-
23
- # If fixed, only categories in this categories can be used for a blog entry.
24
- # @return [Boolean]
25
- def fixed?
26
- @fixed == 'yes'
27
- end
28
-
29
-
30
- private
31
-
32
- def initialize(xml)
33
- @document = REXML::Document.new(xml)
34
- parse_document
35
- end
36
-
37
- def parse_document
38
- @categories = []
39
- @document.each_element("//atom:category") do |category|
40
- @categories << category.attribute('term').to_s
41
- end
42
-
43
- @fixed = @document.elements["/app:categories"].attribute('fixed').to_s
44
- @fixed = 'no' if @fixed.nil?
45
- end
46
- end
@@ -1,97 +0,0 @@
1
- require 'rexml/document'
2
-
3
- class BlogEntry
4
- attr_reader :uri, :edit_uri, :id, :author_name, :title, :content
5
-
6
- # Create a new blog entry from a XML string.
7
- # @param [String] xml XML string representation
8
- # @return [BlogEntry]
9
- def self.load_xml(xml)
10
- BlogEntry.new(xml)
11
- end
12
-
13
- # Create a new blog entry from arguments.
14
- # @param [String] uri entry URI
15
- # @param [String] edit_uri entry URI for editing
16
- # @param [String] author_name entry author name
17
- # @param [String] title entry title
18
- # @param [String] content entry content
19
- # @param [String] draft this entry is draft if 'yes', otherwise it is not draft
20
- # @param [Array] categories categories array
21
- # @return [BlogEntry]
22
- def self.create(uri: '', edit_uri: '', author_name: '', title: '',
23
- content: '', draft: 'no', categories: [])
24
- BlogEntry.new(self.build_xml(uri, edit_uri, author_name, title,
25
- content, draft, categories))
26
- end
27
-
28
- # @return [Boolean]
29
- def draft?
30
- @draft == 'yes'
31
- end
32
-
33
- # @return [Array]
34
- def categories
35
- @categories.dup
36
- end
37
-
38
- def each_category
39
- @categories.each do |category|
40
- yield category
41
- end
42
- end
43
-
44
- # @return [String]
45
- def to_xml
46
- @document.to_s
47
- end
48
-
49
-
50
- private
51
-
52
- def self.build_xml(uri, edit_uri, author_name, title, content, draft, categories)
53
- xml = <<XML
54
- <?xml version='1.0' encoding='UTF-8'?>
55
- <entry xmlns:app='http://www.w3.org/2007/app' xmlns='http://www.w3.org/2005/Atom'>
56
- <link href='%s' rel='edit'/>
57
- <link href='%s' rel='alternate' type='text/html'/>
58
- <author><name>%s</name></author>
59
- <title>%s</title>
60
- <content type='text/x-markdown'>%s</content>
61
- %s
62
- <app:control>
63
- <app:draft>%s</app:draft>
64
- </app:control>
65
- </entry>
66
- XML
67
-
68
- categories_tag = categories.inject('') do |s, c|
69
- s + "<category term=\"#{c}\" />\n"
70
- end
71
- xml % [edit_uri, uri, author_name, title, content, categories_tag, draft]
72
- end
73
-
74
- def initialize(xml)
75
- @document = REXML::Document.new(xml)
76
- parse_document
77
- end
78
-
79
- def parse_document
80
- @uri = @document.elements["//link[@rel='alternate']"].attribute('href').to_s
81
- @edit_uri = @document.elements["//link[@rel='edit']"].attribute('href').to_s
82
- @id = @edit_uri.split('/').last
83
- @author_name = @document.elements["//author/name"].text
84
- @title = @document.elements["//title"].text
85
- @content = @document.elements["//content"].text
86
- @draft = @document.elements["//app:draft"].text
87
- @categories = parse_categories
88
- end
89
-
90
- def parse_categories
91
- categories = []
92
- @document.each_element("//category") do |category|
93
- categories << category.attribute('term').to_s
94
- end
95
- categories
96
- end
97
- end
@@ -1,61 +0,0 @@
1
- require 'blog_entry'
2
- require 'rexml/document'
3
- require 'time'
4
-
5
- class BlogFeed
6
- attr_reader :uri, :next_uri, :title, :author_name, :updated
7
-
8
- # Create a new blog feed from a XML string.
9
- # @param [String] xml XML string representation
10
- # @return [BlogFeed]
11
- def self.load_xml(xml)
12
- BlogFeed.new(xml)
13
- end
14
-
15
- # @return [Array]
16
- def entries
17
- @entries.dup
18
- end
19
-
20
- def each_entry
21
- @entries.each do |entry|
22
- yield entry
23
- end
24
- end
25
-
26
- # Return true if this feed has next feed.
27
- # @return [Boolean]
28
- def has_next?
29
- @next_uri != ''
30
- end
31
-
32
-
33
- private
34
-
35
- def initialize(xml)
36
- @document = REXML::Document.new(xml)
37
- parse_document
38
- end
39
-
40
- def parse_document
41
- @uri = @document.elements["/feed/link[@rel='alternate']"].attribute('href').to_s
42
- @next_uri = if @document.elements["/feed/link[@rel='next']"].nil?
43
- ''
44
- else
45
- @document.elements["/feed/link[@rel='next']"].attribute('href').to_s
46
- end
47
- @title = @document.elements["/feed/title"].text
48
- @author_name = @document.elements["//author/name"].text
49
- @updated = Time.parse(@document.elements["/feed/updated"].text)
50
- parse_entry
51
- end
52
-
53
- def parse_entry
54
- @entries = []
55
- @document.elements.collect("//entry") do |entry|
56
- # add namespace 'app' to recognize XML correctly
57
- entry.add_attribute('xmlns:app', 'http://www.w3.org/2007/app')
58
- @entries << BlogEntry.load_xml(entry.to_s)
59
- end
60
- end
61
- end
@@ -1,21 +0,0 @@
1
- require 'yaml'
2
-
3
- class Configuration
4
- # For OAuth authorization.
5
- attr_reader :consumer_key, :consumer_secret, :access_token, :access_token_secret
6
-
7
- attr_reader :user_id, :blog_id
8
-
9
- # Create a new configuration.
10
- # @param [String] config_file configuration file path
11
- # @return [Configuration]
12
- def initialize(config_file)
13
- config = YAML.load_file(config_file)
14
- @consumer_key = config['consumer_key']
15
- @consumer_secret = config['consumer_secret']
16
- @access_token = config['access_token']
17
- @access_token_secret = config['access_token_secret']
18
- @user_id = config['user_id']
19
- @blog_id = config['blog_id']
20
- end
21
- end