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 # push entire git repo to repo.or.cz @Internet @Computer :pDeployment :created2008-11-25
31 # check out http://www.thefreedictionary.com/sources.htm @Computer @Internet :pImport :created2008-11-25
38 cfgEditorPosix
= "nano,pico,vim,emacs"
39 cfgShortcuts
= ['', '', '', '', '', '', '', '', '', '']
40 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'}
41 cfgPAbbreviations
= {':Pi': ':pImport', ':P1': ':pI18N', ':Pw': ':pWebsite'}
46 from datetime
import date
47 from datetime
import timedelta
54 from threading
import Timer
69 "ikog.py v 1.88 2007-11-23",
70 "Copyright (C) 2006-2007 S. J. Butler",
71 "Visit http://www.henspace.co.uk for more information.",
72 "This program is free software; you can redistribute it and/or modify",
73 "it under the terms of the GNU General Public Licence as published by",
74 "the Free Software Foundation; either version 2 of the License, or",
75 "(at your option) any later version.",
77 "This program is distributed in the hope that it will be useful,",
78 "but WITHOUT ANY WARRANTY; without even the implied warranty of",
79 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
80 "GNU General Public License for more details. The license is available",
81 "from http://www.gnu.org/licenses/gpl.txt"
86 " (_) | | __ ___ __ _",
87 " | | | |/ / / _ \ / _` |",
88 " | | | < | (_) | | (_| |",
89 " |_| |_|\_\ \___/ \__, |",
91 " (_) | |_ | | __ ___ ___ _ __ ___ ___ _ __",
92 " | | | __| | |/ / / _ \ / _ \ | '_ \ / __| / _ \ | '_ \ ",
93 " | | | |_ | < | __/ | __/ | |_) | \__ \ | (_) | | | | |",
94 " |_| \__| |_|\_\ \___| \___| | .__/ |___/ \___/ |_| |_|",
96 " __ _ _ __ ___ __ __ (_) _ __ __ _ ",
97 " / _` | | '__| / _ \ \ \ /\ / / | | | '_ \ / _` |",
98 " | (_| | | | | (_) | \ V V / | | | | | | | (_| | _",
99 " \__, | |_| \___/ \_/\_/ |_| |_| |_| \__, | (_)",
108 ruler
= "~".ljust(gMaxLen
- 1, "~")
109 divider
= "_".ljust(gMaxLen
- 1, "_")
111 print "Error found. Probably wrong version of Python"
117 def safeRawInput(prompt
):
119 entry
= raw_input(prompt
)
125 ### global compare function
126 def compareTodo(a
, b
):
127 return cmp(a
.getEffectivePriority(), b
.getEffectivePriority())
130 print gColor
.code("error") + "ERROR: " + msg
+ gColor
.code("normal")
133 def clearScreen(useSys
= False):
135 if os
.name
== "posix":
137 elif os
.name
in ("dos", "ce", "nt"):
143 ### XTEA algorithm public domain
148 def crypt(self
, key
,data
,iv
='\00\00\00\00\00\00\00\00',n
=32):
149 def keygen(key
,iv
,n
):
151 iv
= self
.xtea_encrypt(key
,iv
,n
)
154 xor
= [ chr(x^y
) for (x
,y
) in zip(map(ord,data
),keygen(key
,iv
,n
)) ]
157 def xtea_encrypt(self
, key
,block
,n
=32):
158 v0
,v1
= struct
.unpack("!2L",block
)
159 k
= struct
.unpack("!4L",key
)
160 sum,delta
,mask
= 0L,0x9e3779b9L
,0xffffffffL
161 for round in range(n
):
162 v0
= (v0
+ (((v1
<<4 ^ v1
>>5) + v1
) ^
(sum + k
[sum & 3]))) & mask
163 sum = (sum + delta
) & mask
164 v1
= (v1
+ (((v0
<<4 ^ v0
>>5) + v0
) ^
(sum + k
[sum>>11 & 3]))) & mask
165 return struct
.pack("!2L",v0
,v1
)
168 def __init__(self
, width
):
173 def addLine(self
, pos
):
175 self
.nLines
= self
.nLines
+ 1
180 def intelliLen(self
, text
):
181 return len(gColor
.stripCodes(text
))
183 def wrap(self
, text
):
185 formatted
= text
.replace("<br>", "\n").replace("<BR>", "\n")
186 lines
= formatted
.splitlines()
189 for thisline
in lines
:
191 words
= thisline
.split()
196 wlen
= self
.intelliLen(w
) + 1
197 if (self
.pos
+ wlen
) == self
.width
:
200 elif (self
.pos
+ wlen
) < self
.width
:
206 self
.pos
= self
.pos
+ wlen
+ 1
213 ### Color code class for handling color text output
217 codes
= [{"normal":"\x1b[0;37;40m",
218 "title":"\x1b[1;32;40m",
219 "heading":"\x1b[1;35;40m",
220 "bold":"\x1b[1;35;40m",
221 "important":"\x1b[1;31;40m",
222 "error":"\x1b[1;31;40m",
223 "reverse":"\x1b[0;7m",
224 "row0":"\x1b[0;35;40m",
225 "row1":"\x1b[0;36;40m"},
226 {"normal":"\x1b[0;37m",
227 "title":"\x1b[1;32m",
228 "heading":"\x1b[1;35m",
230 "important":"\x1b[1;31m",
231 "error":"\x1b[1;31m",
232 "reverse":"\x1b[0;7m",
234 "row1":"\x1b[0;36m"}]
236 def __init__(self
, set):
237 self
.codeSet
= self
.NONE
240 def stripCodes(self
, text
):
241 # strip out the ansi codes
242 ex
= re
.compile("\x1b\[[0-9;]*m")
243 return ex
.sub("", text
)
245 def setCodeSet(self
, set):
248 self
.codeSet
= self
.NONE
249 elif set < len(self
.codes
):
251 return (old
!= self
.codeSet
)
253 def isValidSet(self
, myset
):
254 if myset
< len(self
.codes
):
259 def colorSupported(self
):
260 return (os
.name
== "posix" or os
.name
== "mac")
262 def usingColor(self
):
263 return (self
.codeSet
<> self
.NONE
and self
.colorSupported())
265 def code(self
, type):
266 if self
.codeSet
== self
.NONE
or not self
.colorSupported():
269 return self
.codes
[self
.codeSet
][type]
271 def printCode(self
, type):
272 if self
.codeSet
!= self
.NONE
:
273 print self
.code(type),
275 def getCodeSet(self
):
278 ### Viewer class for paging through multiple lines
280 def __init__(self
, maxlines
):
281 self
.maxlines
= maxlines
283 def show(self
, list, pause
):
286 if count
>= self
.maxlines
or line
== pause
:
287 io
= safeRawInput("--- Press enter for more. Enter s to skip ---").strip()
289 if len(io
) > 0 and io
.upper()[0] == "S":
296 ### Handler for encryption
298 TYPE_OBSCURED
= "xtea_"
304 self
.encryptionType
= self
.TYPE_OBSCURED
306 def setType(self
, codeType
):
307 if codeType
== self
.TYPE_AES
and supportAes
== False:
308 self
.encryptionType
= self
.TYPE_OBSCURED
310 self
.encryptionType
= codeType
311 return self
.encryptionType
313 def setKey(self
, key
):
319 def enterKey(self
, prompt1
, prompt2
):
322 input1
= getpass
.getpass(prompt1
+ " >>>")
324 input2
= getpass
.getpass(prompt2
+ " >>>")
326 print "You must enter the same password. Start again"
334 def complexKey(self
):
335 return md5(self
.key
).digest()
337 def getSecurityClass(self
, encrypted
):
338 if encrypted
.startswith(self
.TYPE_OBSCURED
):
339 return "private xtea"
340 if encrypted
.startswith(self
.TYPE_AES
):
344 def obscure(self
, plainText
):
345 key
= self
.complexKey()
346 obscured
= Xtea().crypt(key
, plainText
, self
.SALT_64
)
347 return self
.TYPE_OBSCURED
+ obscured
.encode('hex_codec')
349 def unobscure(self
, obscured
):
351 data
= obscured
[len(self
.TYPE_OBSCURED
):]
352 data
= data
.decode('hex_codec')
353 key
= self
.complexKey()
354 plain
= Xtea().crypt(key
, data
, self
.SALT_64
)
357 def encryptAes(self
, plainText
):
358 if len(self
.key
) < 16:
359 key
= self
.complexKey()
362 obscured
= pyRijndael
.EncryptData(key
, plainText
)
363 return self
.TYPE_AES
+ obscured
.encode('hex_codec')
365 def decryptAes(self
, encrypted
):
367 data
= encrypted
[len(self
.TYPE_AES
):]
368 data
= data
.decode('hex_codec')
369 if len(self
.key
) < 16:
370 key
= self
.complexKey()
373 plain
= pyRijndael
.DecryptData(key
, data
)
376 def enterKeyAndEncrypt(self
, plainText
):
377 self
.enterKey("Enter the master password.", "Re-enter the master password")
378 return self
.encrypt(plainText
)
380 def encrypt(self
, plainText
):
381 if self
.encryptionType
== self
.TYPE_AES
:
382 return self
.encryptAes(plainText
)
384 return self
.obscure(plainText
)
386 def enterKeyAndDecrypt(self
, encryptedText
):
387 self
.enterKey("Enter your master password", "")
388 return self
.decrypt(encryptedText
)
390 def decrypt(self
, encryptedText
):
391 if encryptedText
.startswith(self
.TYPE_AES
):
393 return "You do not have the pyRinjdael module so the text cannot be decrypted."
395 return self
.decryptAes(encryptedText
)
397 return self
.unobscure(encryptedText
)
399 ### Handler for user input
401 def __init__(self
, prompt
):
404 def read(self
, entry
= ""):
406 entry
= safeRawInput(self
.prompt
)
407 entry
= entry
.strip()
412 if entry
.find(magicTag
) == 0:
413 printError("You cannot begin lines with the sequence " + magicTag
)
416 elif entry
.find(TodoItem
.ENCRYPTION_MARKER
) >= 0:
417 printError ("You cannot use the special sequence " + TodoItem
.ENCRYPTION_MARKER
)
429 return (command
, line
)
431 class EditorLauncher
:
432 WARNING_TEXT
= "# Do not enter secret or private information!"
436 def edit(self
, text
):
439 if os
.name
== "posix":
441 elif os
.name
== "nt":
445 printError("Sorry, but external editing not supported on " + os
.name
.upper())
448 fname
= self
.makeFile(text
, terminator
)
450 printError("Unable to create temporary file.")
452 success
= self
.run(ed
, fname
)
454 (success
, text
) = self
.readFile(fname
)
455 if text
== self
.orgText
:
456 print("No changes made.");
458 self
.scrubFile(fname
)
464 def scrubFile(self
, fname
):
468 printError("Failed to remove file " + fname
+ ". If you entered any private data you should delete this file yourself.")
470 def readFile(self
, fname
):
473 fh
= open(fname
, "rt")
478 thisLine
= self
.safeString(line
)
479 if thisLine
!= self
.WARNING_TEXT
:
482 text
= text
+ thisLine
488 printError("Error reading the edited text. " + str(e
))
489 return (success
, text
)
491 def safeString(self
, text
):
492 return text
.replace("\r","").replace("\n","")
494 def makeFile(self
, text
, terminator
):
496 (fh
, fname
) = tempfile
.mkstemp(".tmpikog","ikog")
497 fout
= os
.fdopen(fh
,"wt")
498 text
= text
.replace("<BR>", "<br>")
500 lines
= text
.split("<br>")
501 fout
.write(self
.WARNING_TEXT
+ terminator
)
502 for thisline
in lines
:
503 fout
.write(self
.safeString(thisline
) + terminator
)
507 def run(self
, program
, file):
508 progs
= program
.split(",")
510 success
= self
.runProgram(prog
.strip(), file)
515 def runProgram(self
, program
, file):
517 if os
.name
== "posix":
520 os
.spawnlp(os
.P_WAIT
, program
, progarg
, file)
526 elif os
.name
== "nt":
527 if file.find(" ") >= 0:
528 file = "\"" + file + "\""
529 for path
in os
.environ
["PATH"].split(os
.pathsep
):
531 prog
= os
.path
.join(path
, program
)
532 if prog
.find(" ") >= 0:
533 progarg
= "\"" + prog
+ "\""
536 os
.spawnl(os
.P_WAIT
, prog
, progarg
, file)
547 ### The main todo list
549 quickCard
= ["Quick reference card:",
550 "? ADD/A/+ text FILTER/FI [filter]",
551 "HELP/H IMMEDIATE/I/++ text TOP/T [N]",
552 "COLOR/COLOUR/C [N] KILL/K/X/- N NEXT/N",
553 "MONOCHROME/MONO CLEAR PREV/P",
554 "EXPORT REP/R N [text] GO/G N",
555 "IMPORT file MOD/M N [text] LIST/L [filter]",
556 "REVIEW/REV ON/OFF EXTEND/E N [text] LIST>/L> [filter]",
558 "V1 SUB/SU N /s1/s2/ :D",
560 "SAVE/S DOWN/D N @>",
561 "AUTOSAVE/AS ON|OFF UP/U N :D>",
562 "VER/VERSION NOTE/NOTES text :P>",
563 "CLEARSCREEN/CLS O/OPEN file SHOW N",
564 "SYS ON|OFF NEW file SETEDxx editor",
565 "!CMD command 2 ABBREV/AB @x @full",
566 "ABBREV/AB ? PAB ? PAB :px :pfull",
567 "SHORTCUT/SC N cmd SHORTCUT/SC ? =N",
568 "ARCHIVE/DONE N [text]",
574 "The program is designed to help manage tasks using techniques",
575 "such as Getting Things Done by David Allen. Check out",
576 "http://www.henspace.co.uk for more information and detailed help.",
577 "To use the program, simply enter the task at the prompt.",
578 "All of the commands are displayed in the next section.",
582 "Commands that have more than one method of entry are shown separated by /",
583 "e.g HELP/H means that you can enter either HELP or an H.",
584 "All commands can be entered in upper or lower case.",
585 "Items shown in square brackets are optional.",
586 "Items shown separated by the | symbol are alternatives. e.g ON|OFF means",
587 "you should type either ON or OFF.",
588 "Note that some commands refer to adding tasks to the top or bottom of the",
589 "list. However the task's position in the list is also determined by its.",
590 "priority. So, for example, adding a task to the top will still not allow",
591 "it to precede tasks that have been assigned a higher priority number. ",
595 "? : displays a quick reference card",
596 "HELP/H : displays this help.",
597 "VERSION/VER : display the version.",
598 "WEB : Go to the website for more information",
599 "CLEARSCREEN/CLS : Clear the screen",
600 "COLOR/COLOUR/C [N] : Use colour display (not Windows) N=1 for no background",
601 "MONOCHROME/MONO : Use monochrome display",
602 "EXPORT : Export the tasks only to filename.tasks.txt",
603 "IMPORT file : Import tasks from the file",
604 "REVIEW/REV ON|OFF : If on, hitting enter moves to the next task",
605 " : If off, enter re-displays the current task",
606 "V0 : Same as REVIEW OFF",
607 "V1 : Same as REVIEW ON",
608 "SAVE/S : Save the tasks",
609 "O/OPEN file : Open a new data file.",
610 "NEW file : Create a new data file.",
611 "AUTOSAVE/AS ON|OFF : Switch autosave on or off",
612 "SYS ON|OFF : Allow the program to use system calls.",
613 "!CMD command : Run a system command.",
614 "2 : Start a two minute timer (for GTD)",
615 "QUIT/Q : quit the program",
617 "TASK ENTRY AND EDITING COMMANDS",
618 "-------------------------------",
619 "For the editing commands that require a task number, you can",
620 "replace N by '^' or 'this' to refer to the current task.",
621 "ADD/A/+ the task : add a task to the bottom of the list.",
622 " : Entering any line that does not begin with",
623 " : a valid command and which is greater than 10",
624 " : characters long is also assumed to be an addition.",
625 "EDIT/ED [N] : Create task, or edit task N, using external editor.",
626 "SUB/SU N /s1/s2/ : Replace text s1 with s2 in task N. Use \/ if you",
627 " : need to include the / character.",
628 "NOTE/NOTES text : shorthand for ADD #0 @Notes text",
629 "IMMEDIATE/I/++ : add a task to the top of the list to do today.",
630 "REP/R N [text] : replace task N",
631 "MOD/M N [text] : modify task N.",
632 "EXTEND/E N [text] : add more text to task N",
633 "FIRST/F N : move task N to the top.",
634 "DOWN/D/ N : move task N down the queue",
635 "UP/U/ N : move task N up the queue",
637 "TASK REMOVAL COMMANDS",
638 "---------------------",
639 "KILL/K/X/- N : kill (delete) task N. You must define N",
640 "DONE N [text] : Remove task N and move to an archive file",
641 "ARCHIVE N [text] : Same as DONE",
642 "CLEAR : Remove all tasks",
646 "SHOW N : display encrypted text for task N",
647 "FILTER/FI [filter] : set a filter. Applies to all displays",
648 " : See list for details of the filter",
649 " : Setting the filter to nothing clears it.",
650 "TOP/T [N] : Go to top, list N tasks, and display the top task",
651 "NEXT/N : display the next task. Same as just hitting enter",
652 "PREV/P : display previous task",
653 "GO/G N : display task N",
654 "LIST/L [filter] : list tasks. Filter = context, project, priority, date",
655 " : or word. Contexts begin with @ and projects with :p",
656 " : Dates begin with :d, anything else is a search word.",
657 " : Precede term with - to exclude e.g. -@Computer",
658 " : e.g LIST @computer or LIST #5",
659 "@ : sorted list by Context.",
660 ":D : sorted list by Dates",
661 ":P : sorted list by Projects",
662 "LIST>/L> [filter] : standard list sent to an HTML report",
663 "@> : sorted list by Context sent to an HTML report",
664 ":D> : sorted list by Dates sent to an HTML report",
665 ":P> : sorted list by Projects sent to an HTML report",
666 " : The HTML reports are sent to todoFilename.html",
670 "The SETEDxxx commands allow you to use an external editor.",
671 "Note the editor you pick should be a simple text editor. If you pick",
672 "something that doesn't work, try the defaults again.",
673 "Because some systems may have different editors installed, you can set",
674 "more than one by separating the editors usng commas. The program will",
675 "use the first one it finds.",
676 "For Windows the default is edit, which works quite well in the terminal",
677 "but you could change it to notepad.",
678 "For Linux, the default is nano,pico,vim,emacs.",
679 "To use external editors you must switch on system calls using the SYS ON",
681 "SETEDNT command : Set the external editor for Windows (NT).",
682 "SETEDPOSIX command : Set the editor for posix systems.",
683 " : e.g. SETEDNT edit",
684 " : SETEDPOSIX nano,vim",
685 "SHORTCUT/SC ? : list shortcuts",
686 "SHORTCUT/SC N cmd : Set shortcut N to command cmd",
687 "=N : Run shortcut N",
689 "ABBREV/AB @x @full : Create new abbreviation. @x expands to @full",
690 "ABBREV/AB ? : List context abbreviations.",
691 "PAB :px :pfull : Project abbreviation. :px expands to :pfull",
692 "PAB ? : List project abbreviations.",
696 "When you enter a task, you can embed any number of contexts in the task.",
697 "You can also embed a project description by preceding it with :p",
698 "You can assign a priority by preceding a number by #. e.g. #9.",
699 "If you don't enter a number, a default of 5 is used. The higher the ",
700 "number the more important it is. Priorities range from 1 to 10.",
701 "Only the first # is used for the priority so you can use # as",
702 "a normal character as long as you precede it with a priority number.",
703 "You can define a date when the task must be done by preceding the date",
704 "with :d, i.e :dYYYY/MM/DD or :dMM/DD or :dDD. If you omit the year/month",
705 "they default to the current date. Adding a date automatically creates an",
706 "@Date context for the task.",
707 "So, for example, to add a new task to e-mail Joe, we could enter:",
708 "+ e-mail joe @computer",
709 "or to add a task to the decorating project, we could enter:",
710 "+ buy wallpaper :pdecorating",
711 "to enter a task with an importance of 9 we could enter:",
712 "+ book that holiday #9 @Internet",
714 "MODIFYING AND EXTENDING TASKS",
715 "-----------------------------",
716 "The modify command allows you to change part of an existing task.",
717 "So for example, imagine you have a task:",
718 "[05] Buy some food #9 @Internet Projects:Shopping",
719 "Enter the command M 5 and then type:",
721 "Because the only element we have entered is a new context, only",
722 "that part is modified, so we get.",
723 "[05] Buy some food #9 @Computer Projects:Shopping",
724 "Likewise, had we entered:",
725 "Buy some tea :pEating",
727 "[05] Buy some tea #9 @Internet Projects:Eating",
728 "The extend command is similar but it appends the entry. So had",
729 "we used the command E 5 instead of M 5 the result would have been",
730 "[05] Buy some food ... Buy some tea #9 @Internet Projects:Eating",
734 "Any word preceded by @ will be used as a context. Contexts are like",
735 "sub-categories or sub-lists. There are a number of pre-defined",
736 "abbreviations that you can use as well. The recognised abbreviations",
738 "@A = @Anywhere (this is the default)",
750 "@S = @Someday/maybe",
756 "An @Date context is created if you embed a date in the task.",
757 "Dates are embedded using the :dDATE format.",
758 "Valid DATE formats are yyyy-mm-dd, mm-dd or dd",
759 "You can also use : or / as the separators. So, for example:",
760 ":d2006/12/22 or :d2006-11-7 or :d9/28 are all valid entries.",
762 "If you set a date, then until that date is reached, the task is given",
763 "an effective priority of 0. Once the date is reached, the task's",
764 "priority is increased by 11, moving it to the of the list.",
766 "A date entry of :d0 can be used to clear a date entry.",
767 "A date entry of :d+X can be used to create a date entry of today + X days.",
768 "So :d+1 is tomorrow and :d+0 is today.",
772 "If you want to encrypt text you can use the <private> or <secret> tags or",
773 "their abbreviations <p> and <s>.",
774 "These tags will result in all text following the tag to be encrypted.",
775 "Note that any special commands, @contexts for example, are treated as plain",
776 "text in the encrypted portion.",
777 "To display the text you will need to use the SHOW command.",
779 "The <private> tag uses the inbuilt XTEA algorithm. This is supposedly a",
780 "relatively secure method but probably not suitable for very sensitive data.",
782 "The <secret> tag can only be used if you have the pyRijndael.py module.",
783 "This uses a 256 bit Rinjdael cipher. The module can be downloaded from ",
784 "http://jclement.ca/software/pyrijndael/",
785 "You can install this in your Python path or just place it alongside your",
787 "Note you cannot use the extend command with encrypted text.",
790 "MARKING TASKS AS COMPLETE",
791 "-------------------------",
792 "The normal way to mark a task as complete is just to remove it using the",
793 "KILL command. If you want to keep track of tasks you have finished, you",
794 "can use the ARCHIVE or DONE command. This gives the task an @Archived",
795 "context, changes the date to today and then moves it from the current",
796 "file to a file with archive.dat appended. The archive file is a valid",
797 "ikog file so you can use the OPEN command to view it, edit it and run",
798 "reports in the normal way. So assuming your current script is ikog.py,",
799 "to archive the current task you could enter:",
801 "ARCHIVE ^ I have finished this",
803 "This would move the task to a file called ikog.py.archive.dat",
806 "USING EXTERNAL DATA",
807 "-------------------",
808 "Normally the tasks are embedded in the main program file so all you have",
809 "to carry around with you is the ikog.py file. The advantage is that you",
810 "only have one file to look after; the disadvantage is that every time you",
811 "save a task you have to save the program as well. If you want, you can",
812 "keep your tasks in a separate file.",
813 "To do this, use the EXPORT function to create a file ikog.py.tasks.txt",
814 "Use the CLEAR command to remove the tasks from your main ikog.py program.",
815 "Rename the exported file from ikog.py.tasks.txt to ikog.py.dat",
816 "Ikog will now use this file for storing your tasks.",
819 "PASSING TASKS VIA THE COMMAND LINE",
820 "----------------------------------",
821 "It is possible to add tasks via the command line. The general format of",
822 "the command line is:",
823 " ikog.py filename commands",
824 "The filename is the name of the data file containing your tasks. You",
825 "can use . to represent the default internal tasks.",
826 "Commands is a set of normal ikog commands separated by the / ",
827 "character. Note there must be a space either side of the /.",
828 "So to add a task and then exit the program we could just enter:",
829 " ikog.py . + here is my task / QUIT",
830 "Note that we added the quit command to exit ikog.",
831 "You must make sure that you do not use any commands that require user",
832 "input. Deleting tasks via the command line is more complicated as you",
833 "need to find the task automatically. If you do try to delete this way,",
834 "use the filter command to find some unique text and then delete it. eg.",
835 " ikog.py . FI my_unique_text / KILL THIS / QUIT",
836 "Use THIS instead of ^ as a caret has a special meaning in Windows.",
837 "If you do intend automating ikog from the command line, you should add",
838 "a unique reference to each task so you can find it later using FILTER. eg.",
839 "+ this is my task ref_1256",
847 def __init__(self
, todoFile
, externalDataFile
):
854 self
.sysCalls
= False
856 self
.globalFilterText
= ""
857 self
.globalFilters
= []
858 self
.localFilterText
= ""
859 self
.localFilters
= []
860 # split the file into the source code and the todo list
861 self
.filename
= todoFile
863 self
.filename
= os
.readlink(todoFile
)
865 pass # probably windows
866 externalDataFile
= self
.makeFilename(externalDataFile
)
867 self
.externalData
= self
.findDataSource(externalDataFile
)
868 if self
.externalData
:
869 self
.filename
= externalDataFile
871 self
.splitFile(self
.filename
, False)
872 self
.exactPriority
= False
875 def setPAbbreviation(self
, line
):
877 elements
= line
.split(" ", 1)
878 if not elements
[0].lower().startswith(":p"):
879 self
.showError("Project abbreviations must begin with :p")
880 elif len(elements
) > 1:
881 if not elements
[1].lower().startswith(":p"):
882 abb
= ":p" + elements
[1].title()
884 abb
= ":p" +elements
[1][2:].title()
885 globalPAbbr
.addAbbreviation(elements
[0], abb
)
888 if not globalPAbbr
.removeAbbreviation(elements
[0]):
889 self
.showError("Could not find project abbreviation " + line
)
891 print "Project abbreviation ", line
, " removed."
895 def showPAbbreviations(self
):
896 print globalPAbbr
.toStringVerbose()
898 def setAbbreviation(self
, line
):
900 elements
= line
.split(" ", 1)
901 if not elements
[0].startswith("@"):
902 self
.showError("Abbreviations must begin with @")
903 elif len(elements
) > 1:
904 if not elements
[1].startswith("@"):
905 abb
= "@" + elements
[1].title()
907 abb
= "@" +elements
[1][1:].title()
908 globalAbbr
.addAbbreviation(elements
[0], abb
)
911 if not globalAbbr
.removeAbbreviation(elements
[0]):
912 self
.showError("Could not find abbreviation " + line
)
914 print "Abbreviation ", line
, " removed."
918 def showAbbreviations(self
):
919 print globalAbbr
.toStringVerbose()
921 def setShortcut(self
, line
, force
= False):
922 elements
= line
.split(" ", 1)
924 index
= int(elements
[0])
925 if len(elements
) > 1:
926 command
= elements
[1]
930 self
.showError("Did not understand the command. Format should be SHORTCUT N my command.")
933 if index
< 0 or index
> len(self
.shortcuts
):
934 self
.showError("The maximum number of shortcuts is " + str(len(self
.shortcuts
)) + ". Shortcuts ignored.")
937 if self
.shortcuts
[index
] != "" and not force
:
938 if safeRawInput("Do you want to change the current command '" + self
.shortcuts
[index
] + "'? Enter Yes to overwrite. >>>").upper() != "YES":
940 self
.shortcuts
[index
] = command
944 def showShortcuts(self
):
946 for s
in self
.shortcuts
:
951 print "=%1d %s" %(index
, msg
)
954 def setShortcuts(self
, settings
= []):
955 if len(settings
) > self
.MAX_SHORTCUTS
:
956 self
.showError("The maximum number of shortcuts is " + str(self
.MAX_SHORTCUTS
) + ". Shortcuts ignored.")
957 self
.shortcuts
= ["" for n
in range(self
.MAX_SHORTCUTS
)]
958 if len(settings
) > 0:
959 self
.shortcuts
[0:len(settings
)] = settings
961 def getShortcutIndex(self
, command
):
962 if len(command
) == 2 and command
[0:1].upper() == "=":
963 index
= ord(command
[1]) - ord("0")
964 if index
>= self
.MAX_SHORTCUTS
:
970 def getShortcut(self
, command
):
971 index
= self
.getShortcutIndex(command
)
973 return self
.shortcuts
[index
]
977 def safeSystemCall(self
, line
):
980 self
.showError("Nothing to do.")
981 elif words
[0].upper() == "RM" or words
[0].upper() == "RMDIR" or words
[0].upper() == "DEL":
982 self
.showError("Sorry, but deletion commands are not permitted.")
987 def processCfgLine(self
, line
):
988 params
= line
.split("=")
991 cmd
= params
[0].strip()
992 if cmd
== "cfgEditorNt":
994 cfgEditorNt
= params
[1].replace("\"", "").strip()
995 elif cmd
== "cfgEditorPosix":
996 global cfgEditorPosix
997 cfgEditorPosix
= params
[1].replace("\"", "").strip()
998 elif cmd
== "cfgShortcuts":
999 elements
= params
[1].strip()[1:-1].split(",")
1002 self
.setShortcut(str(index
) + " " + e
.strip()[1:-1], True)
1004 elif cmd
== "cfgAutoSave":
1005 if params
[1].upper().strip() == "TRUE":
1009 self
.setAutoSave(as_
, False)
1010 elif cmd
== "cfgReviewMode":
1011 if params
[1].upper().strip() == "TRUE":
1012 self
.setReview("ON")
1014 self
.setReview("OFF")
1015 elif cmd
== "cfgSysCalls":
1016 if params
[1].upper().strip() == "TRUE":
1017 self
.setSysCalls("ON")
1019 self
.setSysCalls("OFF")
1020 elif cmd
== "cfgColor":
1021 gColor
.setCodeSet(int(params
[1].strip()))
1022 elif cmd
== "cfgAbbreviations":
1023 abbrs
= eval(params
[1].strip())
1024 globalAbbr
.setAbbreviations(abbrs
)
1025 elif cmd
== "cfgPAbbreviations":
1026 abbrs
= eval(params
[1].strip())
1027 globalPAbbr
.setAbbreviations(abbrs
)
1029 self
.showError("Unrecognised command " + cmd
)
1031 def makeFilename(self
, name
):
1032 (root
, ext
) = os
.path
.splitext(name
)
1033 if ext
.upper() != ".DAT":
1034 name
= name
+ ".dat"
1036 name
= os
.path
.expanduser(name
)
1037 except Exception, e
:
1038 self
.showError("Failed to expand path. " + str(e
))
1041 def findDataSource(self
, filename
):
1045 self
.splitFile(filename
, False)
1046 print "Using external data file ", filename
1049 print "No external data file ", filename
, ", so using internal tasks."
1052 def setSysCalls(self
, mode
):
1053 oldCalls
= self
.sysCalls
1054 mode
= mode
.strip().upper()
1056 self
.sysCalls
= True
1057 print "Using system calls for clear screen"
1059 self
.sysCalls
= False
1060 print "No system calls for clear screen"
1062 self
.showError("Could not understand the sys command. Use SYS ON or OFF.")
1063 return (self
.sysCalls
!= oldCalls
)
1065 def setAutoSave(self
, as_
, save
):
1067 if self
.autoSave
== False:
1068 self
.autoSave
= True
1071 elif self
.autoSave
== True:
1072 self
.autoSave
= False
1076 print "Autosave is on."
1078 print "Autosave is off."
1081 def showError(self
, msg
):
1084 def pause(self
, prompt
= "Press enter to continue."):
1085 if safeRawInput(prompt
).strip() != "":
1086 print "Entry ignored!"
1088 def setReview(self
, mode
):
1089 oldReview
= self
.review
1090 mode
= mode
.strip().upper()
1093 print "In review mode. Enter advances to the next task"
1096 print "Review mode off. Enter re-displays the current task"
1098 self
.showError("Could not understand the review command. Use REVIEW ON or OFF.")
1099 return (self
.review
!= oldReview
)
1101 def sortByPriority(self
):
1102 self
.todo
.sort(key
=TodoItem
.getEffectivePriority
, reverse
= True)
1105 def run(self
, commandList
):
1107 print "AES encryption not available."
1108 print("\nEnter HELP for instructions.")
1112 self
.sortByPriority()
1115 truncateTask
= False
1117 self
.checkCurrentTask()
1119 self
.moveToVisible()
1122 self
.printItemTruncated(self
.currentTask
, "Current: ")
1124 self
.printItemVerbose(self
.currentTask
)
1127 truncateTask
= False
1132 if len(commandList
) >= 1:
1133 enteredLine
= commandList
[0]
1134 commandList
= commandList
[1:]
1136 (rawcommand
, line
) = InputParser(prompt
).read(enteredLine
)
1138 command
= rawcommand
.upper()
1139 if self
.getShortcutIndex(command
) >= 0:
1140 sc
= self
.getShortcut(command
)
1142 (rawcommand
, line
) = InputParser("").read(self
.getShortcut(command
))
1147 print "Shortcut: ", rawcommand
, " ", line
1148 command
= rawcommand
.upper()
1152 elif command
== "PAB":
1153 if line
.strip() == "?":
1154 self
.showPAbbreviations()
1155 elif self
.setPAbbreviation(line
):
1157 elif command
== "ABBREV" or command
== "AB":
1158 if line
.strip() == "?":
1159 self
.showAbbreviations()
1160 elif self
.setAbbreviation(line
):
1162 elif command
== "SHORTCUT" or command
== "SC":
1163 if line
.strip() == "?":
1164 self
.showShortcuts()
1166 if self
.setShortcut(line
):
1168 printCurrent
= False
1169 elif command
== "2":
1170 enteredLine
= self
.runTimer(2)
1171 printCurrent
= False
1172 elif command
== "CLS" or command
== "CLEARSCREEN":
1173 clearScreen(self
.sysCalls
)
1174 elif command
== "SETEDNT":
1178 elif command
== "SETEDPOSIX":
1179 global cfgEditorPosix
1180 cfgEditorPosix
= line
1182 elif command
== "SYS":
1183 if self
.setSysCalls(line
):
1185 elif command
== "!CMD":
1187 self
.safeSystemCall(line
)
1189 self
.showError("System calls are not allowed. Use SYS ON to enable them.")
1190 elif command
== "SHOW" or command
== "SH":
1192 self
.pause("Press enter to clear screen and continue. ")
1193 clearScreen(self
.sysCalls
)
1194 elif command
== "VERSION" or command
== "VER":
1196 elif command
== "SAVE" or command
== "S":
1198 print "There's no need to save now. If the prompt shows >>> "
1199 print "then there is nothing to save. You only need to save if the prompt "
1203 elif command
== "NEW":
1204 filename
= self
.makeFilename(line
)
1205 if self
.createFile(filename
):
1210 printCurrent
= False
1211 elif command
== "OPEN" or command
== "O":
1212 filename
= self
.makeFilename(line
)
1217 printCurrent
= False
1218 elif command
== "AUTOSAVE" or command
== "AS":
1220 self
.showError("You must enter ON or OFF for the autosave command")
1222 self
.setAutoSave(line
.upper() == "ON", True)
1223 elif command
== "REVIEW" or command
== "REV":
1224 if self
.setReview(line
):
1226 elif command
== "V0":
1227 if self
.setReview("OFF"):
1229 elif command
== "V1":
1230 if self
.setReview("ON"):
1232 elif command
== "?":
1233 self
.printHelp(self
.quickCard
)
1234 elif command
== "HELP" or command
== "H":
1235 self
.printHelp(self
.help)
1236 elif command
== "QUIT" or command
== "Q":
1240 printCurrent
= False
1241 elif command
== "WEB":
1243 webbrowser
.open("http://www.henspace.co.uk")
1244 except Exception, e
:
1245 self
.showError("Unable to launch browser. " + str(e
))
1246 elif command
== "COLOR" or command
== "COLOUR" or command
== "C":
1251 if not gColor
.isValidSet(set):
1252 self
.showError("Invalid colour set ignored.")
1253 elif gColor
.setCodeSet(set):
1255 elif command
== "MONOCHROME" or command
== "MONO":
1256 if gColor
.setCodeSet(gColor
.NONE
):
1258 elif command
== "EXPORT":
1260 elif command
== "IMPORT":
1261 if self
.importTasks(line
):
1263 elif command
== "CLEAR" and line
== "":
1266 elif command
== "FILTER" or command
== "FI" or command
== "=":
1267 self
.setFilterArray(False, line
)
1268 elif command
== "NEXT" or command
== "N":
1270 elif command
== "PREV" or command
== "P":
1272 elif command
== "TOP" or command
== "T" or command
== "0":
1273 self
.currentTask
= 0
1275 self
.setFilterArray(True, "")
1276 self
.showLocalFilter()
1277 self
.printShortList(line
)
1279 elif command
== "GO" or command
== "G":
1281 elif command
== "IMMEDIATE" or command
== "I" or command
== "++":
1282 newItem
= self
.createItem(":d+0 " + line
)
1283 if newItem
.hasHiddenTask():
1284 clearScreen(self
.sysCalls
)
1285 if newItem
.hasError():
1286 print "Errors were found:"
1287 print newItem
.getError()
1288 print "The task was not added."
1289 printCurrent
= False
1291 self
.todo
.insert(0, newItem
)
1292 self
.currentTask
= 0
1293 self
.sortByPriority()
1295 elif command
== "KILL" or command
== "K" or command
== "-" or command
== "X":
1296 if self
.removeTask(line
):
1298 elif command
== "ARCHIVE" or command
== "DONE":
1299 if self
.archiveTask(line
):
1301 elif command
== "REP" or command
=="R":
1302 if self
.modifyTask(line
, TodoItem
.REPLACE
):
1303 self
.sortByPriority()
1306 printCurrent
= False
1307 elif command
== "SUB" or command
== "SU":
1308 if self
.substituteText(line
):
1309 self
.sortByPriority()
1311 elif command
== "EDIT" or command
== "ED":
1312 if not self
.sysCalls
:
1313 self
.showError("External editing needs to use system calls. Use SYS ON to enable them.")
1315 self
.addTaskExternal()
1316 elif self
.modifyTask(line
, TodoItem
.MODIFY
, externalEditor
= True):
1317 self
.sortByPriority()
1320 printCurrent
= False
1321 elif command
== "MOD" or command
== "M":
1322 if self
.modifyTask(line
, TodoItem
.MODIFY
):
1323 self
.sortByPriority()
1326 printCurrent
= False
1327 elif command
== "EXTEND" or command
== "E":
1328 if self
.modifyTask(line
, TodoItem
.APPEND
):
1329 self
.sortByPriority()
1332 printCurrent
= False
1333 elif command
== "FIRST" or command
== "F":
1334 if self
.moveTask(line
, self
.MOVE_TOP
):
1335 self
.sortByPriority()
1337 self
.currentTask
= 0
1338 elif command
== "DOWN" or command
== "D":
1339 if self
.moveTask(line
, self
.MOVE_DOWN
):
1340 self
.sortByPriority()
1342 elif command
== "UP" or command
== "U":
1343 if self
.moveTask(line
, self
.MOVE_UP
):
1344 self
.sortByPriority()
1346 elif command
== "LIST" or command
== "L":
1348 self
.setFilterArray(True, line
)
1349 self
.showLocalFilter()
1350 self
.printList(False, "", "")
1351 self
.clearFilterArray(True)
1354 elif command
== "LIST>" or command
== "L>":
1356 self
.setFilterArray(True, line
)
1357 self
.showLocalFilter()
1358 self
.printList(False, "", "")
1359 self
.clearFilterArray(True)
1361 elif command
== "@":
1364 elif command
== ":P":
1365 self
.listByProject()
1367 elif command
== ":D":
1370 elif command
== "@>":
1371 self
.startHtml("Report by Context")
1374 elif command
== ":P>":
1375 self
.startHtml("Report by Project")
1376 self
.listByProject()
1378 elif command
== ":D>":
1379 self
.startHtml("Report by Date")
1382 elif command
== "ADD" or command
== "A" or command
== "+":
1384 elif command
== "NOTE" or command
== "NOTES":
1385 self
.addTask("#0 @Notes " + line
)
1386 elif (len(command
) + len(line
)) > 10:
1387 self
.addTask(rawcommand
+ " " + line
)
1388 elif len(command
) > 0:
1389 self
.showError("Didn't understand. (Make sure you have a space after the command or your entry is longer than 10 characters)")
1390 printCurrent
= False
1394 self
.timerActive
= False
1396 print "\n\x07Timer\x07 complete.\x07\n\x07Press enter to continue.\x07"
1398 def runTimer(self
, delay
):
1399 self
.timerActive
= True
1400 t
= Timer(delay
* 60 , self
.timeout
)
1402 s
= raw_input(str(delay
) + " minute timer running.\nAny entry will cancel the timer:\n>>>")
1403 if self
.timerActive
:
1405 print "Timer cancelled."
1408 print "Input discarded as timer has finished."
1411 def addTaskExternal(self
):
1412 exEdit
= EditorLauncher()
1413 entry
= exEdit
.edit("")
1417 self
.showError("Nothing to add")
1419 def addTask(self
, line
):
1420 newItem
= self
.createItem(line
)
1421 if newItem
.hasError():
1422 print "Errors were found:"
1423 print newItem
.getError()
1424 print "The task was not added."
1425 printCurrent
= False
1427 if newItem
.hasHiddenTask():
1428 clearScreen(self
.sysCalls
)
1429 self
.todo
.append(newItem
)
1430 self
.sortByPriority()
1433 def checkCurrentTask(self
):
1434 if self
.currentTask
> len(self
.todo
) - 1:
1435 self
.currentTask
= len(self
.todo
) - 1
1436 if self
.currentTask
< 0:
1437 self
.currentTask
= 0
1439 def writeArchive(self
, item
):
1441 filename
= self
.filename
+ ".archive.dat"
1443 if not os
.path
.exists(filename
):
1444 f
= open(filename
,"wb")
1445 f
.write("# " + notice
[0] + "\n")
1446 f
.write(magicTag
+ "DATA\n")
1448 f
= open(filename
,"a+b")
1450 f
.write(item
.toString())
1453 print "Tasks archived to " + filename
1455 except Exception, e
:
1456 self
.showError("Error trying to archive the tasks.\n" + str(e
))
1459 def exportTasks(self
):
1460 filename
= self
.filename
+ ".tasks.txt"
1462 f
= open(filename
,"wb")
1463 f
.write("# " + notice
[0] + "\n")
1464 f
.write(magicTag
+ "DATA\n")
1465 for item
in self
.todo
:
1466 f
.write(item
.toString())
1469 print "Tasks exported to " + filename
1470 except Exception, e
:
1471 self
.showError("Error trying to export the file.\n" + str(e
))
1473 def importTasks(self
, filename
):
1475 orgNTasks
= len(self
.todo
)
1477 self
.showError("You must supply the name of the file to import.")
1481 self
.splitFile(filename
, True)
1482 if len(self
.todo
) == orgNTasks
:
1483 self
.showError("Failed to find any tasks to import.")
1486 except Exception, e
:
1487 self
.showError("Error importing tasks. " + str(e
))
1490 def createFile(self
, filename
):
1492 if os
.path
.exists(filename
):
1493 self
.showError("Sorry but " + filename
+ " already exists.")
1496 f
= open(filename
, "wb")
1497 f
.write("#!/usr/bin/env python\n")
1498 f
.write("#" + ruler
+ "\n")
1501 except Exception, e
:
1502 self
.showError("Error trying to create the file " + filename
+ ". " + str(e
))
1505 def save(self
, filename
):
1506 if filename
!= "" or self
.autoSave
:
1507 self
.forceSave(filename
)
1510 print "Autosave is off, so changes not saved yet."
1512 def forceSave(self
, filename
):
1514 filename
= self
.filename
1515 tmpFilename
= filename
+ ".tmp"
1516 backupFilename
= filename
+ ".bak"
1519 f
= open(tmpFilename
,"wb")
1520 f
.write("#!/usr/bin/env python\n")
1521 f
.write("# -*- coding: utf-8 -*-\n")
1522 f
.write("#" + ruler
+ "\n")
1523 f
.write("# Run the script for details of the licence\n")
1524 f
.write("# or refer to the notice section later in the file.\n")
1525 f
.write("#" + ruler
+ "\n")
1526 f
.write(magicTag
+ "DATA\n")
1527 for item
in self
.todo
:
1528 f
.write(item
.toString())
1530 f
.write(magicTag
+ "CONFIG\n")
1531 f
.write("cfgColor = " + str(gColor
.getCodeSet()) + "\n")
1532 f
.write("cfgAutoSave = " + str(self
.autoSave
) + "\n")
1533 f
.write("cfgReviewMode = " + str(self
.review
) + "\n")
1534 f
.write("cfgSysCalls = " + str(self
.sysCalls
) + "\n")
1535 f
.write("cfgEditorNt = \"" + cfgEditorNt
+ "\"\n")
1536 f
.write("cfgEditorPosix = \"" + cfgEditorPosix
+ "\"\n")
1537 f
.write("cfgShortcuts = " + str(self
.shortcuts
) + "\n")
1538 f
.write("cfgAbbreviations = " +str(globalAbbr
.toString()) +"\n")
1539 f
.write("cfgPAbbreviations = " +str(globalPAbbr
.toString()) +"\n")
1540 f
.write(magicTag
+ "CODE\n")
1541 for codeline
in self
.code
:
1542 f
.write(codeline
.rstrip())
1547 except Exception, e
:
1548 self
.showError("Error trying to save the file.\n" + str(e
))
1551 os
.remove(backupFilename
)
1555 oldstat
= os
.stat(filename
)
1556 os
.rename(filename
, backupFilename
)
1557 os
.rename(tmpFilename
, filename
)
1558 os
.chmod(filename
, stat
.S_IMODE(oldstat
.st_mode
)) # ensure permissions carried over
1559 self
.filename
= filename
1561 print "Tasks saved."
1562 except Exception, e
:
1563 self
.showError("Error trying to rename the backups.\n" + str(e
))
1565 def moveTo(self
, indexStr
):
1567 index
= int(indexStr
, 10)
1568 if index
< 0 or index
> len(self
.todo
) - 1:
1569 self
.showError("Sorry but there is no task " + indexStr
)
1571 if not self
.isViewable(self
.todo
[index
]):
1572 print "Switching off your filter so that the task can be displayed."
1573 self
.clearFilterArray(False)
1574 self
.currentTask
= index
1576 self
.showError("Unable to understand the task " + indexStr
+ " you want to show.")
1579 def moveToVisible(self
):
1580 start
= self
.currentTask
1582 if start
< 0 or start
>= len(self
.todo
):
1584 while not self
.isViewable(self
.todo
[self
.currentTask
]):
1586 if self
.currentTask
== start
:
1587 print "Nothing matched your filter. Removing your filter so that the current task can be displayed."
1588 self
.clearFilterArray(False)
1591 def decrypt(self
, indexStr
):
1592 index
= self
.getRequiredTask(indexStr
)
1593 if index
< 0 or index
> len(self
.todo
) - 1:
1594 self
.showError("Sorry but there is no task " + indexStr
+ " to show.")
1595 elif self
.todo
[index
].hasHiddenTask():
1597 print WordWrapper(gMaxLen
).wrap(ec
.enterKeyAndDecrypt(self
.todo
[index
].getHiddenTask()))
1599 print "Task ", index
, " has no encrypted data."
1601 def moveTask(self
, indexStr
, where
):
1604 print "You must supply the number of the task to move."
1607 index
= self
.getRequiredTask(indexStr
)
1608 if index
< 0 or index
> len(self
.todo
) - 1:
1609 self
.showError("Sorry but there is no task " + indexStr
+ " to move.")
1610 elif where
== self
.MOVE_DOWN
:
1611 if index
<= len(self
.todo
) - 2:
1612 item
= self
.todo
[index
]
1613 self
.todo
[index
] = self
.todo
[index
+ 1]
1614 self
.todo
[index
+ 1] = item
1615 print "Task ", index
, " moved down."
1618 self
.showError("Task " + str(index
) + " is already at the bottom.")
1621 if where
== self
.MOVE_TOP
:
1622 self
.todo
.insert(0, self
.todo
.pop(index
))
1625 item
= self
.todo
[dest
]
1626 self
.todo
[dest
] = self
.todo
[index
]
1627 self
.todo
[index
] = item
1628 print "Task ", index
, " moved up."
1631 self
.showError("Task " + str(index
) + " is already at the top.")
1635 self
.showError("Unable to understand the task " + indexStr
+ " you want to move.")
1641 if safeRawInput("Are you really sure you want to remove everything? Yes or No? >>>").upper() != "YES":
1642 print("Nothing has been removed.")
1645 self
.currentTask
= 0
1649 def getRequiredTask(self
, indexStr
):
1650 if indexStr
== "^" or indexStr
.upper() == "THIS":
1651 index
= self
.currentTask
1654 index
= int(indexStr
, 10)
1659 def archiveTask(self
, indexStr
):
1661 line
= indexStr
.split(" ", 1)
1667 index
= self
.getRequiredTask(indexStr
)
1668 if index
< 0 or index
> len(self
.todo
) - 1:
1669 self
.showError("Sorry but there is no task " + indexStr
+ " to mark as done and archive.")
1671 if indexStr
== "^" or indexStr
.upper() == "THIS":
1674 print "Are you sure you want to archive: ' " + self
.todo
[index
].toStringSimple() + "'"
1675 if safeRawInput("Enter Yes to archive this task? >>>").upper() == "YES":
1678 newItem
= self
.createItem(":d+0")
1679 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1680 newItem
= self
.createItem(entry
+ " @Archived")
1681 self
.todo
[index
].copy(newItem
, TodoItem
.APPEND
)
1682 if self
.writeArchive(self
.todo
[index
]):
1683 self
.todo
[index
:index
+ 1] = []
1684 print "Task ", index
, " has been archived."
1687 print "Task ", index
, " marked as archived but not removed."
1689 print "Task ", index
, " has not been archived."
1692 def removeTask(self
, indexStr
):
1694 index
= self
.getRequiredTask(indexStr
)
1695 if index
< 0 or index
> len(self
.todo
) - 1:
1696 self
.showError("Sorry but there is no task " + indexStr
+ " to delete.")
1698 if indexStr
== "^" or indexStr
.upper() == "THIS":
1701 print "Are you sure you want to remove ' " + self
.todo
[index
].toStringSimple() + "'"
1702 if safeRawInput("Enter Yes to delete this task? >>>").upper() == "YES":
1705 self
.todo
[index
:index
+ 1] = []
1706 print "Task ", index
, " has been removed."
1708 print "Task ", index
, " has not been removed."
1711 def substituteText(self
, indexStr
):
1712 line
= indexStr
.split(" ", 1)
1717 self
.showError("You need to define the task and substitution phrases. e.g SUB 0 /old/new/")
1723 print "You must supply the number of the task to change."
1726 index
= self
.getRequiredTask(indexStr
)
1728 if index
< 0 or index
> len(self
.todo
) - 1:
1729 self
.showError("Sorry but there is no task " + indexStr
)
1731 text
= entry
.replace("/", "\n")
1732 text
= text
.replace("\\\n","/")
1733 phrases
= text
.split("\n")
1734 if len(phrases
) != 4:
1735 self
.showError("The format of the command is incorrect. The substitution phrases should be /s1/s2/ ")
1737 oldText
= self
.todo
[index
].getTask()
1738 newText
= oldText
.replace(phrases
[1], phrases
[2])
1739 if newText
== oldText
:
1740 self
.showError("Nothing has changed.")
1742 newItem
= self
.createItem(newText
)
1743 if newItem
.hasError():
1744 print "With the substitution the task had errors:"
1745 print newItem
.getError()
1746 print "Task ", index
, " is unchanged."
1748 if newItem
.hasHiddenTask():
1749 clearScreen(self
.sysCalls
)
1750 self
.showError("It isn't possible to create private or secret data by using the substitition command.")
1752 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1753 print "Task ", index
, " has been changed."
1758 def modifyTask(self
, indexStr
, replace
, externalEditor
= False):
1759 line
= indexStr
.split(" ", 1)
1768 print "You must supply the number of the task to change."
1771 index
= self
.getRequiredTask(indexStr
)
1773 if index
< 0 or index
> len(self
.todo
) - 1:
1774 self
.showError("Sorry but there is no task " + indexStr
)
1778 exEdit
= EditorLauncher()
1779 (key
, entry
) = self
.todo
[index
].toStringEditable()
1780 entry
= exEdit
.edit(entry
)
1782 if replace
== TodoItem
.REPLACE
:
1783 print "This task will completely replace the existing entry,"
1784 print "including any projects and actions."
1785 elif replace
== TodoItem
.MODIFY
:
1786 print "Only the elements you add will be replaced. So, for example,"
1787 print "if you don't enter any projects the original projects will remain."
1789 print "Elements you enter will be appended to the current task"
1790 entry
= safeRawInput("Enter new details >>>")
1792 if replace
== TodoItem
.APPEND
:
1793 newItem
= self
.createItem(entry
, password
="unused") # we will discard the encrypted part on extend
1794 elif externalEditor
:
1795 newItem
= self
.createItem(entry
, password
= key
)
1797 newItem
= self
.createItem(entry
)
1798 if newItem
.hasHiddenTask():
1799 clearScreen(self
.sysCalls
)
1800 if newItem
.hasError():
1801 print "The task had errors:"
1802 print newItem
.getError()
1803 print "Task ", index
, " is unchanged."
1805 if newItem
.hasHiddenTask() and replace
== TodoItem
.APPEND
:
1806 self
.showError("It isn't possible to extend the encrypted part of a task.\nThis part is ignored.")
1807 self
.todo
[index
].copy(newItem
, replace
)
1808 print "Task ", index
, " has been changed."
1811 print "Task ", index
, " has not been touched."
1815 if self
.currentTask
< len(self
.todo
) - 1:
1816 self
.currentTask
= self
.currentTask
+ 1
1818 def incTaskLoop(self
):
1819 if self
.currentTask
< len(self
.todo
) - 1:
1820 self
.currentTask
= self
.currentTask
+ 1
1822 self
.currentTask
= 0
1824 if self
.currentTask
> 0:
1825 self
.currentTask
= self
.currentTask
- 1
1827 def decTaskLoop(self
):
1828 if self
.currentTask
> 0:
1829 self
.currentTask
= self
.currentTask
- 1
1831 self
.currentTask
= len(self
.todo
) - 1
1833 def printItemTruncated(self
, index
, leader
):
1834 if len(self
.todo
) < 1:
1835 print leader
, "no tasks"
1837 scrnline
= leader
+ "[%02d] %s" % (index
, self
.todo
[index
].toStringSimple())
1838 if len(scrnline
) > gMaxLen
:
1839 print scrnline
[0:gMaxLen
- 3] + "..."
1844 def printItem(self
, index
, colorType
):
1845 if len(self
.todo
) < 1:
1846 self
.output("There are no tasks to be done.\n", 0)
1849 wrapper
= WordWrapper(gMaxLen
)
1850 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringSimple()))
1851 if colorType
== "row0":
1852 style
= "class=\"evenTask\""
1854 style
= "class=\"oddTask\""
1855 self
.output("<div %s>[%02d] %s</div>\n" % (style
, index
, self
.todo
[index
].toStringSimple()),
1856 gColor
.code(colorType
) + scrnline
+ gColor
.code("normal") + "\n" )
1857 nlines
= wrapper
.getNLines()
1860 def printItemVerbose(self
, index
):
1861 if len(self
.todo
) < 1:
1862 print "There are no tasks to be done."
1865 wrapper
= WordWrapper(gMaxLen
)
1866 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringVerbose()))
1869 def clearFilterArray(self
, local
):
1871 self
.localFilters
= []
1872 self
.localFilterText
= ""
1874 self
.globalFilters
= []
1875 self
.globalFilterText
= ""
1877 def setFilterArray(self
, local
, requiredFilter
):
1878 filters
= requiredFilter
.split()
1880 destination
= self
.localFilters
1882 destination
= self
.globalFilters
1885 for word
in filters
:
1886 if word
[0:1] == "-":
1891 if word
[0:2].upper() == ":D" and len(word
) > 2:
1892 filter = ":D" + TodoItem("").parseDate(word
[2:].strip(), False)
1893 elif word
[0:2].lower() == ":p":
1894 filter = globalPAbbr
.expandProject(word
)
1896 filter = globalAbbr
.expandAction(word
)
1898 filter = "-" + filter
1899 destination
.append(filter)
1900 if humanVersion
!= "":
1901 humanVersion
= humanVersion
+ " " + filter
1903 humanVersion
= filter
1905 for filter in self
.globalFilters
:
1906 destination
.append(filter)
1907 if humanVersion
!= "":
1908 humanVersion
= humanVersion
+ " " + filter
1910 humanVersion
= filter
1912 self
.localFilterText
= humanVersion
1914 self
.globalFilterText
= humanVersion
1916 def isViewable(self
, item
):
1917 if len(self
.globalFilters
) == 0 and len(self
.localFilters
) == 0:
1921 if len(self
.localFilters
) > 0:
1922 filterArray
= self
.localFilters
1924 filterArray
= self
.globalFilters
1925 if "or" in filterArray
or "OR" in filterArray
:
1929 for filter in filterArray
:
1930 if filter.upper() == "OR":
1936 if filter[0:1] == "+":
1939 if filter[0:1] == "-":
1945 if filter[0:1] == "#":
1946 priority
= int(filter[1:], 10)
1951 if self
.exactPriority
:
1952 if item
.hasPriority(priority
):
1954 elif item
.hasPriorityOrAbove(priority
):
1956 elif filter[0:2].upper() == ":D":
1957 if item
.hasDate(filter[2:]):
1959 elif filter[0:2].upper() == ":P":
1960 view
= item
.hasProject(filter)
1961 elif filter[0:1].upper() == "@":
1962 view
= item
.hasAction(filter)
1963 elif item
.hasWord(filter):
1966 view
= (view
!= True)
1974 if fast
or mustHave
:
1979 def listByAction(self
):
1980 index
= SearchIndex()
1981 for item
in self
.todo
:
1982 index
.addCollection(item
.getActions())
1984 (n
, value
) = index
.getFirstItem()
1988 if not gColor
.usingColor() and n
> 0:
1992 self
.setFilterArray(True, "+" + value
)
1993 self
.printList(div
, "<H2 class=\"hAction\">" + value
+ "</H2>\n", gColor
.code("title") + "\n" + value
+ "\n" + gColor
.code("title"))
1994 self
.clearFilterArray(True)
1995 (n
, value
) = index
.getNextItem(n
)
1998 def listByProject(self
):
1999 index
= SearchIndex()
2000 for item
in self
.todo
:
2001 index
.addCollection(item
.getProjects())
2003 (n
, value
) = index
.getFirstItem()
2007 if not gColor
.usingColor() and n
> 0:
2011 self
.setFilterArray(True, "+:p" + value
)
2012 self
.printList(div
, "<H2 class =\"hProject\">Project: " + value
+ "</H2>\n", gColor
.code("title") + "\nProject: " + value
+ gColor
.code("normal") + "\n")
2013 self
.clearFilterArray(True)
2014 (n
, value
) = index
.getNextItem(n
)
2017 def listByDate(self
):
2018 index
= SearchIndex()
2019 for item
in self
.todo
:
2020 index
.add(item
.getDate())
2022 (n
, value
) = index
.getFirstItem()
2026 if not gColor
.usingColor() and n
> 0:
2030 self
.setFilterArray(True, "+:D" + value
)
2031 self
.printList(div
, "<H2 class =\"hDate\">Date: " + value
+ "</H2>\n", gColor
.code("title") + "\nDate: " + value
+ gColor
.code("normal") + "\n")
2032 self
.clearFilterArray(True)
2033 (n
, value
) = index
.getNextItem(n
)
2037 def showFilter(self
):
2038 if self
.globalFilterText
!= "":
2039 self
.output("<H3 class =\"hFilter\">Filter = " + self
.globalFilterText
+ "</H3>\n",
2040 gColor
.code("bold") + "Filter = " + self
.globalFilterText
+ gColor
.code("normal") + "\n")
2042 def showLocalFilter(self
):
2043 if self
.localFilterText
!= "":
2044 self
.output("<H3 class =\"hFilter\">Filter = " + self
.localFilterText
+ "</H3>\n",
2045 gColor
.code("bold") + "Filter = " + self
.localFilterText
+ gColor
.code("normal") + "\n")
2047 def printList(self
, div
, outHtml
, outStd
):
2048 self
.doPrintList(-1, div
, outHtml
, outStd
)
2050 def printShortList(self
, line
):
2053 count
= int(line
, 10)
2055 self
.showError("Didn't understand the number of tasks you wanted listed.")
2056 self
.doPrintList(count
, False, "", "")
2058 def doPrintList(self
, limitItems
, div
, outHtml
, outStd
):
2066 self
.outputHtml("<div class=\"itemGroup\">\n")
2067 for item
in self
.todo
:
2068 if self
.isViewable(item
):
2072 self
.output(outHtml
, outStd
)
2073 if not gColor
.usingColor() and not first
:
2076 count
= count
+ self
.printItem(n
, color
)
2082 displayed
= displayed
+ 1
2084 if limitItems
>= 0 and displayed
>= limitItems
:
2086 if count
>= maxlines
:
2087 if self
.htmlFile
== "":
2088 msg
= safeRawInput("---press Enter for more. Enter s to skip: ")
2089 if len(msg
) > 0 and msg
.strip().upper()[0] == "S":
2093 self
.outputHtml("</div>\n")
2095 def printHelp(self
, lines
):
2096 ListViewer(24).show(lines
,"!PAUSE!")
2098 def splitFile(self
, filename
, dataOnly
):
2102 f
= open(filename
, 'r')
2104 if line
[0:2] == "#!":
2107 if line
.find(magicTag
+ "DATA") == 0:
2111 elif line
.find(magicTag
+ "CONFIG") == 0:
2115 elif line
.find(magicTag
+ "CODE") == 0:
2122 self
.code
.append(line
)
2124 self
.processCfgLine(line
)
2127 if len(line
) > 0 and line
[0] == "#":
2128 line
= line
[1:].strip()
2130 newItem
= self
.createItem(line
)
2132 self
.todo
.append(newItem
)
2137 def createItem(self
, line
, password
= ""):
2138 item
= TodoItem(line
, password
)
2141 def outputHtml(self
, html
):
2142 if self
.htmlFile
!= "":
2143 self
.htmlFile
.write(html
)
2145 def output(self
, html
, stdout
):
2146 if self
.htmlFile
!= "":
2147 #self.htmlFile.write(html.replace("\n", "<br>\n"))
2148 self
.htmlFile
.write(html
)
2155 def startHtml(self
, title
):
2156 htmlFilename
= self
.filename
+ ".html"
2158 self
.htmlFile
= open(htmlFilename
, "w")
2159 self
.htmlFile
.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n")
2160 self
.htmlFile
.write("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n")
2161 self
.htmlFile
.write("<html>\n<head>\n")
2162 self
.htmlFile
.write("<style>\n")
2163 self
.htmlFile
.write(".footer {text-align:center;}\n")
2164 self
.htmlFile
.write("</style>\n")
2165 self
.htmlFile
.write("<link rel=\"stylesheet\" href=\"ikog.css\" type=\"text/css\">\n")
2166 self
.htmlFile
.write("</head>\n<body>\n")
2167 self
.htmlFile
.write("<div class=\"header\">\n")
2168 self
.htmlFile
.write("<H1 class=\"hTitle\">iKog Todo List</H1>\n")
2169 self
.htmlFile
.write("<H2 class=\"hSubTitle\">" + title
+ " printed " + date
.today().isoformat() + "</H1>\n")
2170 self
.htmlFile
.write("</div>\n")
2171 self
.htmlFile
.write("<div class=\"taskArea\">\n")
2173 print "Failed to create output file:", htmlFilename
2177 name
= self
.htmlFile
.name
2180 self
.htmlFile
.write("</div>\n")
2181 self
.htmlFile
.write("<div class=\"footer\">\n")
2182 self
.htmlFile
.write("--- end of todo list ---<br>\n")
2183 self
.htmlFile
.write("Created using " + notice
[0] + "\n<br>" + notice
[1] + "<br>\n")
2184 self
.htmlFile
.write("</div>\n")
2186 self
.htmlFile
.write("</body>\n</html>\n")
2187 self
.htmlFile
.close()
2189 print "HTML file " + name
+ " created."
2191 except Exception, e
:
2192 self
.showError("Error writing to file. " + str(e
))
2196 safeName
= os
.path
.abspath(name
).replace("\\","/")
2197 safeName
= "file://" + urllib
.quote(safeName
," /:")
2198 webbrowser
.open(safeName
)
2199 except Exception, e
:
2200 self
.showError("Unable to launch html output. " + str(e
))
2202 class Abbreviations
:
2203 def __init__(self
, project
= False):
2204 self
.default(project
)
2206 def default(self
,project
):
2210 self
.abbrevs
= {"@A":"@Anywhere","@C":"@Computer",
2211 "@D":"@Desk", "@E": "@Errands",
2212 "@H":"@Home", "@I":"@Internet","@L":"@Lunch", "@M":"@Meeting", "@N":"@Next",
2213 "@P":"@Phone", "@Pw":"@Password", "@S":"@Someday/Maybe",
2214 "@O":"@Other", "@W4":"@Waiting_For", "@W":"@Work"}
2217 def setAbbreviations(self
, abbr
):
2218 self
.abbrevs
.update(abbr
)
2220 def addAbbreviation(self
, key
, word
):
2221 self
.abbrevs
.update({key
.title():word
})
2223 def removeAbbreviation(self
, key
):
2225 if self
.abbrevs
.has_key(key
):
2226 del self
.abbrevs
[key
]
2230 def expandAction(self
, action
):
2231 if action
[0:1] != "@":
2234 action
= action
.title()
2235 if self
.abbrevs
.has_key(action
):
2236 return self
.abbrevs
[action
]
2239 def expandProject(self
, project
):
2240 if not project
.lower().startswith(":p"):
2242 project
= project
.title()
2243 if self
.abbrevs
.has_key(project
):
2244 return self
.abbrevs
[project
]
2248 return str(self
.abbrevs
)
2250 def toStringVerbose(self
):
2253 for key
in self
.abbrevs
:
2254 output
= output
+ key
.ljust(5) + " = " + self
.abbrevs
[key
].ljust(30)
2257 output
= output
+ "\n"
2259 output
= output
+ "\n"
2267 if ent
!= "" and not ent
in self
.items
:
2268 self
.items
.append(ent
)
2270 def addCollection(self
, collection
):
2271 for ent
in collection
:
2272 if ent
!= "" and not ent
in self
.items
:
2273 self
.items
.append(ent
)
2277 def getFirstItem(self
):
2278 if len(self
.items
) > 0:
2279 return (0, self
.items
[0])
2283 def getNextItem(self
, count
):
2285 if count
> len(self
.items
) - 1:
2288 return (count
, self
.items
[count
])
2292 ENCRYPTION_MARKER
= "{}--xx"
2296 NOT_DUE_PRIORITY
= 0
2297 DEFAULT_PRIORITY
= 5
2298 OVERDUE_PRIORITY
= 11
2299 MEETING_PRIORITY
= 10
2300 def __init__(self
,line
, password
= ""):
2303 self
.hiddenTask
= ""
2307 self
.created
= date
.today().isoformat()
2309 self
.autoAction
= False
2310 self
.autoProject
= False
2311 self
.nullDate
= False
2312 self
.parse(line
, password
)
2314 def makeSafeDate(self
, year
, month
, day
):
2321 newDate
= date(year
, month
, day
)
2328 def parseDate(self
, dateStr
, quiet
):
2329 dateStr
= dateStr
.replace("/","-")
2330 dateStr
= dateStr
.replace(":","-")
2331 entry
= dateStr
.split("-")
2335 elif dateStr
== "0":
2336 self
.nullDate
= True
2341 if dateStr
[0:1] == "+":
2342 days
= int(dateStr
[1:].strip(), 10)
2343 when
= now
+ timedelta(days
)
2346 year
= int(entry
[0], 10)
2347 month
= int(entry
[1], 10)
2348 day
= int(entry
[2], 10)
2351 month
= int(entry
[0], 10)
2352 day
= int(entry
[1], 10)
2356 day
= int(entry
[0], 10)
2364 when
= self
.makeSafeDate(year
, month
, day
)
2366 self
.nullDate
= False
2372 self
.addError("Could not decode the date. Use :dYYYY/MM/DD")
2375 return when
.isoformat()
2377 def parse(self
, line
, password
):
2379 words
= line
.split(" ")
2383 ecmLen
= len(self
.ENCRYPTION_MARKER
)
2384 for word
in words
[start
:]:
2385 wordUC
= word
.strip().upper()
2388 taskToHide
= taskToHide
+ word
.strip() + " "
2389 elif word
[0:ecmLen
] == self
.ENCRYPTION_MARKER
:
2390 self
.hiddenTask
= word
[ecmLen
:]
2391 elif wordUC
.startswith("<PRIVATE>") or wordUC
.startswith("<SECRET>") or wordUC
.startswith("<S>") or wordUC
.startswith("<P>"):
2394 pos
= word
.index(">")
2395 taskToHide
= taskToHide
+ word
[pos
+ 1:].strip() + " "
2398 elif word
[0] == "@" and len(word
) > 1:
2399 if wordUC
== "@DATE":
2400 self
.addError("@Date contexts should not be entered. Use :dYYYY-MM-DD")
2402 act
= globalAbbr
.expandAction(word
.strip())
2403 if not act
in self
.actions
:
2404 self
.actions
.append(act
)
2405 elif word
[0:1] == "#" and len(word
) > 1 and self
.priority
== -1:
2407 self
.priority
= int(word
[1:].strip(), 10)
2408 if self
.priority
< 1:
2410 elif self
.priority
> 10:
2413 self
.addError("Did not understand priority.")
2415 elif wordUC
[0:2] == ":P" and len(word
) > 2:
2416 proj
= globalPAbbr
.expandProject(word
.strip())[2:].title()
2417 if not proj
in self
.projects
:
2418 self
.projects
.append(proj
)
2419 elif wordUC
[0:8] == ":CREATED" and len(word
) > 8:
2420 self
.created
= word
[8:].strip()
2421 elif wordUC
[0:2] == ":D" and len(word
) > 2:
2422 self
.when
= self
.parseDate(word
[2:].strip(), False)
2424 self
.task
= self
.task
+ word
.strip() + " "
2425 if taskToHide
!= "":
2428 if ec
.setType(ec
.TYPE_AES
) != ec
.TYPE_AES
:
2429 self
.addError("AES encryption is not available.")
2432 ec
.setType(ec
.TYPE_OBSCURED
)
2433 if taskToHide
!= "":
2435 self
.hiddenTask
= ec
.enterKeyAndEncrypt(taskToHide
)
2438 self
.hiddenTask
= ec
.encrypt(taskToHide
)
2440 if len(self
.actions
) == 0:
2441 self
.actions
.append("@Anywhere")
2442 self
.autoAction
= True
2443 if len(self
.projects
) == 0:
2444 self
.projects
.append("None")
2445 self
.autoProject
= True
2447 def addError(self
, err
):
2448 if len(self
.error
) > 0:
2449 self
.error
= self
.error
+ "\n"
2450 self
.error
= self
.error
+ err
2453 return self
.error
!= ""
2460 def hasWord(self
, word
):
2461 return (self
.task
.upper().find(word
.upper()) >= 0)
2463 def hasAction(self
, loc
):
2464 if self
.when
!= "" and loc
.upper() == "@DATE":
2467 return loc
.title() in self
.actions
2469 def copy(self
, todoItem
, replace
):
2470 if replace
== TodoItem
.REPLACE
or len(todoItem
.task
.strip()) > 0:
2471 if replace
== TodoItem
.APPEND
:
2472 self
.task
= self
.task
+ " ..." + todoItem
.task
2474 self
.task
= todoItem
.task
2475 if replace
== TodoItem
.REPLACE
or todoItem
.autoAction
== False:
2476 if replace
!= TodoItem
.APPEND
:
2478 for loc
in todoItem
.actions
:
2479 if not loc
in self
.actions
:
2480 self
.actions
.append(loc
)
2481 if replace
== TodoItem
.REPLACE
or todoItem
.autoProject
== False:
2482 if replace
!= TodoItem
.APPEND
:
2484 for proj
in todoItem
.projects
:
2485 if not proj
in self
.projects
:
2486 self
.projects
.append(proj
)
2487 if replace
== TodoItem
.REPLACE
or (todoItem
.when
!= "" or todoItem
.nullDate
== True):
2488 self
.when
= todoItem
.when
2489 if todoItem
.priority
>= 0:
2490 self
.priority
= todoItem
.priority
2492 if replace
== TodoItem
.REPLACE
or len(todoItem
.hiddenTask
.strip()) > 0:
2493 if replace
== TodoItem
.APPEND
:
2496 self
.hiddenTask
= todoItem
.hiddenTask
2498 def hasHiddenTask(self
):
2499 return self
.hiddenTask
!= ""
2502 return len(self
.task
.strip()) > 0
2504 def hasProject(self
, proj
):
2505 if proj
[0:2].upper() == ":P":
2507 return proj
.title() in self
.projects
2509 def hasDate(self
, dt
):
2510 dt
= self
.parseDate(dt
, True)
2514 return self
.when
== dt
2516 def hasPriorityOrAbove(self
, priority
):
2517 return (self
.getEffectivePriority() >= priority
)
2519 def hasPriority(self
, priority
):
2520 return (self
.getEffectivePriority() == priority
)
2522 def getHiddenTask(self
):
2523 return self
.hiddenTask
2528 def getActions(self
):
2532 return self
.actions
+ ["@Date"]
2534 def getProjects(self
):
2535 return self
.projects
2540 def getPriority(self
):
2541 if self
.priority
< 0:
2542 return self
.DEFAULT_PRIORITY
2544 return self
.priority
2546 def getEffectivePriority(self
):
2547 userP
= self
.getPriority()
2549 if self
.when
<= date
.today().isoformat():
2550 userP
= self
.OVERDUE_PRIORITY
+ userP
2551 if self
.hasAction("@Meeting"):
2552 userP
= userP
+ self
.MEETING_PRIORITY
2554 userP
= self
.NOT_DUE_PRIORITY
2560 entry
= entry
+ " " + self
.task
2561 for action
in self
.actions
:
2562 entry
= entry
+ " " + action
2563 for project
in self
.projects
:
2564 entry
= entry
+ " :p" + project
2565 entry
= entry
+ " :created" + self
.created
2567 entry
= entry
+ " :d" + self
.when
2568 if self
.priority
>= 0:
2569 entry
= entry
+ " #" + str(self
.priority
)
2570 if self
.hiddenTask
!= "":
2571 entry
= entry
+ " " + self
.ENCRYPTION_MARKER
+ self
.hiddenTask
2574 def toStringEditable(self
, includeHidden
= False):
2578 entry
= entry
+ ":d" + self
.when
+ " "
2579 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2580 if len(self
.actions
) > 0:
2581 for action
in self
.actions
:
2582 entry
= entry
+ " " + action
2583 if len(self
.projects
) > 0:
2584 for project
in self
.projects
:
2586 if project
!= "None":
2587 entry
= entry
+ " :p" + project
2588 if self
.hiddenTask
!= "" and includeHidden
:
2590 entry
= entry
+ " <" + Encryptor().getSecurityClass(self
.hiddenTask
)[0:1] + ">"
2591 entry
= entry
+ ec
.enterKeyAndDecrypt(self
.hiddenTask
)
2592 password
= ec
.getKey()
2593 return (password
, entry
.strip())
2595 def toStringSimple(self
):
2598 entry
= entry
+ "@Date " + self
.when
+ " "
2599 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2600 if self
.hiddenTask
!= "":
2601 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2602 if len(self
.actions
) > 0:
2603 for action
in self
.actions
:
2604 entry
= entry
+ " " + action
2605 if len(self
.projects
) > 0:
2607 for project
in self
.projects
:
2609 if project
!= "None":
2611 entry
= entry
+ " Projects: " + project
2614 entry
= entry
+ ", " + project
2615 #entry = entry + " [" + self.created + "]"
2618 def toStringVerbose(self
):
2619 entry
= gColor
.code("title") + self
.task
2620 if self
.hiddenTask
!= "":
2621 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2622 entry
= entry
+ gColor
.code("bold") + "\nPriority: %02d" % (self
.getPriority())
2623 if len(self
.actions
) or self
.when
!= "" > 0:
2624 entry
= entry
+ gColor
.code("heading") + "\nContext: "
2626 entry
= entry
+ gColor
.code("important") + "@Date " + self
.when
2627 entry
= entry
+ gColor
.code("normal")
2628 for action
in self
.actions
:
2629 entry
= entry
+ " " + action
;
2630 if len(self
.projects
) > 0:
2632 for project
in self
.projects
:
2633 if project
!= "None":
2635 entry
= entry
+ gColor
.code("heading") + "\nProjects: " + gColor
.code("normal");
2636 entry
= entry
+ project
2639 entry
= entry
+ ", " + project
2640 entry
= entry
+ gColor
.code("normal") + "\nCreated: [" + self
.created
+ "]"
2644 #for line in notice:
2647 pythonVer
= platform
.python_version()
2648 ver
= pythonVer
.split(".")
2649 if int(ver
[0]) < gReqPythonMajor
or (int(ver
[0]) == gReqPythonMajor
and int(ver
[1]) < gReqPythonMinor
):
2650 print "\nSorry but this program requires Python ", \
2651 str(gReqPythonMajor
) + "." + str(gReqPythonMinor
), \
2652 "\nYour current version is ", \
2653 str(ver
[0]) + "." + str(ver
[1]), \
2654 "\nTo run the program you will need to install the current version of Python."
2657 # signal.signal(signal.SIGINT, signalHandler)
2658 gColor
= ColorCoder(cfgColor
)
2659 globalAbbr
= Abbreviations()
2660 globalPAbbr
= Abbreviations(project
=True)
2662 if len(sys
.argv
) > 2:
2664 reopen
= sys
.argv
[1]
2666 reopen
= sys
.argv
[0] + ".dat"
2667 for word
in sys
.argv
[2:]:
2669 commandList
.append(command
)
2672 command
= command
+ word
+ " "
2673 commandList
.append(command
)
2674 elif len(sys
.argv
) > 1:
2675 reopen
= sys
.argv
[1]
2677 reopen
= sys
.argv
[0] + ".dat"
2680 todoList
= TodoList(sys
.argv
[0], reopen
)
2681 reopen
= todoList
.run(commandList
)