zlx_hacker_term 0.0.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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +22 -0
- data/README.md +17 -0
- data/Rakefile +3 -0
- data/bin/hacker_term +6 -0
- data/data/data.json +314 -0
- data/hacker_term.gemspec +23 -0
- data/lib/hacker_term/page_data.rb +125 -0
- data/lib/hacker_term/ui.rb +153 -0
- data/lib/hacker_term/version.rb +3 -0
- data/lib/hacker_term.rb +102 -0
- data/spec/page_data_spec.rb +165 -0
- metadata +125 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
hacker_term (0.0.4)
|
|
5
|
+
clipboard
|
|
6
|
+
launchy
|
|
7
|
+
rest-client
|
|
8
|
+
socksify
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: https://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
addressable (2.3.2)
|
|
14
|
+
clipboard (1.0.1)
|
|
15
|
+
launchy (2.1.2)
|
|
16
|
+
addressable (~> 2.3)
|
|
17
|
+
mime-types (1.19)
|
|
18
|
+
rest-client (1.6.7)
|
|
19
|
+
mime-types (>= 1.16)
|
|
20
|
+
socksify (1.4.1)
|
|
21
|
+
|
|
22
|
+
PLATFORMS
|
|
23
|
+
ruby
|
|
24
|
+
|
|
25
|
+
DEPENDENCIES
|
|
26
|
+
hacker_term!
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Ciaran Archer
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
hacker_term
|
|
2
|
+
==========
|
|
3
|
+
Hacker News on the Terminal.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Requirements
|
|
8
|
+
------------
|
|
9
|
+
* Ruby 1.9.3
|
|
10
|
+
* socksify
|
|
11
|
+
* socks proxy
|
|
12
|
+
|
|
13
|
+
Installation
|
|
14
|
+
------------
|
|
15
|
+
* Install with `gem install hacker_term`
|
|
16
|
+
* Run using `socksify_ruby 127.0.0.1 1080 hacker_term`
|
|
17
|
+
* Tests included; I run them using `rspec -fd` in the project directory
|
data/Rakefile
ADDED
data/bin/hacker_term
ADDED
data/data/data.json
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
{"items":[
|
|
2
|
+
{
|
|
3
|
+
"title":"PowWow - Collaborative Screen Sharing",
|
|
4
|
+
"url":"http://powwow.cc/",
|
|
5
|
+
"score":"124 points",
|
|
6
|
+
"user":"siong1987",
|
|
7
|
+
"comments":"43 comments",
|
|
8
|
+
"time":"7 hours ago",
|
|
9
|
+
"item_id":"4924763",
|
|
10
|
+
"description":"124 points by siong1987 7 hours ago | 43 comments"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"title":"Ray Kurzweil joins Google",
|
|
14
|
+
"url":"http://www.kurzweilai.net/kurzweil-joins-google-to-work-on-new-projects-involving-machine-learning-and-language-processing?utm_source=twitterfeed&utm_medium=twitter",
|
|
15
|
+
"score":"260 points",
|
|
16
|
+
"user":"dumitrue",
|
|
17
|
+
"comments":"122 comments",
|
|
18
|
+
"time":"14 hours ago",
|
|
19
|
+
"item_id":"4923914",
|
|
20
|
+
"description":"260 points by dumitrue 14 hours ago | 122 comments"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"title":"The Pinboard Investment Co-Prosperity Cloud",
|
|
24
|
+
"url":"http://static.pinboard.in/prosperity_cloud.htm",
|
|
25
|
+
"score":"381 points",
|
|
26
|
+
"user":"adulau",
|
|
27
|
+
"comments":"136 comments",
|
|
28
|
+
"time":"17 hours ago",
|
|
29
|
+
"item_id":"4923136",
|
|
30
|
+
"description":"381 points by adulau 17 hours ago | 136 comments"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"title":"Web Revenue Models - 9 Categories, 80+ variations",
|
|
34
|
+
"url":"https://hackpad.com/EgXuEtSibE7#Web-And-Mobile-Revenue-Models-%28final%29",
|
|
35
|
+
"score":"84 points",
|
|
36
|
+
"user":"stickhandle",
|
|
37
|
+
"comments":"6 comments",
|
|
38
|
+
"time":"8 hours ago",
|
|
39
|
+
"item_id":"4924647",
|
|
40
|
+
"description":"84 points by stickhandle 8 hours ago | 6 comments"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"title":"The 2012 Perl 6 Coding Contest",
|
|
44
|
+
"url":"http://strangelyconsistent.org/blog/the-2012-perl-6-coding-contest",
|
|
45
|
+
"score":"20 points",
|
|
46
|
+
"user":"draegtun",
|
|
47
|
+
"comments":"discuss",
|
|
48
|
+
"time":"4 hours ago",
|
|
49
|
+
"item_id":"4925036",
|
|
50
|
+
"description":"20 points by draegtun 4 hours ago | discuss"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"title":"Brython - Python to Javascript translator",
|
|
54
|
+
"url":"http://www.brython.info/index_en.html",
|
|
55
|
+
"score":"222 points",
|
|
56
|
+
"user":"toni",
|
|
57
|
+
"comments":"55 comments",
|
|
58
|
+
"time":"16 hours ago",
|
|
59
|
+
"item_id":"4923530",
|
|
60
|
+
"description":"222 points by toni 16 hours ago | 55 comments"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"title":"Dear Open Source Project Leader: Quit Being A Jerk",
|
|
64
|
+
"url":"http://lostechies.com/derickbailey/2012/12/14/dear-open-source-project-leader-quit-being-a-jerk/",
|
|
65
|
+
"score":"362 points",
|
|
66
|
+
"user":"derickbailey",
|
|
67
|
+
"comments":"158 comments",
|
|
68
|
+
"time":"22 hours ago",
|
|
69
|
+
"item_id":"4921152",
|
|
70
|
+
"description":"362 points by derickbailey 22 hours ago | 158 comments"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"title":"Google Disabling Exchange Sync for Free Accounts",
|
|
74
|
+
"url":"http://support.google.com/a/bin/answer.py?hl=en&answer=135937",
|
|
75
|
+
"score":"145 points",
|
|
76
|
+
"user":"HaloZero",
|
|
77
|
+
"comments":"115 comments",
|
|
78
|
+
"time":"14 hours ago",
|
|
79
|
+
"item_id":"4923832",
|
|
80
|
+
"description":"145 points by HaloZero 14 hours ago | 115 comments"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"title":"Want to learn to code? Don't copy and paste, type out other people's code",
|
|
84
|
+
"url":"http://www.shockoe.com/blog/typingcodeout/",
|
|
85
|
+
"score":"283 points",
|
|
86
|
+
"user":"tomasien",
|
|
87
|
+
"comments":"167 comments",
|
|
88
|
+
"time":"22 hours ago",
|
|
89
|
+
"item_id":"4921258",
|
|
90
|
+
"description":"283 points by tomasien 22 hours ago | 167 comments"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"title":"Moravec's paradox",
|
|
94
|
+
"url":"https://en.wikipedia.org/wiki/Moravec%27s_paradox",
|
|
95
|
+
"score":"144 points",
|
|
96
|
+
"user":"kristiandupont",
|
|
97
|
+
"comments":"34 comments",
|
|
98
|
+
"time":"16 hours ago",
|
|
99
|
+
"item_id":"4923299",
|
|
100
|
+
"description":"144 points by kristiandupont 16 hours ago | 34 comments"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"title":"A Bad UI Pattern",
|
|
104
|
+
"url":"http://www.kapilkale.com/blog/the-worst-ui-pattern-in-existence/",
|
|
105
|
+
"score":"91 points",
|
|
106
|
+
"user":"kapilkale",
|
|
107
|
+
"comments":"45 comments",
|
|
108
|
+
"time":"13 hours ago",
|
|
109
|
+
"item_id":"4923971",
|
|
110
|
+
"description":"91 points by kapilkale 13 hours ago | 45 comments"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"title":"I love you, dad",
|
|
114
|
+
"url":"http://notch.tumblr.com/post/37823268132/i-love-you-dad",
|
|
115
|
+
"score":"987 points",
|
|
116
|
+
"user":"kjackson2012",
|
|
117
|
+
"comments":"204 comments",
|
|
118
|
+
"time":"1 day ago",
|
|
119
|
+
"item_id":"4916629",
|
|
120
|
+
"description":"987 points by kjackson2012 1 day ago | 204 comments"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"title":"What euros Facebooktrades responsibility when the nation seeks to lynch someone?",
|
|
124
|
+
"url":"http://pandodaily.com/2012/12/14/whats-facebooks-responsibility-when-the-nation-seeks-to-lynch-someone-on-only-a-name/",
|
|
125
|
+
"score":"81 points",
|
|
126
|
+
"user":"muratmutlu",
|
|
127
|
+
"comments":"71 comments",
|
|
128
|
+
"time":"11 hours ago",
|
|
129
|
+
"item_id":"4924361",
|
|
130
|
+
"description":"81 points by muratmutlu 11 hours ago | 71 comments"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"title":"A vim interface for gmail: Vmail",
|
|
134
|
+
"url":"http://www.danielchoi.com/software/vmail.html",
|
|
135
|
+
"score":"128 points",
|
|
136
|
+
"user":"ezl",
|
|
137
|
+
"comments":"67 comments",
|
|
138
|
+
"time":"18 hours ago",
|
|
139
|
+
"item_id":"4922542",
|
|
140
|
+
"description":"128 points by ezl 18 hours ago | 67 comments"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"title":"E Online has left a Gist url on the top of their site",
|
|
144
|
+
"url":"http://www.eonline.com/news/371827/newtown-school-shooting-jack-reacher-u-s-premiere-postponed-out-of-respect-for-victims",
|
|
145
|
+
"score":"39 points",
|
|
146
|
+
"user":"Moto7451",
|
|
147
|
+
"comments":"14 comments",
|
|
148
|
+
"time":"8 hours ago",
|
|
149
|
+
"item_id":"4924624",
|
|
150
|
+
"description":"39 points by Moto7451 8 hours ago | 14 comments"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"title":"The Web We Lost",
|
|
154
|
+
"url":"http://dashes.com/anil/2012/12/the-web-we-lost.html",
|
|
155
|
+
"score":"593 points",
|
|
156
|
+
"user":"kzasada",
|
|
157
|
+
"comments":"151 comments",
|
|
158
|
+
"time":"1 day ago",
|
|
159
|
+
"item_id":"4917828",
|
|
160
|
+
"description":"593 points by kzasada 1 day ago | 151 comments"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"title":"NASA Eyes Mission To Icy Jupiter Moon Europa To Gauge Habitability",
|
|
164
|
+
"url":"http://www.space.com/18901-nasa-mission-jupiter-moon-europa.html",
|
|
165
|
+
"score":"26 points",
|
|
166
|
+
"user":"rpm4321",
|
|
167
|
+
"comments":"5 comments",
|
|
168
|
+
"time":"9 hours ago",
|
|
169
|
+
"item_id":"4924607",
|
|
170
|
+
"description":"26 points by rpm4321 9 hours ago | 5 comments"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"title":"Tcl the misunderstood (2006)",
|
|
174
|
+
"url":"http://antirez.com/articoli/tclmisunderstood.html?",
|
|
175
|
+
"score":"170 points",
|
|
176
|
+
"user":"zeitg3ist",
|
|
177
|
+
"comments":"96 comments",
|
|
178
|
+
"time":"1 day ago",
|
|
179
|
+
"item_id":"4920831",
|
|
180
|
+
"description":"170 points by zeitg3ist 1 day ago | 96 comments"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"title":"EFF: Stop Congress from Reauthorizing the Warrantless Spying Bill",
|
|
184
|
+
"url":"https://www.eff.org/deeplinks/2012/12/congress-poised-reauthorize-fisa-amendment-act-warrantless-spying-bill",
|
|
185
|
+
"score":"191 points",
|
|
186
|
+
"user":"mtgx",
|
|
187
|
+
"comments":"38 comments",
|
|
188
|
+
"time":"1 day ago",
|
|
189
|
+
"item_id":"4920542",
|
|
190
|
+
"description":"191 points by mtgx 1 day ago | 38 comments"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"title":"Thank HN: You helped the FreeBSD Foundation raise over $43K in three days",
|
|
194
|
+
"url":"http://freebsdfoundation.blogspot.com/2012/12/stunning-news-website-fundraising.html#",
|
|
195
|
+
"score":"154 points",
|
|
196
|
+
"user":"profquail",
|
|
197
|
+
"comments":"23 comments",
|
|
198
|
+
"time":"23 hours ago",
|
|
199
|
+
"item_id":"4920891",
|
|
200
|
+
"description":"154 points by profquail 23 hours ago | 23 comments"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"title":"Meet the World trades Cheapest Venture Capitalist",
|
|
204
|
+
"url":"http://www.wired.com/business/2012/12/worlds-cheapest-venture-capitalist/",
|
|
205
|
+
"score":"34 points",
|
|
206
|
+
"user":"wyclif",
|
|
207
|
+
"comments":"9 comments",
|
|
208
|
+
"time":"8 hours ago",
|
|
209
|
+
"item_id":"4924651",
|
|
210
|
+
"description":"34 points by wyclif 8 hours ago | 9 comments"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"title":"Programmer creates 800,000 books algorithmically, starts selling them on Amazon",
|
|
214
|
+
"url":"http://www.extremetech.com/extreme/143382-programmer-creates-800000-books-algorithmically-starts-selling-them-on-amazon?utm_source=rss&utm_medium=rss&utm_campaign=programmer-creates-800000-books-algorithmically-starts-selling-them-on-amazon",
|
|
215
|
+
"score":"131 points",
|
|
216
|
+
"user":"Libertatea",
|
|
217
|
+
"comments":"119 comments",
|
|
218
|
+
"time":"17 hours ago",
|
|
219
|
+
"item_id":"4923208",
|
|
220
|
+
"description":"131 points by Libertatea 17 hours ago | 119 comments"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"title":"Internet porn: Automatic block rejected (UK)",
|
|
224
|
+
"url":"http://www.bbc.co.uk/news/uk-politics-20738746",
|
|
225
|
+
"score":"6 points",
|
|
226
|
+
"user":"andrewaylett",
|
|
227
|
+
"comments":"4 comments",
|
|
228
|
+
"time":"3 hours ago",
|
|
229
|
+
"item_id":"4925047",
|
|
230
|
+
"description":"6 points by andrewaylett 3 hours ago | 4 comments"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"title":"Working alone sucks",
|
|
234
|
+
"url":"http://fleetadmiral.tumblr.com/post/37907736486/working-alone-sucks",
|
|
235
|
+
"score":"139 points",
|
|
236
|
+
"user":"labaraka",
|
|
237
|
+
"comments":"108 comments",
|
|
238
|
+
"time":"23 hours ago",
|
|
239
|
+
"item_id":"4921047",
|
|
240
|
+
"description":"139 points by labaraka 23 hours ago | 108 comments"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"title":"Ole Roemer and the Speed of Light",
|
|
244
|
+
"url":"http://www.amnh.org/education/resources/rfl/web/essaybooks/cosmic/p_roemer.html",
|
|
245
|
+
"score":"307 points",
|
|
246
|
+
"user":"tmoretti",
|
|
247
|
+
"comments":"66 comments",
|
|
248
|
+
"time":"1 day ago",
|
|
249
|
+
"item_id":"4919594",
|
|
250
|
+
"description":"307 points by tmoretti 1 day ago | 66 comments"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"title":"Show HN: Introducing KA Lite, an offline version of Khan Academy",
|
|
254
|
+
"url":"http://jamiealexandre.com/blog/2012/12/12/ka-lite-offline-khan-academy/",
|
|
255
|
+
"score":"49 points",
|
|
256
|
+
"user":"jamalex",
|
|
257
|
+
"comments":"3 comments",
|
|
258
|
+
"time":"14 hours ago",
|
|
259
|
+
"item_id":"4923821",
|
|
260
|
+
"description":"49 points by jamalex 14 hours ago | 3 comments"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"title":"Google Maps for iOS",
|
|
264
|
+
"url":"https://itunes.apple.com/us/app/google-maps/id585027354?mt=8",
|
|
265
|
+
"score":"808 points",
|
|
266
|
+
"user":"zacharytamas",
|
|
267
|
+
"comments":"440 comments",
|
|
268
|
+
"time":"2 days ago",
|
|
269
|
+
"item_id":"4914089",
|
|
270
|
+
"description":"808 points by zacharytamas 2 days ago | 440 comments"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"title":"Perfect Audience (YC S11) seeks full-stack engineer",
|
|
274
|
+
"url":"item?id=4924955",
|
|
275
|
+
"time":"5 hours ago",
|
|
276
|
+
"description":"5 hours ago"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"title":"AngelList Raising A Big Round, To Be Valued at $150 Million Or More",
|
|
280
|
+
"url":"http://techcrunch.com/2012/12/14/angellist-to-be-valued-at-150-million-or-more/",
|
|
281
|
+
"score":"33 points",
|
|
282
|
+
"user":"zosegal",
|
|
283
|
+
"comments":"12 comments",
|
|
284
|
+
"time":"12 hours ago",
|
|
285
|
+
"item_id":"4924134",
|
|
286
|
+
"description":"33 points by zosegal 12 hours ago | 12 comments"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"title":"Data crunching to find the cheapest airline in the world",
|
|
290
|
+
"url":"http://www.tnooz.com/2012/12/14/news/data-crunching-to-find-the-cheapest-airline-in-the-world/",
|
|
291
|
+
"score":"28 points",
|
|
292
|
+
"user":"tomhoward",
|
|
293
|
+
"comments":"2 comments",
|
|
294
|
+
"time":"11 hours ago",
|
|
295
|
+
"item_id":"4924319",
|
|
296
|
+
"description":"28 points by tomhoward 11 hours ago | 2 comments"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"title":"NextId",
|
|
300
|
+
"url":"/news2",
|
|
301
|
+
"description":"hn next id news2 "
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"title": "Ask HN: Who is hiring? (January 2013)",
|
|
305
|
+
"url": "item?id=4992617",
|
|
306
|
+
"score": "175 points",
|
|
307
|
+
"user": "whoishiring",
|
|
308
|
+
"comments": "140 comments",
|
|
309
|
+
"time": "11 hours ago",
|
|
310
|
+
"item_id": "4992617",
|
|
311
|
+
"description": "175 points points by whoishiring 11 hours ago | 140 comments"
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
}
|
data/hacker_term.gemspec
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'hacker_term/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "zlx_hacker_term"
|
|
8
|
+
gem.version = HackerTerm::VERSION
|
|
9
|
+
gem.authors = ["soffolk zhu"]
|
|
10
|
+
gem.email = ["zlx.star@gmail.com"]
|
|
11
|
+
gem.description = %q{Read Hacker News on the Terminal}
|
|
12
|
+
gem.summary = %q{Allows the reading, sorting and opening of HN articles from the terminal.}
|
|
13
|
+
gem.homepage = "https://github.com/zlx/hacker_term"
|
|
14
|
+
gem.add_dependency('rest-client')
|
|
15
|
+
gem.add_dependency('launchy')
|
|
16
|
+
gem.add_dependency('clipboard')
|
|
17
|
+
gem.add_dependency('socksify')
|
|
18
|
+
|
|
19
|
+
gem.files = `git ls-files`.split($/)
|
|
20
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
21
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
22
|
+
gem.require_paths = ["lib"]
|
|
23
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
# Controversial monkeypatch of String class so it can tell us if a string is a number
|
|
4
|
+
class String
|
|
5
|
+
def is_num?
|
|
6
|
+
self =~ /^[-+]?[0-9]*\.?[0-9]+$/
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module HackerTerm
|
|
11
|
+
class PageData
|
|
12
|
+
attr_reader :data, :mean_score, :median_score, :mode_score, :sorted_by, :line_pos
|
|
13
|
+
|
|
14
|
+
def initialize(data)
|
|
15
|
+
@data = JSON.parse(data)['items']
|
|
16
|
+
|
|
17
|
+
add_missing_keys!
|
|
18
|
+
format_numbers!
|
|
19
|
+
format_urls!
|
|
20
|
+
|
|
21
|
+
calculate_mean_score
|
|
22
|
+
calculate_median_score
|
|
23
|
+
calculate_mode_score
|
|
24
|
+
|
|
25
|
+
@sorted_by = 'RANK'
|
|
26
|
+
@line_pos = 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def sort_on!(mode)
|
|
30
|
+
case mode
|
|
31
|
+
when :score
|
|
32
|
+
@data = @data.sort_by { |a| -a['score'].to_f } # desc
|
|
33
|
+
when :comments
|
|
34
|
+
@data = @data.sort_by { |a| -a['comments'].to_f } # desc
|
|
35
|
+
when :rank
|
|
36
|
+
@data = @data.sort_by { |a| a['rank'].to_f }
|
|
37
|
+
when :title
|
|
38
|
+
@data = @data.sort_by { |a| a['title'].upcase }
|
|
39
|
+
else
|
|
40
|
+
throw "Sorting mode #{mode} not supported!"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@sorted_by = mode.to_s.upcase
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def change_line_pos(direction)
|
|
47
|
+
if direction == :up
|
|
48
|
+
@line_pos += 1 unless @line_pos == @data.length
|
|
49
|
+
elsif direction == :down
|
|
50
|
+
@line_pos -= 1 unless @line_pos == 1
|
|
51
|
+
elsif direction == :reset
|
|
52
|
+
@line_pos = 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def selected_url
|
|
57
|
+
@data[@line_pos - 1]['url']
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def selected_comments_url
|
|
61
|
+
"http://news.ycombinator.com/item?id=" + @data[@line_pos - 1]['item_id']
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def calculate_mode_score
|
|
67
|
+
freq = @data.inject(Hash.new(0)) { |h,v| h[v['score'].to_f] += 1; h }
|
|
68
|
+
# Call sort_by on hash to create an array which each contains two elements, the key and value
|
|
69
|
+
# So we grab the last item, and return the 'key' from our original hash
|
|
70
|
+
@mode_score = freq.sort_by { |k, v| v }.last.first
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def calculate_mean_score
|
|
74
|
+
@mean_score = @data.inject(0.0) { |sum, el| sum + el['score'].to_f } / @data.size
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def calculate_median_score
|
|
78
|
+
# Read our numbers and sort them first
|
|
79
|
+
sorted_scores = @data.map { |el| el['score'].to_f }.sort
|
|
80
|
+
len = sorted_scores.length
|
|
81
|
+
@median_score = len % 2 == 1 ? sorted_scores[len / 2] : (sorted_scores[len / 2 - 1] + sorted_scores[len / 2]).to_f / 2
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def add_missing_keys!
|
|
85
|
+
# Here we're looking to fix nodes with missing/incorrect data
|
|
86
|
+
counter = 1
|
|
87
|
+
@data.each do |item|
|
|
88
|
+
|
|
89
|
+
# Add rank (so we can re-sort in 'natural' order)
|
|
90
|
+
unless item.has_key? 'rank'
|
|
91
|
+
item['rank'] = counter.to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
unless item.has_key? 'score'
|
|
95
|
+
item['score'] = '0'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
unless item.has_key? 'comments'
|
|
99
|
+
item['comments'] = '0'
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
counter += 1
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def format_numbers!
|
|
107
|
+
# Assumption here is a format like '10 comments' or '35 points'
|
|
108
|
+
# Also chucks anything left over that isn't a number
|
|
109
|
+
@data.each do |item|
|
|
110
|
+
item['comments'] = item['comments'].split(' ').first if item['comments'].include? ' '
|
|
111
|
+
item['comments'] = '0' unless item['comments'].is_num?
|
|
112
|
+
item['score'] = item['score'].split(' ').first if item['score'].include? ' '
|
|
113
|
+
item['score'] = '0' unless item['score'].is_num?
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def format_urls!
|
|
118
|
+
# Add HN domain for posts without an external link
|
|
119
|
+
@data.each do |item|
|
|
120
|
+
item['url'] = "http://news.ycombinator.com/#{item['url']}" if item['url'] =~ /^item\?id=[0-9]+/
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require 'curses'
|
|
2
|
+
|
|
3
|
+
module HackerTerm
|
|
4
|
+
class UI
|
|
5
|
+
include Curses
|
|
6
|
+
|
|
7
|
+
def initialize(opts={})
|
|
8
|
+
|
|
9
|
+
opts = defaults.merge(opts) # Ununsed for now
|
|
10
|
+
|
|
11
|
+
raw # Intercept everything
|
|
12
|
+
noecho # Do not echo user input to stdout
|
|
13
|
+
stdscr.keypad(true) # Enable arrows
|
|
14
|
+
|
|
15
|
+
if can_change_color?
|
|
16
|
+
start_color
|
|
17
|
+
# foreground / background colours
|
|
18
|
+
init_pair(0, COLOR_WHITE, COLOR_BLACK)
|
|
19
|
+
init_pair(1, COLOR_WHITE, COLOR_BLUE)
|
|
20
|
+
init_pair(2, COLOR_WHITE, COLOR_RED)
|
|
21
|
+
init_pair(3, COLOR_BLACK, COLOR_GREEN)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@total_width = cols
|
|
25
|
+
@total_height = lines
|
|
26
|
+
@padding_left = 2
|
|
27
|
+
@title_width = 0
|
|
28
|
+
@cols = ['rank', 'title', 'score', 'comments']
|
|
29
|
+
@line_num = -1
|
|
30
|
+
|
|
31
|
+
clear!
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def next_line_num
|
|
35
|
+
@line_num += 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def output_line(line_num, data)
|
|
39
|
+
setpos(line_num, 0)
|
|
40
|
+
padding_right = @total_width - data.length - @padding_left
|
|
41
|
+
padding_right = 0 if padding_right < 0
|
|
42
|
+
addstr((" " * @padding_left) + data + (" " * padding_right))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def output_divider(line_num)
|
|
46
|
+
setpos(line_num, 0)
|
|
47
|
+
attrset color_pair(0)
|
|
48
|
+
addstr('-' * @total_width)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def draw_header
|
|
52
|
+
output_divider(next_line_num)
|
|
53
|
+
attrset color_pair(1)
|
|
54
|
+
output_line(next_line_num, "HACKER NEWS TERMINAL - thanks to http://hndroidapi.appspot.com")
|
|
55
|
+
output_line(next_line_num, "CMDS: Select (Arrows), Open Item (O), Open Item Discussion (D), Refresh (A)")
|
|
56
|
+
output_line(next_line_num, "CMDS CONT: Sort by Rank (R), Score (S), Comments (C), Title (T) | Quit (Q)")
|
|
57
|
+
output_divider(next_line_num)
|
|
58
|
+
|
|
59
|
+
# Get width_excl_title, i.e. width of all columns + some extra for |'s and spacing.
|
|
60
|
+
# Once obtained, pad out the title column with the any width remaining
|
|
61
|
+
# A nicer way to do this is always put the title last, and assume last column gets
|
|
62
|
+
# remaining width. That way we can just loop through our cols, rather than hardcoding
|
|
63
|
+
# them as per example below. I'm sticking to this because I want the title listed second.
|
|
64
|
+
width_excl_title = @cols.inject(0) do |width, col|
|
|
65
|
+
width += (3 + col.length)
|
|
66
|
+
end
|
|
67
|
+
attrset color_pair(2)
|
|
68
|
+
@title_width = @total_width - width_excl_title + 'title'.length
|
|
69
|
+
output_line(next_line_num, "RANK | TITLE " + " " * (@total_width - width_excl_title) + "| SCORE | COMMENTS")
|
|
70
|
+
output_divider(next_line_num)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def draw_footer(sorted_by, mean, median, mode)
|
|
74
|
+
output_divider(next_line_num)
|
|
75
|
+
attrset color_pair(1)
|
|
76
|
+
formatted = sprintf("Sorted by: %7s | Scores: Mean: %4.2f | Median: %4.2f | Mode: %4.2f",
|
|
77
|
+
sorted_by, mean, median, mode)
|
|
78
|
+
output_line(next_line_num, formatted)
|
|
79
|
+
output_divider(next_line_num)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def draw_item_line(rank, data, selected)
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
# Truncate if too long
|
|
86
|
+
title = truncate_line! data
|
|
87
|
+
|
|
88
|
+
# Format and output
|
|
89
|
+
if selected
|
|
90
|
+
rank = '> ' + rank
|
|
91
|
+
attrset color_pair(3)
|
|
92
|
+
else
|
|
93
|
+
attrset color_pair(0)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
formatted = sprintf("%4s | %-#{@title_width}s | %5s | %8s", rank, title, data['score'], data['comments'])
|
|
97
|
+
output_line(next_line_num, formatted)
|
|
98
|
+
rescue => ex
|
|
99
|
+
p "error: #{ex.to_s}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def truncate_line!(data)
|
|
104
|
+
return data['title'][0, @title_width - 3] + '...' if data['title'].length >= @title_width
|
|
105
|
+
data['title']
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def show(page_data)
|
|
109
|
+
draw_header
|
|
110
|
+
|
|
111
|
+
page_data.data.each_index do |i|
|
|
112
|
+
line_data = page_data.data.fetch(i)
|
|
113
|
+
draw_item_line(line_data['rank'], line_data, page_data.line_pos == i + 1)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
draw_footer(page_data.sorted_by,
|
|
117
|
+
page_data.mean_score,
|
|
118
|
+
page_data.median_score,
|
|
119
|
+
page_data.mode_score
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def get_char
|
|
124
|
+
interpret_char(getch)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def interpret_char(c)
|
|
128
|
+
case c
|
|
129
|
+
when Curses::Key::UP
|
|
130
|
+
'up'
|
|
131
|
+
when Curses::Key::DOWN
|
|
132
|
+
'down'
|
|
133
|
+
else
|
|
134
|
+
c
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def close
|
|
139
|
+
close_screen
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def clear!
|
|
143
|
+
setpos(0, 0)
|
|
144
|
+
clear
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def defaults
|
|
150
|
+
@options ||= {}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/hacker_term.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'hacker_term/page_data'
|
|
2
|
+
require 'hacker_term/ui'
|
|
3
|
+
require 'rest_client'
|
|
4
|
+
require 'launchy'
|
|
5
|
+
require 'clipboard'
|
|
6
|
+
|
|
7
|
+
module HackerTerm
|
|
8
|
+
class TerminalApp
|
|
9
|
+
def initialize
|
|
10
|
+
@raw_json = read_json
|
|
11
|
+
load
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run!
|
|
15
|
+
clear_and_show
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
char = @ui.get_char
|
|
19
|
+
|
|
20
|
+
case char.to_s.upcase.chomp
|
|
21
|
+
when "Q"
|
|
22
|
+
@ui.close
|
|
23
|
+
exit
|
|
24
|
+
|
|
25
|
+
when "UP"
|
|
26
|
+
@page.change_line_pos :down
|
|
27
|
+
|
|
28
|
+
when "DOWN"
|
|
29
|
+
@page.change_line_pos :up
|
|
30
|
+
|
|
31
|
+
when "K"
|
|
32
|
+
@page.change_line_pos :down
|
|
33
|
+
|
|
34
|
+
when "J"
|
|
35
|
+
@page.change_line_pos :up
|
|
36
|
+
|
|
37
|
+
when "O"
|
|
38
|
+
open_link(@page.selected_url)
|
|
39
|
+
|
|
40
|
+
when "D"
|
|
41
|
+
open_link(@page.selected_comments_url)
|
|
42
|
+
|
|
43
|
+
when "A"
|
|
44
|
+
load
|
|
45
|
+
@page.change_line_pos :reset
|
|
46
|
+
|
|
47
|
+
when "S"
|
|
48
|
+
@page.sort_on!(:score)
|
|
49
|
+
|
|
50
|
+
when "R"
|
|
51
|
+
@page.sort_on!(:rank)
|
|
52
|
+
|
|
53
|
+
when "T"
|
|
54
|
+
@page.sort_on!(:title)
|
|
55
|
+
|
|
56
|
+
when "C"
|
|
57
|
+
@page.sort_on!(:comments)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
clear_and_show
|
|
61
|
+
|
|
62
|
+
end while true
|
|
63
|
+
|
|
64
|
+
0 # Zero exit code means everything was OK...
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def open_link(url)
|
|
70
|
+
# Attempts to launch a browser; writes URL to clipboard in any case
|
|
71
|
+
begin
|
|
72
|
+
Launchy.open url # May not work in some Linux flavors
|
|
73
|
+
rescue
|
|
74
|
+
ensure
|
|
75
|
+
Clipboard.copy url
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def load
|
|
80
|
+
@page = PageData.new @raw_json
|
|
81
|
+
@ui = UI.new
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def read_json
|
|
85
|
+
local_proxy = get_local_proxy
|
|
86
|
+
#RestClient.proxy = local_proxy unless local_proxy.nil?
|
|
87
|
+
RestClient.get 'http://hndroidapi.appspot.com/news/format/json/page/'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def get_local_proxy
|
|
91
|
+
# Cater for both upper and lower case env variables
|
|
92
|
+
local_proxy = ENV['all_proxy']
|
|
93
|
+
return local_proxy unless local_proxy.nil?
|
|
94
|
+
ENV['http_proxy']
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def clear_and_show
|
|
98
|
+
@ui.clear!
|
|
99
|
+
@ui.show @page
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
require 'hacker_term/page_data'
|
|
2
|
+
|
|
3
|
+
module HackerTerm
|
|
4
|
+
describe PageData do
|
|
5
|
+
describe 'replace missing nodes and format numbers' do
|
|
6
|
+
before(:each) do
|
|
7
|
+
@data =
|
|
8
|
+
'{"items":[
|
|
9
|
+
{
|
|
10
|
+
"title":"NextId",
|
|
11
|
+
"url":"/news2",
|
|
12
|
+
"description":"hn next id news2 "
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"title":"Ray Kurzweil joins Google",
|
|
16
|
+
"url":"http://www.kurzweilai.net/kurzweil-joins-google-to-work-on-new-projects-involving-machine-learning-and-language-processing?utm_source=twitterfeed&utm_medium=twitter",
|
|
17
|
+
"score":"260 points",
|
|
18
|
+
"user":"dumitrue",
|
|
19
|
+
"comments":"122 comments",
|
|
20
|
+
"time":"14 hours ago",
|
|
21
|
+
"item_id":"4923914",
|
|
22
|
+
"description":"260 points by dumitrue 14 hours ago | 122 comments"
|
|
23
|
+
}
|
|
24
|
+
]}'
|
|
25
|
+
@pd = PageData.new @data
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'adds score node' do
|
|
29
|
+
@pd.data.first.should have_key 'score'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'adds comments node' do
|
|
33
|
+
@pd.data.first.should have_key 'comments'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'formats score node as a number when the node didn\'t exist' do
|
|
37
|
+
@pd.data.first['score'].should == '0'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'formats score node as a number when text is present' do
|
|
41
|
+
@pd.data.last['score'].should == '260'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'formats comments node as a number when the node didn\'t exist' do
|
|
45
|
+
@pd.data.first['comments'].should == '0'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'formats comments node as a number when text is present' do
|
|
49
|
+
@pd.data.last['comments'].should == '122'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe 'calculating stats' do
|
|
55
|
+
before(:each) do
|
|
56
|
+
@page_data = HackerTerm::PageData.new File.read './data/data.json'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'provides a mean' do
|
|
60
|
+
@page_data.mean_score.should == 193.59375
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'provides a median' do
|
|
64
|
+
@page_data.median_score.should == 135.0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'provides a mode' do
|
|
68
|
+
@page_data.mode_score.should == 0
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe 'formatting URLs' do
|
|
73
|
+
before(:each) do
|
|
74
|
+
@pg = HackerTerm::PageData.new File.read './data/data.json'
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'provides a URL for actual article' do
|
|
78
|
+
@pg.selected_url.should == "http://powwow.cc/"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'provides a URL for article comments' do
|
|
82
|
+
@pg.selected_comments_url.should == "http://news.ycombinator.com/item?id=4924763"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'links to HN directly if URL is not absolute' do
|
|
86
|
+
@pg.data.last['url'].should == 'http://news.ycombinator.com/item?id=4992617'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe 'sorting' do
|
|
91
|
+
before(:each) do
|
|
92
|
+
@data =
|
|
93
|
+
'{"items":[
|
|
94
|
+
{
|
|
95
|
+
"title":"First Article",
|
|
96
|
+
"url":"http://google.com",
|
|
97
|
+
"score":"0 points",
|
|
98
|
+
"user":"dumitrue",
|
|
99
|
+
"comments":"100 comments",
|
|
100
|
+
"time":"14 hours ago",
|
|
101
|
+
"item_id":"4923914",
|
|
102
|
+
"description":"260 points by dumitrue 14 hours ago | 122 comments"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"title":"Second Article",
|
|
106
|
+
"url":"http://google.com",
|
|
107
|
+
"score":"50 points",
|
|
108
|
+
"user":"dumitrue",
|
|
109
|
+
"comments":"5 comments",
|
|
110
|
+
"time":"14 hours ago",
|
|
111
|
+
"item_id":"4923914",
|
|
112
|
+
"description":"260 points by dumitrue 14 hours ago | 122 comments"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"title":"Third Article",
|
|
116
|
+
"url":"http://google.com",
|
|
117
|
+
"score":"25 points",
|
|
118
|
+
"user":"dumitrue",
|
|
119
|
+
"comments":"0 comments",
|
|
120
|
+
"time":"14 hours ago",
|
|
121
|
+
"item_id":"4923914",
|
|
122
|
+
"description":"260 points by dumitrue 14 hours ago | 122 comments"
|
|
123
|
+
}
|
|
124
|
+
]}'
|
|
125
|
+
@pd = PageData.new @data
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'preserves natural ordering as default' do
|
|
129
|
+
@pd.data.first['title'].should == 'First Article'
|
|
130
|
+
@pd.data.last['title'].should == 'Third Article'
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'sorts by score when requested' do
|
|
134
|
+
@pd.sort_on!(:score)
|
|
135
|
+
@pd.data.first['title'].should == 'Second Article'
|
|
136
|
+
@pd.data.last['title'].should == 'First Article'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'sorts by number of comments when requested' do
|
|
140
|
+
@pd.sort_on!(:comments)
|
|
141
|
+
@pd.data.first['title'].should == 'First Article'
|
|
142
|
+
@pd.data.last['title'].should == 'Third Article'
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'sorts by rank when requested' do
|
|
146
|
+
@pd.sort_on!(:rank)
|
|
147
|
+
@pd.data.first['title'].should == 'First Article'
|
|
148
|
+
@pd.data.last['title'].should == 'Third Article'
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'sorts by title when requested' do
|
|
152
|
+
@pd.sort_on!(:title)
|
|
153
|
+
@pd.data.first['title'].should == 'First Article'
|
|
154
|
+
@pd.data.last['title'].should == 'Third Article'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 're-sorts by rank when requested' do
|
|
158
|
+
@pd.sort_on!(:comments)
|
|
159
|
+
@pd.sort_on!(:rank)
|
|
160
|
+
@pd.data.first['title'].should == 'First Article'
|
|
161
|
+
@pd.data.last['title'].should == 'Third Article'
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zlx_hacker_term
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease:
|
|
5
|
+
version: 0.0.1
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- soffolk zhu
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-01-07 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
type: :runtime
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
none: false
|
|
25
|
+
requirements:
|
|
26
|
+
- - ! '>='
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '0'
|
|
29
|
+
name: rest-client
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
type: :runtime
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
none: false
|
|
41
|
+
requirements:
|
|
42
|
+
- - ! '>='
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '0'
|
|
45
|
+
name: launchy
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
type: :runtime
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
none: false
|
|
57
|
+
requirements:
|
|
58
|
+
- - ! '>='
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
name: clipboard
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
type: :runtime
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
prerelease: false
|
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
72
|
+
none: false
|
|
73
|
+
requirements:
|
|
74
|
+
- - ! '>='
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0'
|
|
77
|
+
name: socksify
|
|
78
|
+
description: Read Hacker News on the Terminal
|
|
79
|
+
email:
|
|
80
|
+
- zlx.star@gmail.com
|
|
81
|
+
executables:
|
|
82
|
+
- hacker_term
|
|
83
|
+
extensions: []
|
|
84
|
+
extra_rdoc_files: []
|
|
85
|
+
files:
|
|
86
|
+
- .gitignore
|
|
87
|
+
- Gemfile
|
|
88
|
+
- Gemfile.lock
|
|
89
|
+
- LICENSE.txt
|
|
90
|
+
- README.md
|
|
91
|
+
- Rakefile
|
|
92
|
+
- bin/hacker_term
|
|
93
|
+
- data/data.json
|
|
94
|
+
- hacker_term.gemspec
|
|
95
|
+
- lib/hacker_term.rb
|
|
96
|
+
- lib/hacker_term/page_data.rb
|
|
97
|
+
- lib/hacker_term/ui.rb
|
|
98
|
+
- lib/hacker_term/version.rb
|
|
99
|
+
- spec/page_data_spec.rb
|
|
100
|
+
homepage: https://github.com/zlx/hacker_term
|
|
101
|
+
licenses: []
|
|
102
|
+
post_install_message:
|
|
103
|
+
rdoc_options: []
|
|
104
|
+
require_paths:
|
|
105
|
+
- lib
|
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
|
+
none: false
|
|
108
|
+
requirements:
|
|
109
|
+
- - ! '>='
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
|
+
none: false
|
|
114
|
+
requirements:
|
|
115
|
+
- - ! '>='
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
requirements: []
|
|
119
|
+
rubyforge_project:
|
|
120
|
+
rubygems_version: 1.8.24
|
|
121
|
+
signing_key:
|
|
122
|
+
specification_version: 3
|
|
123
|
+
summary: Allows the reading, sorting and opening of HN articles from the terminal.
|
|
124
|
+
test_files:
|
|
125
|
+
- spec/page_data_spec.rb
|