Skip to content

Commit 41f7ea3

Browse files
Merge pull request #89 from NamamiShanker/namami
Create navigation enabled question panel #81
2 parents fcbd41b + 274fcc7 commit 41f7ea3

File tree

4 files changed

+124
-109
lines changed

4 files changed

+124
-109
lines changed

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ colorama==0.4.4
1111
configparser==5.0.2
1212
crayons==0.4.0
1313
selenium==3.141.0
14-
webdriver-manager==3.3.0
14+
webdriver-manager==3.3.0
15+
simple-term-menu==1.0.1

src/arguments/markdown.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,3 @@ def __len__(self):
3333

3434
def __str__(self):
3535
return str(self.render)
36-
37-
def __repr__(self):
38-
return str(self.render)

src/arguments/search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def search_for_results(self, save=False):
5454
queries = ["What do you want to search", "Tags"]
5555
query_solutions = []
5656

57-
# ask quesiton
57+
# ask question
5858
for each_query in queries:
5959
# Be careful if there are
6060
# KeyBoard Interrupts or EOErrors

src/arguments/utility.py

Lines changed: 121 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
from termcolor import colored
22
import requests
33
from rich.console import Console
4+
from rich.markdown import Markdown
45
import sys as sys
6+
7+
# Required for Questions Panel
58
import os
69
import time
710
from collections import defaultdict
11+
from simple_term_menu import TerminalMenu
12+
import webbrowser
813

914
from .error import SearchError
1015
from .save import SaveSearchResults
@@ -28,6 +33,95 @@
2833

2934
console = Console()
3035

36+
class QuestionsPanelStackoverflow():
37+
def __init__(self):
38+
self.questions_data = [] # list( list( question_title, question_id, question_link )... )
39+
self.answer_data = defaultdict(lambda: False) # dict( question_id:list( body, link )) corresponding to self.questions_data
40+
self.line_color = "bold red"
41+
self.heading_color = "bold blue"
42+
self.utility = Utility()
43+
44+
def populate_question_data(self, questions_list):
45+
"""
46+
Function to populate question data property
47+
Creates batch request to stackexchange API and to get question details of
48+
questions with id in the list. Stores the returned data data in the following format:
49+
list( list( question_title, question_link, question_id ) )
50+
"""
51+
with console.status("Getting the questions..."):
52+
try:
53+
resp = requests.get(
54+
self.utility.get_batch_ques_url(questions_list)
55+
)
56+
except:
57+
SearchError("Search Failed", "Try connecting to the internet")
58+
sys.exit()
59+
json_ques_data = resp.json()
60+
self.questions_data = [[item['title'].replace('|',''), item['question_id'], item['link']] for item in json_ques_data["items"]]
61+
62+
def populate_answer_data(self, questions_list):
63+
"""
64+
Function to populate answer data property
65+
Creates batch request to stackexchange API to get ans of questions with
66+
question id in the list. Stores the returned data data in the following format:
67+
dict( question_id:list( body, link ) )
68+
"""
69+
with console.status("Searching answers..."):
70+
try:
71+
resp = requests.get(
72+
self.utility.get_batch_ans_url(questions_list)
73+
)
74+
except:
75+
SearchError("Search Failed", "Try connecting to the internet")
76+
sys.exit()
77+
json_ans_data = resp.json()
78+
for item in json_ans_data["items"]:
79+
if not(self.answer_data[item['question_id']]):
80+
self.answer_data[item['question_id']] = item['body_markdown']
81+
# Sometimes the StackExchange API fails to deliver some answers. The below code is to fetch them
82+
failed_ques_id = [question[1] for question in self.questions_data if not(self.answer_data[question[1]])]
83+
if not(len(failed_ques_id) == 0):
84+
self.populate_answer_data(failed_ques_id)
85+
86+
def return_formatted_ans(self, ques_id):
87+
# This function uses pygments lexers ad formatters to format the content in the preview screen
88+
body_markdown = self.answer_data[int(ques_id)]
89+
body_markdown = str(body_markdown)
90+
xml_markup_replacement = [("&amp;", "&"), ("&lt;", "<"), ("&gt;", ">"), ("&quot;", "\""), ("&apos;", "\'"), ("&#39;", "\'")]
91+
for convert_from, convert_to in xml_markup_replacement:
92+
body_markdown = body_markdown.replace(convert_from, convert_to)
93+
width = os.get_terminal_size().columns
94+
console = Console(width=width-4)
95+
markdown = Markdown(body_markdown, hyperlinks=False)
96+
with console.capture() as capture:
97+
console.print(markdown)
98+
highlighted = capture.get()
99+
box_replacement = [("─", "-"), ("═","="), ("║","|"), ("│", "|"), ('┌', '+'), ("└", "+"), ("┐", "+"), ("┘", "+"), ("╔", "+"), ("╚", "+"), ("╗","+"), ("╝", "+"), ("•","*")]
100+
for convert_from, convert_to in box_replacement:
101+
highlighted = highlighted.replace(convert_from, convert_to)
102+
return highlighted
103+
104+
def navigate_questions_panel(self):
105+
# Code for navigating through the question panel
106+
console.rule('[bold blue] Relevant Questions', style="bold red")
107+
console.print("[yellow] Use arrow keys to navigate. 'q' or 'Esc' to quit. 'Enter' to open in a browser")
108+
console.print()
109+
options = ["|".join(map(str, question)) for question in self.questions_data]
110+
question_menu = TerminalMenu(options, preview_command=self.return_formatted_ans, preview_size=0.75, )
111+
quitting = False
112+
while not(quitting):
113+
options_index = question_menu.show()
114+
try:
115+
question_link = self.questions_data[options_index][2]
116+
except Exception:
117+
return
118+
else:
119+
webbrowser.open(question_link)
120+
121+
def display_panel(self, questions_list):
122+
self.populate_question_data(questions_list)
123+
self.populate_answer_data(questions_list)
124+
self.navigate_questions_panel()
31125

32126
class Utility():
33127
def __init__(self):
@@ -42,6 +136,22 @@ def __get_search_url(self, question, tags):
42136
"""
43137
return f"{self.search_content_url}/2.2/search/advanced?order=desc&sort=relevance&tagged={tags}&title={question}&site=stackoverflow"
44138

139+
def get_batch_ques_url(self, ques_id_list):
140+
"""
141+
Returns URL which contains ques_ids which can be use to get
142+
get the details of all the corresponding questions
143+
"""
144+
batch_ques_id = ""
145+
for question_id in ques_id_list:
146+
batch_ques_id += str(question_id) + ";"
147+
return f"{self.search_content_url}/2.2/questions/{batch_ques_id[:-1]}?order=desc&sort=votes&site=stackoverflow&filter=!--1nZwsgqvRX"
148+
149+
def get_batch_ans_url(self, ques_id_list):
150+
batch_ques_id = ""
151+
for question_id in ques_id_list:
152+
batch_ques_id += str(question_id) + ";"
153+
return f"{self.search_content_url}/2.2/questions/{batch_ques_id[:-1]}/answers?order=desc&sort=votes&site=stackoverflow&filter=!--1nZwsgqvRX"
154+
45155
def make_request(self, que, tag: str):
46156
"""
47157
This function uses the requests library to make the rest api call to the stackexchange server.
@@ -52,12 +162,12 @@ def make_request(self, que, tag: str):
52162
:return: Json response from the api call.
53163
:rtype: Json format data
54164
"""
55-
print("\U0001F50E Searching for the answer")
56-
try:
57-
resp = requests.get(self.__get_search_url(que, tag))
58-
except:
59-
SearchError("\U0001F613 Search Failed", "\U0001F4BB Try connecting to the internet")
60-
sys.exit()
165+
with console.status("Searching..."):
166+
try:
167+
resp = requests.get(self.__get_search_url(que, tag))
168+
except:
169+
SearchError("\U0001F613 Search Failed", "\U0001F4BB Try connecting to the internet")
170+
sys.exit()
61171
return resp.json()
62172

63173
def get_que(self, json_data):
@@ -74,105 +184,12 @@ def get_que(self, json_data):
74184

75185
def get_ans(self, questions_list):
76186
"""
77-
This function prints the answer to the queries
78-
(question and tags) provided by the user. It does so
79-
in the following manner :
80-
1) Gets the details of all the relevant question and stores their title, link and id in "question_data" list.
81-
2) I have introduced the concept of active question, i.e. , the question whose answer is currently being displayed.
82-
3) The index of the active question in "question_data" array is stored in "question_posx"
83-
2) By Default, shows the answer to the first question. Creates an breakable infinite loop asking the user answer to which question he wants to see.
84-
4) The value of "question_posx" changes according to user input, displaying answer to different questions in "questions_data" list.
85-
3) The answers to the questions requested by the user are stored in cache for faster access time during subsequent calls.
86-
"""
87-
# Create batch request to get details of all the questions
88-
batch_ques_id = ""
89-
for question_id in questions_list:
90-
batch_ques_id += str(question_id) + ";"
91-
try:
92-
resp = requests.get(
93-
f"{self.search_content_url}/2.2/questions/{batch_ques_id[:-1]}?order=desc&sort=votes&site=stackoverflow&filter=!--1nZwsgqvRX"
94-
)
95-
except:
96-
SearchError("Search Failed", "Try connecting to the internet")
97-
sys.exit()
98-
json_ques_data = resp.json()
99-
"""
100-
Store the received questions data into the following data format:
101-
list( list( question_title, question_link, question_id ) )
187+
This Function creates QuestionsPanel_stackoverflow class which supports
188+
Rendering, navigation, searching and redirecting capabilities
102189
"""
103-
questions_data = [[item['title'], item['link'], item['question_id']] for item in json_ques_data["items"] ]
104-
# Clear terminal
105-
os.system('cls' if os.name == 'nt' else 'clear')
106-
107-
# cache array to store the requested answers. Format of storage { question_id:[answer_body, answer_link] }
108-
downloaded_questions_cache = defaultdict(lambda: False)
109-
110-
# Stores the currently showing question index in questions_data
111-
question_posx = 0
112-
113-
while True:
114-
os.system('cls' if os.name == 'nt' else 'clear')
115-
# Printing all the questions. The active question is printed GREEN.
116-
console.rule('[bold blue] Relevant Questions', style="bold red")
117-
for idx, question_data in enumerate(questions_data):
118-
if question_posx == idx:
119-
console.print("[green]{}. {} | {}".format(idx+1, question_data[0], question_data[1]))
120-
else:
121-
console.print("{}. {} | {}".format(idx+1, question_data[0], question_data[1]))
122-
console.rule("[bold blue] Answer of question {}".format(question_posx+1), style="bold red")
123-
124-
# Gets the question_id of active question
125-
current_question_id = questions_data[question_posx][2]
126-
127-
# Searches for the id in cache. If present then prints it
128-
if(downloaded_questions_cache[current_question_id]):
129-
output_content = downloaded_questions_cache[current_question_id]
130-
for output_index, output_text in enumerate(output_content):
131-
"""
132-
Loop through the output_text and print the element
133-
if it is the last one, the text[0] is printed
134-
along with text[-1]
135-
136-
if text is markdown , render the markdown
137-
"""
138-
if output_index == len(output_content) - 1:
139-
console.print("Link to the answer: " + output_text)
140-
break
141-
142-
if output_index == len(output_content) - 2:
143-
MarkdownRenderer(output_text)
144-
continue
145-
146-
console.print(output_text)
147-
# If the cache has no entry for the said question id, then downloads the answer
148-
# and makes an for it entry in the cache array in the said format and restarts the loop.
149-
else:
150-
try:
151-
resp = requests.get(
152-
f"{self.search_content_url}/2.2/questions/{current_question_id}/answers?order=desc&sort=votes&site=stackoverflow&filter=!--1nZwsgqvRX"
153-
)
154-
except:
155-
SearchError("Search Failed", "Try connecting to the internet")
156-
sys.exit()
157-
json_ans_data = resp.json()
158-
print(json_ans_data["items"])
159-
most_upvoted = json_ans_data["items"][0]
160-
downloaded_questions_cache[current_question_id] = [most_upvoted["body_markdown"], most_upvoted['link']]
161-
del most_upvoted
162-
continue
163-
164-
console.rule("[bold blue]", style="bold red", align="right")
165-
# Asks the user for next question number. Makes it the active question and the loop restarts
166-
while True:
167-
posx = int(input("Enter the question number you want to view (Press 0 to quit): ")) - 1
168-
if (posx == -1):
169-
return
170-
elif (0<=posx<len(questions_data)):
171-
question_posx = posx
172-
break
173-
else:
174-
console.print("Please enter a valid question number")
175-
continue
190+
stackoverflow_panel = QuestionsPanelStackoverflow()
191+
stackoverflow_panel.display_panel(questions_list)
192+
# Support for reddit searching can also be implemented from here
176193

177194
# Get an access token and extract to a JSON file "access_token.json"
178195
@classmethod

0 commit comments

Comments
 (0)