hackercli 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|