2 # -*- coding: utf-8 -*-
3 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 # Run the script for details of the licence
5 # or refer to the notice section later in the file.
6 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
8 # get word list from wiktionary @Computer :pImport :created2008-11-23
9 # get word list from wordnet db @Computer :pImport :created2008-11-23
10 # update etymology javascript + form @Computer :pWebsite :created2008-11-22
11 # add etymology in a new language form @Computer :pWebsite :created2008-11-22
12 # cleanup webfaction @Internet @Computer :pDeployment :created2008-11-22
13 # group translations based on language @Computer :pWebsite :created2008-11-22
14 # show shorts with language @Computer :pWebsite :created2008-11-22
15 # show shorts in word complete @Computer :pWebsite :created2008-11-22
16 # on multi word, there are extra "edit" links @Computer :pWebsite :created2008-11-23
17 # get translations from wiktionary @Computer :pImport :created2008-11-23
18 # get meaning from wordnet @Computer :pImport :created2008-11-23
19 # i18n tokens in all templates/views/models/js @Computer :pI18N :created2008-11-23
20 # create translation files @Computer :pI18N :created2008-11-23
21 # enable i18n in django to select translation based on browser settings @Computer :pI18N :created2008-11-23
22 # create language list from wiktionary @Computer :pImport :created2008-11-23
23 # edit short @Computer :pWebsite :created2008-11-23
24 # add short in new language @Computer :pWebsite :created2008-11-23
25 # multi word ui issue @Computer :pWebsite :created2008-11-23
26 # multi word view # based collapse @Computer :pWebsite :created2008-11-23
27 # javascript to follow 18n preference @Computer :pI18N :created2008-11-23
28 # add language selector @Computer :pI18N :created2008-11-23
29 # push to wiktionary @Computer :pImport :created2008-11-23
30 # remove git checkin's containing local_settings.py @Computer @Internet :pDeployment :created2008-11-25
31 # push entire git repo to repo.or.cz @Internet @Computer :pDeployment :created2008-11-25
32 # check out http://www.thefreedictionary.com/sources.htm @Computer @Internet :pImport :created2008-11-25
39 cfgEditorPosix
= "nano,pico,vim,emacs"
40 cfgShortcuts
= ['', '', '', '', '', '', '', '', '', '']
41 cfgAbbreviations
= {'@C': '@Computer', '@A': '@Anywhere', '@Pw': '@Password', '@D': '@Desk', '@E': '@Errands', '@H': '@Home', '@I': '@Internet', '@N': '@Next', '@O': '@Other', '@L': '@Lunch', '@M': '@Meeting', '@S': '@Someday/Maybe', '@P': '@Phone', '@W': '@Work', '@W4': '@Waiting_For'}
42 cfgPAbbreviations
= {':Pi': ':pImport', ':P1': ':pI18N', ':Pw': ':pWebsite'}
47 from datetime
import date
48 from datetime
import timedelta
55 from threading
import Timer
70 "ikog.py v 1.88 2007-11-23",
71 "Copyright (C) 2006-2007 S. J. Butler",
72 "Visit http://www.henspace.co.uk for more information.",
73 "This program is free software; you can redistribute it and/or modify",
74 "it under the terms of the GNU General Public Licence as published by",
75 "the Free Software Foundation; either version 2 of the License, or",
76 "(at your option) any later version.",
78 "This program is distributed in the hope that it will be useful,",
79 "but WITHOUT ANY WARRANTY; without even the implied warranty of",
80 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
81 "GNU General Public License for more details. The license is available",
82 "from http://www.gnu.org/licenses/gpl.txt"
87 " (_) | | __ ___ __ _",
88 " | | | |/ / / _ \ / _` |",
89 " | | | < | (_) | | (_| |",
90 " |_| |_|\_\ \___/ \__, |",
92 " (_) | |_ | | __ ___ ___ _ __ ___ ___ _ __",
93 " | | | __| | |/ / / _ \ / _ \ | '_ \ / __| / _ \ | '_ \ ",
94 " | | | |_ | < | __/ | __/ | |_) | \__ \ | (_) | | | | |",
95 " |_| \__| |_|\_\ \___| \___| | .__/ |___/ \___/ |_| |_|",
97 " __ _ _ __ ___ __ __ (_) _ __ __ _ ",
98 " / _` | | '__| / _ \ \ \ /\ / / | | | '_ \ / _` |",
99 " | (_| | | | | (_) | \ V V / | | | | | | | (_| | _",
100 " \__, | |_| \___/ \_/\_/ |_| |_| |_| \__, | (_)",
109 ruler
= "~".ljust(gMaxLen
- 1, "~")
110 divider
= "_".ljust(gMaxLen
- 1, "_")
112 print "Error found. Probably wrong version of Python"
118 def safeRawInput(prompt
):
120 entry
= raw_input(prompt
)
126 ### global compare function
127 def compareTodo(a
, b
):
128 return cmp(a
.getEffectivePriority(), b
.getEffectivePriority())
131 print gColor
.code("error") + "ERROR: " + msg
+ gColor
.code("normal")
134 def clearScreen(useSys
= False):
136 if os
.name
== "posix":
138 elif os
.name
in ("dos", "ce", "nt"):
144 ### XTEA algorithm public domain
149 def crypt(self
, key
,data
,iv
='\00\00\00\00\00\00\00\00',n
=32):
150 def keygen(key
,iv
,n
):
152 iv
= self
.xtea_encrypt(key
,iv
,n
)
155 xor
= [ chr(x^y
) for (x
,y
) in zip(map(ord,data
),keygen(key
,iv
,n
)) ]
158 def xtea_encrypt(self
, key
,block
,n
=32):
159 v0
,v1
= struct
.unpack("!2L",block
)
160 k
= struct
.unpack("!4L",key
)
161 sum,delta
,mask
= 0L,0x9e3779b9L
,0xffffffffL
162 for round in range(n
):
163 v0
= (v0
+ (((v1
<<4 ^ v1
>>5) + v1
) ^
(sum + k
[sum & 3]))) & mask
164 sum = (sum + delta
) & mask
165 v1
= (v1
+ (((v0
<<4 ^ v0
>>5) + v0
) ^
(sum + k
[sum>>11 & 3]))) & mask
166 return struct
.pack("!2L",v0
,v1
)
169 def __init__(self
, width
):
174 def addLine(self
, pos
):
176 self
.nLines
= self
.nLines
+ 1
181 def intelliLen(self
, text
):
182 return len(gColor
.stripCodes(text
))
184 def wrap(self
, text
):
186 formatted
= text
.replace("<br>", "\n").replace("<BR>", "\n")
187 lines
= formatted
.splitlines()
190 for thisline
in lines
:
192 words
= thisline
.split()
197 wlen
= self
.intelliLen(w
) + 1
198 if (self
.pos
+ wlen
) == self
.width
:
201 elif (self
.pos
+ wlen
) < self
.width
:
207 self
.pos
= self
.pos
+ wlen
+ 1
214 ### Color code class for handling color text output
218 codes
= [{"normal":"\x1b[0;37;40m",
219 "title":"\x1b[1;32;40m",
220 "heading":"\x1b[1;35;40m",
221 "bold":"\x1b[1;35;40m",
222 "important":"\x1b[1;31;40m",
223 "error":"\x1b[1;31;40m",
224 "reverse":"\x1b[0;7m",
225 "row0":"\x1b[0;35;40m",
226 "row1":"\x1b[0;36;40m"},
227 {"normal":"\x1b[0;37m",
228 "title":"\x1b[1;32m",
229 "heading":"\x1b[1;35m",
231 "important":"\x1b[1;31m",
232 "error":"\x1b[1;31m",
233 "reverse":"\x1b[0;7m",
235 "row1":"\x1b[0;36m"}]
237 def __init__(self
, set):
238 self
.codeSet
= self
.NONE
241 def stripCodes(self
, text
):
242 # strip out the ansi codes
243 ex
= re
.compile("\x1b\[[0-9;]*m")
244 return ex
.sub("", text
)
246 def setCodeSet(self
, set):
249 self
.codeSet
= self
.NONE
250 elif set < len(self
.codes
):
252 return (old
!= self
.codeSet
)
254 def isValidSet(self
, myset
):
255 if myset
< len(self
.codes
):
260 def colorSupported(self
):
261 return (os
.name
== "posix" or os
.name
== "mac")
263 def usingColor(self
):
264 return (self
.codeSet
<> self
.NONE
and self
.colorSupported())
266 def code(self
, type):
267 if self
.codeSet
== self
.NONE
or not self
.colorSupported():
270 return self
.codes
[self
.codeSet
][type]
272 def printCode(self
, type):
273 if self
.codeSet
!= self
.NONE
:
274 print self
.code(type),
276 def getCodeSet(self
):
279 ### Viewer class for paging through multiple lines
281 def __init__(self
, maxlines
):
282 self
.maxlines
= maxlines
284 def show(self
, list, pause
):
287 if count
>= self
.maxlines
or line
== pause
:
288 io
= safeRawInput("--- Press enter for more. Enter s to skip ---").strip()
290 if len(io
) > 0 and io
.upper()[0] == "S":
297 ### Handler for encryption
299 TYPE_OBSCURED
= "xtea_"
305 self
.encryptionType
= self
.TYPE_OBSCURED
307 def setType(self
, codeType
):
308 if codeType
== self
.TYPE_AES
and supportAes
== False:
309 self
.encryptionType
= self
.TYPE_OBSCURED
311 self
.encryptionType
= codeType
312 return self
.encryptionType
314 def setKey(self
, key
):
320 def enterKey(self
, prompt1
, prompt2
):
323 input1
= getpass
.getpass(prompt1
+ " >>>")
325 input2
= getpass
.getpass(prompt2
+ " >>>")
327 print "You must enter the same password. Start again"
335 def complexKey(self
):
336 return md5(self
.key
).digest()
338 def getSecurityClass(self
, encrypted
):
339 if encrypted
.startswith(self
.TYPE_OBSCURED
):
340 return "private xtea"
341 if encrypted
.startswith(self
.TYPE_AES
):
345 def obscure(self
, plainText
):
346 key
= self
.complexKey()
347 obscured
= Xtea().crypt(key
, plainText
, self
.SALT_64
)
348 return self
.TYPE_OBSCURED
+ obscured
.encode('hex_codec')
350 def unobscure(self
, obscured
):
352 data
= obscured
[len(self
.TYPE_OBSCURED
):]
353 data
= data
.decode('hex_codec')
354 key
= self
.complexKey()
355 plain
= Xtea().crypt(key
, data
, self
.SALT_64
)
358 def encryptAes(self
, plainText
):
359 if len(self
.key
) < 16:
360 key
= self
.complexKey()
363 obscured
= pyRijndael
.EncryptData(key
, plainText
)
364 return self
.TYPE_AES
+ obscured
.encode('hex_codec')
366 def decryptAes(self
, encrypted
):
368 data
= encrypted
[len(self
.TYPE_AES
):]
369 data
= data
.decode('hex_codec')
370 if len(self
.key
) < 16:
371 key
= self
.complexKey()
374 plain
= pyRijndael
.DecryptData(key
, data
)
377 def enterKeyAndEncrypt(self
, plainText
):
378 self
.enterKey("Enter the master password.", "Re-enter the master password")
379 return self
.encrypt(plainText
)
381 def encrypt(self
, plainText
):
382 if self
.encryptionType
== self
.TYPE_AES
:
383 return self
.encryptAes(plainText
)
385 return self
.obscure(plainText
)
387 def enterKeyAndDecrypt(self
, encryptedText
):
388 self
.enterKey("Enter your master password", "")
389 return self
.decrypt(encryptedText
)
391 def decrypt(self
, encryptedText
):
392 if encryptedText
.startswith(self
.TYPE_AES
):
394 return "You do not have the pyRinjdael module so the text cannot be decrypted."
396 return self
.decryptAes(encryptedText
)
398 return self
.unobscure(encryptedText
)
400 ### Handler for user input
402 def __init__(self
, prompt
):
405 def read(self
, entry
= ""):
407 entry
= safeRawInput(self
.prompt
)
408 entry
= entry
.strip()
413 if entry
.find(magicTag
) == 0:
414 printError("You cannot begin lines with the sequence " + magicTag
)
417 elif entry
.find(TodoItem
.ENCRYPTION_MARKER
) >= 0:
418 printError ("You cannot use the special sequence " + TodoItem
.ENCRYPTION_MARKER
)
430 return (command
, line
)
432 class EditorLauncher
:
433 WARNING_TEXT
= "# Do not enter secret or private information!"
437 def edit(self
, text
):
440 if os
.name
== "posix":
442 elif os
.name
== "nt":
446 printError("Sorry, but external editing not supported on " + os
.name
.upper())
449 fname
= self
.makeFile(text
, terminator
)
451 printError("Unable to create temporary file.")
453 success
= self
.run(ed
, fname
)
455 (success
, text
) = self
.readFile(fname
)
456 if text
== self
.orgText
:
457 print("No changes made.");
459 self
.scrubFile(fname
)
465 def scrubFile(self
, fname
):
469 printError("Failed to remove file " + fname
+ ". If you entered any private data you should delete this file yourself.")
471 def readFile(self
, fname
):
474 fh
= open(fname
, "rt")
479 thisLine
= self
.safeString(line
)
480 if thisLine
!= self
.WARNING_TEXT
:
483 text
= text
+ thisLine
489 printError("Error reading the edited text. " + str(e
))
490 return (success
, text
)
492 def safeString(self
, text
):
493 return text
.replace("\r","").replace("\n","")
495 def makeFile(self
, text
, terminator
):
497 (fh
, fname
) = tempfile
.mkstemp(".tmpikog","ikog")
498 fout
= os
.fdopen(fh
,"wt")
499 text
= text
.replace("<BR>", "<br>")
501 lines
= text
.split("<br>")
502 fout
.write(self
.WARNING_TEXT
+ terminator
)
503 for thisline
in lines
:
504 fout
.write(self
.safeString(thisline
) + terminator
)
508 def run(self
, program
, file):
509 progs
= program
.split(",")
511 success
= self
.runProgram(prog
.strip(), file)
516 def runProgram(self
, program
, file):
518 if os
.name
== "posix":
521 os
.spawnlp(os
.P_WAIT
, program
, progarg
, file)
527 elif os
.name
== "nt":
528 if file.find(" ") >= 0:
529 file = "\"" + file + "\""
530 for path
in os
.environ
["PATH"].split(os
.pathsep
):
532 prog
= os
.path
.join(path
, program
)
533 if prog
.find(" ") >= 0:
534 progarg
= "\"" + prog
+ "\""
537 os
.spawnl(os
.P_WAIT
, prog
, progarg
, file)
548 ### The main todo list
550 quickCard
= ["Quick reference card:",
551 "? ADD/A/+ text FILTER/FI [filter]",
552 "HELP/H IMMEDIATE/I/++ text TOP/T [N]",
553 "COLOR/COLOUR/C [N] KILL/K/X/- N NEXT/N",
554 "MONOCHROME/MONO CLEAR PREV/P",
555 "EXPORT REP/R N [text] GO/G N",
556 "IMPORT file MOD/M N [text] LIST/L [filter]",
557 "REVIEW/REV ON/OFF EXTEND/E N [text] LIST>/L> [filter]",
559 "V1 SUB/SU N /s1/s2/ :D",
561 "SAVE/S DOWN/D N @>",
562 "AUTOSAVE/AS ON|OFF UP/U N :D>",
563 "VER/VERSION NOTE/NOTES text :P>",
564 "CLEARSCREEN/CLS O/OPEN file SHOW N",
565 "SYS ON|OFF NEW file SETEDxx editor",
566 "!CMD command 2 ABBREV/AB @x @full",
567 "ABBREV/AB ? PAB ? PAB :px :pfull",
568 "SHORTCUT/SC N cmd SHORTCUT/SC ? =N",
569 "ARCHIVE/DONE N [text]",
575 "The program is designed to help manage tasks using techniques",
576 "such as Getting Things Done by David Allen. Check out",
577 "http://www.henspace.co.uk for more information and detailed help.",
578 "To use the program, simply enter the task at the prompt.",
579 "All of the commands are displayed in the next section.",
583 "Commands that have more than one method of entry are shown separated by /",
584 "e.g HELP/H means that you can enter either HELP or an H.",
585 "All commands can be entered in upper or lower case.",
586 "Items shown in square brackets are optional.",
587 "Items shown separated by the | symbol are alternatives. e.g ON|OFF means",
588 "you should type either ON or OFF.",
589 "Note that some commands refer to adding tasks to the top or bottom of the",
590 "list. However the task's position in the list is also determined by its.",
591 "priority. So, for example, adding a task to the top will still not allow",
592 "it to precede tasks that have been assigned a higher priority number. ",
596 "? : displays a quick reference card",
597 "HELP/H : displays this help.",
598 "VERSION/VER : display the version.",
599 "WEB : Go to the website for more information",
600 "CLEARSCREEN/CLS : Clear the screen",
601 "COLOR/COLOUR/C [N] : Use colour display (not Windows) N=1 for no background",
602 "MONOCHROME/MONO : Use monochrome display",
603 "EXPORT : Export the tasks only to filename.tasks.txt",
604 "IMPORT file : Import tasks from the file",
605 "REVIEW/REV ON|OFF : If on, hitting enter moves to the next task",
606 " : If off, enter re-displays the current task",
607 "V0 : Same as REVIEW OFF",
608 "V1 : Same as REVIEW ON",
609 "SAVE/S : Save the tasks",
610 "O/OPEN file : Open a new data file.",
611 "NEW file : Create a new data file.",
612 "AUTOSAVE/AS ON|OFF : Switch autosave on or off",
613 "SYS ON|OFF : Allow the program to use system calls.",
614 "!CMD command : Run a system command.",
615 "2 : Start a two minute timer (for GTD)",
616 "QUIT/Q : quit the program",
618 "TASK ENTRY AND EDITING COMMANDS",
619 "-------------------------------",
620 "For the editing commands that require a task number, you can",
621 "replace N by '^' or 'this' to refer to the current task.",
622 "ADD/A/+ the task : add a task to the bottom of the list.",
623 " : Entering any line that does not begin with",
624 " : a valid command and which is greater than 10",
625 " : characters long is also assumed to be an addition.",
626 "EDIT/ED [N] : Create task, or edit task N, using external editor.",
627 "SUB/SU N /s1/s2/ : Replace text s1 with s2 in task N. Use \/ if you",
628 " : need to include the / character.",
629 "NOTE/NOTES text : shorthand for ADD #0 @Notes text",
630 "IMMEDIATE/I/++ : add a task to the top of the list to do today.",
631 "REP/R N [text] : replace task N",
632 "MOD/M N [text] : modify task N.",
633 "EXTEND/E N [text] : add more text to task N",
634 "FIRST/F N : move task N to the top.",
635 "DOWN/D/ N : move task N down the queue",
636 "UP/U/ N : move task N up the queue",
638 "TASK REMOVAL COMMANDS",
639 "---------------------",
640 "KILL/K/X/- N : kill (delete) task N. You must define N",
641 "DONE N [text] : Remove task N and move to an archive file",
642 "ARCHIVE N [text] : Same as DONE",
643 "CLEAR : Remove all tasks",
647 "SHOW N : display encrypted text for task N",
648 "FILTER/FI [filter] : set a filter. Applies to all displays",
649 " : See list for details of the filter",
650 " : Setting the filter to nothing clears it.",
651 "TOP/T [N] : Go to top, list N tasks, and display the top task",
652 "NEXT/N : display the next task. Same as just hitting enter",
653 "PREV/P : display previous task",
654 "GO/G N : display task N",
655 "LIST/L [filter] : list tasks. Filter = context, project, priority, date",
656 " : or word. Contexts begin with @ and projects with :p",
657 " : Dates begin with :d, anything else is a search word.",
658 " : Precede term with - to exclude e.g. -@Computer",
659 " : e.g LIST @computer or LIST #5",
660 "@ : sorted list by Context.",
661 ":D : sorted list by Dates",
662 ":P : sorted list by Projects",
663 "LIST>/L> [filter] : standard list sent to an HTML report",
664 "@> : sorted list by Context sent to an HTML report",
665 ":D> : sorted list by Dates sent to an HTML report",
666 ":P> : sorted list by Projects sent to an HTML report",
667 " : The HTML reports are sent to todoFilename.html",
671 "The SETEDxxx commands allow you to use an external editor.",
672 "Note the editor you pick should be a simple text editor. If you pick",
673 "something that doesn't work, try the defaults again.",
674 "Because some systems may have different editors installed, you can set",
675 "more than one by separating the editors usng commas. The program will",
676 "use the first one it finds.",
677 "For Windows the default is edit, which works quite well in the terminal",
678 "but you could change it to notepad.",
679 "For Linux, the default is nano,pico,vim,emacs.",
680 "To use external editors you must switch on system calls using the SYS ON",
682 "SETEDNT command : Set the external editor for Windows (NT).",
683 "SETEDPOSIX command : Set the editor for posix systems.",
684 " : e.g. SETEDNT edit",
685 " : SETEDPOSIX nano,vim",
686 "SHORTCUT/SC ? : list shortcuts",
687 "SHORTCUT/SC N cmd : Set shortcut N to command cmd",
688 "=N : Run shortcut N",
690 "ABBREV/AB @x @full : Create new abbreviation. @x expands to @full",
691 "ABBREV/AB ? : List context abbreviations.",
692 "PAB :px :pfull : Project abbreviation. :px expands to :pfull",
693 "PAB ? : List project abbreviations.",
697 "When you enter a task, you can embed any number of contexts in the task.",
698 "You can also embed a project description by preceding it with :p",
699 "You can assign a priority by preceding a number by #. e.g. #9.",
700 "If you don't enter a number, a default of 5 is used. The higher the ",
701 "number the more important it is. Priorities range from 1 to 10.",
702 "Only the first # is used for the priority so you can use # as",
703 "a normal character as long as you precede it with a priority number.",
704 "You can define a date when the task must be done by preceding the date",
705 "with :d, i.e :dYYYY/MM/DD or :dMM/DD or :dDD. If you omit the year/month",
706 "they default to the current date. Adding a date automatically creates an",
707 "@Date context for the task.",
708 "So, for example, to add a new task to e-mail Joe, we could enter:",
709 "+ e-mail joe @computer",
710 "or to add a task to the decorating project, we could enter:",
711 "+ buy wallpaper :pdecorating",
712 "to enter a task with an importance of 9 we could enter:",
713 "+ book that holiday #9 @Internet",
715 "MODIFYING AND EXTENDING TASKS",
716 "-----------------------------",
717 "The modify command allows you to change part of an existing task.",
718 "So for example, imagine you have a task:",
719 "[05] Buy some food #9 @Internet Projects:Shopping",
720 "Enter the command M 5 and then type:",
722 "Because the only element we have entered is a new context, only",
723 "that part is modified, so we get.",
724 "[05] Buy some food #9 @Computer Projects:Shopping",
725 "Likewise, had we entered:",
726 "Buy some tea :pEating",
728 "[05] Buy some tea #9 @Internet Projects:Eating",
729 "The extend command is similar but it appends the entry. So had",
730 "we used the command E 5 instead of M 5 the result would have been",
731 "[05] Buy some food ... Buy some tea #9 @Internet Projects:Eating",
735 "Any word preceded by @ will be used as a context. Contexts are like",
736 "sub-categories or sub-lists. There are a number of pre-defined",
737 "abbreviations that you can use as well. The recognised abbreviations",
739 "@A = @Anywhere (this is the default)",
751 "@S = @Someday/maybe",
757 "An @Date context is created if you embed a date in the task.",
758 "Dates are embedded using the :dDATE format.",
759 "Valid DATE formats are yyyy-mm-dd, mm-dd or dd",
760 "You can also use : or / as the separators. So, for example:",
761 ":d2006/12/22 or :d2006-11-7 or :d9/28 are all valid entries.",
763 "If you set a date, then until that date is reached, the task is given",
764 "an effective priority of 0. Once the date is reached, the task's",
765 "priority is increased by 11, moving it to the of the list.",
767 "A date entry of :d0 can be used to clear a date entry.",
768 "A date entry of :d+X can be used to create a date entry of today + X days.",
769 "So :d+1 is tomorrow and :d+0 is today.",
773 "If you want to encrypt text you can use the <private> or <secret> tags or",
774 "their abbreviations <p> and <s>.",
775 "These tags will result in all text following the tag to be encrypted.",
776 "Note that any special commands, @contexts for example, are treated as plain",
777 "text in the encrypted portion.",
778 "To display the text you will need to use the SHOW command.",
780 "The <private> tag uses the inbuilt XTEA algorithm. This is supposedly a",
781 "relatively secure method but probably not suitable for very sensitive data.",
783 "The <secret> tag can only be used if you have the pyRijndael.py module.",
784 "This uses a 256 bit Rinjdael cipher. The module can be downloaded from ",
785 "http://jclement.ca/software/pyrijndael/",
786 "You can install this in your Python path or just place it alongside your",
788 "Note you cannot use the extend command with encrypted text.",
791 "MARKING TASKS AS COMPLETE",
792 "-------------------------",
793 "The normal way to mark a task as complete is just to remove it using the",
794 "KILL command. If you want to keep track of tasks you have finished, you",
795 "can use the ARCHIVE or DONE command. This gives the task an @Archived",
796 "context, changes the date to today and then moves it from the current",
797 "file to a file with archive.dat appended. The archive file is a valid",
798 "ikog file so you can use the OPEN command to view it, edit it and run",
799 "reports in the normal way. So assuming your current script is ikog.py,",
800 "to archive the current task you could enter:",
802 "ARCHIVE ^ I have finished this",
804 "This would move the task to a file called ikog.py.archive.dat",
807 "USING EXTERNAL DATA",
808 "-------------------",
809 "Normally the tasks are embedded in the main program file so all you have",
810 "to carry around with you is the ikog.py file. The advantage is that you",
811 "only have one file to look after; the disadvantage is that every time you",
812 "save a task you have to save the program as well. If you want, you can",
813 "keep your tasks in a separate file.",
814 "To do this, use the EXPORT function to create a file ikog.py.tasks.txt",
815 "Use the CLEAR command to remove the tasks from your main ikog.py program.",
816 "Rename the exported file from ikog.py.tasks.txt to ikog.py.dat",
817 "Ikog will now use this file for storing your tasks.",
820 "PASSING TASKS VIA THE COMMAND LINE",
821 "----------------------------------",
822 "It is possible to add tasks via the command line. The general format of",
823 "the command line is:",
824 " ikog.py filename commands",
825 "The filename is the name of the data file containing your tasks. You",
826 "can use . to represent the default internal tasks.",
827 "Commands is a set of normal ikog commands separated by the / ",
828 "character. Note there must be a space either side of the /.",
829 "So to add a task and then exit the program we could just enter:",
830 " ikog.py . + here is my task / QUIT",
831 "Note that we added the quit command to exit ikog.",
832 "You must make sure that you do not use any commands that require user",
833 "input. Deleting tasks via the command line is more complicated as you",
834 "need to find the task automatically. If you do try to delete this way,",
835 "use the filter command to find some unique text and then delete it. eg.",
836 " ikog.py . FI my_unique_text / KILL THIS / QUIT",
837 "Use THIS instead of ^ as a caret has a special meaning in Windows.",
838 "If you do intend automating ikog from the command line, you should add",
839 "a unique reference to each task so you can find it later using FILTER. eg.",
840 "+ this is my task ref_1256",
848 def __init__(self
, todoFile
, externalDataFile
):
855 self
.sysCalls
= False
857 self
.globalFilterText
= ""
858 self
.globalFilters
= []
859 self
.localFilterText
= ""
860 self
.localFilters
= []
861 # split the file into the source code and the todo list
862 self
.filename
= todoFile
864 self
.filename
= os
.readlink(todoFile
)
866 pass # probably windows
867 externalDataFile
= self
.makeFilename(externalDataFile
)
868 self
.externalData
= self
.findDataSource(externalDataFile
)
869 if self
.externalData
:
870 self
.filename
= externalDataFile
872 self
.splitFile(self
.filename
, False)
873 self
.exactPriority
= False
876 def setPAbbreviation(self
, line
):
878 elements
= line
.split(" ", 1)
879 if not elements
[0].lower().startswith(":p"):
880 self
.showError("Project abbreviations must begin with :p")
881 elif len(elements
) > 1:
882 if not elements
[1].lower().startswith(":p"):
883 abb
= ":p" + elements
[1].title()
885 abb
= ":p" +elements
[1][2:].title()
886 globalPAbbr
.addAbbreviation(elements
[0], abb
)
889 if not globalPAbbr
.removeAbbreviation(elements
[0]):
890 self
.showError("Could not find project abbreviation " + line
)
892 print "Project abbreviation ", line
, " removed."
896 def showPAbbreviations(self
):
897 print globalPAbbr
.toStringVerbose()
899 def setAbbreviation(self
, line
):
901 elements
= line
.split(" ", 1)
902 if not elements
[0].startswith("@"):
903 self
.showError("Abbreviations must begin with @")
904 elif len(elements
) > 1:
905 if not elements
[1].startswith("@"):
906 abb
= "@" + elements
[1].title()
908 abb
= "@" +elements
[1][1:].title()
909 globalAbbr
.addAbbreviation(elements
[0], abb
)
912 if not globalAbbr
.removeAbbreviation(elements
[0]):
913 self
.showError("Could not find abbreviation " + line
)
915 print "Abbreviation ", line
, " removed."
919 def showAbbreviations(self
):
920 print globalAbbr
.toStringVerbose()
922 def setShortcut(self
, line
, force
= False):
923 elements
= line
.split(" ", 1)
925 index
= int(elements
[0])
926 if len(elements
) > 1:
927 command
= elements
[1]
931 self
.showError("Did not understand the command. Format should be SHORTCUT N my command.")
934 if index
< 0 or index
> len(self
.shortcuts
):
935 self
.showError("The maximum number of shortcuts is " + str(len(self
.shortcuts
)) + ". Shortcuts ignored.")
938 if self
.shortcuts
[index
] != "" and not force
:
939 if safeRawInput("Do you want to change the current command '" + self
.shortcuts
[index
] + "'? Enter Yes to overwrite. >>>").upper() != "YES":
941 self
.shortcuts
[index
] = command
945 def showShortcuts(self
):
947 for s
in self
.shortcuts
:
952 print "=%1d %s" %(index
, msg
)
955 def setShortcuts(self
, settings
= []):
956 if len(settings
) > self
.MAX_SHORTCUTS
:
957 self
.showError("The maximum number of shortcuts is " + str(self
.MAX_SHORTCUTS
) + ". Shortcuts ignored.")
958 self
.shortcuts
= ["" for n
in range(self
.MAX_SHORTCUTS
)]
959 if len(settings
) > 0:
960 self
.shortcuts
[0:len(settings
)] = settings
962 def getShortcutIndex(self
, command
):
963 if len(command
) == 2 and command
[0:1].upper() == "=":
964 index
= ord(command
[1]) - ord("0")
965 if index
>= self
.MAX_SHORTCUTS
:
971 def getShortcut(self
, command
):
972 index
= self
.getShortcutIndex(command
)
974 return self
.shortcuts
[index
]
978 def safeSystemCall(self
, line
):
981 self
.showError("Nothing to do.")
982 elif words
[0].upper() == "RM" or words
[0].upper() == "RMDIR" or words
[0].upper() == "DEL":
983 self
.showError("Sorry, but deletion commands are not permitted.")
988 def processCfgLine(self
, line
):
989 params
= line
.split("=")
992 cmd
= params
[0].strip()
993 if cmd
== "cfgEditorNt":
995 cfgEditorNt
= params
[1].replace("\"", "").strip()
996 elif cmd
== "cfgEditorPosix":
997 global cfgEditorPosix
998 cfgEditorPosix
= params
[1].replace("\"", "").strip()
999 elif cmd
== "cfgShortcuts":
1000 elements
= params
[1].strip()[1:-1].split(",")
1003 self
.setShortcut(str(index
) + " " + e
.strip()[1:-1], True)
1005 elif cmd
== "cfgAutoSave":
1006 if params
[1].upper().strip() == "TRUE":
1010 self
.setAutoSave(as, False)
1011 elif cmd
== "cfgReviewMode":
1012 if params
[1].upper().strip() == "TRUE":
1013 self
.setReview("ON")
1015 self
.setReview("OFF")
1016 elif cmd
== "cfgSysCalls":
1017 if params
[1].upper().strip() == "TRUE":
1018 self
.setSysCalls("ON")
1020 self
.setSysCalls("OFF")
1021 elif cmd
== "cfgColor":
1022 gColor
.setCodeSet(int(params
[1].strip()))
1023 elif cmd
== "cfgAbbreviations":
1024 abbrs
= eval(params
[1].strip())
1025 globalAbbr
.setAbbreviations(abbrs
)
1026 elif cmd
== "cfgPAbbreviations":
1027 abbrs
= eval(params
[1].strip())
1028 globalPAbbr
.setAbbreviations(abbrs
)
1030 self
.showError("Unrecognised command " + cmd
)
1032 def makeFilename(self
, name
):
1033 (root
, ext
) = os
.path
.splitext(name
)
1034 if ext
.upper() != ".DAT":
1035 name
= name
+ ".dat"
1037 name
= os
.path
.expanduser(name
)
1038 except Exception, e
:
1039 self
.showError("Failed to expand path. " + str(e
))
1042 def findDataSource(self
, filename
):
1046 self
.splitFile(filename
, False)
1047 print "Using external data file ", filename
1050 print "No external data file ", filename
, ", so using internal tasks."
1053 def setSysCalls(self
, mode
):
1054 oldCalls
= self
.sysCalls
1055 mode
= mode
.strip().upper()
1057 self
.sysCalls
= True
1058 print "Using system calls for clear screen"
1060 self
.sysCalls
= False
1061 print "No system calls for clear screen"
1063 self
.showError("Could not understand the sys command. Use SYS ON or OFF.")
1064 return (self
.sysCalls
!= oldCalls
)
1066 def setAutoSave(self
, as, save
):
1068 if self
.autoSave
== False:
1069 self
.autoSave
= True
1072 elif self
.autoSave
== True:
1073 self
.autoSave
= False
1077 print "Autosave is on."
1079 print "Autosave is off."
1082 def showError(self
, msg
):
1085 def pause(self
, prompt
= "Press enter to continue."):
1086 if safeRawInput(prompt
).strip() != "":
1087 print "Entry ignored!"
1089 def setReview(self
, mode
):
1090 oldReview
= self
.review
1091 mode
= mode
.strip().upper()
1094 print "In review mode. Enter advances to the next task"
1097 print "Review mode off. Enter re-displays the current task"
1099 self
.showError("Could not understand the review command. Use REVIEW ON or OFF.")
1100 return (self
.review
!= oldReview
)
1102 def sortByPriority(self
):
1103 self
.todo
.sort(key
=TodoItem
.getEffectivePriority
, reverse
= True)
1106 def run(self
, commandList
):
1108 print "AES encryption not available."
1109 print("\nEnter HELP for instructions.")
1113 self
.sortByPriority()
1116 truncateTask
= False
1118 self
.checkCurrentTask()
1120 self
.moveToVisible()
1123 self
.printItemTruncated(self
.currentTask
, "Current: ")
1125 self
.printItemVerbose(self
.currentTask
)
1128 truncateTask
= False
1133 if len(commandList
) >= 1:
1134 enteredLine
= commandList
[0]
1135 commandList
= commandList
[1:]
1137 (rawcommand
, line
) = InputParser(prompt
).read(enteredLine
)
1139 command
= rawcommand
.upper()
1140 if self
.getShortcutIndex(command
) >= 0:
1141 sc
= self
.getShortcut(command
)
1143 (rawcommand
, line
) = InputParser("").read(self
.getShortcut(command
))
1148 print "Shortcut: ", rawcommand
, " ", line
1149 command
= rawcommand
.upper()
1153 elif command
== "PAB":
1154 if line
.strip() == "?":
1155 self
.showPAbbreviations()
1156 elif self
.setPAbbreviation(line
):
1158 elif command
== "ABBREV" or command
== "AB":
1159 if line
.strip() == "?":
1160 self
.showAbbreviations()
1161 elif self
.setAbbreviation(line
):
1163 elif command
== "SHORTCUT" or command
== "SC":
1164 if line
.strip() == "?":
1165 self
.showShortcuts()
1167 if self
.setShortcut(line
):
1169 printCurrent
= False
1170 elif command
== "2":
1171 enteredLine
= self
.runTimer(2)
1172 printCurrent
= False
1173 elif command
== "CLS" or command
== "CLEARSCREEN":
1174 clearScreen(self
.sysCalls
)
1175 elif command
== "SETEDNT":
1179 elif command
== "SETEDPOSIX":
1180 global cfgEditorPosix
1181 cfgEditorPosix
= line
1183 elif command
== "SYS":
1184 if self
.setSysCalls(line
):
1186 elif command
== "!CMD":
1188 self
.safeSystemCall(line
)
1190 self
.showError("System calls are not allowed. Use SYS ON to enable them.")
1191 elif command
== "SHOW" or command
== "SH":
1193 self
.pause("Press enter to clear screen and continue. ")
1194 clearScreen(self
.sysCalls
)
1195 elif command
== "VERSION" or command
== "VER":
1197 elif command
== "SAVE" or command
== "S":
1199 print "There's no need to save now. If the prompt shows >>> "
1200 print "then there is nothing to save. You only need to save if the prompt "
1204 elif command
== "NEW":
1205 filename
= self
.makeFilename(line
)
1206 if self
.createFile(filename
):
1211 printCurrent
= False
1212 elif command
== "OPEN" or command
== "O":
1213 filename
= self
.makeFilename(line
)
1218 printCurrent
= False
1219 elif command
== "AUTOSAVE" or command
== "AS":
1221 self
.showError("You must enter ON or OFF for the autosave command")
1223 self
.setAutoSave(line
.upper() == "ON", True)
1224 elif command
== "REVIEW" or command
== "REV":
1225 if self
.setReview(line
):
1227 elif command
== "V0":
1228 if self
.setReview("OFF"):
1230 elif command
== "V1":
1231 if self
.setReview("ON"):
1233 elif command
== "?":
1234 self
.printHelp(self
.quickCard
)
1235 elif command
== "HELP" or command
== "H":
1236 self
.printHelp(self
.help)
1237 elif command
== "QUIT" or command
== "Q":
1241 printCurrent
= False
1242 elif command
== "WEB":
1244 webbrowser
.open("http://www.henspace.co.uk")
1245 except Exception, e
:
1246 self
.showError("Unable to launch browser. " + str(e
))
1247 elif command
== "COLOR" or command
== "COLOUR" or command
== "C":
1252 if not gColor
.isValidSet(set):
1253 self
.showError("Invalid colour set ignored.")
1254 elif gColor
.setCodeSet(set):
1256 elif command
== "MONOCHROME" or command
== "MONO":
1257 if gColor
.setCodeSet(gColor
.NONE
):
1259 elif command
== "EXPORT":
1261 elif command
== "IMPORT":
1262 if self
.importTasks(line
):
1264 elif command
== "CLEAR" and line
== "":
1267 elif command
== "FILTER" or command
== "FI" or command
== "=":
1268 self
.setFilterArray(False, line
)
1269 elif command
== "NEXT" or command
== "N":
1271 elif command
== "PREV" or command
== "P":
1273 elif command
== "TOP" or command
== "T" or command
== "0":
1274 self
.currentTask
= 0
1276 self
.setFilterArray(True, "")
1277 self
.showLocalFilter()
1278 self
.printShortList(line
)
1280 elif command
== "GO" or command
== "G":
1282 elif command
== "IMMEDIATE" or command
== "I" or command
== "++":
1283 newItem
= self
.createItem(":d+0 " + line
)
1284 if newItem
.hasHiddenTask():
1285 clearScreen(self
.sysCalls
)
1286 if newItem
.hasError():
1287 print "Errors were found:"
1288 print newItem
.getError()
1289 print "The task was not added."
1290 printCurrent
= False
1292 self
.todo
.insert(0, newItem
)
1293 self
.currentTask
= 0
1294 self
.sortByPriority()
1296 elif command
== "KILL" or command
== "K" or command
== "-" or command
== "X":
1297 if self
.removeTask(line
):
1299 elif command
== "ARCHIVE" or command
== "DONE":
1300 if self
.archiveTask(line
):
1302 elif command
== "REP" or command
=="R":
1303 if self
.modifyTask(line
, TodoItem
.REPLACE
):
1304 self
.sortByPriority()
1307 printCurrent
= False
1308 elif command
== "SUB" or command
== "SU":
1309 if self
.substituteText(line
):
1310 self
.sortByPriority()
1312 elif command
== "EDIT" or command
== "ED":
1313 if not self
.sysCalls
:
1314 self
.showError("External editing needs to use system calls. Use SYS ON to enable them.")
1316 self
.addTaskExternal()
1317 elif self
.modifyTask(line
, TodoItem
.MODIFY
, externalEditor
= True):
1318 self
.sortByPriority()
1321 printCurrent
= False
1322 elif command
== "MOD" or command
== "M":
1323 if self
.modifyTask(line
, TodoItem
.MODIFY
):
1324 self
.sortByPriority()
1327 printCurrent
= False
1328 elif command
== "EXTEND" or command
== "E":
1329 if self
.modifyTask(line
, TodoItem
.APPEND
):
1330 self
.sortByPriority()
1333 printCurrent
= False
1334 elif command
== "FIRST" or command
== "F":
1335 if self
.moveTask(line
, self
.MOVE_TOP
):
1336 self
.sortByPriority()
1338 self
.currentTask
= 0
1339 elif command
== "DOWN" or command
== "D":
1340 if self
.moveTask(line
, self
.MOVE_DOWN
):
1341 self
.sortByPriority()
1343 elif command
== "UP" or command
== "U":
1344 if self
.moveTask(line
, self
.MOVE_UP
):
1345 self
.sortByPriority()
1347 elif command
== "LIST" or command
== "L":
1349 self
.setFilterArray(True, line
)
1350 self
.showLocalFilter()
1351 self
.printList(False, "", "")
1352 self
.clearFilterArray(True)
1355 elif command
== "LIST>" or command
== "L>":
1357 self
.setFilterArray(True, line
)
1358 self
.showLocalFilter()
1359 self
.printList(False, "", "")
1360 self
.clearFilterArray(True)
1362 elif command
== "@":
1365 elif command
== ":P":
1366 self
.listByProject()
1368 elif command
== ":D":
1371 elif command
== "@>":
1372 self
.startHtml("Report by Context")
1375 elif command
== ":P>":
1376 self
.startHtml("Report by Project")
1377 self
.listByProject()
1379 elif command
== ":D>":
1380 self
.startHtml("Report by Date")
1383 elif command
== "ADD" or command
== "A" or command
== "+":
1385 elif command
== "NOTE" or command
== "NOTES":
1386 self
.addTask("#0 @Notes " + line
)
1387 elif (len(command
) + len(line
)) > 10:
1388 self
.addTask(rawcommand
+ " " + line
)
1389 elif len(command
) > 0:
1390 self
.showError("Didn't understand. (Make sure you have a space after the command or your entry is longer than 10 characters)")
1391 printCurrent
= False
1395 self
.timerActive
= False
1397 print "\n\x07Timer\x07 complete.\x07\n\x07Press enter to continue.\x07"
1399 def runTimer(self
, delay
):
1400 self
.timerActive
= True
1401 t
= Timer(delay
* 60 , self
.timeout
)
1403 s
= raw_input(str(delay
) + " minute timer running.\nAny entry will cancel the timer:\n>>>")
1404 if self
.timerActive
:
1406 print "Timer cancelled."
1409 print "Input discarded as timer has finished."
1412 def addTaskExternal(self
):
1413 exEdit
= EditorLauncher()
1414 entry
= exEdit
.edit("")
1418 self
.showError("Nothing to add")
1420 def addTask(self
, line
):
1421 newItem
= self
.createItem(line
)
1422 if newItem
.hasError():
1423 print "Errors were found:"
1424 print newItem
.getError()
1425 print "The task was not added."
1426 printCurrent
= False
1428 if newItem
.hasHiddenTask():
1429 clearScreen(self
.sysCalls
)
1430 self
.todo
.append(newItem
)
1431 self
.sortByPriority()
1434 def checkCurrentTask(self
):
1435 if self
.currentTask
> len(self
.todo
) - 1:
1436 self
.currentTask
= len(self
.todo
) - 1
1437 if self
.currentTask
< 0:
1438 self
.currentTask
= 0
1440 def writeArchive(self
, item
):
1442 filename
= self
.filename
+ ".archive.dat"
1444 if not os
.path
.exists(filename
):
1445 f
= open(filename
,"wb")
1446 f
.write("# " + notice
[0] + "\n")
1447 f
.write(magicTag
+ "DATA\n")
1449 f
= open(filename
,"a+b")
1451 f
.write(item
.toString())
1454 print "Tasks archived to " + filename
1456 except Exception, e
:
1457 self
.showError("Error trying to archive the tasks.\n" + str(e
))
1460 def exportTasks(self
):
1461 filename
= self
.filename
+ ".tasks.txt"
1463 f
= open(filename
,"wb")
1464 f
.write("# " + notice
[0] + "\n")
1465 f
.write(magicTag
+ "DATA\n")
1466 for item
in self
.todo
:
1467 f
.write(item
.toString())
1470 print "Tasks exported to " + filename
1471 except Exception, e
:
1472 self
.showError("Error trying to export the file.\n" + str(e
))
1474 def importTasks(self
, filename
):
1476 orgNTasks
= len(self
.todo
)
1478 self
.showError("You must supply the name of the file to import.")
1482 self
.splitFile(filename
, True)
1483 if len(self
.todo
) == orgNTasks
:
1484 self
.showError("Failed to find any tasks to import.")
1487 except Exception, e
:
1488 self
.showError("Error importing tasks. " + str(e
))
1491 def createFile(self
, filename
):
1493 if os
.path
.exists(filename
):
1494 self
.showError("Sorry but " + filename
+ " already exists.")
1497 f
= open(filename
, "wb")
1498 f
.write("#!/usr/bin/env python\n")
1499 f
.write("#" + ruler
+ "\n")
1502 except Exception, e
:
1503 self
.showError("Error trying to create the file " + filename
+ ". " + str(e
))
1506 def save(self
, filename
):
1507 if filename
!= "" or self
.autoSave
:
1508 self
.forceSave(filename
)
1511 print "Autosave is off, so changes not saved yet."
1513 def forceSave(self
, filename
):
1515 filename
= self
.filename
1516 tmpFilename
= filename
+ ".tmp"
1517 backupFilename
= filename
+ ".bak"
1520 f
= open(tmpFilename
,"wb")
1521 f
.write("#!/usr/bin/env python\n")
1522 f
.write("# -*- coding: utf-8 -*-\n")
1523 f
.write("#" + ruler
+ "\n")
1524 f
.write("# Run the script for details of the licence\n")
1525 f
.write("# or refer to the notice section later in the file.\n")
1526 f
.write("#" + ruler
+ "\n")
1527 f
.write(magicTag
+ "DATA\n")
1528 for item
in self
.todo
:
1529 f
.write(item
.toString())
1531 f
.write(magicTag
+ "CONFIG\n")
1532 f
.write("cfgColor = " + str(gColor
.getCodeSet()) + "\n")
1533 f
.write("cfgAutoSave = " + str(self
.autoSave
) + "\n")
1534 f
.write("cfgReviewMode = " + str(self
.review
) + "\n")
1535 f
.write("cfgSysCalls = " + str(self
.sysCalls
) + "\n")
1536 f
.write("cfgEditorNt = \"" + cfgEditorNt
+ "\"\n")
1537 f
.write("cfgEditorPosix = \"" + cfgEditorPosix
+ "\"\n")
1538 f
.write("cfgShortcuts = " + str(self
.shortcuts
) + "\n")
1539 f
.write("cfgAbbreviations = " +str(globalAbbr
.toString()) +"\n")
1540 f
.write("cfgPAbbreviations = " +str(globalPAbbr
.toString()) +"\n")
1541 f
.write(magicTag
+ "CODE\n")
1542 for codeline
in self
.code
:
1543 f
.write(codeline
.rstrip())
1548 except Exception, e
:
1549 self
.showError("Error trying to save the file.\n" + str(e
))
1552 os
.remove(backupFilename
)
1556 oldstat
= os
.stat(filename
)
1557 os
.rename(filename
, backupFilename
)
1558 os
.rename(tmpFilename
, filename
)
1559 os
.chmod(filename
, stat
.S_IMODE(oldstat
.st_mode
)) # ensure permissions carried over
1560 self
.filename
= filename
1562 print "Tasks saved."
1563 except Exception, e
:
1564 self
.showError("Error trying to rename the backups.\n" + str(e
))
1566 def moveTo(self
, indexStr
):
1568 index
= int(indexStr
, 10)
1569 if index
< 0 or index
> len(self
.todo
) - 1:
1570 self
.showError("Sorry but there is no task " + indexStr
)
1572 if not self
.isViewable(self
.todo
[index
]):
1573 print "Switching off your filter so that the task can be displayed."
1574 self
.clearFilterArray(False)
1575 self
.currentTask
= index
1577 self
.showError("Unable to understand the task " + indexStr
+ " you want to show.")
1580 def moveToVisible(self
):
1581 start
= self
.currentTask
1583 if start
< 0 or start
>= len(self
.todo
):
1585 while not self
.isViewable(self
.todo
[self
.currentTask
]):
1587 if self
.currentTask
== start
:
1588 print "Nothing matched your filter. Removing your filter so that the current task can be displayed."
1589 self
.clearFilterArray(False)
1592 def decrypt(self
, indexStr
):
1593 index
= self
.getRequiredTask(indexStr
)
1594 if index
< 0 or index
> len(self
.todo
) - 1:
1595 self
.showError("Sorry but there is no task " + indexStr
+ " to show.")
1596 elif self
.todo
[index
].hasHiddenTask():
1598 print WordWrapper(gMaxLen
).wrap(ec
.enterKeyAndDecrypt(self
.todo
[index
].getHiddenTask()))
1600 print "Task ", index
, " has no encrypted data."
1602 def moveTask(self
, indexStr
, where
):
1605 print "You must supply the number of the task to move."
1608 index
= self
.getRequiredTask(indexStr
)
1609 if index
< 0 or index
> len(self
.todo
) - 1:
1610 self
.showError("Sorry but there is no task " + indexStr
+ " to move.")
1611 elif where
== self
.MOVE_DOWN
:
1612 if index
<= len(self
.todo
) - 2:
1613 item
= self
.todo
[index
]
1614 self
.todo
[index
] = self
.todo
[index
+ 1]
1615 self
.todo
[index
+ 1] = item
1616 print "Task ", index
, " moved down."
1619 self
.showError("Task " + str(index
) + " is already at the bottom.")
1622 if where
== self
.MOVE_TOP
:
1623 self
.todo
.insert(0, self
.todo
.pop(index
))
1626 item
= self
.todo
[dest
]
1627 self
.todo
[dest
] = self
.todo
[index
]
1628 self
.todo
[index
] = item
1629 print "Task ", index
, " moved up."
1632 self
.showError("Task " + str(index
) + " is already at the top.")
1636 self
.showError("Unable to understand the task " + indexStr
+ " you want to move.")
1642 if safeRawInput("Are you really sure you want to remove everything? Yes or No? >>>").upper() != "YES":
1643 print("Nothing has been removed.")
1646 self
.currentTask
= 0
1650 def getRequiredTask(self
, indexStr
):
1651 if indexStr
== "^" or indexStr
.upper() == "THIS":
1652 index
= self
.currentTask
1655 index
= int(indexStr
, 10)
1660 def archiveTask(self
, indexStr
):
1662 line
= indexStr
.split(" ", 1)
1668 index
= self
.getRequiredTask(indexStr
)
1669 if index
< 0 or index
> len(self
.todo
) - 1:
1670 self
.showError("Sorry but there is no task " + indexStr
+ " to mark as done and archive.")
1672 if indexStr
== "^" or indexStr
.upper() == "THIS":
1675 print "Are you sure you want to archive: ' " + self
.todo
[index
].toStringSimple() + "'"
1676 if safeRawInput("Enter Yes to archive this task? >>>").upper() == "YES":
1679 newItem
= self
.createItem(":d+0")
1680 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1681 newItem
= self
.createItem(entry
+ " @Archived")
1682 self
.todo
[index
].copy(newItem
, TodoItem
.APPEND
)
1683 if self
.writeArchive(self
.todo
[index
]):
1684 self
.todo
[index
:index
+ 1] = []
1685 print "Task ", index
, " has been archived."
1688 print "Task ", index
, " marked as archived but not removed."
1690 print "Task ", index
, " has not been archived."
1693 def removeTask(self
, indexStr
):
1695 index
= self
.getRequiredTask(indexStr
)
1696 if index
< 0 or index
> len(self
.todo
) - 1:
1697 self
.showError("Sorry but there is no task " + indexStr
+ " to delete.")
1699 if indexStr
== "^" or indexStr
.upper() == "THIS":
1702 print "Are you sure you want to remove ' " + self
.todo
[index
].toStringSimple() + "'"
1703 if safeRawInput("Enter Yes to delete this task? >>>").upper() == "YES":
1706 self
.todo
[index
:index
+ 1] = []
1707 print "Task ", index
, " has been removed."
1709 print "Task ", index
, " has not been removed."
1712 def substituteText(self
, indexStr
):
1713 line
= indexStr
.split(" ", 1)
1718 self
.showError("You need to define the task and substitution phrases. e.g SUB 0 /old/new/")
1724 print "You must supply the number of the task to change."
1727 index
= self
.getRequiredTask(indexStr
)
1729 if index
< 0 or index
> len(self
.todo
) - 1:
1730 self
.showError("Sorry but there is no task " + indexStr
)
1732 text
= entry
.replace("/", "\n")
1733 text
= text
.replace("\\\n","/")
1734 phrases
= text
.split("\n")
1735 if len(phrases
) != 4:
1736 self
.showError("The format of the command is incorrect. The substitution phrases should be /s1/s2/ ")
1738 oldText
= self
.todo
[index
].getTask()
1739 newText
= oldText
.replace(phrases
[1], phrases
[2])
1740 if newText
== oldText
:
1741 self
.showError("Nothing has changed.")
1743 newItem
= self
.createItem(newText
)
1744 if newItem
.hasError():
1745 print "With the substitution the task had errors:"
1746 print newItem
.getError()
1747 print "Task ", index
, " is unchanged."
1749 if newItem
.hasHiddenTask():
1750 clearScreen(self
.sysCalls
)
1751 self
.showError("It isn't possible to create private or secret data by using the substitition command.")
1753 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1754 print "Task ", index
, " has been changed."
1759 def modifyTask(self
, indexStr
, replace
, externalEditor
= False):
1760 line
= indexStr
.split(" ", 1)
1769 print "You must supply the number of the task to change."
1772 index
= self
.getRequiredTask(indexStr
)
1774 if index
< 0 or index
> len(self
.todo
) - 1:
1775 self
.showError("Sorry but there is no task " + indexStr
)
1779 exEdit
= EditorLauncher()
1780 (key
, entry
) = self
.todo
[index
].toStringEditable()
1781 entry
= exEdit
.edit(entry
)
1783 if replace
== TodoItem
.REPLACE
:
1784 print "This task will completely replace the existing entry,"
1785 print "including any projects and actions."
1786 elif replace
== TodoItem
.MODIFY
:
1787 print "Only the elements you add will be replaced. So, for example,"
1788 print "if you don't enter any projects the original projects will remain."
1790 print "Elements you enter will be appended to the current task"
1791 entry
= safeRawInput("Enter new details >>>")
1793 if replace
== TodoItem
.APPEND
:
1794 newItem
= self
.createItem(entry
, password
="unused") # we will discard the encrypted part on extend
1795 elif externalEditor
:
1796 newItem
= self
.createItem(entry
, password
= key
)
1798 newItem
= self
.createItem(entry
)
1799 if newItem
.hasHiddenTask():
1800 clearScreen(self
.sysCalls
)
1801 if newItem
.hasError():
1802 print "The task had errors:"
1803 print newItem
.getError()
1804 print "Task ", index
, " is unchanged."
1806 if newItem
.hasHiddenTask() and replace
== TodoItem
.APPEND
:
1807 self
.showError("It isn't possible to extend the encrypted part of a task.\nThis part is ignored.")
1808 self
.todo
[index
].copy(newItem
, replace
)
1809 print "Task ", index
, " has been changed."
1812 print "Task ", index
, " has not been touched."
1816 if self
.currentTask
< len(self
.todo
) - 1:
1817 self
.currentTask
= self
.currentTask
+ 1
1819 def incTaskLoop(self
):
1820 if self
.currentTask
< len(self
.todo
) - 1:
1821 self
.currentTask
= self
.currentTask
+ 1
1823 self
.currentTask
= 0
1825 if self
.currentTask
> 0:
1826 self
.currentTask
= self
.currentTask
- 1
1828 def decTaskLoop(self
):
1829 if self
.currentTask
> 0:
1830 self
.currentTask
= self
.currentTask
- 1
1832 self
.currentTask
= len(self
.todo
) - 1
1834 def printItemTruncated(self
, index
, leader
):
1835 if len(self
.todo
) < 1:
1836 print leader
, "no tasks"
1838 scrnline
= leader
+ "[%02d] %s" % (index
, self
.todo
[index
].toStringSimple())
1839 if len(scrnline
) > gMaxLen
:
1840 print scrnline
[0:gMaxLen
- 3] + "..."
1845 def printItem(self
, index
, colorType
):
1846 if len(self
.todo
) < 1:
1847 self
.output("There are no tasks to be done.\n", 0)
1850 wrapper
= WordWrapper(gMaxLen
)
1851 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringSimple()))
1852 if colorType
== "row0":
1853 style
= "class=\"evenTask\""
1855 style
= "class=\"oddTask\""
1856 self
.output("<div %s>[%02d] %s</div>\n" % (style
, index
, self
.todo
[index
].toStringSimple()),
1857 gColor
.code(colorType
) + scrnline
+ gColor
.code("normal") + "\n" )
1858 nlines
= wrapper
.getNLines()
1861 def printItemVerbose(self
, index
):
1862 if len(self
.todo
) < 1:
1863 print "There are no tasks to be done."
1866 wrapper
= WordWrapper(gMaxLen
)
1867 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringVerbose()))
1870 def clearFilterArray(self
, local
):
1872 self
.localFilters
= []
1873 self
.localFilterText
= ""
1875 self
.globalFilters
= []
1876 self
.globalFilterText
= ""
1878 def setFilterArray(self
, local
, requiredFilter
):
1879 filters
= requiredFilter
.split()
1881 destination
= self
.localFilters
1883 destination
= self
.globalFilters
1886 for word
in filters
:
1887 if word
[0:1] == "-":
1892 if word
[0:2].upper() == ":D" and len(word
) > 2:
1893 filter = ":D" + TodoItem("").parseDate(word
[2:].strip(), False)
1894 elif word
[0:2].lower() == ":p":
1895 filter = globalPAbbr
.expandProject(word
)
1897 filter = globalAbbr
.expandAction(word
)
1899 filter = "-" + filter
1900 destination
.append(filter)
1901 if humanVersion
!= "":
1902 humanVersion
= humanVersion
+ " " + filter
1904 humanVersion
= filter
1906 for filter in self
.globalFilters
:
1907 destination
.append(filter)
1908 if humanVersion
!= "":
1909 humanVersion
= humanVersion
+ " " + filter
1911 humanVersion
= filter
1913 self
.localFilterText
= humanVersion
1915 self
.globalFilterText
= humanVersion
1917 def isViewable(self
, item
):
1918 if len(self
.globalFilters
) == 0 and len(self
.localFilters
) == 0:
1922 if len(self
.localFilters
) > 0:
1923 filterArray
= self
.localFilters
1925 filterArray
= self
.globalFilters
1926 if "or" in filterArray
or "OR" in filterArray
:
1930 for filter in filterArray
:
1931 if filter.upper() == "OR":
1937 if filter[0:1] == "+":
1940 if filter[0:1] == "-":
1946 if filter[0:1] == "#":
1947 priority
= int(filter[1:], 10)
1952 if self
.exactPriority
:
1953 if item
.hasPriority(priority
):
1955 elif item
.hasPriorityOrAbove(priority
):
1957 elif filter[0:2].upper() == ":D":
1958 if item
.hasDate(filter[2:]):
1960 elif filter[0:2].upper() == ":P":
1961 view
= item
.hasProject(filter)
1962 elif filter[0:1].upper() == "@":
1963 view
= item
.hasAction(filter)
1964 elif item
.hasWord(filter):
1967 view
= (view
!= True)
1975 if fast
or mustHave
:
1980 def listByAction(self
):
1981 index
= SearchIndex()
1982 for item
in self
.todo
:
1983 index
.addCollection(item
.getActions())
1985 (n
, value
) = index
.getFirstItem()
1989 if not gColor
.usingColor() and n
> 0:
1993 self
.setFilterArray(True, "+" + value
)
1994 self
.printList(div
, "<H2 class=\"hAction\">" + value
+ "</H2>\n", gColor
.code("title") + "\n" + value
+ "\n" + gColor
.code("title"))
1995 self
.clearFilterArray(True)
1996 (n
, value
) = index
.getNextItem(n
)
1999 def listByProject(self
):
2000 index
= SearchIndex()
2001 for item
in self
.todo
:
2002 index
.addCollection(item
.getProjects())
2004 (n
, value
) = index
.getFirstItem()
2008 if not gColor
.usingColor() and n
> 0:
2012 self
.setFilterArray(True, "+:p" + value
)
2013 self
.printList(div
, "<H2 class =\"hProject\">Project: " + value
+ "</H2>\n", gColor
.code("title") + "\nProject: " + value
+ gColor
.code("normal") + "\n")
2014 self
.clearFilterArray(True)
2015 (n
, value
) = index
.getNextItem(n
)
2018 def listByDate(self
):
2019 index
= SearchIndex()
2020 for item
in self
.todo
:
2021 index
.add(item
.getDate())
2023 (n
, value
) = index
.getFirstItem()
2027 if not gColor
.usingColor() and n
> 0:
2031 self
.setFilterArray(True, "+:D" + value
)
2032 self
.printList(div
, "<H2 class =\"hDate\">Date: " + value
+ "</H2>\n", gColor
.code("title") + "\nDate: " + value
+ gColor
.code("normal") + "\n")
2033 self
.clearFilterArray(True)
2034 (n
, value
) = index
.getNextItem(n
)
2038 def showFilter(self
):
2039 if self
.globalFilterText
!= "":
2040 self
.output("<H3 class =\"hFilter\">Filter = " + self
.globalFilterText
+ "</H3>\n",
2041 gColor
.code("bold") + "Filter = " + self
.globalFilterText
+ gColor
.code("normal") + "\n")
2043 def showLocalFilter(self
):
2044 if self
.localFilterText
!= "":
2045 self
.output("<H3 class =\"hFilter\">Filter = " + self
.localFilterText
+ "</H3>\n",
2046 gColor
.code("bold") + "Filter = " + self
.localFilterText
+ gColor
.code("normal") + "\n")
2048 def printList(self
, div
, outHtml
, outStd
):
2049 self
.doPrintList(-1, div
, outHtml
, outStd
)
2051 def printShortList(self
, line
):
2054 count
= int(line
, 10)
2056 self
.showError("Didn't understand the number of tasks you wanted listed.")
2057 self
.doPrintList(count
, False, "", "")
2059 def doPrintList(self
, limitItems
, div
, outHtml
, outStd
):
2067 self
.outputHtml("<div class=\"itemGroup\">\n")
2068 for item
in self
.todo
:
2069 if self
.isViewable(item
):
2073 self
.output(outHtml
, outStd
)
2074 if not gColor
.usingColor() and not first
:
2077 count
= count
+ self
.printItem(n
, color
)
2083 displayed
= displayed
+ 1
2085 if limitItems
>= 0 and displayed
>= limitItems
:
2087 if count
>= maxlines
:
2088 if self
.htmlFile
== "":
2089 msg
= safeRawInput("---press Enter for more. Enter s to skip: ")
2090 if len(msg
) > 0 and msg
.strip().upper()[0] == "S":
2094 self
.outputHtml("</div>\n")
2096 def printHelp(self
, lines
):
2097 ListViewer(24).show(lines
,"!PAUSE!")
2099 def splitFile(self
, filename
, dataOnly
):
2103 f
= open(filename
, 'r')
2105 if line
[0:2] == "#!":
2108 if line
.find(magicTag
+ "DATA") == 0:
2112 elif line
.find(magicTag
+ "CONFIG") == 0:
2116 elif line
.find(magicTag
+ "CODE") == 0:
2123 self
.code
.append(line
)
2125 self
.processCfgLine(line
)
2128 if len(line
) > 0 and line
[0] == "#":
2129 line
= line
[1:].strip()
2131 newItem
= self
.createItem(line
)
2133 self
.todo
.append(newItem
)
2138 def createItem(self
, line
, password
= ""):
2139 item
= TodoItem(line
, password
)
2142 def outputHtml(self
, html
):
2143 if self
.htmlFile
!= "":
2144 self
.htmlFile
.write(html
)
2146 def output(self
, html
, stdout
):
2147 if self
.htmlFile
!= "":
2148 #self.htmlFile.write(html.replace("\n", "<br>\n"))
2149 self
.htmlFile
.write(html
)
2156 def startHtml(self
, title
):
2157 htmlFilename
= self
.filename
+ ".html"
2159 self
.htmlFile
= open(htmlFilename
, "w")
2160 self
.htmlFile
.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n")
2161 self
.htmlFile
.write("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n")
2162 self
.htmlFile
.write("<html>\n<head>\n")
2163 self
.htmlFile
.write("<style>\n")
2164 self
.htmlFile
.write(".footer {text-align:center;}\n")
2165 self
.htmlFile
.write("</style>\n")
2166 self
.htmlFile
.write("<link rel=\"stylesheet\" href=\"ikog.css\" type=\"text/css\">\n")
2167 self
.htmlFile
.write("</head>\n<body>\n")
2168 self
.htmlFile
.write("<div class=\"header\">\n")
2169 self
.htmlFile
.write("<H1 class=\"hTitle\">iKog Todo List</H1>\n")
2170 self
.htmlFile
.write("<H2 class=\"hSubTitle\">" + title
+ " printed " + date
.today().isoformat() + "</H1>\n")
2171 self
.htmlFile
.write("</div>\n")
2172 self
.htmlFile
.write("<div class=\"taskArea\">\n")
2174 print "Failed to create output file:", htmlFilename
2178 name
= self
.htmlFile
.name
2181 self
.htmlFile
.write("</div>\n")
2182 self
.htmlFile
.write("<div class=\"footer\">\n")
2183 self
.htmlFile
.write("--- end of todo list ---<br>\n")
2184 self
.htmlFile
.write("Created using " + notice
[0] + "\n<br>" + notice
[1] + "<br>\n")
2185 self
.htmlFile
.write("</div>\n")
2187 self
.htmlFile
.write("</body>\n</html>\n")
2188 self
.htmlFile
.close()
2190 print "HTML file " + name
+ " created."
2192 except Exception, e
:
2193 self
.showError("Error writing to file. " + str(e
))
2197 safeName
= os
.path
.abspath(name
).replace("\\","/")
2198 safeName
= "file://" + urllib
.quote(safeName
," /:")
2199 webbrowser
.open(safeName
)
2200 except Exception, e
:
2201 self
.showError("Unable to launch html output. " + str(e
))
2203 class Abbreviations
:
2204 def __init__(self
, project
= False):
2205 self
.default(project
)
2207 def default(self
,project
):
2211 self
.abbrevs
= {"@A":"@Anywhere","@C":"@Computer",
2212 "@D":"@Desk", "@E": "@Errands",
2213 "@H":"@Home", "@I":"@Internet","@L":"@Lunch", "@M":"@Meeting", "@N":"@Next",
2214 "@P":"@Phone", "@Pw":"@Password", "@S":"@Someday/Maybe",
2215 "@O":"@Other", "@W4":"@Waiting_For", "@W":"@Work"}
2218 def setAbbreviations(self
, abbr
):
2219 self
.abbrevs
.update(abbr
)
2221 def addAbbreviation(self
, key
, word
):
2222 self
.abbrevs
.update({key
.title():word
})
2224 def removeAbbreviation(self
, key
):
2226 if self
.abbrevs
.has_key(key
):
2227 del self
.abbrevs
[key
]
2231 def expandAction(self
, action
):
2232 if action
[0:1] != "@":
2235 action
= action
.title()
2236 if self
.abbrevs
.has_key(action
):
2237 return self
.abbrevs
[action
]
2240 def expandProject(self
, project
):
2241 if not project
.lower().startswith(":p"):
2243 project
= project
.title()
2244 if self
.abbrevs
.has_key(project
):
2245 return self
.abbrevs
[project
]
2249 return str(self
.abbrevs
)
2251 def toStringVerbose(self
):
2254 for key
in self
.abbrevs
:
2255 output
= output
+ key
.ljust(5) + " = " + self
.abbrevs
[key
].ljust(30)
2258 output
= output
+ "\n"
2260 output
= output
+ "\n"
2268 if ent
!= "" and not ent
in self
.items
:
2269 self
.items
.append(ent
)
2271 def addCollection(self
, collection
):
2272 for ent
in collection
:
2273 if ent
!= "" and not ent
in self
.items
:
2274 self
.items
.append(ent
)
2278 def getFirstItem(self
):
2279 if len(self
.items
) > 0:
2280 return (0, self
.items
[0])
2284 def getNextItem(self
, count
):
2286 if count
> len(self
.items
) - 1:
2289 return (count
, self
.items
[count
])
2293 ENCRYPTION_MARKER
= "{}--xx"
2297 NOT_DUE_PRIORITY
= 0
2298 DEFAULT_PRIORITY
= 5
2299 OVERDUE_PRIORITY
= 11
2300 MEETING_PRIORITY
= 10
2301 def __init__(self
,line
, password
= ""):
2304 self
.hiddenTask
= ""
2308 self
.created
= date
.today().isoformat()
2310 self
.autoAction
= False
2311 self
.autoProject
= False
2312 self
.nullDate
= False
2313 self
.parse(line
, password
)
2315 def makeSafeDate(self
, year
, month
, day
):
2322 newDate
= date(year
, month
, day
)
2329 def parseDate(self
, dateStr
, quiet
):
2330 dateStr
= dateStr
.replace("/","-")
2331 dateStr
= dateStr
.replace(":","-")
2332 entry
= dateStr
.split("-")
2336 elif dateStr
== "0":
2337 self
.nullDate
= True
2342 if dateStr
[0:1] == "+":
2343 days
= int(dateStr
[1:].strip(), 10)
2344 when
= now
+ timedelta(days
)
2347 year
= int(entry
[0], 10)
2348 month
= int(entry
[1], 10)
2349 day
= int(entry
[2], 10)
2352 month
= int(entry
[0], 10)
2353 day
= int(entry
[1], 10)
2357 day
= int(entry
[0], 10)
2365 when
= self
.makeSafeDate(year
, month
, day
)
2367 self
.nullDate
= False
2373 self
.addError("Could not decode the date. Use :dYYYY/MM/DD")
2376 return when
.isoformat()
2378 def parse(self
, line
, password
):
2380 words
= line
.split(" ")
2384 ecmLen
= len(self
.ENCRYPTION_MARKER
)
2385 for word
in words
[start
:]:
2386 wordUC
= word
.strip().upper()
2389 taskToHide
= taskToHide
+ word
.strip() + " "
2390 elif word
[0:ecmLen
] == self
.ENCRYPTION_MARKER
:
2391 self
.hiddenTask
= word
[ecmLen
:]
2392 elif wordUC
.startswith("<PRIVATE>") or wordUC
.startswith("<SECRET>") or wordUC
.startswith("<S>") or wordUC
.startswith("<P>"):
2395 pos
= word
.index(">")
2396 taskToHide
= taskToHide
+ word
[pos
+ 1:].strip() + " "
2399 elif word
[0] == "@" and len(word
) > 1:
2400 if wordUC
== "@DATE":
2401 self
.addError("@Date contexts should not be entered. Use :dYYYY-MM-DD")
2403 act
= globalAbbr
.expandAction(word
.strip())
2404 if not act
in self
.actions
:
2405 self
.actions
.append(act
)
2406 elif word
[0:1] == "#" and len(word
) > 1 and self
.priority
== -1:
2408 self
.priority
= int(word
[1:].strip(), 10)
2409 if self
.priority
< 1:
2411 elif self
.priority
> 10:
2414 self
.addError("Did not understand priority.")
2416 elif wordUC
[0:2] == ":P" and len(word
) > 2:
2417 proj
= globalPAbbr
.expandProject(word
.strip())[2:].title()
2418 if not proj
in self
.projects
:
2419 self
.projects
.append(proj
)
2420 elif wordUC
[0:8] == ":CREATED" and len(word
) > 8:
2421 self
.created
= word
[8:].strip()
2422 elif wordUC
[0:2] == ":D" and len(word
) > 2:
2423 self
.when
= self
.parseDate(word
[2:].strip(), False)
2425 self
.task
= self
.task
+ word
.strip() + " "
2426 if taskToHide
!= "":
2429 if ec
.setType(ec
.TYPE_AES
) != ec
.TYPE_AES
:
2430 self
.addError("AES encryption is not available.")
2433 ec
.setType(ec
.TYPE_OBSCURED
)
2434 if taskToHide
!= "":
2436 self
.hiddenTask
= ec
.enterKeyAndEncrypt(taskToHide
)
2439 self
.hiddenTask
= ec
.encrypt(taskToHide
)
2441 if len(self
.actions
) == 0:
2442 self
.actions
.append("@Anywhere")
2443 self
.autoAction
= True
2444 if len(self
.projects
) == 0:
2445 self
.projects
.append("None")
2446 self
.autoProject
= True
2448 def addError(self
, err
):
2449 if len(self
.error
) > 0:
2450 self
.error
= self
.error
+ "\n"
2451 self
.error
= self
.error
+ err
2454 return self
.error
!= ""
2461 def hasWord(self
, word
):
2462 return (self
.task
.upper().find(word
.upper()) >= 0)
2464 def hasAction(self
, loc
):
2465 if self
.when
!= "" and loc
.upper() == "@DATE":
2468 return loc
.title() in self
.actions
2470 def copy(self
, todoItem
, replace
):
2471 if replace
== TodoItem
.REPLACE
or len(todoItem
.task
.strip()) > 0:
2472 if replace
== TodoItem
.APPEND
:
2473 self
.task
= self
.task
+ " ..." + todoItem
.task
2475 self
.task
= todoItem
.task
2476 if replace
== TodoItem
.REPLACE
or todoItem
.autoAction
== False:
2477 if replace
!= TodoItem
.APPEND
:
2479 for loc
in todoItem
.actions
:
2480 if not loc
in self
.actions
:
2481 self
.actions
.append(loc
)
2482 if replace
== TodoItem
.REPLACE
or todoItem
.autoProject
== False:
2483 if replace
!= TodoItem
.APPEND
:
2485 for proj
in todoItem
.projects
:
2486 if not proj
in self
.projects
:
2487 self
.projects
.append(proj
)
2488 if replace
== TodoItem
.REPLACE
or (todoItem
.when
!= "" or todoItem
.nullDate
== True):
2489 self
.when
= todoItem
.when
2490 if todoItem
.priority
>= 0:
2491 self
.priority
= todoItem
.priority
2493 if replace
== TodoItem
.REPLACE
or len(todoItem
.hiddenTask
.strip()) > 0:
2494 if replace
== TodoItem
.APPEND
:
2497 self
.hiddenTask
= todoItem
.hiddenTask
2499 def hasHiddenTask(self
):
2500 return self
.hiddenTask
!= ""
2503 return len(self
.task
.strip()) > 0
2505 def hasProject(self
, proj
):
2506 if proj
[0:2].upper() == ":P":
2508 return proj
.title() in self
.projects
2510 def hasDate(self
, dt
):
2511 dt
= self
.parseDate(dt
, True)
2515 return self
.when
== dt
2517 def hasPriorityOrAbove(self
, priority
):
2518 return (self
.getEffectivePriority() >= priority
)
2520 def hasPriority(self
, priority
):
2521 return (self
.getEffectivePriority() == priority
)
2523 def getHiddenTask(self
):
2524 return self
.hiddenTask
2529 def getActions(self
):
2533 return self
.actions
+ ["@Date"]
2535 def getProjects(self
):
2536 return self
.projects
2541 def getPriority(self
):
2542 if self
.priority
< 0:
2543 return self
.DEFAULT_PRIORITY
2545 return self
.priority
2547 def getEffectivePriority(self
):
2548 userP
= self
.getPriority()
2550 if self
.when
<= date
.today().isoformat():
2551 userP
= self
.OVERDUE_PRIORITY
+ userP
2552 if self
.hasAction("@Meeting"):
2553 userP
= userP
+ self
.MEETING_PRIORITY
2555 userP
= self
.NOT_DUE_PRIORITY
2561 entry
= entry
+ " " + self
.task
2562 for action
in self
.actions
:
2563 entry
= entry
+ " " + action
2564 for project
in self
.projects
:
2565 entry
= entry
+ " :p" + project
2566 entry
= entry
+ " :created" + self
.created
2568 entry
= entry
+ " :d" + self
.when
2569 if self
.priority
>= 0:
2570 entry
= entry
+ " #" + str(self
.priority
)
2571 if self
.hiddenTask
!= "":
2572 entry
= entry
+ " " + self
.ENCRYPTION_MARKER
+ self
.hiddenTask
2575 def toStringEditable(self
, includeHidden
= False):
2579 entry
= entry
+ ":d" + self
.when
+ " "
2580 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2581 if len(self
.actions
) > 0:
2582 for action
in self
.actions
:
2583 entry
= entry
+ " " + action
2584 if len(self
.projects
) > 0:
2585 for project
in self
.projects
:
2587 if project
!= "None":
2588 entry
= entry
+ " :p" + project
2589 if self
.hiddenTask
!= "" and includeHidden
:
2591 entry
= entry
+ " <" + Encryptor().getSecurityClass(self
.hiddenTask
)[0:1] + ">"
2592 entry
= entry
+ ec
.enterKeyAndDecrypt(self
.hiddenTask
)
2593 password
= ec
.getKey()
2594 return (password
, entry
.strip())
2596 def toStringSimple(self
):
2599 entry
= entry
+ "@Date " + self
.when
+ " "
2600 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2601 if self
.hiddenTask
!= "":
2602 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2603 if len(self
.actions
) > 0:
2604 for action
in self
.actions
:
2605 entry
= entry
+ " " + action
2606 if len(self
.projects
) > 0:
2608 for project
in self
.projects
:
2610 if project
!= "None":
2612 entry
= entry
+ " Projects: " + project
2615 entry
= entry
+ ", " + project
2616 #entry = entry + " [" + self.created + "]"
2619 def toStringVerbose(self
):
2620 entry
= gColor
.code("title") + self
.task
2621 if self
.hiddenTask
!= "":
2622 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2623 entry
= entry
+ gColor
.code("bold") + "\nPriority: %02d" % (self
.getPriority())
2624 if len(self
.actions
) or self
.when
!= "" > 0:
2625 entry
= entry
+ gColor
.code("heading") + "\nContext: "
2627 entry
= entry
+ gColor
.code("important") + "@Date " + self
.when
2628 entry
= entry
+ gColor
.code("normal")
2629 for action
in self
.actions
:
2630 entry
= entry
+ " " + action
;
2631 if len(self
.projects
) > 0:
2633 for project
in self
.projects
:
2634 if project
!= "None":
2636 entry
= entry
+ gColor
.code("heading") + "\nProjects: " + gColor
.code("normal");
2637 entry
= entry
+ project
2640 entry
= entry
+ ", " + project
2641 entry
= entry
+ gColor
.code("normal") + "\nCreated: [" + self
.created
+ "]"
2645 #for line in notice:
2648 pythonVer
= platform
.python_version()
2649 ver
= pythonVer
.split(".")
2650 if int(ver
[0]) < gReqPythonMajor
or (int(ver
[0]) == gReqPythonMajor
and int(ver
[1]) < gReqPythonMinor
):
2651 print "\nSorry but this program requires Python ", \
2652 str(gReqPythonMajor
) + "." + str(gReqPythonMinor
), \
2653 "\nYour current version is ", \
2654 str(ver
[0]) + "." + str(ver
[1]), \
2655 "\nTo run the program you will need to install the current version of Python."
2658 # signal.signal(signal.SIGINT, signalHandler)
2659 gColor
= ColorCoder(cfgColor
)
2660 globalAbbr
= Abbreviations()
2661 globalPAbbr
= Abbreviations(project
=True)
2663 if len(sys
.argv
) > 2:
2665 reopen
= sys
.argv
[1]
2667 reopen
= sys
.argv
[0] + ".dat"
2668 for word
in sys
.argv
[2:]:
2670 commandList
.append(command
)
2673 command
= command
+ word
+ " "
2674 commandList
.append(command
)
2675 elif len(sys
.argv
) > 1:
2676 reopen
= sys
.argv
[1]
2678 reopen
= sys
.argv
[0] + ".dat"
2681 todoList
= TodoList(sys
.argv
[0], reopen
)
2682 reopen
= todoList
.run(commandList
)