22import requests
33from rich .console import Console
44import sys as sys
5+
6+ # Required for Questions Panel
57import os
68import time
79from collections import defaultdict
10+ from simple_term_menu import TerminalMenu
11+ import webbrowser
12+ from pygments import highlight
13+ from pygments .lexers .markup import MarkdownLexer
14+ from pygments .formatters import Terminal256Formatter
815
916from .error import SearchError
1017from .save import SaveSearchResults
2835
2936console = Console ()
3037
38+ class Questions_Panel_Stackoverflow ():
39+ def __init__ (self ):
40+ self .questions_data = [] # list( list( question_title, question_link, question_id )... )
41+ self .answer_data = defaultdict (lambda : False ) # dict( question_id:list( body, link )) corresponding to self.questions_data
42+ self .batch_ques_id = ""
43+ self .line_color = "bold red"
44+ self .heading_color = "bold blue"
45+ self .search_content_url = "https://api.stackexchange.com/"
46+
47+ def populate_question_data (self , questions_list ):
48+ """ Function to populate question data property
49+ Creates batch_id request to stackexchange API and
50+ Stores the returned data data in the following format:
51+ list( list( question_title, question_link, question_id ) )
52+ """
53+ for question_id in questions_list :
54+ self .batch_ques_id += str (question_id ) + ";"
55+ try :
56+ resp = requests .get (
57+ f"{ self .search_content_url } /2.2/questions/{ self .batch_ques_id [:- 1 ]} ?order=desc&sort=votes&site=stackoverflow&filter=!--1nZwsgqvRX"
58+ )
59+ except :
60+ SearchError ("Search Failed" , "Try connecting to the internet" )
61+ sys .exit ()
62+ json_ques_data = resp .json ()
63+ self .questions_data = [[item ['title' ], item ['question_id' ], item ['link' ]] for item in json_ques_data ["items" ]]
64+ return
65+
66+ def populate_answer_data (self ):
67+ """ Function to populate answer data property
68+ Creates batch_id request to stackexchange API and
69+ Stores the returned data data in the following format:
70+ list( list( question_title, question_link, question_id ) )
71+ """
72+ try :
73+ resp = requests .get (
74+ f"{ self .search_content_url } /2.2/questions/{ self .batch_ques_id [:- 1 ]} /answers?order=desc&sort=votes&site=stackoverflow&filter=!--1nZwsgqvRX"
75+ )
76+ except :
77+ SearchError ("Search Failed" , "Try connecting to the internet" )
78+ sys .exit ()
79+ json_ans_data = resp .json ()
80+ for item in json_ans_data ["items" ]:
81+ self .answer_data [item ['question_id' ]] = item ['body_markdown' ]
82+
83+ def return_formatted_ans (self , id ):
84+ # This function uses pygments lexers ad formatters to format the content in the preview screen
85+ body_markdown = self .answer_data [int (id )]
86+ if (body_markdown ):
87+ body_markdown = str (body_markdown )
88+ body_markdown = body_markdown .replace ("&" , "&" )
89+ body_markdown = body_markdown .replace ("<" , "<" )
90+ body_markdown = body_markdown .replace (">" , ">" )
91+ body_markdown = body_markdown .replace (""" , "\" " )
92+ body_markdown = body_markdown .replace ("'" , "\' " )
93+ body_markdown = body_markdown .replace ("'" , "\' " )
94+ lexer = MarkdownLexer ()
95+ formatter = Terminal256Formatter (bg = "light" )
96+ highlighted = highlight (body_markdown , lexer , formatter )
97+ else :
98+ highlighted = "Answer not viewable. Press enter to open in a browser"
99+ return highlighted
100+
101+ def navigate_questions_panel (self ):
102+ # Code for navigating through the question panel
103+ console .rule ('[bold blue] Relevant Questions' , style = "bold red" )
104+ console .print ("[yellow] Use arrow keys to navigate. 'q' or 'Esc' to quit. 'Enter' to open in a browser" )
105+ console .print ()
106+ options = ["|" .join (map (str , question )) for question in self .questions_data ]
107+ question_menu = TerminalMenu (options , preview_command = self .return_formatted_ans )
108+ quitting = False
109+ while not (quitting ):
110+ options_index = question_menu .show ()
111+ try :
112+ options_choice = options [options_index ]
113+ except :
114+ return
115+ else :
116+ question_link = self .questions_data [options_index ][2 ]
117+ webbrowser .open (question_link )
31118
32119class Utility ():
33120 def __init__ (self ):
@@ -52,12 +139,12 @@ def make_request(self, que, tag: str):
52139 :return: Json response from the api call.
53140 :rtype: Json format data
54141 """
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 ()
142+ with console . status ( " Searching..." ):
143+ try :
144+ resp = requests .get (self .__get_search_url (que , tag ))
145+ except :
146+ SearchError ("\U0001F613 Search Failed" , "\U0001F4BB Try connecting to the internet" )
147+ sys .exit ()
61148 return resp .json ()
62149
63150 def get_que (self , json_data ):
@@ -73,109 +160,10 @@ def get_que(self, json_data):
73160 return que_id
74161
75162 def get_ans (self , questions_list ):
76- """
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 ) )
102- """
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- try :
168- posx = int (input ("Enter the question number you want to view (Press 0 to quit): " )) - 1
169- except ValueError :
170- SearchError ("You didn't enter a question number" , "Enter a question number from the relevant questions list" )
171- if (posx == - 1 ):
172- return
173- elif (0 <= posx < len (questions_data )):
174- question_posx = posx
175- break
176- else :
177- console .print ("Please enter a valid question number" )
178- continue
163+ stackoverflow_panel = Questions_Panel_Stackoverflow ()
164+ stackoverflow_panel .populate_question_data (questions_list )
165+ stackoverflow_panel .populate_answer_data ()
166+ stackoverflow_panel .navigate_questions_panel ()
179167
180168 # Get an access token and extract to a JSON file "access_token.json"
181169 @classmethod
0 commit comments