pilha 0.1.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/lib/pilha.rb ADDED
@@ -0,0 +1,92 @@
1
+ path = File.expand_path(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
3
+
4
+ require 'open-uri'
5
+ require 'json'
6
+ require 'forwardable'
7
+
8
+ require 'pilha/stack_overflow/statistics'
9
+ require 'pilha/stack_overflow/badge'
10
+ require 'pilha/stack_overflow/user'
11
+
12
+ module StackExchange
13
+
14
+ module StackOverflow
15
+ class Client
16
+ URL = 'http://api.stackoverflow.com/'
17
+ API_VERSION = '0.8'
18
+
19
+ attr_reader :url
20
+ attr_reader :api_version
21
+ attr_reader :api_key
22
+
23
+ class << self
24
+ def config &block
25
+ options = {}
26
+ yield options if block_given?
27
+ client = new(options)
28
+ include_client(client, Badge, Statistics, User)
29
+ end
30
+
31
+ def include_client(client, *classes)
32
+ classes.each do |klass|
33
+ klass.instance_variable_set(:@client, client)
34
+ end
35
+ end
36
+ end
37
+
38
+ def initialize(options = {})
39
+ @url = normalize(options[:url] || URL )
40
+ @api_version = options[:api_version] || options[:version] || API_VERSION
41
+ @api_key = options[:api_key] || options[:key]
42
+ end
43
+
44
+ def api_method_path(pattern, options = {})
45
+ pattern << '/' unless pattern.end_with? '/'
46
+ parts = pattern.split('/').select { |part| part =~ /^:/ }
47
+
48
+ parts.each do |part|
49
+ key = part.sub(':', '').intern
50
+ pattern.sub!(part, options[key].to_s)
51
+ options.delete key
52
+ end
53
+
54
+ pattern
55
+ end
56
+
57
+ def api_method_url(method, options = {})
58
+ options.merge! :api_key => api_key if api_key
59
+ root_path + api_method_path(method, options) + query_string(options)
60
+ end
61
+
62
+ def get(url)
63
+ stream = open(url) { |stream| stream.read }
64
+ JSON.parse stream
65
+ end
66
+
67
+ def root_path
68
+ url + api_version
69
+ end
70
+
71
+ private
72
+ def key?
73
+ !!@api_key
74
+ end
75
+
76
+ def query_string(options)
77
+ params = options[:query]
78
+ if params
79
+ '?' + params.inject([]) do |arr, (key, value)|
80
+ arr << "#{key}=#{value}"
81
+ end.join('&')
82
+ else
83
+ ''
84
+ end
85
+ end
86
+
87
+ def normalize(url)
88
+ url.end_with?('/') ? url : url + '/'
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,29 @@
1
+ module StackExchange
2
+ module StackOverflow
3
+ class Badge
4
+
5
+ extend Forwardable
6
+
7
+ def_delegators :@struct, :badge_id, :rank, :name, :description,
8
+ :award_count, :tag_based, :badges_recipients_url
9
+
10
+ class << self
11
+ attr_reader :client
12
+
13
+ def all
14
+ response = client.get client.api_method_url('/badges')
15
+ badges = response['badges']
16
+ badges.map { |badge| Badge.new badge }
17
+ end
18
+ end
19
+
20
+ def initialize(hash)
21
+ @struct = OpenStruct.new hash
22
+ end
23
+
24
+ def id
25
+ @struct.badge_id
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module StackExchange
2
+ module StackOverflow
3
+
4
+ class Statistics
5
+
6
+ extend Forwardable
7
+
8
+ def_delegators :@struct, :total_questions, :total_unanswered, :total_answers,
9
+ :total_comments, :total_votes, :total_badges,
10
+ :total_users, :questions_per_minute, :answers_per_minute,
11
+ :badges_per_minute, :api_version
12
+
13
+ def initialize(hash)
14
+ @struct = OpenStruct.new hash
15
+ end
16
+
17
+ class << self
18
+ attr_reader :client
19
+
20
+ def all
21
+ response = client.get client.api_method_url('/stats')
22
+ stats = response['statistics'].first
23
+ Statistics.new stats
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module StackExchange
2
+ module StackOverflow
3
+
4
+ class User
5
+ extend Forwardable
6
+
7
+ def_delegators :@struct, :user_id, :user_type, :creation_date, :display_name,
8
+ :reputation, :email_hash, :age, :last_access_date,
9
+ :website_url, :location, :about_me, :question_count,
10
+ :answer_count, :view_count, :up_vote_count, :down_vote_count,
11
+ :user_questions_url, :user_answers_url, :user_favorites_url,
12
+ :user_tags_url, :user_badges_url, :user_timeline_url,
13
+ :user_mentioned_url, :user_comments_url, :user_reputation_url,
14
+ :badge_counts
15
+
16
+ def initialize(hash)
17
+ @struct = OpenStruct.new hash
18
+ end
19
+
20
+ class << self
21
+
22
+ attr_reader :client
23
+
24
+ def find_by_badge_id(id, query_options = {})
25
+ response = client.get client.api_method_url('/badges/:id', :id => id, :query => query_options)
26
+ users = response['users'].map { |user| User.new(user) }
27
+ response['users'] = users
28
+ OpenStruct.new response
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
data/pilha.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "pilha"
3
+ gem.version = "0.1.1"
4
+ gem.authors = ["Dalto Curvelano Junior"]
5
+ gem.description = "A ruby wrapper to the StackExchange (StackOverflow and friends) API."
6
+ gem.summary = "A ruby wrapper to the StackExchange (StackOverflow and friends) API."
7
+ gem.files = [
8
+ "pilha.gemspec",
9
+ "lib/pilha/stack_overflow/badge.rb",
10
+ "lib/pilha/stack_overflow/statistics.rb",
11
+ "lib/pilha/stack_overflow/user.rb",
12
+ "lib/pilha.rb",
13
+ "spec/fixtures/badges.json",
14
+ "spec/fixtures/badges_by_id.json",
15
+ "spec/fixtures/badges_by_id_page2.json",
16
+ "spec/fixtures/badges_name.json",
17
+ "spec/fixtures/stats.json",
18
+ "spec/pilha/stack_overflow/badge_spec.rb",
19
+ "spec/pilha/stack_overflow/stack_overflow_spec.rb",
20
+ "spec/pilha/stack_overflow/statistics_spec.rb",
21
+ "spec/pilha/stack_overflow/user_spec.rb",
22
+ "spec/spec.opts",
23
+ "spec/spec_helper.rb"
24
+ ]
25
+ gem.homepage = "http://github.com/dlt/stack_exchange"
26
+ end
@@ -0,0 +1,400 @@
1
+ {
2
+ "badges": [
3
+ {
4
+ "badge_id": 9,
5
+ "rank": "bronze",
6
+ "name": "Autobiographer",
7
+ "description": "Completed all user profile fields",
8
+ "award_count": 28065,
9
+ "tag_based": false,
10
+ "badges_recipients_url": "/badges/9"
11
+ },
12
+ {
13
+ "badge_id": 30,
14
+ "rank": "silver",
15
+ "name": "Beta",
16
+ "description": "Actively participated in the Stack Overflow private beta",
17
+ "award_count": 2606,
18
+ "tag_based": false,
19
+ "badges_recipients_url": "/badges/30"
20
+ },
21
+ {
22
+ "badge_id": 8,
23
+ "rank": "bronze",
24
+ "name": "Citizen Patrol",
25
+ "description": "First flagged post",
26
+ "award_count": 5689,
27
+ "tag_based": false,
28
+ "badges_recipients_url": "/badges/8"
29
+ },
30
+ {
31
+ "badge_id": 32,
32
+ "rank": "silver",
33
+ "name": "Civic Duty",
34
+ "description": "Voted 300 or more times",
35
+ "award_count": 4693,
36
+ "tag_based": false,
37
+ "badges_recipients_url": "/badges/32"
38
+ },
39
+ {
40
+ "badge_id": 4,
41
+ "rank": "bronze",
42
+ "name": "Cleanup",
43
+ "description": "First rollback",
44
+ "award_count": 3812,
45
+ "tag_based": false,
46
+ "badges_recipients_url": "/badges/4"
47
+ },
48
+ {
49
+ "badge_id": 31,
50
+ "rank": "bronze",
51
+ "name": "Commentator",
52
+ "description": "Left 10 comments",
53
+ "award_count": 31090,
54
+ "tag_based": false,
55
+ "badges_recipients_url": "/badges/31"
56
+ },
57
+ {
58
+ "badge_id": 7,
59
+ "rank": "bronze",
60
+ "name": "Critic",
61
+ "description": "First down vote",
62
+ "award_count": 25323,
63
+ "tag_based": false,
64
+ "badges_recipients_url": "/badges/7"
65
+ },
66
+ {
67
+ "badge_id": 37,
68
+ "rank": "bronze",
69
+ "name": "Disciplined",
70
+ "description": "Deleted own post with score of 3 or higher",
71
+ "award_count": 1665,
72
+ "tag_based": false,
73
+ "badges_recipients_url": "/badges/37"
74
+ },
75
+ {
76
+ "badge_id": 3,
77
+ "rank": "bronze",
78
+ "name": "Editor",
79
+ "description": "First edit",
80
+ "award_count": 63219,
81
+ "tag_based": false,
82
+ "badges_recipients_url": "/badges/3"
83
+ },
84
+ {
85
+ "badge_id": 155,
86
+ "rank": "gold",
87
+ "name": "Electorate",
88
+ "description": "Voted on 600 questions and 25% or more of total votes are on questions",
89
+ "award_count": 276,
90
+ "tag_based": false,
91
+ "badges_recipients_url": "/badges/155"
92
+ },
93
+ {
94
+ "badge_id": 19,
95
+ "rank": "silver",
96
+ "name": "Enlightened",
97
+ "description": "First answer was accepted with score of 10 or more",
98
+ "award_count": 11403,
99
+ "tag_based": false,
100
+ "badges_recipients_url": "/badges/19"
101
+ },
102
+ {
103
+ "badge_id": 71,
104
+ "rank": "silver",
105
+ "name": "Enthusiast",
106
+ "description": "Visited the site each day for 30 consecutive days",
107
+ "award_count": 6940,
108
+ "tag_based": false,
109
+ "badges_recipients_url": "/badges/71"
110
+ },
111
+ {
112
+ "badge_id": 145,
113
+ "rank": "silver",
114
+ "name": "Epic",
115
+ "description": "Hit the daily reputation cap on 50 days",
116
+ "award_count": 86,
117
+ "tag_based": false,
118
+ "badges_recipients_url": "/badges/145"
119
+ },
120
+ {
121
+ "badge_id": 28,
122
+ "rank": "gold",
123
+ "name": "Famous Question",
124
+ "description": "Asked a question with 10,000 views",
125
+ "award_count": 1063,
126
+ "tag_based": false,
127
+ "badges_recipients_url": "/badges/28"
128
+ },
129
+ {
130
+ "badge_id": 83,
131
+ "rank": "gold",
132
+ "name": "Fanatic",
133
+ "description": "Visited the site each day for 100 consecutive days",
134
+ "award_count": 937,
135
+ "tag_based": false,
136
+ "badges_recipients_url": "/badges/83"
137
+ },
138
+ {
139
+ "badge_id": 33,
140
+ "rank": "silver",
141
+ "name": "Favorite Question",
142
+ "description": "Question favorited by 25 users",
143
+ "award_count": 1082,
144
+ "tag_based": false,
145
+ "badges_recipients_url": "/badges/33"
146
+ },
147
+ {
148
+ "badge_id": 15,
149
+ "rank": "silver",
150
+ "name": "Generalist",
151
+ "description": "Provided non-wiki answers of 15 total score in 20 of top 40 tags",
152
+ "award_count": 133,
153
+ "tag_based": false,
154
+ "badges_recipients_url": "/badges/15"
155
+ },
156
+ {
157
+ "badge_id": 24,
158
+ "rank": "silver",
159
+ "name": "Good Answer",
160
+ "description": "Answer score of 25 or more",
161
+ "award_count": 9093,
162
+ "tag_based": false,
163
+ "badges_recipients_url": "/badges/24"
164
+ },
165
+ {
166
+ "badge_id": 21,
167
+ "rank": "silver",
168
+ "name": "Good Question",
169
+ "description": "Question score of 25 or more",
170
+ "award_count": 2267,
171
+ "tag_based": false,
172
+ "badges_recipients_url": "/badges/21"
173
+ },
174
+ {
175
+ "badge_id": 25,
176
+ "rank": "gold",
177
+ "name": "Great Answer",
178
+ "description": "Answer score of 100 or more",
179
+ "award_count": 1020,
180
+ "tag_based": false,
181
+ "badges_recipients_url": "/badges/25"
182
+ },
183
+ {
184
+ "badge_id": 22,
185
+ "rank": "gold",
186
+ "name": "Great Question",
187
+ "description": "Question score of 100 or more",
188
+ "award_count": 196,
189
+ "tag_based": false,
190
+ "badges_recipients_url": "/badges/22"
191
+ },
192
+ {
193
+ "badge_id": 18,
194
+ "rank": "silver",
195
+ "name": "Guru",
196
+ "description": "Accepted answer and score of 40 or more",
197
+ "award_count": 1128,
198
+ "tag_based": false,
199
+ "badges_recipients_url": "/badges/18"
200
+ },
201
+ {
202
+ "badge_id": 146,
203
+ "rank": "gold",
204
+ "name": "Legendary",
205
+ "description": "Hit the daily reputation cap on 150 days",
206
+ "award_count": 18,
207
+ "tag_based": false,
208
+ "badges_recipients_url": "/badges/146"
209
+ },
210
+ {
211
+ "badge_id": 144,
212
+ "rank": "bronze",
213
+ "name": "Mortarboard",
214
+ "description": "Hit the daily reputation cap for the first time",
215
+ "award_count": 4128,
216
+ "tag_based": false,
217
+ "badges_recipients_url": "/badges/144"
218
+ },
219
+ {
220
+ "badge_id": 17,
221
+ "rank": "silver",
222
+ "name": "Necromancer",
223
+ "description": "Answered a question more than 60 days later with score of 5 or more",
224
+ "award_count": 4894,
225
+ "tag_based": false,
226
+ "badges_recipients_url": "/badges/17"
227
+ },
228
+ {
229
+ "badge_id": 23,
230
+ "rank": "bronze",
231
+ "name": "Nice Answer",
232
+ "description": "Answer score of 10 or more",
233
+ "award_count": 55506,
234
+ "tag_based": false,
235
+ "badges_recipients_url": "/badges/23"
236
+ },
237
+ {
238
+ "badge_id": 20,
239
+ "rank": "bronze",
240
+ "name": "Nice Question",
241
+ "description": "Question score of 10 or more",
242
+ "award_count": 13354,
243
+ "tag_based": false,
244
+ "badges_recipients_url": "/badges/20"
245
+ },
246
+ {
247
+ "badge_id": 27,
248
+ "rank": "silver",
249
+ "name": "Notable Question",
250
+ "description": "Asked a question with 2,500 views",
251
+ "award_count": 14371,
252
+ "tag_based": false,
253
+ "badges_recipients_url": "/badges/27"
254
+ },
255
+ {
256
+ "badge_id": 5,
257
+ "rank": "bronze",
258
+ "name": "Organizer",
259
+ "description": "First retag",
260
+ "award_count": 12069,
261
+ "tag_based": false,
262
+ "badges_recipients_url": "/badges/5"
263
+ },
264
+ {
265
+ "badge_id": 38,
266
+ "rank": "bronze",
267
+ "name": "Peer Pressure",
268
+ "description": "Deleted own post with score of -3 or lower",
269
+ "award_count": 2965,
270
+ "tag_based": false,
271
+ "badges_recipients_url": "/badges/38"
272
+ },
273
+ {
274
+ "badge_id": 26,
275
+ "rank": "bronze",
276
+ "name": "Popular Question",
277
+ "description": "Asked a question with 1,000 views",
278
+ "award_count": 51931,
279
+ "tag_based": false,
280
+ "badges_recipients_url": "/badges/26"
281
+ },
282
+ {
283
+ "badge_id": 62,
284
+ "rank": "gold",
285
+ "name": "Populist",
286
+ "description": "Answer outscored an accepted answer with score of more than 10 by more than 2x",
287
+ "award_count": 344,
288
+ "tag_based": false,
289
+ "badges_recipients_url": "/badges/62"
290
+ },
291
+ {
292
+ "badge_id": 94,
293
+ "rank": "silver",
294
+ "name": "Pundit",
295
+ "description": "Left 10 comments with score of 10 or more",
296
+ "award_count": 83,
297
+ "tag_based": false,
298
+ "badges_recipients_url": "/badges/94"
299
+ },
300
+ {
301
+ "badge_id": 95,
302
+ "rank": "gold",
303
+ "name": "Reversal",
304
+ "description": "Provided answer of +20 score to a question of -5 score",
305
+ "award_count": 38,
306
+ "tag_based": false,
307
+ "badges_recipients_url": "/badges/95"
308
+ },
309
+ {
310
+ "badge_id": 10,
311
+ "rank": "bronze",
312
+ "name": "Scholar",
313
+ "description": "Asked a question and accepted an answer",
314
+ "award_count": 69346,
315
+ "tag_based": false,
316
+ "badges_recipients_url": "/badges/10"
317
+ },
318
+ {
319
+ "badge_id": 14,
320
+ "rank": "bronze",
321
+ "name": "Self-Learner",
322
+ "description": "Answered your own question with score of 3 or more",
323
+ "award_count": 3841,
324
+ "tag_based": false,
325
+ "badges_recipients_url": "/badges/14"
326
+ },
327
+ {
328
+ "badge_id": 36,
329
+ "rank": "gold",
330
+ "name": "Stellar Question",
331
+ "description": "Question favorited by 100 users",
332
+ "award_count": 153,
333
+ "tag_based": false,
334
+ "badges_recipients_url": "/badges/36"
335
+ },
336
+ {
337
+ "badge_id": 12,
338
+ "rank": "silver",
339
+ "name": "Strunk & White",
340
+ "description": "Edited 100 entries",
341
+ "award_count": 649,
342
+ "tag_based": false,
343
+ "badges_recipients_url": "/badges/12"
344
+ },
345
+ {
346
+ "badge_id": 2,
347
+ "rank": "bronze",
348
+ "name": "Student",
349
+ "description": "Asked first question with at least one up vote",
350
+ "award_count": 85741,
351
+ "tag_based": false,
352
+ "badges_recipients_url": "/badges/2"
353
+ },
354
+ {
355
+ "badge_id": 6,
356
+ "rank": "bronze",
357
+ "name": "Supporter",
358
+ "description": "First up vote",
359
+ "award_count": 61316,
360
+ "tag_based": false,
361
+ "badges_recipients_url": "/badges/6"
362
+ },
363
+ {
364
+ "badge_id": 11,
365
+ "rank": "silver",
366
+ "name": "Taxonomist",
367
+ "description": "Created a tag used by 50 questions",
368
+ "award_count": 2082,
369
+ "tag_based": false,
370
+ "badges_recipients_url": "/badges/11"
371
+ },
372
+ {
373
+ "badge_id": 1,
374
+ "rank": "bronze",
375
+ "name": "Teacher",
376
+ "description": "Answered first question with score of 1 or more",
377
+ "award_count": 71521,
378
+ "tag_based": false,
379
+ "badges_recipients_url": "/badges/1"
380
+ },
381
+ {
382
+ "badge_id": 63,
383
+ "rank": "bronze",
384
+ "name": "Tumbleweed",
385
+ "description": "Asked a question with no answers, no comments, and low views for a week",
386
+ "award_count": 23224,
387
+ "tag_based": false,
388
+ "badges_recipients_url": "/badges/63"
389
+ },
390
+ {
391
+ "badge_id": 13,
392
+ "rank": "silver",
393
+ "name": "Yearling",
394
+ "description": "Active member for a year, earning at least 200 reputation",
395
+ "award_count": 19443,
396
+ "tag_based": false,
397
+ "badges_recipients_url": "/badges/13"
398
+ }
399
+ ]
400
+ }