hackercli 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +62 -10
- data/bin/hackercli.rb +100 -13
- data/bin/hackman.rb +531 -0
- data/lib/hackercli/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75c4a562ea1421cb25e6e931f248fe548dca1455
|
4
|
+
data.tar.gz: 5eae9a7fae225f5466ce8c3e821cc3a438d72fad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ecaef63610e4e8337bd71b3073c71f979c50b00e2629c3528125844ca2c3bf4b0fc5dd7744b63855ec4315e8ac937f831cb1b48764dcd5e96289eca07a080648
|
7
|
+
data.tar.gz: 0016e69972886d8ec5ed937019b48e326b2f56cfa3105bb31014ceab1c4e9b7d3538b5ef39e7402e90f5d843daabaab259b59d2329520f406e3589ab27afe837
|
data/README.md
CHANGED
@@ -6,51 +6,94 @@ provided. May be used as a filter for other commands.
|
|
6
6
|
This is a single file and so may be placed anywhere in the path. Also, one may include it in a project
|
7
7
|
and get the array of hashes for each article and use as per requirements.
|
8
8
|
|
9
|
-
This is NOT dependent on any other gem as it uses and parses the simple RSS feed using
|
9
|
+
This is NOT dependent on any other gem as it uses and parses the simple RSS feed using `String.scan` only.
|
10
10
|
|
11
11
|
The feed used for Hacker News is:
|
12
12
|
|
13
|
-
|
13
|
+
https://news.ycombinator.com/bigrss
|
14
14
|
|
15
15
|
The feed used for Reddit News is (replace ruby with any other subreddit):
|
16
16
|
|
17
17
|
http://www.reddit.com/r/ruby/.rss
|
18
18
|
|
19
19
|
Please click these links and check if they are working if there is any problem.
|
20
|
+
To see current options:
|
20
21
|
|
21
|
-
hackercli.rb --help
|
22
|
+
hackercli.rb --help
|
22
23
|
|
23
24
|
|
24
25
|
## Installation
|
25
26
|
|
26
|
-
Or install it yourself as:
|
27
|
-
|
28
27
|
$ gem install hackercli
|
29
28
|
|
30
29
|
## Usage
|
31
30
|
|
32
31
|
hackercli.rb --help
|
33
32
|
|
34
|
-
To view hacker news titles and
|
33
|
+
To view hacker news titles and urls
|
35
34
|
|
36
35
|
hackercli.rb
|
36
|
+
|
37
|
+
hackercli.rb slashdot
|
38
|
+
|
39
|
+
Pipe to other commands (default separatar is a TAB)
|
40
|
+
|
41
|
+
hackercli.rb | cut -f1,2 | nl | sort -n -r
|
37
42
|
|
38
43
|
|
39
44
|
To view only titles:
|
40
45
|
|
41
46
|
hackercli.rb -t
|
42
47
|
|
48
|
+
|
49
|
+
To view description column also (this is normally not printed since it can be long):
|
50
|
+
|
51
|
+
hackercli.rb -v
|
52
|
+
|
43
53
|
To view reddit ruby:
|
44
54
|
|
45
|
-
hackercli.rb
|
55
|
+
hackercli.rb ruby
|
56
|
+
|
57
|
+
hackercli.rb <subreddit>
|
46
58
|
|
47
59
|
Present the URL of some other RSS feed:
|
48
60
|
|
49
|
-
hackercli.rb -u
|
61
|
+
hackercli.rb -u http://feeds.arstechnica.com/arstechnica/index
|
62
|
+
|
63
|
+
hackercli.rb -u http://feeds.boingboing.net/boingboing/iBag
|
64
|
+
|
65
|
+
Save output to a YML file:
|
66
|
+
|
67
|
+
hackercli.rb -y ruby.yml ruby
|
68
|
+
|
69
|
+
Please try the `--help` option to see current feeds supported, and latest options.
|
50
70
|
|
51
71
|
NOTE: this has been tested only with Hackernews bigrss and reddit news' RSS, so I cannot gaurantee
|
52
72
|
how it will behave with others. Some tags such as item, title and link are expected to be present.
|
53
73
|
|
74
|
+
## Changes
|
75
|
+
|
76
|
+
### 0.0.2
|
77
|
+
|
78
|
+
- Earlier the `-s` option was used to specify subforum. Now, it is not an option. subforum is passed
|
79
|
+
as an argument.
|
80
|
+
|
81
|
+
- Save to YML. I would prefer to use this in a client program rather than use tabbed output.
|
82
|
+
The YML file contains descriptive field which some callers may require.
|
83
|
+
The tabbed output does not contain description.
|
84
|
+
|
85
|
+
- Several other RSS feeds have been checked out such as **slashdot** and **arstechnica**.
|
86
|
+
You may pass "*ars:open-source*" or "*ars:software*" as an argument.
|
87
|
+
Check `--help` for latest options and features.
|
88
|
+
|
89
|
+
## Upcoming
|
90
|
+
|
91
|
+
I've added a curses frontend to the generated yml files in bin named `hackman`, which depends on the
|
92
|
+
curses library `canis`. I am making it key-binding compliant with `corvus`, so one may switch from one
|
93
|
+
to the other without problems.
|
94
|
+
|
95
|
+
Should store the files in given location, read up forum list and other things from an info file.
|
96
|
+
(Perhaps share with corvusinfo).
|
54
97
|
|
55
98
|
## Testing and debugging
|
56
99
|
|
@@ -62,11 +105,17 @@ the program.
|
|
62
105
|
|
63
106
|
## See Also:
|
64
107
|
|
108
|
+
### rubygems
|
109
|
+
|
110
|
+
https://rubygems.org/gems/hackercli
|
111
|
+
|
65
112
|
### hacker-curse
|
66
113
|
|
67
|
-
|
114
|
+
hacker-curse uses nokogiri to parse the actual Hackernews (or reddit) page, and can print on the CLI as well as
|
68
115
|
be used as a library for another application. hacker-curse also provides a curses interface for viewing titles
|
69
|
-
and comments, and launching the article or comments page in the GUI browser.
|
116
|
+
and comments, and launching the article or comments page in the GUI browser. Currently, the frontend is being
|
117
|
+
developed. The library is ready and is on github. It contains a non-curses client named `corvus` which you should
|
118
|
+
try.
|
70
119
|
|
71
120
|
## Contributing
|
72
121
|
|
@@ -75,3 +124,6 @@ and comments, and launching the article or comments page in the GUI browser.
|
|
75
124
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
125
|
4. Push to the branch (`git push origin my-new-feature`)
|
77
126
|
5. Create a new Pull Request
|
127
|
+
|
128
|
+
Please let me know if you find this useful, or write a front-end for this. I'd like to know how to make this more
|
129
|
+
useful, and include a link to your repo.
|
data/bin/hackercli.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# ----------------------------------------------------------------------------- #
|
3
|
-
# File:
|
3
|
+
# File: hackercli.rb
|
4
4
|
# Description: reads Hacker News bigrss feed and prints out
|
5
5
|
# Also works with reddit's rss feed
|
6
6
|
#
|
@@ -8,15 +8,18 @@
|
|
8
8
|
# titles and connecting to the page. HN's rss does not provide any info
|
9
9
|
# such as points/age etc. Reddit provides a little more but has to be parsed.
|
10
10
|
#
|
11
|
-
# Currently saves downloaded file as "
|
11
|
+
# Currently saves downloaded file as "<forum>.rss" so you may rerun queries on it
|
12
12
|
# using the "-u" flag.
|
13
13
|
#
|
14
|
+
# Caveats: fails if newlines in rss feed as in case of arstechnical
|
15
|
+
# We need to join the lines first so that scan can work
|
16
|
+
#
|
14
17
|
# Author: j kepler http://github.com/mare-imbrium/canis/
|
15
18
|
# Date: 2014-07-20 - 11:37
|
16
19
|
# License: MIT
|
17
20
|
# Last update: 2014-07-21 17:50
|
18
21
|
# ----------------------------------------------------------------------------- #
|
19
|
-
#
|
22
|
+
# hackercli.rb Copyright (C) 2012-2014 j kepler
|
20
23
|
|
21
24
|
require 'open-uri'
|
22
25
|
require 'cgi'
|
@@ -31,16 +34,28 @@ class Bigrss
|
|
31
34
|
#instance_eval &block if block_given?
|
32
35
|
end
|
33
36
|
|
37
|
+
# # returns a hash containing :articles array, and one or two outer fields such as page_url and time
|
38
|
+
# returns an array containing a hash for each article
|
39
|
+
# The hash contains :title, :url, :comments_url and in some case :pubdata and comments_count
|
34
40
|
def run
|
41
|
+
page = {}
|
42
|
+
|
35
43
|
resp = []
|
36
44
|
filename = @options[:url]
|
45
|
+
page[:page_url] = filename
|
46
|
+
now = Time.now
|
47
|
+
page[:create_time_seconds] = now.to_i
|
48
|
+
page[:create_time] = now.to_s
|
49
|
+
page[:articles] = resp
|
37
50
|
f = open(filename)
|
38
|
-
|
51
|
+
outfile = @options[:subreddit] || "last"
|
52
|
+
# ars technical sends in new lines
|
53
|
+
content = f.read.delete("\n")
|
39
54
|
content.gsub!('/',"/")
|
40
55
|
content.gsub!(''',"'")
|
41
56
|
content.gsub!('4','"')
|
42
57
|
content = CGI.unescapeHTML(content)
|
43
|
-
File.open("
|
58
|
+
File.open("#{outfile}.rss","w") {|ff| ff.write(content) }
|
44
59
|
items = content.scan(/<item>(.*?)<\/item>/)
|
45
60
|
items.each_with_index do |e,i|
|
46
61
|
e = e.first
|
@@ -48,6 +63,8 @@ class Bigrss
|
|
48
63
|
title = e.scan(/<title>(.*?)<\/title/).first.first
|
49
64
|
h[:title] = title
|
50
65
|
url = e.scan(/<link>(.*?)<\/link/).first.first
|
66
|
+
# in the case of hacker news this is the article url
|
67
|
+
# In the case of reddit, this is the comment url along with comment url
|
51
68
|
h[:url] = url
|
52
69
|
comment_url = ""
|
53
70
|
# HN has comments links in a tag
|
@@ -73,7 +90,7 @@ class Bigrss
|
|
73
90
|
end
|
74
91
|
#puts " #{title}#{sep}#{url}#{sep}#{comment_url}"
|
75
92
|
end
|
76
|
-
return
|
93
|
+
return page unless block_given?
|
77
94
|
end
|
78
95
|
def extract_part e, tag, hash
|
79
96
|
if e.index("<#{tag}>")
|
@@ -103,6 +120,12 @@ end
|
|
103
120
|
|
104
121
|
|
105
122
|
|
123
|
+
def to_yml outfile, arr
|
124
|
+
require 'yaml'
|
125
|
+
File.open(outfile, 'w' ) do |f|
|
126
|
+
f << YAML::dump(arr)
|
127
|
+
end
|
128
|
+
end
|
106
129
|
|
107
130
|
|
108
131
|
|
@@ -111,11 +134,40 @@ end
|
|
111
134
|
if true
|
112
135
|
begin
|
113
136
|
url = nil
|
137
|
+
ymlfile = nil
|
114
138
|
# http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html
|
115
139
|
require 'optparse'
|
116
140
|
options = {}
|
141
|
+
appname = File.basename $0
|
117
142
|
OptionParser.new do |opts|
|
118
|
-
opts.banner =
|
143
|
+
opts.banner =
|
144
|
+
%Q{
|
145
|
+
Usage: #{$0} [options]
|
146
|
+
Examples:
|
147
|
+
Display Hacker News titles and urls:
|
148
|
+
#{appname}
|
149
|
+
#{appname} hacker
|
150
|
+
Display subreddits from reddit.com:
|
151
|
+
#{appname} programming
|
152
|
+
#{appname} ruby
|
153
|
+
Display info from other sites:
|
154
|
+
#{appname} ars # index from arstechnica
|
155
|
+
#{appname} ars:software # software from arstechnica
|
156
|
+
#{appname} ars:open-source # open-source from arstechnica
|
157
|
+
#{appname} slashdot
|
158
|
+
Display info from a saved rss file:
|
159
|
+
#{appname} -u ruby.rss
|
160
|
+
Save info from reddit ruby to a YML file:
|
161
|
+
#{appname} -y ruby.yml ruby
|
162
|
+
Display info from another url:
|
163
|
+
#{appname} -u http://feeds.boingboing.net/boingboing/iBag
|
164
|
+
#{appname} -u http://www.lifehacker.co.in/rss_tag_section_feeds.cms?query=productivity
|
165
|
+
#{appname} -u http://rss.slashdot.org/Slashdot/slashdot
|
166
|
+
#{appname} -u http://feeds.arstechnica.com/arstechnica/open-source
|
167
|
+
#{appname} -u http://feeds.arstechnica.com/arstechnica/software
|
168
|
+
|
169
|
+
|
170
|
+
}
|
119
171
|
|
120
172
|
opts.on("-v", "--[no-]verbose", "Print description also") do |v|
|
121
173
|
options[:verbose] = v
|
@@ -129,10 +181,13 @@ if true
|
|
129
181
|
opts.on("-d SEP", String,"--delimiter", "Delimit columns with SEP") do |v|
|
130
182
|
options[:delimiter] = v
|
131
183
|
end
|
132
|
-
opts.on("-
|
133
|
-
|
134
|
-
url = "http://www.reddit.com/r/#{v}/.rss"
|
184
|
+
opts.on("-y yml path", String,"--yml-path", "save as YML file") do |v|
|
185
|
+
ymlfile = v
|
135
186
|
end
|
187
|
+
#opts.on("-s SUBREDDIT", String,"--subreddit", "Get articles from subreddit named SUBREDDIT") do |v|
|
188
|
+
#options[:subreddit] = v
|
189
|
+
#url = "http://www.reddit.com/r/#{v}/.rss"
|
190
|
+
#end
|
136
191
|
opts.on("-u URL", String,"--url", "Get articles from URL/file") do |v|
|
137
192
|
url = v
|
138
193
|
end
|
@@ -141,11 +196,43 @@ if true
|
|
141
196
|
#p options
|
142
197
|
#p ARGV
|
143
198
|
|
144
|
-
|
145
|
-
|
199
|
+
v = ARGV[0];
|
200
|
+
if v
|
201
|
+
case v
|
202
|
+
when "news", "hacker", "hn"
|
203
|
+
url = "https://news.ycombinator.com/bigrss"
|
204
|
+
options[:subreddit] = "hacker"
|
205
|
+
when "slashdot"
|
206
|
+
url = "http://rss.slashdot.org/Slashdot/slashdot"
|
207
|
+
options[:subreddit] = "slashdot"
|
208
|
+
when "ars"
|
209
|
+
url = "http://feeds.arstechnica.com/arstechnica/index"
|
210
|
+
options[:subreddit] = "arstechnica"
|
211
|
+
else
|
212
|
+
if v.index("ars:") == 0
|
213
|
+
subf = v.split(":")
|
214
|
+
url = "http://feeds.arstechnica.com/arstechnica/#{subf.last}"
|
215
|
+
options[:subreddit] = subf.join("_")
|
216
|
+
else
|
217
|
+
url = "http://www.reddit.com/r/#{v}/.rss"
|
218
|
+
options[:subreddit] = v
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
unless url
|
223
|
+
url ||= "https://news.ycombinator.com/bigrss"
|
224
|
+
options[:subreddit] = "hacker"
|
225
|
+
end
|
226
|
+
#$stderr.puts "url is: #{url} "
|
227
|
+
|
146
228
|
options[:url] = url
|
147
229
|
klass = Bigrss.new options
|
148
|
-
|
230
|
+
page = klass.run
|
231
|
+
if ymlfile
|
232
|
+
to_yml ymlfile, page
|
233
|
+
exit
|
234
|
+
end
|
235
|
+
arr = page[:articles]
|
149
236
|
titles_only = options[:titles]
|
150
237
|
sep = options[:delimiter] || "\t"
|
151
238
|
limit = options[:number] || arr.count
|
data/bin/hackman.rb
ADDED
@@ -0,0 +1,531 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ----------------------------------------------------------------------------- #
|
3
|
+
# File: hackman.rb
|
4
|
+
# Description: curses frontend for rss feeds generated by hackercli
|
5
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
6
|
+
# Date: 2014-08-09 - 10:12
|
7
|
+
# License: MIT
|
8
|
+
# Last update: 2014-08-11 21:30
|
9
|
+
# ----------------------------------------------------------------------------- #
|
10
|
+
# hackman.rb Copyright (C) 2012-2014 j kepler
|
11
|
+
# encoding: utf-8
|
12
|
+
require 'canis/core/util/app'
|
13
|
+
require 'canis/core/util/rcommandwindow'
|
14
|
+
require 'fileutils'
|
15
|
+
require 'pathname'
|
16
|
+
require 'canis/core/include/defaultfilerenderer'
|
17
|
+
require 'canis/core/include/appmethods'
|
18
|
+
|
19
|
+
# TODO :
|
20
|
+
# - add to forum list, remove, save
|
21
|
+
# - specify gui browser and text browser, and on commandline use same keys as corvus
|
22
|
+
# - create a class and put stuff in there, these methods are going into global, and can conflict
|
23
|
+
# - we should have same mechanism for key bindings as corvus, something that can even be loaded?
|
24
|
+
#
|
25
|
+
# - Use ' as bookmark etc just as in cetus and corvus, keep to same keys
|
26
|
+
# - multibuffers so user can backspace and do M-n etc
|
27
|
+
#
|
28
|
+
VERSION="0.0.2"
|
29
|
+
COLOR_SCHEMES=[
|
30
|
+
[20,19,17, 18, :white], # 0 band in header, 1 - menu bgcolor. 2 - bgcolor of main screen, 3 - status, 4 fg color body
|
31
|
+
[17,19,18, 20, :white], # 0 band in header, 1 - menu bgcolor. 2 - bgcolor of main screen, 3 - status
|
32
|
+
[236,236,232, 234,:white], # 0 band in header, 1 - menu bgcolor. 2 - bgcolor of main screen, 3 - status
|
33
|
+
[236,236,244, 234, :black] # 0 band in header, 1 - menu bgcolor. 2 - bgcolor of main screen, 3 - status
|
34
|
+
]
|
35
|
+
$color_scheme = COLOR_SCHEMES[0]
|
36
|
+
$toggle_titles_only = false
|
37
|
+
$fg = :white
|
38
|
+
$forumlist = %w{ hacker ruby programming scifi science haskell java scala cpp c_programming d_language golang vim emacs unix linux bash zsh commandline vimplugins python ars slashdot }
|
39
|
+
def choose_forum
|
40
|
+
# scrollable filterable list
|
41
|
+
str = display_list $forumlist, :title => "Select a forum"
|
42
|
+
return unless str
|
43
|
+
return if str == ""
|
44
|
+
$current_forum = str
|
45
|
+
forum = str
|
46
|
+
get_data forum if forum
|
47
|
+
end
|
48
|
+
def next_forum
|
49
|
+
index = $forumlist.index($current_forum)
|
50
|
+
index = index >= $forumlist.count - 1 ? 0 : index + 1
|
51
|
+
get_data $forumlist[index]
|
52
|
+
end
|
53
|
+
def prev_forum
|
54
|
+
index = $forumlist.index($current_forum)
|
55
|
+
index = index == 0? $forumlist.count - 1 : index - 1
|
56
|
+
get_data $forumlist[index]
|
57
|
+
end
|
58
|
+
# if components have some commands, can we find a way of passing the command to them
|
59
|
+
# method_missing gave a stack overflow.
|
60
|
+
def execute_this(meth, *args)
|
61
|
+
alert " #{meth} not found ! "
|
62
|
+
$log.debug "app email got #{meth} " if $log.debug?
|
63
|
+
cc = @form.get_current_field
|
64
|
+
[cc].each do |c|
|
65
|
+
if c.respond_to?(meth, true)
|
66
|
+
c.send(meth, *args)
|
67
|
+
return true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
false
|
71
|
+
end
|
72
|
+
def open_url url
|
73
|
+
shell_out "elinks #{url}"
|
74
|
+
#Window.refresh_all
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Menu creator which displays a menu and executes methods based on keys.
|
79
|
+
# In some cases, we call this and then do a case statement on either key or binding.
|
80
|
+
# @param String title
|
81
|
+
# @param hash of keys and methods to call
|
82
|
+
# @return key pressed, and binding (if found, and responded)
|
83
|
+
#
|
84
|
+
def menu title, hash, config={}, &block
|
85
|
+
raise ArgumentError, "Nil hash received by menu" unless hash
|
86
|
+
list = []
|
87
|
+
hash.each_pair { |k, v| list << " #[fg=yellow, bold] #{k} #[/end] #[fg=green] #{v} #[/end]" }
|
88
|
+
# s="#[fg=green]hello there#[fg=yellow, bg=black, dim]"
|
89
|
+
config[:title] = title
|
90
|
+
config[:width] = hash.values.max_by(&:length).length + 13
|
91
|
+
ch = padpopup list, config, &block
|
92
|
+
return unless ch
|
93
|
+
if ch.size > 1
|
94
|
+
# could be a string due to pressing enter
|
95
|
+
# but what if we format into multiple columns
|
96
|
+
ch = ch.strip[0]
|
97
|
+
end
|
98
|
+
|
99
|
+
binding = hash[ch]
|
100
|
+
binding = hash[ch.to_sym] unless binding
|
101
|
+
if binding
|
102
|
+
if respond_to?(binding, true)
|
103
|
+
send(binding)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
return ch, binding
|
107
|
+
end
|
108
|
+
# pops up a list, taking a single key and returning if it is in range of 33 and 126
|
109
|
+
# Called by menu, print_help, show_marks etc
|
110
|
+
# You may pass valid chars or ints so it only returns on pressing those.
|
111
|
+
#
|
112
|
+
# @param Array of lines to print which may be formatted using :tmux format
|
113
|
+
# @return character pressed (ch.chr)
|
114
|
+
# @return nil if escape or C-q pressed
|
115
|
+
#
|
116
|
+
def padpopup list, config={}, &block
|
117
|
+
max_visible_items = config[:max_visible_items]
|
118
|
+
row = config[:row] || 5
|
119
|
+
col = config[:col] || 5
|
120
|
+
# format options are :ansi :tmux :none
|
121
|
+
fmt = config[:format] || :tmux
|
122
|
+
config.delete :format
|
123
|
+
relative_to = config[:relative_to]
|
124
|
+
if relative_to
|
125
|
+
layout = relative_to.form.window.layout
|
126
|
+
row += layout[:top]
|
127
|
+
col += layout[:left]
|
128
|
+
end
|
129
|
+
config.delete :relative_to
|
130
|
+
# still has the formatting in the string so length is wrong.
|
131
|
+
#longest = list.max_by(&:length)
|
132
|
+
width = config[:width] || 60
|
133
|
+
if config[:title]
|
134
|
+
width = config[:title].size + 2 if width < config[:title].size
|
135
|
+
end
|
136
|
+
height = config[:height]
|
137
|
+
height ||= [max_visible_items || 25, list.length+2].min
|
138
|
+
#layout(1+height, width+4, row, col)
|
139
|
+
layout = { :height => 0+height, :width => 0+width, :top => row, :left => col }
|
140
|
+
window = Canis::Window.new(layout)
|
141
|
+
form = Canis::Form.new window
|
142
|
+
|
143
|
+
## added 2013-03-13 - 18:07 so caller can be more specific on what is to be returned
|
144
|
+
valid_keys_int = config.delete :valid_keys_int
|
145
|
+
valid_keys_char = config.delete :valid_keys_char
|
146
|
+
|
147
|
+
listconfig = config[:listconfig] || {}
|
148
|
+
#listconfig[:list] = list
|
149
|
+
listconfig[:width] = width
|
150
|
+
listconfig[:height] = height
|
151
|
+
listconfig[:bgcolor] = $color_scheme[1]
|
152
|
+
#listconfig[:selection_mode] ||= :single
|
153
|
+
listconfig.merge!(config)
|
154
|
+
listconfig.delete(:row);
|
155
|
+
listconfig.delete(:col);
|
156
|
+
# trying to pass populists block to listbox
|
157
|
+
lb = Canis::TextPad.new form, listconfig, &block
|
158
|
+
if fmt == :none
|
159
|
+
lb.text(list)
|
160
|
+
else
|
161
|
+
lb.text(list, fmt)
|
162
|
+
end
|
163
|
+
#
|
164
|
+
#window.bkgd(Ncurses.COLOR_PAIR($reversecolor));
|
165
|
+
form.repaint
|
166
|
+
Ncurses::Panel.update_panels
|
167
|
+
if valid_keys_int.nil? && valid_keys_char.nil?
|
168
|
+
# changed 32 to 33 so space can scroll list
|
169
|
+
valid_keys_int = (33..126)
|
170
|
+
end
|
171
|
+
|
172
|
+
begin
|
173
|
+
while((ch = window.getchar()) != 999 )
|
174
|
+
|
175
|
+
# if a char range or array has been sent, check if the key is in it and send back
|
176
|
+
# else just stay here
|
177
|
+
if valid_keys_char
|
178
|
+
if ch > 32 && ch < 127
|
179
|
+
chr = ch.chr
|
180
|
+
return chr if valid_keys_char.include? chr
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# if the user specified an array or range of ints check against that
|
185
|
+
# therwise use the range of 33 .. 126
|
186
|
+
return ch.chr if valid_keys_int.include? ch
|
187
|
+
|
188
|
+
case ch
|
189
|
+
when ?\C-q.getbyte(0)
|
190
|
+
break
|
191
|
+
else
|
192
|
+
if ch == 13 || ch == 10
|
193
|
+
s = lb.current_value.to_s # .strip #if lb.selection_mode != :multiple
|
194
|
+
return s
|
195
|
+
end
|
196
|
+
# close if escape or double escape
|
197
|
+
if ch == 27 || ch == 2727
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
lb.handle_key ch
|
201
|
+
form.repaint
|
202
|
+
end
|
203
|
+
end
|
204
|
+
ensure
|
205
|
+
window.destroy
|
206
|
+
end
|
207
|
+
return nil
|
208
|
+
end
|
209
|
+
# main options, invokable on backtick.
|
210
|
+
# TODO add selection of browser
|
211
|
+
def main_menu
|
212
|
+
h = {
|
213
|
+
:f => :choose_forum,
|
214
|
+
:c => :color_scheme_select,
|
215
|
+
:s => :sort_menu,
|
216
|
+
:F => :filter_menu,
|
217
|
+
:x => :extras
|
218
|
+
}
|
219
|
+
ch, binding = menu "Main Menu", h
|
220
|
+
#alert "Menu got #{ch}, #{binding}" if ch
|
221
|
+
end
|
222
|
+
def toggle_menu
|
223
|
+
h = {
|
224
|
+
"t" => :toggle_titles_only,
|
225
|
+
:x => :extras
|
226
|
+
}
|
227
|
+
ch, binding = menu "Main Menu", h
|
228
|
+
#alert "Menu got #{ch}, #{binding}" if ch
|
229
|
+
end
|
230
|
+
def color_scheme_select ch=nil
|
231
|
+
unless ch
|
232
|
+
h = {
|
233
|
+
"0" => 'dark blue body',
|
234
|
+
"1" => 'medium blue body',
|
235
|
+
"2" => 'black body',
|
236
|
+
"3" => 'grey body',
|
237
|
+
"b" => 'change body color',
|
238
|
+
"f" => 'change body fg color',
|
239
|
+
"c" => 'cycle body color'
|
240
|
+
}
|
241
|
+
ch, binding = menu "Color Menu", h
|
242
|
+
end
|
243
|
+
case ch
|
244
|
+
when "1", "2", "0", "3"
|
245
|
+
$color_scheme = COLOR_SCHEMES[ch.to_i] || COLOR_SCHEMES.first
|
246
|
+
$fg = $color_scheme[4]
|
247
|
+
when "b"
|
248
|
+
n = get_string "Enter a number for background color (0..255): "
|
249
|
+
n = n.to_i
|
250
|
+
$color_scheme[2] = n
|
251
|
+
when "4", "f"
|
252
|
+
n = get_string "Enter a number for fg color (0..255) : "
|
253
|
+
$fg = n.to_i
|
254
|
+
when "c"
|
255
|
+
# increment bg color
|
256
|
+
n = $color_scheme[2]
|
257
|
+
n += 1
|
258
|
+
n = 0 if n > 255
|
259
|
+
$color_scheme[2] = n
|
260
|
+
when "C"
|
261
|
+
# decrement bg color
|
262
|
+
n = $color_scheme[2]
|
263
|
+
n -= 1
|
264
|
+
n = 255 if n < 0
|
265
|
+
$color_scheme[2] = n
|
266
|
+
end
|
267
|
+
|
268
|
+
h = @form.by_name["header"]
|
269
|
+
tv = @form.by_name["tv"]
|
270
|
+
sl = @form.by_name["sl"]
|
271
|
+
tv.bgcolor = $color_scheme[2]
|
272
|
+
#tv.color = 255
|
273
|
+
tv.color = $fg
|
274
|
+
sl.color = $color_scheme[3]
|
275
|
+
h.bgcolor = $color_scheme[0]
|
276
|
+
message "bgcolor is #{$color_scheme[2]}. :: #{$color_scheme.join(",")}, CP:#{tv.color_pair}=#{tv.color} / #{tv.bgcolor} "
|
277
|
+
refresh
|
278
|
+
end
|
279
|
+
def refresh
|
280
|
+
show $current_file
|
281
|
+
end
|
282
|
+
|
283
|
+
def toggle_titles_only
|
284
|
+
$toggle_titles_only = !$toggle_titles_only
|
285
|
+
show $current_file
|
286
|
+
end
|
287
|
+
App.new do
|
288
|
+
@startdir ||= File.expand_path("..")
|
289
|
+
@hash = nil
|
290
|
+
def get_item_for_line line
|
291
|
+
index = (line - @hash[:first]) / @hash[:diff]
|
292
|
+
@hash[:articles][index]
|
293
|
+
end
|
294
|
+
def title_right text
|
295
|
+
w = @form.by_name["header"]
|
296
|
+
w.text_right text
|
297
|
+
end
|
298
|
+
def title text
|
299
|
+
w = @form.by_name["header"]
|
300
|
+
w.text_center text
|
301
|
+
end
|
302
|
+
def color_line(fg,bg,attr,text)
|
303
|
+
a = "#["
|
304
|
+
a = []
|
305
|
+
a << "fg=#{fg}" if fg
|
306
|
+
a << "bg=#{bg}" if bg
|
307
|
+
a << "#{attr}" if attr
|
308
|
+
str = "#[" + a.join(",") + "]#{text}#[end]"
|
309
|
+
end
|
310
|
+
def goto_article n=$multiplier
|
311
|
+
i = ((n-1) * @hash[:diff]) + @hash[:first]
|
312
|
+
w = @form.by_name["tv"]
|
313
|
+
w.goto_line i
|
314
|
+
end
|
315
|
+
|
316
|
+
def OLDshow file
|
317
|
+
w = @form.by_name["tv"]
|
318
|
+
if File.directory? file
|
319
|
+
lines = Dir.entries(file)
|
320
|
+
w.text lines
|
321
|
+
w.title "[ #{file} ]"
|
322
|
+
elsif File.exists? file
|
323
|
+
lines = File.open(file,'r').readlines
|
324
|
+
w.text lines
|
325
|
+
w.title "[ #{file} ]"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
# display the given yml file.
|
329
|
+
# Converts the yml object to an array for textpad
|
330
|
+
def display_yml file
|
331
|
+
w = @form.by_name["tv"]
|
332
|
+
|
333
|
+
obj = YAML::load( File.open( file ) )
|
334
|
+
lines = Array.new
|
335
|
+
url = obj[:page_url]
|
336
|
+
host = nil
|
337
|
+
if url.index("reddit")
|
338
|
+
host = "reddit"
|
339
|
+
elsif url.index("ycombinator")
|
340
|
+
host = "hacker"
|
341
|
+
elsif url.index("ars")
|
342
|
+
host = "ars"
|
343
|
+
elsif url.index("slashdot")
|
344
|
+
host = "slashdot"
|
345
|
+
else
|
346
|
+
alert "Host not known: #{url} "
|
347
|
+
end
|
348
|
+
articles = obj[:articles]
|
349
|
+
count = articles.count
|
350
|
+
#lines << color_line(:red,COLOR_SCHEME[1],nil,"#{file} #{obj[:page_url]} | #{count} articles | fetched #{obj[:create_time]}")
|
351
|
+
#lines << ("-" * lines.last.size )
|
352
|
+
@hash = Hash.new
|
353
|
+
@hash[:first] = lines.size
|
354
|
+
@hash[:articles] = articles
|
355
|
+
|
356
|
+
articles.each_with_index do |a, i|
|
357
|
+
bg = i
|
358
|
+
bg = 0 if i > 255
|
359
|
+
line = "%3s %s " % [i+1 , a[:title] ]
|
360
|
+
#lines << color_line($fg, bg, nil, line)
|
361
|
+
lines << line
|
362
|
+
if !$toggle_titles_only
|
363
|
+
url = a[:article_url] || a[:url]
|
364
|
+
l = " %s | %s" % [url, a[:comments_url] ]
|
365
|
+
l = "#[fg=green, underline]" + l + "#[end]"
|
366
|
+
lines << l
|
367
|
+
detail = []
|
368
|
+
if a.key? :comment_count
|
369
|
+
detail << a[:comment_count]
|
370
|
+
end
|
371
|
+
if a.key? :pubdate
|
372
|
+
detail << a[:pubdate]
|
373
|
+
end
|
374
|
+
unless detail.empty?
|
375
|
+
l = "#[fg=green]" + " " + detail.join(" | ") + "#[end]"
|
376
|
+
lines << l
|
377
|
+
end
|
378
|
+
end
|
379
|
+
@hash[:diff] ||= lines.size - @hash[:first]
|
380
|
+
end
|
381
|
+
w.text(lines, :content_type => :tmux)
|
382
|
+
w.title "[ #{file} ]"
|
383
|
+
|
384
|
+
i = @hash[:first] || 1
|
385
|
+
w.goto_line i
|
386
|
+
$current_file = file
|
387
|
+
$current_forum = file.sub(File.extname(file),"")
|
388
|
+
title "#{$current_forum} (#{count} articles) "
|
389
|
+
title_right obj[:create_time]
|
390
|
+
end
|
391
|
+
alias :show :display_yml
|
392
|
+
def get_data forum
|
393
|
+
file = forum + ".yml"
|
394
|
+
if File.exists? file and fresh(file)
|
395
|
+
else
|
396
|
+
progress_dialog :color_pair => $reversecolor do |sw|
|
397
|
+
#sw.printstring 0,1, "Fetching #{forum} ..."
|
398
|
+
sw.print "Fetching #{forum} ..."
|
399
|
+
system("hackercli.rb -y #{forum}.yml #{forum}")
|
400
|
+
end
|
401
|
+
end
|
402
|
+
display_yml file
|
403
|
+
end
|
404
|
+
# return true if younger than one hour
|
405
|
+
def fresh file
|
406
|
+
f = File.stat(file)
|
407
|
+
now = Time.now
|
408
|
+
return (( now - f.mtime) < 7200)
|
409
|
+
end
|
410
|
+
def show_links art
|
411
|
+
links = {}
|
412
|
+
keys = %w{a b c d e f}
|
413
|
+
i = 0
|
414
|
+
art.each_pair do |k, p|
|
415
|
+
if p.index("http") == 0
|
416
|
+
links[keys[i]] = p
|
417
|
+
i += 1
|
418
|
+
end
|
419
|
+
end
|
420
|
+
ch, binding = menu "Links Menu", links
|
421
|
+
#alert "is #{index}: #{art[:title]} #{ch}:#{binding} "
|
422
|
+
if binding
|
423
|
+
open_url binding
|
424
|
+
end
|
425
|
+
end
|
426
|
+
ht = 24
|
427
|
+
borderattrib = :reverse
|
428
|
+
@header = app_header "hackman #{VERSION}", :text_center => "RSS Reader", :name => "header",
|
429
|
+
:text_right =>"Press =", :color => :white, :bgcolor => $color_scheme[0]
|
430
|
+
message "Press F10 (or qq) to exit, F1 Help, ` for Menu "
|
431
|
+
|
432
|
+
|
433
|
+
|
434
|
+
# commands that can be mapped to or executed using M-x
|
435
|
+
# however, commands of components aren't yet accessible.
|
436
|
+
def get_commands
|
437
|
+
%w{ choose_forum next_forum prev_forum }
|
438
|
+
end
|
439
|
+
def help_text
|
440
|
+
<<-eos
|
441
|
+
rCommandLine HELP
|
442
|
+
|
443
|
+
These are some features for either getting filenames from user
|
444
|
+
at the bottom of the window like vim and others do, or filtering
|
445
|
+
from a list (like ControlP plugin). Or seeing a file at bottom
|
446
|
+
of screen for a quick preview.
|
447
|
+
|
448
|
+
: - Command mode
|
449
|
+
F1 - Help
|
450
|
+
F10 - Quit application
|
451
|
+
qq - Quit application
|
452
|
+
= - file selection (interface like Ctrl-P, very minimal)
|
453
|
+
|
454
|
+
Some commands for using bottom of screen as vim and emacs do.
|
455
|
+
These may be selected by pressing ':'
|
456
|
+
|
457
|
+
testchoosedir - filter directory list as you type
|
458
|
+
'>' to step into a dir, '<' to go up.
|
459
|
+
testchoosefile - filter file list as you type
|
460
|
+
ENTER to select, C-c or Esc-Esc to quit
|
461
|
+
testdir - vim style, tabbing completes matching files
|
462
|
+
testnumberedmenu - use menu indexes to select options
|
463
|
+
choose_forum - display a list at bottom of screen
|
464
|
+
Press <ENTER> to select, arrow keys to traverse,
|
465
|
+
and characters to filter list.
|
466
|
+
testdisplaytext - display text at bottom (current file contents)
|
467
|
+
Press <ENTER> when done.
|
468
|
+
|
469
|
+
The file/dir selection options are very minimally functional. Improvements
|
470
|
+
and thorough testing are required. I've only tested them out gingerly.
|
471
|
+
|
472
|
+
testchoosedir and file were earlier like Emacs/memacs with TAB completion
|
473
|
+
but have now moved to the much faster and friendlier ControlP plugin like
|
474
|
+
'filter as you type' format.
|
475
|
+
|
476
|
+
-----------------------------------------------------------------------
|
477
|
+
:n or Alt-n for general help.
|
478
|
+
eos
|
479
|
+
end
|
480
|
+
|
481
|
+
#install_help_text help_text
|
482
|
+
|
483
|
+
def app_menu
|
484
|
+
@curdir ||= Dir.pwd
|
485
|
+
Dir.chdir(@curdir) if Dir.pwd != @curdir
|
486
|
+
require 'canis/core/util/promptmenu'
|
487
|
+
menu = PromptMenu.new self do
|
488
|
+
item :f, :choose_forum
|
489
|
+
item :t, :testdisplay_text
|
490
|
+
end
|
491
|
+
menu.display_new :title => "Menu"
|
492
|
+
end
|
493
|
+
# BINDING SECTION
|
494
|
+
@form.bind_key(?:, "App Menu") { app_menu; }
|
495
|
+
@form.bind_key(?`, "Main Menu") { main_menu; }
|
496
|
+
@form.bind_key(FFI::NCurses::KEY_F2, "Main Menu") { choose_forum; }
|
497
|
+
@form.bind_key(FFI::NCurses::KEY_F3, "Cycle bgcolor") { color_scheme_select "c"; }
|
498
|
+
@form.bind_key(FFI::NCurses::KEY_F4, "Cycle bgcolor") { color_scheme_select "C"; }
|
499
|
+
@form.bind_key($kh_int["S-F3"], "Cycle bgcolor") { color_scheme_select "C"; }
|
500
|
+
@form.bind_key(?=, "Toggle Menu") {
|
501
|
+
toggle_menu;
|
502
|
+
}
|
503
|
+
@form.bind_key(?<, "Previous Forum") { prev_forum; }
|
504
|
+
@form.bind_key(?>, "Next Forum") { next_forum; }
|
505
|
+
|
506
|
+
stack :margin_top => 1, :margin_left => 0, :width => :expand , :height => FFI::NCurses.LINES-2 do
|
507
|
+
tv = textpad :height_pc => 100, :width_pc => 100, :name => "tv", :suppress_borders => true,
|
508
|
+
:bgcolor => $color_scheme[2], :color => 255, :attr => NORMAL
|
509
|
+
#tv.renderer ruby_renderer
|
510
|
+
tv.bind(:PRESS) {|ev|
|
511
|
+
index = ev.current_index
|
512
|
+
art = get_item_for_line index
|
513
|
+
show_links art
|
514
|
+
}
|
515
|
+
tv.bind_key(?z) { goto_article }
|
516
|
+
tv.bind_key(?o) {
|
517
|
+
# if multiplier is 0, use current line
|
518
|
+
art = @hash[:articles][$multiplier - 1]
|
519
|
+
if $multiplier == 0
|
520
|
+
index = tv.current_index
|
521
|
+
art = get_item_for_line index
|
522
|
+
end
|
523
|
+
show_links art
|
524
|
+
}
|
525
|
+
tv.text_patterns[:articles] = Regexp.new(/^ *\d+ /)
|
526
|
+
tv.bind_key(KEY_TAB, "goto article") { tv.next_regex(:articles) }
|
527
|
+
end # stack
|
528
|
+
|
529
|
+
sl = status_line :row => Ncurses.LINES-1, :bgcolor => :yellow, :color => $color_scheme[3]
|
530
|
+
choose_forum
|
531
|
+
end # app
|
data/lib/hackercli/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hackercli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kepler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -44,6 +44,7 @@ email:
|
|
44
44
|
- githubkepler.50s@gishpuppy.com
|
45
45
|
executables:
|
46
46
|
- hackercli.rb
|
47
|
+
- hackman.rb
|
47
48
|
extensions: []
|
48
49
|
extra_rdoc_files: []
|
49
50
|
files:
|
@@ -53,6 +54,7 @@ files:
|
|
53
54
|
- README.md
|
54
55
|
- Rakefile
|
55
56
|
- bin/hackercli.rb
|
57
|
+
- bin/hackman.rb
|
56
58
|
- hackercli.gemspec
|
57
59
|
- lib/hackercli.rb
|
58
60
|
- lib/hackercli/version.rb
|