forked from Psyhister/scrybe
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathuserLib.py
395 lines (372 loc) · 15.5 KB
/
userLib.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
import os
import dbLib
import time
import re
class Session:
def __init__(self):
self.conn = dbLib.ConnectionHandler()
self.conf = self.getConfig()
def getConfig(self):
conf = {}
with open(".scrybe.conf", "r") as configFile:
for line in configFile.readlines():
if(line.strip() and line.strip()[0] != "#"):
try:
key, val = line.split(":")
except:
continue
conf[key.strip()] = val.strip()
return(conf)
def start(self):
self.choice = ""
self.displayHelp()
while(self.choice.lower() != "q"):
self.choice = raw_input("->")
self.parseInput(self.choice)
def displayHelp(self, choiceList=None):
print("Check the README.md file for command references")
print("Options as follows:")
print("h/H : Display this message")
print("l/L : List notes")
print("t/T : List tags, and the number of notes each contains")
print("s/S : Search notes")
print("f/F : Filter notes")
print("a/A : Add a note")
# print("i/I;title;/path/to/file;[tags] : import a note from file")
print("arch : Move a note into or out of archive")
print("d/D : Delete a note forever")
print("e/E : Edit a note that already exists - save an empty file to cancel")
print("g/G : View a specific note")
print("c/C : Clear screen")
print("q/Q : Quit Scrybe")
def parseInput(self, choiceString):
if(not choiceString):#user accidentally pressed enter
return
choiceList = []
for choice in choiceString.split(";"):
choiceList.append(choice.strip())
choiceList[0] = choiceList[0].lower()
choiceFuncs = {"h":self.displayHelp,
"l":self.listNotes,
"s":self.searchNotes,
"a":self.addNote,
"i":self.importNote,
"arch":self.archiveHandler,
"d":self.deleteNote,
"e":self.editNote,
"g":self.getNote,
"c":self.clear,
"f":self.filter,
"t":self.getTags,
"q":self.quit}
if(choiceList[0] not in choiceFuncs.keys()):
print("Sorry, " + choiceList[0] + " isn't a valid command (enter 'h' for help)")
return
choiceFuncs[choiceList[0]](choiceList)
def listNotes(self, choiceList):#list all notes in db
modeSwitchDict = {"c":"current", "a":"archived", "b":"all"}
if(len(choiceList) < 2):
choiceList.append("c")
if(choiceList[1] not in modeSwitchDict.keys()):
print("Sorry, " + choiceList[1] + " isn't a valid option for list")
return
requestMode = modeSwitchDict[choiceList[1]]
notesList = self.conn.getNotes(mode=requestMode)
printString = ""
if(not notesList):
printString = "No notes found"
else:
for note in notesList:
printString += self.oneLineStringGen(note)
print(printString.strip())
def oneLineStringGen(self, note, maxChars=60):#one line repr of passed note
noteId = note.id
title = note.title
body = note.body
createTimeString = time.strftime("%d/%m/%Y %H:%M", time.localtime(note.createTime))
archived = ["Current", "Achived"][note.archived]
appendString = " | " + createTimeString + " | " + archived + "\n"
printString = ""
printString += str(noteId) + " | "
printString += title + " | "
printString += body
printString = printString.replace("\n", " ")
if(len(printString + appendString) > maxChars):
appendString = "..." + appendString
maxLengthBeforeAppend = maxChars - len(appendString)
printString = printString[0:maxLengthBeforeAppend]
printString += appendString
return(printString)
def searchNotes(self, choiceList):#weighted search of title, body and tags
if(len(choiceList) < 2):
print("You need to specify a search string")
return
searchTerm = choiceList[1].lower()
if(len(choiceList) < 3):
choiceList.append("c")
if(choiceList[2] not in ["a","b","c"]):
print("Sorry, " + choiceList[2] + " isn't a valid search option")
return
selectorMode = {"c":"current", "a":"archived", "b":"all"}[choiceList[2]]
matchingNotes = []
for note in self.conn.getNotes(mode=selectorMode):
matchValue = 0
if(searchTerm in note.title.lower()):
matchValue += 2
matchValue += note.body.lower().count(searchTerm)
matchValue += note.tags.count(searchTerm) * 3
if(time.time() - note.createTime < 7*24*60*60):
matchValue += 1
if(matchValue > 0):
matchingNotes.append((note, matchValue))
matchingNotes.sort(key=lambda x: x[1], reverse=True)
printString = ""
for noteTuple in matchingNotes:
printString += self.oneLineStringGen(noteTuple[0])
if(printString):
print(printString.strip())
else:
print("Nothing found matching those search terms, sorry")
def addNote(self, choiceList):#adds a note to the db
if(len(choiceList) > 2 and choiceList[1]):
title = choiceList[1]
else:
print("Notes require a title")
return
tags = ""
if(len(choiceList) > 2):
tags = choiceList[2]
if([char for char in ["(", ")", "[", "]"] if char in tags]):
print("Brackets (square and rounded) aren't needed for tag")
print("lists, and will be included as part of the tag they")
print("are beside, e.g [tag1, tag2] becomes ['[tag1', 'tag2]']")
print("in the notes tag list. If this wasn't intended, enter")
print("'c' to cancel, anything else will add a note with the")
print("tag list as it currently is")
confirm = raw_input("->")
if(confirm.lower() == "c"):
print("Note addition cancelled")
return
os.system(self.conf["editor"] + " .scrybe.tmp")
with open(".scrybe.tmp", "r") as tmpFile:
body = tmpFile.read().strip()
os.remove(".scrybe.tmp")
if(not body):
print("Note addition cancelled due to empty body")
self.conn.addNote(title, body, tags)
print("Note added")
def fullStringGen(self, note):#generates a string repr of an entire note
noteId = note.id
title = note.title
body = note.body
tagString = "Tags: | "
for tag in note.tags:
tagString += tag + " | "
createTimeString = time.strftime("%d/%m/%Y %H:%M", time.localtime(note.createTime))
archived = ["Current", "Archived"][note.archived]
printString = ""
printString += title + "\n"
printString += str(noteId) + " | " + createTimeString + " | " + archived + "\n"
printString += tagString + "\n"
printString += body
return(printString.strip())
def archiveHandler(self, choiceList):#TODO - improve functionality
if(len(choiceList) < 2):
print("You need to specify a note to archive")
return
try:
noteId = int(choiceList[1])
except:
print("Sorry, note ids need to be an integer")
return
archive = 1
if(len(choiceList) > 2):
if(choiceList[2].lower() not in ["in", "out"]):
print("Sorry, " + choiceList + " isn't an option for archive")
return
archive = {"out":0, "in":1}[choiceList[2]]
self.conn.archiveNote(noteId, archive)
print("Note archived")
def deleteNote(self, choiceList):
if(len(choiceList) < 2):
print("You must specify a note to be deleted")
return
try:
noteId = int(choiceList[1])
except:
print("Note index must be an integer")
return
verify = raw_input("Are you sure (y/n)?")
if(verify.strip().lower() == "y"):
self.conn.deleteNote(noteId)
print("Note deleted")
else:
print("Deletion cancelled")
def editNote(self, choiceList):
#boilerplate to check if the mandatory params are ther
if(len(choiceList) < 2):
print("You need to specify a note by id")
return
try:
noteId = int(choiceList[1])
except:
print("Note id must be an integer")
return
try:
note = self.conn.getNote(noteId)
except:
print("No note found of that id (did you delete it?)")
return
#end of boilerplate
newTitle = note.title
if(len(choiceList) > 2 and choiceList[2]):
newTitle = choiceList[2]
oldBody = note.body
newTags = ",".join(note.tags)
if(len(choiceList) > 3 and choiceList[3]):
if(choiceList[3][0] == "+"):#append mode for tag modification
newTags += "," + choiceList[3]
else:#otherwise replace mode
newTags = choiceList[3]
with open(".scrybe.tmp", "w") as tmpFile:
tmpFile.write(oldBody)
os.system(self.conf["editor"] + " .scrybe.tmp")
with open(".scrybe.tmp", "r") as tmpFile:
newBody = tmpFile.read().strip()
if(not newBody):#if the user saved an empty file, cancel edit
print("Edit cancelled due to empty note body")
return
os.remove(".scrybe.tmp")
if(newBody == oldBody and newTags == note.tags):
print("Note edit cancelled")
return
self.conn.editNote(noteId, newTitle, newBody, newTags)
print("Note " + str(noteId) + " edited")
def getNote(self, choiceList):
if(len(choiceList) < 2):
print("You need to specify a note by it's id (number on the left)")
return
try:
requestId = int(choiceList[1])
except:
print("Note ids need to be integers")
return
try:
note = self.conn.getNote(requestId)
except IndexError:
print("Sorry, there's no note with that id (did you delete it?)")
return
print(self.fullStringGen(note))
def quit(self, choiceList):
self.choice = "q"
def clear(self, choiceList):
os.system("clear")
def importNote(self, choiceList):#NOTE - currently hidden from the user
if(len(choiceList) < 3):
print("You must specify title and path when importing a file")
return
title = choiceList[1]
path = choiceList[2]
tags = ""
if(len(choiceList) > 3):
tags = choiceList[3]
try:
with open(path, "r") as noteFile:
body = noteFile.read()
except:
print("Sorry, failed to read file. Did you get the path right?")
return
self.conn.addNote(title, body, tags)
print("Note imported")
def filter(self, choiceList):#function to work out filter mode; date or tag
if(len(choiceList) < 4):
self.tagFilter( choiceList)
elif(choiceList[3].lower() not in ["t", "d"]):
print("Sorry, " + choiceList[3] + " isn't a valid filter mode")
else:
filterMatch = {"t":self.tagFilter, "d":self.dateFilter}
filterMatch[choiceList[3].lower()](choiceList)
def tagFilter(self, choiceList):#current default filter type
if(len(choiceList) < 2 or not choiceList[1]):
print("You need to supply at least one tag to filter by")
return
modeSelector = {"a":"archived", "b":"all", "c":"current"}
if(len(choiceList) > 2 and choiceList[2]):#choose note get mode
if(choiceList[2] not in modeSelector.keys()):
print("Sorry, " + choiceList[2] + " isn't a valid option")
return
mode = modeSelector[choiceList[2]]
else:
mode = modeSelector["c"]
tagList = choiceList[1].split(",")
tagList = map(lambda x: x.strip(), tagList)
notes = self.conn.getNotes(mode)
printString = ""
for note in notes:
matchingTags = [tag for tag in tagList if tag in note.tags]
if(len(matchingTags) == len(tagList)):
printString += (self.oneLineStringGen(note))
if(not printString):
printString = "Sorry, nothing matching that filter found"
print(printString.strip())
def getTags(self, choiceList):#NOTE - should this have archive selector?
notes = self.conn.getNotes()
tagPairs = []
for note in notes:
for tag in note.tags:
tagList = [tagPair[0] for tagPair in tagPairs]
if(tag in tagList):
tagPairs[tagList.index(tag)][1] += 1
else:
tagPairs.append([tag, 1])
if(not tagPairs):
print("You haven't tagged anything")
return
for tagPair in tagPairs:
print(" | " + tagPair[0] + " : " + str(tagPair[1]) + " | ")
def dateFilter(self, choiceList):
#TODO -- See if you can clean this up
choiceList[1] = choiceList[1].lower()
lowPass = time.time()
highPass = 0
keywordMap = {"day":60*60*24,
"week":60*60*24*7,
"month":60*60*24*7*31,
"quarter":60*60*24*7*31*3,
"year":60*60*24*365}#hazy second-to-keyword mapping
singleDate = "\d\d/\d\d\/\d\d\d\d"
singleDateRe = re.compile("^"+singleDate+"$")
doubleDateRe = re.compile("^"+singleDate + ":" + singleDate+"$")
if(choiceList[1] in keywordMap.keys()):
lowPass -= keywordMap[choiceList[1]]
elif(singleDateRe.match(choiceList[1])):
lowPass = time.mktime(time.strptime(choiceList[1], "%d/%m/%Y"))
elif(doubleDateRe.match(choiceList[1])):
dates = choiceList[1].split(":")
lowPass = time.mktime(time.strptime(dates[0], "%d/%m/%Y"))
highPass = time.mktime(time.strptime(dates[1], "%d/%m/%Y"))
else:
print("Sorry, that date format isn't valid")
return
if(not highPass):
highPass = time.time()
modeSelector = {"a":"archived", "b":"all", "c":"current"}
mode = "current"
if(choiceList[2]):
if(choiceList[2] in modeSelector.keys()):
mode = modeSelector[choiceList[2]]
else:
print("Sorry, " + choiceList[2] + " isn't a valid option")
return
notes = self.conn.getNotes(mode)
if(not notes):
print("You don't have any notes to filter")
matchingNotes = []
for note in notes:
if(note.createTime > lowPass and note.createTime < highPass):
matchingNotes.append(note)
matchingNotes.sort(key = lambda x: x.createTime, reverse=True)
printString = ""
for note in matchingNotes:
printString += self.oneLineStringGen(note)
print(printString.strip())