Skip to content

Commit 70f199b

Browse files
Merge pull request #92 from NamamiShanker/namami
Add playbook feature #92
2 parents 41f7ea3 + f5b2a31 commit 70f199b

File tree

3 files changed

+142
-14
lines changed

3 files changed

+142
-14
lines changed

main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
help="Make a GET request to an API",
5151
action='store_true')
5252

53+
parser.add_argument("-p",
54+
"--playbook",
55+
help="View and organise the playbook",
56+
action='store_true')
57+
5358
ARGV = parser.parse_args()
5459

5560
search_flag = Search(ARGV)

src/arguments/search.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .error import SearchError
88
from .utility import Utility
9+
from .utility import Playbook
910
from .save import SaveSearchResults
1011
from .update import UpdateApplication
1112
from .api_test import ApiTesting
@@ -30,12 +31,15 @@ def __init__(self, arguments):
3031
self.arguments = arguments
3132
self.utility_object = Utility()
3233
self.api_test_object = ApiTesting()
34+
self.playbook_object = Playbook()
3335

3436
def search_args(self):
3537
if self.arguments.search:
3638
self.search_for_results()
3739
elif self.arguments.file:
3840
self.search_for_results(True)
41+
elif self.arguments.playbook:
42+
self.playbook_object.display_panel()
3943
elif self.arguments.new:
4044
url = "https://stackoverflow.com/questions/ask"
4145
if type(self.arguments.new) == str:

src/arguments/utility.py

Lines changed: 133 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# Required for Questions Panel
88
import os
99
import time
10+
import locale
1011
from collections import defaultdict
1112
from simple_term_menu import TerminalMenu
1213
import webbrowser
@@ -33,13 +34,123 @@
3334

3435
console = Console()
3536

37+
class Playbook():
38+
def __init__(self):
39+
self.linux_path = "/home/{}/Documents/dynamic".format(os.getenv('USER'))
40+
self.mac_path = "/Users/{}/Documents/dynamic".format(os.getenv('USER'))
41+
self.file_name = 'dynamic_playbook.json'
42+
self.key = 'DYNAMIC'
43+
44+
@property
45+
def playbook_path(self):
46+
# Create an environment variable 'DYNAMIC' containing the path of dynamic_playbook.json and returns it
47+
if not os.getenv(self.key):
48+
if(sys.platform=='linux'):
49+
os.environ[self.key] = os.path.join(self.linux_path, self.file_name)
50+
elif(sys.platform=='darwin'):
51+
os.environ[self.key] = os.path.join(self.mac_path, self.file_name)
52+
return os.getenv(self.key)
53+
54+
@property
55+
def playbook_template(self):
56+
# Basic template and fields of playbook
57+
return {"time_of_update": time.time(),"items_stackoverflow":[]}
58+
59+
@property
60+
def playbook_content(self):
61+
# Reads playbook data from local storage and returns it
62+
try:
63+
with open(self.playbook_path, 'r') as playbook:
64+
return json.load(playbook)
65+
except FileNotFoundError:
66+
os.makedirs(os.path.dirname(self.playbook_path), exist_ok=True)
67+
with open(self.playbook_path, 'w') as playbook:
68+
json.dump(self.playbook_template, playbook, ensure_ascii=False)
69+
return self.playbook_content
70+
71+
@playbook_content.setter
72+
def playbook_content(self, value):
73+
if isinstance(value, dict):
74+
with open(self.playbook_path, 'w') as playbook:
75+
json.dump(value, playbook, ensure_ascii=False)
76+
else:
77+
raise ValueError("value should be of type dict")
78+
79+
def is_question_in_playbook(self, question_id):
80+
content = self.playbook_content
81+
for entry in content['items_stackoverflow']:
82+
if int(entry['question_id']) == int(question_id):
83+
return True
84+
return False
85+
86+
def add_to_playbook(self, stackoverflow_object, question_id):
87+
"""
88+
Receives a QuestionsPanelStackoverflow object and
89+
saves data of a particular question into playbook
90+
Saves playbook in the following format
91+
{
92+
time_of_update: unix,
93+
items_stackoverflow:
94+
[
95+
{
96+
time: unix timestamp
97+
question_id: 123456,
98+
question_title: 'question_title',
99+
question_link: 'link',
100+
answer_body: 'body of the answer'
101+
},
102+
...
103+
]
104+
"""
105+
if self.is_question_in_playbook(question_id):
106+
console.print("[red] Question is already in the playbook, No need to add")
107+
return
108+
for question in stackoverflow_object.questions_data:
109+
if(int(question[1])==int(question_id)):
110+
content = self.playbook_content
111+
now = time.time()
112+
content['time_of_update'] = now
113+
content['items_stackoverflow'].append({
114+
'time_of_creation': now,
115+
'question_id': int(question_id),
116+
'question_title': question[0],
117+
'question_link': question[2],
118+
'answer_body': stackoverflow_object.answer_data[int(question_id)]
119+
})
120+
self.playbook_content = content
121+
console.print("[green] Question added to the playbook")
122+
123+
def delete_from_playbook(self, stackoverflow_object, question_id):
124+
content = self.playbook_content
125+
for i in range(len(content["items_stackoverflow"])):
126+
if content["items_stackoverflow"][i]["question_id"] == question_id:
127+
del content["items_stackoverflow"][i]
128+
break
129+
self.playbook_content = content
130+
os.system('cls' if os.name == 'nt' else 'clear')
131+
self = Playbook()
132+
self.display_panel()
133+
134+
def display_panel(self):
135+
playbook_data = self.playbook_content
136+
if(len(playbook_data['items_stackoverflow']) == 0):
137+
SearchError("You have no entries in the playbook", "Browse and save entries in playbook with 'p' key")
138+
sys.exit()
139+
# Creates QuestionPanelStackoverflow object, populates its question_data and answer_data and displays it
140+
question_panel = QuestionsPanelStackoverflow()
141+
for item in playbook_data['items_stackoverflow']:
142+
question_panel.questions_data.append( [item['question_title'], item['question_id'], item['question_link']] )
143+
question_panel.answer_data[item['question_id']] = item['answer_body']
144+
question_panel.display_panel([], playbook=True)
145+
36146
class QuestionsPanelStackoverflow():
37147
def __init__(self):
38148
self.questions_data = [] # list( list( question_title, question_id, question_link )... )
39149
self.answer_data = defaultdict(lambda: False) # dict( question_id:list( body, link )) corresponding to self.questions_data
40150
self.line_color = "bold red"
41151
self.heading_color = "bold blue"
42152
self.utility = Utility()
153+
self.playbook = Playbook()
43154

44155
def populate_question_data(self, questions_list):
45156
"""
@@ -84,7 +195,7 @@ def populate_answer_data(self, questions_list):
84195
self.populate_answer_data(failed_ques_id)
85196

86197
def return_formatted_ans(self, ques_id):
87-
# This function uses pygments lexers ad formatters to format the content in the preview screen
198+
# This function uses uses Rich Markdown to format answers body.
88199
body_markdown = self.answer_data[int(ques_id)]
89200
body_markdown = str(body_markdown)
90201
xml_markup_replacement = [("&amp;", "&"), ("&lt;", "<"), ("&gt;", ">"), ("&quot;", "\""), ("&apos;", "\'"), ("&#39;", "\'")]
@@ -96,32 +207,40 @@ def return_formatted_ans(self, ques_id):
96207
with console.capture() as capture:
97208
console.print(markdown)
98209
highlighted = capture.get()
99-
box_replacement = [("─", "-"), ("═","="), ("║","|"), ("│", "|"), ('┌', '+'), ("└", "+"), ("┐", "+"), ("┘", "+"), ("╔", "+"), ("╚", "+"), ("╗","+"), ("╝", "+"), ("•","*")]
100-
for convert_from, convert_to in box_replacement:
101-
highlighted = highlighted.replace(convert_from, convert_to)
210+
if locale.getlocale()[1] !='UTF-8':
211+
box_replacement = [("─", "-"), ("═","="), ("║","|"), ("│", "|"), ('┌', '+'), ("└", "+"), ("┐", "+"), ("┘", "+"), ("╔", "+"), ("╚", "+"), ("╗","+"), ("╝", "+"), ("•","*")]
212+
for convert_from, convert_to in box_replacement:
213+
highlighted = highlighted.replace(convert_from, convert_to)
102214
return highlighted
103215

104-
def navigate_questions_panel(self):
216+
def navigate_questions_panel(self, playbook=False):
105217
# 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")
218+
(message, instructions, keys) = ('Playbook Questions', ". Press 'd' to delete from playbook", ('enter', 'd')) if(playbook) else ('Relevant Questions', ". Press 'p' to save in playbook", ('p', 'enter'))
219+
console.rule('[bold blue] {}'.format(message), style="bold red")
220+
console.print("[yellow] Use arrow keys to navigate. 'q' or 'Esc' to quit. 'Enter' to open in a browser" + instructions)
108221
console.print()
109222
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, )
223+
question_menu = TerminalMenu(options, preview_command=self.return_formatted_ans, preview_size=0.75, accept_keys=keys)
111224
quitting = False
112225
while not(quitting):
113226
options_index = question_menu.show()
114227
try:
115228
question_link = self.questions_data[options_index][2]
116229
except Exception:
117-
return
230+
return sys.exit() if playbook else None
118231
else:
119-
webbrowser.open(question_link)
232+
if(question_menu.chosen_accept_key == 'enter'):
233+
webbrowser.open(question_link)
234+
elif(question_menu.chosen_accept_key == 'p'):
235+
self.playbook.add_to_playbook(self, self.questions_data[options_index][1])
236+
elif(question_menu.chosen_accept_key == 'd' and playbook):
237+
self.playbook.delete_from_playbook(self, self.questions_data[options_index][1])
120238

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()
239+
def display_panel(self, questions_list, playbook=False):
240+
if not playbook:
241+
self.populate_question_data(questions_list)
242+
self.populate_answer_data(questions_list)
243+
self.navigate_questions_panel(playbook=playbook)
125244

126245
class Utility():
127246
def __init__(self):

0 commit comments

Comments
 (0)