stack_exchange 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +310 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/lib/stack_exchange.rb +6 -0
- data/lib/stack_exchange/base.rb +42 -0
- data/stack_exchange.gemspec +52 -0
- data/test/helper.rb +9 -0
- data/test/test_stack_exchange.rb +9 -0
- metadata +79 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Sam Coles
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
= stack_exchange
|
2
|
+
|
3
|
+
A thin Ruby wrapper for the Stack Exchange API. Designed to match the API as closely as possible.
|
4
|
+
|
5
|
+
== Examples:
|
6
|
+
|
7
|
+
> s = StackExchange::Base.new
|
8
|
+
> s.questions(2877955, :answers)
|
9
|
+
> pp s.questions(2877955, :body => 'true')
|
10
|
+
|
11
|
+
{
|
12
|
+
"total": 1,
|
13
|
+
"page": 1,
|
14
|
+
"pagesize": 30,
|
15
|
+
"questions": [
|
16
|
+
{
|
17
|
+
"tags": [
|
18
|
+
"java",
|
19
|
+
"concurrency"
|
20
|
+
],
|
21
|
+
"answer_count": 5,
|
22
|
+
"answers": [
|
23
|
+
{
|
24
|
+
"answer_id": 2878021,
|
25
|
+
"accepted": false,
|
26
|
+
"answer_comments_url": "/answers/2878021/comments",
|
27
|
+
"question_id": 2877955,
|
28
|
+
"owner": {
|
29
|
+
"user_id": 142446,
|
30
|
+
"user_type": "registered",
|
31
|
+
"display_name": "bkail",
|
32
|
+
"reputation": 1268,
|
33
|
+
"email_hash": "0e8190d89907e24bf6b4438eaef71af4"
|
34
|
+
},
|
35
|
+
"creation_date": 1274391221,
|
36
|
+
"last_edit_date": 1274396565,
|
37
|
+
"last_activity_date": 1274396565,
|
38
|
+
"up_vote_count": 2,
|
39
|
+
"down_vote_count": 1,
|
40
|
+
"view_count": 80,
|
41
|
+
"score": 1,
|
42
|
+
"community_owned": false,
|
43
|
+
"title": "Controlling race condition at startup.",
|
44
|
+
"body": "<p>A <code>synchronized</code> block will automatically block other threads. Just use a simple lock object + status variable:</p>\n\n<pre><code>public class MyClass {\n private static boolean initialised;\n private static final Object lockObject = new Object();\n\n public void initialise() {\n synchronized (lockObject) {\n if (!initialised) {\n initStuff();\n initialised = true;\n }\n }\n }\n\n public void doStuff() {\n initialise();\n doOtherStuff();\n }\n}\n</code></pre>\n"
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"answer_id": 2878026,
|
48
|
+
"accepted": false,
|
49
|
+
"answer_comments_url": "/answers/2878026/comments",
|
50
|
+
"question_id": 2877955,
|
51
|
+
"owner": {
|
52
|
+
"user_id": 326480,
|
53
|
+
"user_type": "registered",
|
54
|
+
"display_name": "mdma",
|
55
|
+
"reputation": 4572,
|
56
|
+
"email_hash": "12778253a95d10ba54e696ffab85103d"
|
57
|
+
},
|
58
|
+
"creation_date": 1274391233,
|
59
|
+
"last_edit_date": 1274391655,
|
60
|
+
"last_activity_date": 1274391655,
|
61
|
+
"up_vote_count": 0,
|
62
|
+
"down_vote_count": 0,
|
63
|
+
"view_count": 80,
|
64
|
+
"score": 0,
|
65
|
+
"community_owned": false,
|
66
|
+
"title": "Controlling race condition at startup.",
|
67
|
+
"body": "<p>You're using <code>AtomicBoolean</code> always from inside a synchronized block. There's not much point to that since only one thread can access it. Atomic variables are intended for use in lock-free solutions - you can get and set the value as an uninterruptable unit.</p>\n\n<p>I guess you are looking for a lock free solution once the intiialization has happened:</p>\n\n<pre><code>public class MyClass {\n private static final AtomicBoolean initialised = new AtomicBoolean(false);\n\n public void initialise() {\n if (!intialized.get())\n {\n synchornized (this)\n {\n if (!initialized.getAndSet(true))\n doInitialize();\n }\n }\n }\n\n public void doStuff() {\n initialize();\n doOtherStuff();\n }\n</code></pre>\n\n<p>You could also do this with a simple <code>volatile boolean</code> which is actually a little more efficient than an AtomicBoolean.</p>\n"
|
68
|
+
},
|
69
|
+
{
|
70
|
+
"answer_id": 2878031,
|
71
|
+
"accepted": false,
|
72
|
+
"answer_comments_url": "/answers/2878031/comments",
|
73
|
+
"question_id": 2877955,
|
74
|
+
"owner": {
|
75
|
+
"user_id": 162273,
|
76
|
+
"user_type": "registered",
|
77
|
+
"display_name": "Jason",
|
78
|
+
"reputation": 855,
|
79
|
+
"email_hash": "ca27a5c424b2e8f1b9f9ea3747305751"
|
80
|
+
},
|
81
|
+
"creation_date": 1274391259,
|
82
|
+
"last_activity_date": 1274391259,
|
83
|
+
"up_vote_count": 0,
|
84
|
+
"down_vote_count": 0,
|
85
|
+
"view_count": 80,
|
86
|
+
"score": 0,
|
87
|
+
"community_owned": false,
|
88
|
+
"title": "Controlling race condition at startup.",
|
89
|
+
"body": "<p>It this is right at startup, why not wait to start the other threads until the initialization is complete?</p>\n\n<p>Also, you can do a thread-synchronized IsComplete boolean that is set to false until it is set to true by the initialization routine.</p>\n"
|
90
|
+
},
|
91
|
+
{
|
92
|
+
"answer_id": 2878040,
|
93
|
+
"accepted": true,
|
94
|
+
"answer_comments_url": "/answers/2878040/comments",
|
95
|
+
"question_id": 2877955,
|
96
|
+
"owner": {
|
97
|
+
"user_id": 3474,
|
98
|
+
"user_type": "registered",
|
99
|
+
"display_name": "erickson",
|
100
|
+
"reputation": 34431,
|
101
|
+
"email_hash": "ffbf4e85b8ffbae4e9039b9c0cf07bc8"
|
102
|
+
},
|
103
|
+
"creation_date": 1274391344,
|
104
|
+
"last_activity_date": 1274391344,
|
105
|
+
"up_vote_count": 4,
|
106
|
+
"down_vote_count": 0,
|
107
|
+
"view_count": 80,
|
108
|
+
"score": 4,
|
109
|
+
"community_owned": false,
|
110
|
+
"title": "Controlling race condition at startup.",
|
111
|
+
"body": "<p>That's a strange mix of library and built-in concurrency controls. Something like this is much cleaner:</p>\n\n<pre><code>public class MyClass {\n\n private static final CountDownLatch latch = new CountDownLatch(1);\n\n public void initialise() {\n initStuff();\n latch.countDown();\n }\n\n public void doStuff() {\n try {\n latch.await();\n } catch (InterruptedException ex) {\n throw new RuntimeException(\"Uh oh!\", ex);\n }\n doOtherStuff();\n }\n\n}\n</code></pre>\n"
|
112
|
+
},
|
113
|
+
{
|
114
|
+
"answer_id": 2878440,
|
115
|
+
"accepted": false,
|
116
|
+
"answer_comments_url": "/answers/2878440/comments",
|
117
|
+
"question_id": 2877955,
|
118
|
+
"owner": {
|
119
|
+
"user_id": 115432,
|
120
|
+
"user_type": "registered",
|
121
|
+
"display_name": "Kathy Van Stone",
|
122
|
+
"reputation": 3675,
|
123
|
+
"email_hash": "eca023f50940b65931dfe75a03e6e00a"
|
124
|
+
},
|
125
|
+
"creation_date": 1274395987,
|
126
|
+
"last_activity_date": 1274395987,
|
127
|
+
"up_vote_count": 0,
|
128
|
+
"down_vote_count": 0,
|
129
|
+
"view_count": 80,
|
130
|
+
"score": 0,
|
131
|
+
"community_owned": false,
|
132
|
+
"title": "Controlling race condition at startup.",
|
133
|
+
"body": "<p>The best may be to use a static initializer (as mentioned by SB):</p>\n\n<pre><code>public class MyClass {\n\n public static void doInitialize() {\n ...\n }\n\n public void doStuff() {\n doOtherStuff();\n }\n\n static {\n doInitialize();\n }\n}\n</code></pre>\n\n<p>This will get executed once before any other code is allowed to be called. If you will always have to initialize anytime the class is used then there is no performance hit as the class will not be loaded until it is used. See the answers to <a href=\"http://stackoverflow.com/questions/878577/are-java-static-initializers-thread-safe\">this question</a> for more details.</p>\n"
|
134
|
+
}
|
135
|
+
],
|
136
|
+
"accepted_answer_id": 2878040,
|
137
|
+
"favorite_count": 3,
|
138
|
+
"question_timeline_url": "/questions/2877955/timeline",
|
139
|
+
"question_comments_url": "/questions/2877955/comments",
|
140
|
+
"question_answers_url": "/questions/2877955/answers",
|
141
|
+
"question_id": 2877955,
|
142
|
+
"owner": {
|
143
|
+
"user_id": 13663,
|
144
|
+
"user_type": "registered",
|
145
|
+
"display_name": "Will Hartung",
|
146
|
+
"reputation": 13650,
|
147
|
+
"email_hash": "92f0c7fe372960ea92560487665a8720"
|
148
|
+
},
|
149
|
+
"creation_date": 1274390502,
|
150
|
+
"last_edit_date": 1274393269,
|
151
|
+
"last_activity_date": 1274396565,
|
152
|
+
"up_vote_count": 3,
|
153
|
+
"down_vote_count": 0,
|
154
|
+
"view_count": 80,
|
155
|
+
"score": 3,
|
156
|
+
"community_owned": false,
|
157
|
+
"title": "Controlling race condition at startup.",
|
158
|
+
"body": "<p>I have some code that I want to have some one time initialisation performed. But this code doesn't have a definite lifecycle, so my logic can be potentially invoked by multiple threads before my initialisation is done. So, I want to basically ensure that my logic code \"waits\" until initialisation is done.</p>\n\n<p>This is my first cut.</p>\n\n<pre><code>public class MyClass {\n private static final AtomicBoolean initialised = new AtomicBoolean(false);\n\n public void initialise() {\n synchronized(initialised) {\n initStuff();\n initialised.getAndSet(true);\n initialised.notifyAll();\n }\n }\n\n public void doStuff() {\n synchronized(initialised) {\n if (!initialised.get()) {\n try {\n initialised.wait();\n } catch (InterruptedException ex) {\n throw new RuntimeException(\"Uh oh!\", ex);\n }\n }\n }\n\n doOtherStuff();\n }\n}\n</code></pre>\n\n<p>I basically want to make sure this is going to do what I think it's going to do -- block doStuff until the initialised is true, and that I'm not missing a race condition where doStuff might get stuck on a Object.wait() that will never arrive.</p>\n\n<p>Edit:</p>\n\n<p>I have no control over the threads. And I want to be able to control when all of the initialisation is done, which is why doStuff() can't call initialise().</p>\n\n<p>I used an AtomicBoolean as it was a combination of a value holder, and an object I could synchronize. I could have also simply had a \"public static final Object lock = new Object();\" and a simple boolean flag. AtomicBoolean conveniently gave me both. A Boolean can not be modified.</p>\n\n<p>The CountDownLatch is exactly what I was looking for. I also considered using a Sempahore with 0 permits. But the CountDownLatch is perfect for just this task.</p>\n"
|
159
|
+
}
|
160
|
+
]
|
161
|
+
}
|
162
|
+
{"questions"=>
|
163
|
+
[{"question_id"=>2877955,
|
164
|
+
"up_vote_count"=>3,
|
165
|
+
"last_activity_date"=>1274396565,
|
166
|
+
"body"=>
|
167
|
+
"<p>I have some code that I want to have some one time initialisation performed. But this code doesn't have a definite lifecycle, so my logic can be potentially invoked by multiple threads before my initialisation is done. So, I want to basically ensure that my logic code \"waits\" until initialisation is done.</p>\n\n<p>This is my first cut.</p>\n\n<pre><code>public class MyClass {\n private static final AtomicBoolean initialised = new AtomicBoolean(false);\n\n public void initialise() {\n synchronized(initialised) {\n initStuff();\n initialised.getAndSet(true);\n initialised.notifyAll();\n }\n }\n\n public void doStuff() {\n synchronized(initialised) {\n if (!initialised.get()) {\n try {\n initialised.wait();\n } catch (InterruptedException ex) {\n throw new RuntimeException(\"Uh oh!\", ex);\n }\n }\n }\n\n doOtherStuff();\n }\n}\n</code></pre>\n\n<p>I basically want to make sure this is going to do what I think it's going to do -- block doStuff until the initialised is true, and that I'm not missing a race condition where doStuff might get stuck on a Object.wait() that will never arrive.</p>\n\n<p>Edit:</p>\n\n<p>I have no control over the threads. And I want to be able to control when all of the initialisation is done, which is why doStuff() can't call initialise().</p>\n\n<p>I used an AtomicBoolean as it was a combination of a value holder, and an object I could synchronize. I could have also simply had a \"public static final Object lock = new Object();\" and a simple boolean flag. AtomicBoolean conveniently gave me both. A Boolean can not be modified.</p>\n\n<p>The CountDownLatch is exactly what I was looking for. I also considered using a Sempahore with 0 permits. But the CountDownLatch is perfect for just this task.</p>\n",
|
168
|
+
"title"=>"Controlling race condition at startup.",
|
169
|
+
"down_vote_count"=>0,
|
170
|
+
"creation_date"=>1274390502,
|
171
|
+
"question_answers_url"=>"/questions/2877955/answers",
|
172
|
+
"favorite_count"=>3,
|
173
|
+
"answer_count"=>5,
|
174
|
+
"tags"=>["java", "concurrency"],
|
175
|
+
"last_edit_date"=>1274393269,
|
176
|
+
"accepted_answer_id"=>2878040,
|
177
|
+
"question_comments_url"=>"/questions/2877955/comments",
|
178
|
+
"community_owned"=>false,
|
179
|
+
"score"=>3,
|
180
|
+
"view_count"=>80,
|
181
|
+
"owner"=>
|
182
|
+
{"reputation"=>13650,
|
183
|
+
"user_type"=>"registered",
|
184
|
+
"email_hash"=>"92f0c7fe372960ea92560487665a8720",
|
185
|
+
"user_id"=>13663,
|
186
|
+
"display_name"=>"Will Hartung"},
|
187
|
+
"question_timeline_url"=>"/questions/2877955/timeline",
|
188
|
+
"answers"=>
|
189
|
+
[{"question_id"=>2877955,
|
190
|
+
"up_vote_count"=>2,
|
191
|
+
"last_activity_date"=>1274396565,
|
192
|
+
"body"=>
|
193
|
+
"<p>A <code>synchronized</code> block will automatically block other threads. Just use a simple lock object + status variable:</p>\n\n<pre><code>public class MyClass {\n private static boolean initialised;\n private static final Object lockObject = new Object();\n\n public void initialise() {\n synchronized (lockObject) {\n if (!initialised) {\n initStuff();\n initialised = true;\n }\n }\n }\n\n public void doStuff() {\n initialise();\n doOtherStuff();\n }\n}\n</code></pre>\n",
|
194
|
+
"title"=>"Controlling race condition at startup.",
|
195
|
+
"down_vote_count"=>1,
|
196
|
+
"creation_date"=>1274391221,
|
197
|
+
"last_edit_date"=>1274396565,
|
198
|
+
"community_owned"=>false,
|
199
|
+
"answer_id"=>2878021,
|
200
|
+
"score"=>1,
|
201
|
+
"answer_comments_url"=>"/answers/2878021/comments",
|
202
|
+
"accepted"=>false,
|
203
|
+
"view_count"=>80,
|
204
|
+
"owner"=>
|
205
|
+
{"reputation"=>1268,
|
206
|
+
"user_type"=>"registered",
|
207
|
+
"email_hash"=>"0e8190d89907e24bf6b4438eaef71af4",
|
208
|
+
"user_id"=>142446,
|
209
|
+
"display_name"=>"bkail"}},
|
210
|
+
{"question_id"=>2877955,
|
211
|
+
"up_vote_count"=>0,
|
212
|
+
"last_activity_date"=>1274391655,
|
213
|
+
"body"=>
|
214
|
+
"<p>You're using <code>AtomicBoolean</code> always from inside a synchronized block. There's not much point to that since only one thread can access it. Atomic variables are intended for use in lock-free solutions - you can get and set the value as an uninterruptable unit.</p>\n\n<p>I guess you are looking for a lock free solution once the intiialization has happened:</p>\n\n<pre><code>public class MyClass {\n private static final AtomicBoolean initialised = new AtomicBoolean(false);\n\n public void initialise() {\n if (!intialized.get())\n {\n synchornized (this)\n {\n if (!initialized.getAndSet(true))\n doInitialize();\n }\n }\n }\n\n public void doStuff() {\n initialize();\n doOtherStuff();\n }\n</code></pre>\n\n<p>You could also do this with a simple <code>volatile boolean</code> which is actually a little more efficient than an AtomicBoolean.</p>\n",
|
215
|
+
"title"=>"Controlling race condition at startup.",
|
216
|
+
"down_vote_count"=>0,
|
217
|
+
"creation_date"=>1274391233,
|
218
|
+
"last_edit_date"=>1274391655,
|
219
|
+
"community_owned"=>false,
|
220
|
+
"answer_id"=>2878026,
|
221
|
+
"score"=>0,
|
222
|
+
"answer_comments_url"=>"/answers/2878026/comments",
|
223
|
+
"accepted"=>false,
|
224
|
+
"view_count"=>80,
|
225
|
+
"owner"=>
|
226
|
+
{"reputation"=>4572,
|
227
|
+
"user_type"=>"registered",
|
228
|
+
"email_hash"=>"12778253a95d10ba54e696ffab85103d",
|
229
|
+
"user_id"=>326480,
|
230
|
+
"display_name"=>"mdma"}},
|
231
|
+
{"question_id"=>2877955,
|
232
|
+
"up_vote_count"=>0,
|
233
|
+
"last_activity_date"=>1274391259,
|
234
|
+
"body"=>
|
235
|
+
"<p>It this is right at startup, why not wait to start the other threads until the initialization is complete?</p>\n\n<p>Also, you can do a thread-synchronized IsComplete boolean that is set to false until it is set to true by the initialization routine.</p>\n",
|
236
|
+
"title"=>"Controlling race condition at startup.",
|
237
|
+
"down_vote_count"=>0,
|
238
|
+
"creation_date"=>1274391259,
|
239
|
+
"community_owned"=>false,
|
240
|
+
"answer_id"=>2878031,
|
241
|
+
"score"=>0,
|
242
|
+
"answer_comments_url"=>"/answers/2878031/comments",
|
243
|
+
"accepted"=>false,
|
244
|
+
"view_count"=>80,
|
245
|
+
"owner"=>
|
246
|
+
{"reputation"=>855,
|
247
|
+
"user_type"=>"registered",
|
248
|
+
"email_hash"=>"ca27a5c424b2e8f1b9f9ea3747305751",
|
249
|
+
"user_id"=>162273,
|
250
|
+
"display_name"=>"Jason"}},
|
251
|
+
{"question_id"=>2877955,
|
252
|
+
"up_vote_count"=>4,
|
253
|
+
"last_activity_date"=>1274391344,
|
254
|
+
"body"=>
|
255
|
+
"<p>That's a strange mix of library and built-in concurrency controls. Something like this is much cleaner:</p>\n\n<pre><code>public class MyClass {\n\n private static final CountDownLatch latch = new CountDownLatch(1);\n\n public void initialise() {\n initStuff();\n latch.countDown();\n }\n\n public void doStuff() {\n try {\n latch.await();\n } catch (InterruptedException ex) {\n throw new RuntimeException(\"Uh oh!\", ex);\n }\n doOtherStuff();\n }\n\n}\n</code></pre>\n",
|
256
|
+
"title"=>"Controlling race condition at startup.",
|
257
|
+
"down_vote_count"=>0,
|
258
|
+
"creation_date"=>1274391344,
|
259
|
+
"community_owned"=>false,
|
260
|
+
"answer_id"=>2878040,
|
261
|
+
"score"=>4,
|
262
|
+
"answer_comments_url"=>"/answers/2878040/comments",
|
263
|
+
"accepted"=>true,
|
264
|
+
"view_count"=>80,
|
265
|
+
"owner"=>
|
266
|
+
{"reputation"=>34431,
|
267
|
+
"user_type"=>"registered",
|
268
|
+
"email_hash"=>"ffbf4e85b8ffbae4e9039b9c0cf07bc8",
|
269
|
+
"user_id"=>3474,
|
270
|
+
"display_name"=>"erickson"}},
|
271
|
+
{"question_id"=>2877955,
|
272
|
+
"up_vote_count"=>0,
|
273
|
+
"last_activity_date"=>1274395987,
|
274
|
+
"body"=>
|
275
|
+
"<p>The best may be to use a static initializer (as mentioned by SB):</p>\n\n<pre><code>public class MyClass {\n\n public static void doInitialize() {\n ...\n }\n\n public void doStuff() {\n doOtherStuff();\n }\n\n static {\n doInitialize();\n }\n}\n</code></pre>\n\n<p>This will get executed once before any other code is allowed to be called. If you will always have to initialize anytime the class is used then there is no performance hit as the class will not be loaded until it is used. See the answers to <a href=\"http://stackoverflow.com/questions/878577/are-java-static-initializers-thread-safe\">this question</a> for more details.</p>\n",
|
276
|
+
"title"=>"Controlling race condition at startup.",
|
277
|
+
"down_vote_count"=>0,
|
278
|
+
"creation_date"=>1274395987,
|
279
|
+
"community_owned"=>false,
|
280
|
+
"answer_id"=>2878440,
|
281
|
+
"score"=>0,
|
282
|
+
"answer_comments_url"=>"/answers/2878440/comments",
|
283
|
+
"accepted"=>false,
|
284
|
+
"view_count"=>80,
|
285
|
+
"owner"=>
|
286
|
+
{"reputation"=>3675,
|
287
|
+
"user_type"=>"registered",
|
288
|
+
"email_hash"=>"eca023f50940b65931dfe75a03e6e00a",
|
289
|
+
"user_id"=>115432,
|
290
|
+
"display_name"=>"Kathy Van Stone"}}]}],
|
291
|
+
"total"=>1,
|
292
|
+
"page"=>1,
|
293
|
+
"pagesize"=>30}
|
294
|
+
|
295
|
+
|
296
|
+
|
297
|
+
|
298
|
+
== Note on Patches/Pull Requests
|
299
|
+
|
300
|
+
* Fork the project.
|
301
|
+
* Make your feature addition or bug fix.
|
302
|
+
* Add tests for it. This is important so I don't break it in a
|
303
|
+
future version unintentionally.
|
304
|
+
* Commit, do not mess with rakefile, version, or history.
|
305
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
306
|
+
* Send me a pull request. Bonus points for topic branches.
|
307
|
+
|
308
|
+
== Copyright
|
309
|
+
|
310
|
+
Copyright (c) 2010 Sam Coles. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "stack_exchange"
|
8
|
+
gem.summary = %Q{A Ruby interface to the Stack Exchange API}
|
9
|
+
gem.description = %Q{Access any Stack Exchange site (stackoverflow.com, ect.) from Ruby.}
|
10
|
+
gem.email = "sam@coredumplings.com"
|
11
|
+
gem.homepage = "http://github.com/coredumplings/stack_exchange"
|
12
|
+
gem.authors = ["Sam Coles"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/test_*.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "stack_exchange #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
require 'zlib'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
module StackExchange
|
8
|
+
class Base
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
:site => 'stackoverflow.com',
|
11
|
+
:version => '0.8',
|
12
|
+
:http_basic_authentication => nil,
|
13
|
+
'Accept-Encoding' => 'gzip',
|
14
|
+
'User-Agent' => "Ruby StackExchange Client/#{VERSION}"
|
15
|
+
}
|
16
|
+
|
17
|
+
attr_reader :base_uri
|
18
|
+
attr_reader :http_options
|
19
|
+
|
20
|
+
def initialize(opts={})
|
21
|
+
opts = DEFAULT_OPTIONS.merge(opts)
|
22
|
+
@base_uri = URI.parse("http://api.#{opts.delete(:site)}/#{opts.delete(:version)}/")
|
23
|
+
@http_options = opts
|
24
|
+
end
|
25
|
+
|
26
|
+
def hash_to_querystring(hash)
|
27
|
+
hash.keys.inject('') do |query_string, key|
|
28
|
+
query_string << '&' unless key == hash.keys.first
|
29
|
+
query_string << "#{URI.encode(key.to_s)}=#{URI.encode(hash[key])}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(method_name, *args)
|
34
|
+
query = hash_to_querystring(args.pop) if args && args.last.is_a?(Hash)
|
35
|
+
uri = base_uri + ([method_name.to_s] + args).join('/')
|
36
|
+
uri.query = query if defined?(query)
|
37
|
+
response = open( uri, http_options ).read
|
38
|
+
uncompressed_response = Zlib::GzipReader.new(StringIO.new( response )).read
|
39
|
+
JSON.parse( uncompressed_response )
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{stack_exchange}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Sam Coles"]
|
12
|
+
s.date = %q{2010-05-24}
|
13
|
+
s.description = %q{Access any Stack Exchange site (stackoverflow.com, ect.) from Ruby.}
|
14
|
+
s.email = %q{sam@coredumplings.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/stack_exchange.rb",
|
27
|
+
"lib/stack_exchange/base.rb",
|
28
|
+
"stack_exchange.gemspec",
|
29
|
+
"test/helper.rb",
|
30
|
+
"test/test_stack_exchange.rb"
|
31
|
+
]
|
32
|
+
s.homepage = %q{http://github.com/coredumplings/stack_exchange}
|
33
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = %q{1.3.7}
|
36
|
+
s.summary = %q{A Ruby interface to the Stack Exchange API}
|
37
|
+
s.test_files = [
|
38
|
+
"test/helper.rb",
|
39
|
+
"test/test_stack_exchange.rb"
|
40
|
+
]
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
else
|
48
|
+
end
|
49
|
+
else
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stack_exchange
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sam Coles
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-05-24 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Access any Stack Exchange site (stackoverflow.com, ect.) from Ruby.
|
23
|
+
email: sam@coredumplings.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- LICENSE
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- .document
|
33
|
+
- .gitignore
|
34
|
+
- LICENSE
|
35
|
+
- README.rdoc
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- lib/stack_exchange.rb
|
39
|
+
- lib/stack_exchange/base.rb
|
40
|
+
- stack_exchange.gemspec
|
41
|
+
- test/helper.rb
|
42
|
+
- test/test_stack_exchange.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/coredumplings/stack_exchange
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --charset=UTF-8
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.7
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: A Ruby interface to the Stack Exchange API
|
77
|
+
test_files:
|
78
|
+
- test/helper.rb
|
79
|
+
- test/test_stack_exchange.rb
|