stackoverflow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/stackoverflow.rb +178 -0
  2. metadata +64 -0
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'open-uri'
5
+ require 'net/http'
6
+ require 'json'
7
+ require 'nokogiri'
8
+ require 'terminal-table/import'
9
+ require 'sqlite3'
10
+ require 'optparse'
11
+
12
+
13
+ module StackOverflow
14
+ class API
15
+ def search(search_string)
16
+ search_string = URI::encode(search_string)
17
+ api_get("/2.0/similar?order=desc&sort=votes&title=#{search_string}&site=stackoverflow&filter=!9Tk5iz1Gf")
18
+ end
19
+
20
+ def get_question(question_id)
21
+ api_get("/2.0/questions/#{question_id}?order=desc&sort=activity&site=stackoverflow&filter=!9Tk5izFWA")
22
+ end
23
+
24
+ def get_answers(question_id)
25
+ api_get("/2.0/questions/#{question_id}/answers?order=desc&sort=votes&site=stackoverflow&filter=!9Tk6JYC_e")
26
+ end
27
+
28
+ private
29
+
30
+ def api_get(path)
31
+ url = "https://api.stackexchange.com" + path
32
+ u = URI.parse(url)
33
+ Net::HTTP.start(u.host, u.port, :use_ssl => true) do |http|
34
+ response = http.get(u.request_uri)
35
+ return JSON(response.body)['items']
36
+ end
37
+ end
38
+ end
39
+
40
+ class DB
41
+ def initialize
42
+ @db = SQLite3::Database.new("../data/stackoverflow.db")
43
+ end
44
+
45
+ def search(search_string)
46
+ sql = "SELECT id, score, body, title FROM posts WHERE post_type_id=1 AND "
47
+ sub_sql = []
48
+ for search_term in search_string.split(' ')
49
+ sub_sql << "title LIKE '%#{search_term}%"
50
+ end
51
+ sql += sub_sql.join(' AND ')
52
+ sql += ' ORDER BY score DESC'
53
+
54
+ questions = []
55
+ @db.execute(sql) do |row|
56
+ questions << { :id => row[0],
57
+ :score => row[1],
58
+ :body => row[2],
59
+ :title => row[3],
60
+ :link => '',
61
+ :answers => get_answers(row[0]) }
62
+ end
63
+ end
64
+
65
+ def get_answers(question_id)
66
+ answers = []
67
+ sql = "SELECT id, score, body FROM posts WHERE post_type_id=2 AND parent_id=#{question_id} ORDER BY score DESC"
68
+ @db.execute(sql) do |row|
69
+ answers << { :id => row[0],
70
+ :score => row[1],
71
+ :body => row[2] }
72
+ end
73
+ end
74
+ end
75
+
76
+ class Formatter
77
+ def questions_list(questions)
78
+ nb = 1
79
+
80
+ table = Terminal::Table.new do |t|
81
+ questions.each do |question|
82
+ score = question['score'] > 0 ? "+#{question['score']}" : question['score']
83
+ t << ["[#{nb}]", "(#{score})", question['title']]
84
+ nb += 1
85
+ end
86
+ t.style = {:padding_left => 2, :border_x => " ", :border_i => " ", :border_y => " "}
87
+ end
88
+ puts table
89
+ end
90
+
91
+ def question_viewer(question)
92
+ answers = question['answers']
93
+ nb = 1
94
+
95
+ table = Terminal::Table.new do |t|
96
+ t << ["Question", html2text(question['body'])]
97
+ t.title = question['title'] + "\n\n" + question['link']
98
+ t << :separator
99
+ t << :separator
100
+
101
+ answers.each do |answer|
102
+ text = html2text(answer['body'])
103
+ t << ["[#{nb}] (+#{answer['score']})", "#{text}"]
104
+ t << :separator
105
+ nb += 1
106
+ end
107
+ end
108
+
109
+ IO.popen("less", "w") { |f| f.puts table }
110
+ end
111
+
112
+ def html2text(html)
113
+ doc = Nokogiri::HTML(html)
114
+ doc = doc.css('body').text.squeeze(" ").squeeze("\n").gsub(/[\n]+/, "\n\n")
115
+ wordwrap(doc)
116
+ end
117
+
118
+ def wordwrap(str, columns=80)
119
+ str.gsub(/\t/, " ").gsub(/.{1,#{ columns }}(?:\s|\Z)/) do
120
+ ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n")
121
+ end
122
+ end
123
+ end
124
+
125
+ class Command
126
+ def run
127
+ options = {}
128
+ OptionParser.new do |opts|
129
+ opts.banner = "Usage: so [options] <search string> [<question_id>]"
130
+ opts.on("-h", "--help", "Help") do |v|
131
+ help(opts)
132
+ end
133
+ opts.on("-o", "--offline", "Offline mode") do |v|
134
+ options['offline'] = true
135
+ end
136
+ end.parse!
137
+
138
+ # last argument is integer when user is specifing a question_nb from the results
139
+ question_nb = nil
140
+ if ARGV[-1] =~ /^[0-9]+$/
141
+ question_nb = ARGV.pop.to_i
142
+ end
143
+
144
+ search_string = ARGV.join(' ')
145
+
146
+ search(search_string, question_nb, options)
147
+ end
148
+
149
+ def help(opts)
150
+ puts opts.banner + "\n\n"
151
+ puts "\t<search_string>: Search Stack Overflow for a combination of words"
152
+ puts "\t<question_id>: (Optional) Display the question with this #id from the search results"
153
+ end
154
+
155
+ def search(search_string, question_nb, options)
156
+ if options['offline']
157
+ api = API.new
158
+ #api = DB.new
159
+ else
160
+ api = API.new
161
+ end
162
+
163
+ questions = api.search(search_string)
164
+ if !questions or questions.length == 0
165
+ puts "No record found - Try a less specific (or sometimes, more specific) query"
166
+ return
167
+ end
168
+
169
+ if !question_nb
170
+ Formatter.new.questions_list(questions)
171
+ else
172
+ question = questions[question_nb.to_i - 1]
173
+ Formatter.new.question_viewer(question)
174
+ end
175
+ end
176
+ end
177
+ end
178
+
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stackoverflow
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Xavier Antoviaque
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-03-24 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Query StackOverflow from the command line (offline/online modes)
22
+ email: xavier@antoviaque.org
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/stackoverflow.rb
31
+ has_rdoc: true
32
+ homepage: https://github.com/antoviaque/stack-overflow-command-line
33
+ licenses: []
34
+
35
+ post_install_message:
36
+ rdoc_options: []
37
+
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.7
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Query StackOverflow from the command line (offline/online modes)
63
+ test_files: []
64
+