google 1.0.8 → 1.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/google +32 -1
- data/google.gemspec +11 -11
- data/lib/google.rb +137 -62
- data/lib/{reverse-markdown → google/reverse-markdown}/reverse_markdown.rb +0 -0
- metadata +21 -13
- data/lib/google/display_serp.rb +0 -53
- data/lib/google/grab.rb +0 -26
- data/lib/google/input.rb +0 -57
- data/lib/google/pipe-view.rb +0 -20
- data/lib/google/request.rb +0 -20
- data/lib/google/search.rb +0 -26
- data/lib/trollop/lib/trollop.rb +0 -791
- data/lib/trollop/test/test_trollop.rb +0 -1094
data/bin/google
CHANGED
@@ -2,4 +2,35 @@
|
|
2
2
|
|
3
3
|
require_relative '../lib/google'
|
4
4
|
|
5
|
-
|
5
|
+
opts = Trollop::options do
|
6
|
+
version "google v1.0.9 (c) 2012 Kerrick Long http://kerrick.github.com/google"
|
7
|
+
banner <<-EOM
|
8
|
+
The google gem is a simple tool to search Gooogle with via a CLI.
|
9
|
+
Usage:
|
10
|
+
google [options] "my search query string here"
|
11
|
+
where [options] are:
|
12
|
+
EOM
|
13
|
+
|
14
|
+
opt :page, "Start by showing the <i>th result page.", :type => :int, :default => 1
|
15
|
+
opt :size, "Show <i> results on each SERP. Must be between 1 and 8.", :type => :int, :default => 4
|
16
|
+
opt :result, "Skip the SERP and show the <i>th result.", :type => :int
|
17
|
+
opt :lucky, "I'm feeling lucky! Skip the SERP and show the first result. (Alias to --result 1)"
|
18
|
+
opt :readability, "Filter the results through readability to get rid of extra content.", :default => true
|
19
|
+
opt :markdown, "Change the results from raw HTML to markdown.", :default => true
|
20
|
+
opt :version, "Print the version and exit."
|
21
|
+
opt :help, "Show this information and exit."
|
22
|
+
end
|
23
|
+
|
24
|
+
query = ARGV.join(' ')
|
25
|
+
raise Trollop::die "no search query specified" if query.empty?
|
26
|
+
|
27
|
+
opts[:result] = 1 if opts[:lucky]
|
28
|
+
raise Trollop::die "Must use --no-markdown if you use --no-readability" if !opts[:readability] && opts[:markdown]
|
29
|
+
|
30
|
+
begin
|
31
|
+
g = Google.new query, opts
|
32
|
+
g.search
|
33
|
+
rescue Interrupt
|
34
|
+
puts "\nInterrupt received. Exiting application."
|
35
|
+
exit
|
36
|
+
end
|
data/google.gemspec
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
|
-
s.name
|
3
|
-
s.version
|
2
|
+
s.name = "google"
|
3
|
+
s.version = "1.0.9"
|
4
4
|
s.executables << 'google'
|
5
|
+
s.add_runtime_dependency "trollop", ["~> 1"]
|
5
6
|
s.add_runtime_dependency "json", ["~> 1"]
|
6
7
|
s.add_runtime_dependency "htmlentities", ["~> 4"]
|
7
8
|
s.add_runtime_dependency "formatador", ["~> 0.2"]
|
8
9
|
s.add_runtime_dependency "ruby-readability", ["~> 0.5"]
|
9
|
-
s.date
|
10
|
-
s.summary
|
10
|
+
s.date = "2012-05-25"
|
11
|
+
s.summary = "Google Search on the command line"
|
11
12
|
s.description = "A ruby gem to give you the power of Google Search in your command line."
|
12
|
-
s.authors
|
13
|
-
s.email
|
14
|
-
s.files
|
15
|
-
s.files
|
16
|
-
s.files
|
17
|
-
s.
|
18
|
-
s.homepage = "http://kerrick.github.com/google/"
|
13
|
+
s.authors = ["Kerrick Long"]
|
14
|
+
s.email = "me@kerricklong.com"
|
15
|
+
s.files = ["bin/google"]
|
16
|
+
s.files += ["lib/google.rb", "lib/google/utils.rb", "lib/google/reverse-markdown/reverse_markdown.rb"]
|
17
|
+
s.files += ["LICENSE.md", "README.md", "google.gemspec"]
|
18
|
+
s.homepage = "http://kerrick.github.com/google/"
|
19
19
|
end
|
data/lib/google.rb
CHANGED
@@ -1,66 +1,141 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
opts = Trollop::options do
|
11
|
-
version "google v1.0.8 (c) 2012 Kerrick Long http://kerrick.github.com/google"
|
12
|
-
banner <<-EOM
|
13
|
-
The google gem is a simple tool to search Gooogle with via a CLI.
|
14
|
-
Usage:
|
15
|
-
google [options] "my search query string here"
|
16
|
-
where [options] are:
|
17
|
-
EOM
|
18
|
-
|
19
|
-
opt :page,
|
20
|
-
"Start by showing the <i>th result page.",
|
21
|
-
:type => :int,
|
22
|
-
:default => 1
|
23
|
-
|
24
|
-
opt :size,
|
25
|
-
"Show <i> results on each SERP. Must be between 1 and 8.",
|
26
|
-
:type => :int,
|
27
|
-
:default => 4
|
28
|
-
|
29
|
-
opt :result,
|
30
|
-
"Skip the SERP and show the <i>th result.",
|
31
|
-
:type => :int
|
32
|
-
|
33
|
-
opt :lucky,
|
34
|
-
"I'm feeling lucky! Skip the SERP and show the first result. " +
|
35
|
-
"(Alias to --result 1)"
|
36
|
-
|
37
|
-
opt :readability,
|
38
|
-
"Filter the results through readability to get rid of extra content.",
|
39
|
-
:default => true
|
40
|
-
|
41
|
-
opt :markdown,
|
42
|
-
"Change the results from raw HTML to markdown.",
|
43
|
-
:default => true
|
44
|
-
|
45
|
-
opt :version,
|
46
|
-
"Print the version and exit."
|
47
|
-
|
48
|
-
opt :help,
|
49
|
-
"Show this information and exit."
|
50
|
-
end
|
1
|
+
require 'google/utils'
|
2
|
+
require 'google/reverse-markdown/reverse_markdown'
|
3
|
+
require 'trollop'
|
4
|
+
require 'uri'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'json'
|
7
|
+
require 'formatador'
|
8
|
+
require 'htmlentities'
|
9
|
+
require 'ruby-readability'
|
51
10
|
|
52
|
-
|
53
|
-
|
11
|
+
class Google
|
12
|
+
def initialize(query, opts)
|
13
|
+
@query = URI.escape(query)
|
14
|
+
@opts = opts
|
15
|
+
@opts[:size] = 8 if opts[:size] > 7
|
16
|
+
@opts[:size] = 1 if opts[:size] <= 1
|
17
|
+
end
|
54
18
|
|
55
|
-
|
56
|
-
if
|
57
|
-
|
58
|
-
|
19
|
+
def search
|
20
|
+
if @opts[:result]
|
21
|
+
results = request :q => @query, :rsz => 1, :start => (@opts[:result] - 1)
|
22
|
+
view results[:results]['responseData']['results'][0]['url']
|
23
|
+
else
|
24
|
+
results = request :q => @query, :rsz => @opts[:size], :start => ((@opts[:page] - 1) * @opts[:size])
|
25
|
+
display_serp results
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(query_strings)
|
30
|
+
@api_url = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0"\
|
31
|
+
"&rsz=#{query_strings[:rsz]}&start=#{query_strings[:start]}&q=#{query_strings[:q]}"
|
32
|
+
results = JSON.parse(open(@api_url).read)
|
33
|
+
if results['responseStatus'].to_i != 200
|
34
|
+
Trollop::die Utils::wrap("Google Search API Status #{results['responseStatus']}. Details:\n"\
|
35
|
+
"#{results['responseDetails']}\nTry again in a moment")
|
36
|
+
end
|
37
|
+
|
38
|
+
{:results => results, :query_strings => query_strings}
|
39
|
+
end
|
40
|
+
|
41
|
+
def display_serp(info)
|
42
|
+
results = info[:results]
|
43
|
+
query_strings = info[:query_strings]
|
44
|
+
coder = HTMLEntities.new
|
45
|
+
current_page = results['responseData']['cursor']['currentPageIndex']+1
|
46
|
+
max_result = query_strings[:start] + query_strings[:rsz]
|
47
|
+
estimated_results = results['responseData']['cursor']['resultCount']
|
48
|
+
result_array = results['responseData']['results']
|
49
|
+
|
50
|
+
Formatador.display_line "\n#{' ' * (max_result.to_s.length + 2)}[yellow]Powered by Google[/]"
|
51
|
+
|
52
|
+
result_array.each_with_index do |result, i|
|
53
|
+
this_num = (i + query_strings[:start] + 1).to_s
|
54
|
+
|
55
|
+
serp_title = "\n#{' ' * (max_result.to_s.length - this_num.length)}[bold][blue]#{this_num}. "\
|
56
|
+
"[normal]#{result["titleNoFormatting"]}[/]\n"
|
57
|
+
serp_url = "#{' ' * max_result.to_s.length}[green]#{result["url"]}[/]\n"
|
58
|
+
serp_desc = ' ' * max_result.to_s.length + result["content"].gsub(/<b>/, "[bold]").gsub(/<\/b>/, "[/]").squeeze(" ")
|
59
|
+
|
60
|
+
Formatador.display_line coder.decode(Utils::wrap(serp_title, :prefix => max_result.to_s.length + 2))
|
61
|
+
Formatador.display_line coder.decode(Utils::wrap(serp_url, :prefix => max_result.to_s.length + 2))
|
62
|
+
Formatador.display_line coder.decode(Utils::wrap(serp_desc, :prefix => max_result.to_s.length + 2))
|
63
|
+
end
|
64
|
+
|
65
|
+
Formatador.display_line "\n#{' ' * (max_result.to_s.length + 2)}[yellow]Displaying results #{query_strings[:start] + 1}"\
|
66
|
+
" through #{max_result} of #{estimated_results} (Page #{current_page})"
|
67
|
+
|
68
|
+
input info, result_array
|
69
|
+
end
|
70
|
+
|
71
|
+
def input(info, result_array)
|
72
|
+
Formatador.display Utils::wrap("\n[yellow]Enter N or P for pagination, E or Q to quit, or a number to see that result.")
|
73
|
+
Formatador.display "\n[bold]>[/] "
|
74
|
+
choice = STDIN.gets
|
75
|
+
if choice.nil? # Likely because the user submitted EOT via ^D
|
76
|
+
choice = ' '
|
77
|
+
else
|
78
|
+
choice.chomp + ' '
|
79
|
+
end
|
80
|
+
|
81
|
+
case
|
82
|
+
# Quit
|
83
|
+
when choice[0].downcase == 'e' || choice[0].downcase == 'q'
|
84
|
+
exit
|
85
|
+
# Next Page
|
86
|
+
when choice[0].downcase == 'n'
|
87
|
+
display_serp request(:q => @query, :rsz => @opts[:size], :start => info[:query_strings][:start] + @opts[:size])
|
88
|
+
# Previous Page
|
89
|
+
when choice[0].downcase == 'p'
|
90
|
+
if info[:query_strings][:start] < 1
|
91
|
+
Formatador.display Utils::wrap("[yellow]! Already at page one.")
|
92
|
+
input info, result_array
|
93
|
+
else
|
94
|
+
display_serp request(:q => @query, :rsz => @opts[:size], :start => info[:query_strings][:start] - @opts[:size])
|
95
|
+
end
|
96
|
+
# Numerical Choice
|
97
|
+
when choice[0].match(/\d/)
|
98
|
+
/(\d+)(\s*\|(.*))*/.match(choice) do |str|
|
99
|
+
num = str[1].to_i - 1 # Remember, we are 1-indexing things for the user
|
100
|
+
# Is the result on the current page?
|
101
|
+
if info[:query_strings][:start] <= num && info[:query_strings][:start] + @opts[:size] > num
|
102
|
+
# Is the user piping the result to something?
|
103
|
+
if str[3].nil?
|
104
|
+
view result_array[num - info[:query_strings][:start]]['url']
|
105
|
+
else
|
106
|
+
pipe str[3].strip, result_array[num - info[:query_strings][:start]]['url']
|
107
|
+
end
|
108
|
+
else
|
109
|
+
Formatador.display Utils::wrap("[yellow]! Result not on this page.")
|
110
|
+
input info, result_array
|
111
|
+
end
|
112
|
+
end
|
113
|
+
# Catch-all to grab input again
|
114
|
+
else
|
115
|
+
input info, result_array
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def grab(url)
|
120
|
+
source = open(url).read
|
121
|
+
content = @opts[:readability] ? Readability::Document.new(source,
|
122
|
+
:tags => %w[div p a pre code h1 h2 h3 h4 h5 h6 blockquote ul ol li],
|
123
|
+
:attributes => %w[href],
|
124
|
+
:remote_empty_nodes => true).content : source
|
125
|
+
output = @opts[:markdown] ? ReverseMarkdown.new.parse_string(content) : content
|
126
|
+
output
|
127
|
+
end
|
128
|
+
|
129
|
+
def pipe(command, url)
|
130
|
+
text = grab(url)
|
131
|
+
IO.popen(command, 'w+') do |process|
|
132
|
+
process.write(text)
|
133
|
+
process.close_write
|
134
|
+
puts process.read
|
135
|
+
end
|
136
|
+
end
|
59
137
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
rescue Interrupt
|
64
|
-
puts "\nInterrupt received. Exiting application."
|
65
|
-
exit
|
138
|
+
def view(url)
|
139
|
+
Formatador.display Utils::wrap(grab(url))
|
140
|
+
end
|
66
141
|
end
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: google
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: trollop
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: json
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,21 +98,13 @@ executables:
|
|
82
98
|
extensions: []
|
83
99
|
extra_rdoc_files: []
|
84
100
|
files:
|
85
|
-
- lib/google.rb
|
86
101
|
- bin/google
|
102
|
+
- lib/google.rb
|
103
|
+
- lib/google/utils.rb
|
104
|
+
- lib/google/reverse-markdown/reverse_markdown.rb
|
87
105
|
- LICENSE.md
|
88
106
|
- README.md
|
89
107
|
- google.gemspec
|
90
|
-
- lib/google/display_serp.rb
|
91
|
-
- lib/google/request.rb
|
92
|
-
- lib/google/pipe-view.rb
|
93
|
-
- lib/google/search.rb
|
94
|
-
- lib/google/utils.rb
|
95
|
-
- lib/google/input.rb
|
96
|
-
- lib/google/grab.rb
|
97
|
-
- lib/reverse-markdown/reverse_markdown.rb
|
98
|
-
- lib/trollop/test/test_trollop.rb
|
99
|
-
- lib/trollop/lib/trollop.rb
|
100
108
|
homepage: http://kerrick.github.com/google/
|
101
109
|
licenses: []
|
102
110
|
post_install_message:
|
data/lib/google/display_serp.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
require 'formatador'
|
2
|
-
require 'htmlentities'
|
3
|
-
|
4
|
-
class Google
|
5
|
-
def display_serp info
|
6
|
-
results = info[:results]
|
7
|
-
query_strings = info[:query_strings]
|
8
|
-
coder = HTMLEntities.new
|
9
|
-
current_page = results['responseData']['cursor']['currentPageIndex']+1
|
10
|
-
max_result = query_strings[:start] + query_strings[:rsz]
|
11
|
-
estimated_results = results['responseData']['cursor']['resultCount']
|
12
|
-
result_array = results['responseData']['results']
|
13
|
-
|
14
|
-
attribution = "\n"
|
15
|
-
attribution << ' ' * (max_result.to_s.length + 2)
|
16
|
-
attribution << "[yellow]Powered by Google[/]"
|
17
|
-
Formatador.display_line attribution
|
18
|
-
|
19
|
-
result_array.each_with_index do | result, i |
|
20
|
-
this_num = (i + query_strings[:start] + 1).to_s
|
21
|
-
|
22
|
-
serp_title = "\n#{' ' * (max_result.to_s.length - this_num.length)}"
|
23
|
-
serp_title << "[bold][blue]#{this_num}. "
|
24
|
-
serp_title << "[normal]#{result["titleNoFormatting"]}[/]\n"
|
25
|
-
serp_url = ' ' * max_result.to_s.length
|
26
|
-
serp_url << "[green]#{result["url"]}[/]\n"
|
27
|
-
serp_desc = ' ' * max_result.to_s.length
|
28
|
-
serp_desc << result["content"].gsub(/<b>/, "[bold]")
|
29
|
-
.gsub(/<\/b>/, "[/]").squeeze(" ")
|
30
|
-
|
31
|
-
Formatador.display_line coder.decode(
|
32
|
-
Utils::wrap(serp_title, :prefix => max_result.to_s.length + 2)
|
33
|
-
)
|
34
|
-
Formatador.display_line coder.decode(
|
35
|
-
Utils::wrap(serp_url, :prefix => max_result.to_s.length + 2)
|
36
|
-
)
|
37
|
-
Formatador.display_line coder.decode(
|
38
|
-
Utils::wrap(serp_desc, :prefix => max_result.to_s.length + 2)
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
metadata = ''
|
43
|
-
metadata << "\n#{' ' * (max_result.to_s.length + 2)}"
|
44
|
-
metadata << "[yellow]Displaying results "
|
45
|
-
metadata << "#{query_strings[:start] + 1} through "
|
46
|
-
metadata << "#{max_result} of "
|
47
|
-
metadata << "#{estimated_results} "
|
48
|
-
metadata << "(Page #{current_page})"
|
49
|
-
Formatador.display_line metadata
|
50
|
-
|
51
|
-
input info, result_array
|
52
|
-
end
|
53
|
-
end
|
data/lib/google/grab.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'open-uri'
|
2
|
-
require 'ruby-readability'
|
3
|
-
require_relative '../reverse-markdown/reverse_markdown'
|
4
|
-
|
5
|
-
class Google
|
6
|
-
def grab url
|
7
|
-
source = open(url).read
|
8
|
-
|
9
|
-
if @opts[:readability]
|
10
|
-
content = Readability::Document.new(source,
|
11
|
-
:tags => %w[div p a pre code h1 h2 h3 h4 h5 h6 blockquote ul ol li],
|
12
|
-
:attributes => %w[href],
|
13
|
-
:remote_empty_nodes => true).content
|
14
|
-
else
|
15
|
-
content = source
|
16
|
-
end
|
17
|
-
|
18
|
-
if @opts[:markdown]
|
19
|
-
output = ReverseMarkdown.new.parse_string(content)
|
20
|
-
else
|
21
|
-
output = content
|
22
|
-
end
|
23
|
-
|
24
|
-
output
|
25
|
-
end
|
26
|
-
end
|
data/lib/google/input.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
require 'formatador'
|
2
|
-
|
3
|
-
class Google
|
4
|
-
def input info, result_array
|
5
|
-
prompt = "\n[yellow]"
|
6
|
-
prompt << "Enter N or P for Next or Previous page, E or Q to quit, "
|
7
|
-
prompt << "or a number to see that result."
|
8
|
-
|
9
|
-
Formatador.display Utils::wrap(prompt)
|
10
|
-
|
11
|
-
Formatador.display "\n[bold]>[/] "
|
12
|
-
choice = STDIN.gets
|
13
|
-
if choice.nil?
|
14
|
-
choice = ' '
|
15
|
-
else
|
16
|
-
choice.chomp + ' '
|
17
|
-
end
|
18
|
-
|
19
|
-
case
|
20
|
-
when choice[0].downcase == 'e' || choice[0].downcase == 'q'
|
21
|
-
exit
|
22
|
-
when choice[0].downcase == 'n'
|
23
|
-
results = request :q => @query,
|
24
|
-
:rsz => @opts[:size],
|
25
|
-
:start => info[:query_strings][:start] + @opts[:size]
|
26
|
-
display_serp results
|
27
|
-
when choice[0].downcase == 'p'
|
28
|
-
if info[:query_strings][:start] < 1
|
29
|
-
Formatador.display Utils::wrap("[yellow]! Already at page one.")
|
30
|
-
input info
|
31
|
-
else
|
32
|
-
results = request :q => @query,
|
33
|
-
:rsz => @opts[:size],
|
34
|
-
:start => info[:query_strings][:start] - @opts[:size]
|
35
|
-
display_serp results
|
36
|
-
end
|
37
|
-
when choice[0].match(/\d/)
|
38
|
-
/(\d+)(\s*\|(.*))*/.match(choice) do | str |
|
39
|
-
num = str[1].to_i - 1
|
40
|
-
if info[:query_strings][:start] <= num &&
|
41
|
-
info[:query_strings][:start] + @opts[:size] > num
|
42
|
-
if str[3].nil?
|
43
|
-
view result_array[num - info[:query_strings][:start]]['url']
|
44
|
-
else
|
45
|
-
pipe str[3].strip,
|
46
|
-
result_array[num - info[:query_strings][:start]]['url']
|
47
|
-
end
|
48
|
-
else
|
49
|
-
Formatador.display Utils::wrap("[yellow]! Result not on this page.")
|
50
|
-
input info, result_array
|
51
|
-
end
|
52
|
-
end
|
53
|
-
else
|
54
|
-
input info, result_array
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|