google 1.0.8 → 1.0.9
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/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
|