stackoverflow 0.1.0

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.
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
+