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 # group translations based on language @Computer :pWebsite :created2008-11-22
13 # show shorts with language @Computer :pWebsite :created2008-11-22
14 # show shorts in word complete @Computer :pWebsite :created2008-11-22
15 # on multi word, there are extra "edit" links @Computer :pWebsite :created2008-11-23
16 # get translations from wiktionary @Computer :pImport :created2008-11-23
17 # get meaning from wordnet @Computer :pImport :created2008-11-23
18 # i18n tokens in all templates/views/models/js @Computer :pI18N :created2008-11-23
19 # create translation files @Computer :pI18N :created2008-11-23
20 # enable i18n in django to select translation based on browser settings @Computer :pI18N :created2008-11-23
21 # create language list from wiktionary @Computer :pImport :created2008-11-23
22 # edit short @Computer :pWebsite :created2008-11-23
23 # add short in new language @Computer :pWebsite :created2008-11-23
24 # multi word ui issue @Computer :pWebsite :created2008-11-23
25 # multi word view # based collapse @Computer :pWebsite :created2008-11-23
26 # javascript to follow 18n preference @Computer :pI18N :created2008-11-23
27 # add language selector @Computer :pI18N :created2008-11-23
28 # push to wiktionary @Computer :pImport :created2008-11-23
29 # check out http://www.thefreedictionary.com/sources.htm @Computer @Internet :pImport :created2008-11-25
36 cfgEditorPosix
= "nano,pico,vim,emacs"
37 cfgShortcuts
= ['', '', '', '', '', '', '', '', '', '']
38 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'}
39 cfgPAbbreviations
= {':Pi': ':pImport', ':P1': ':pI18N', ':Pw': ':pWebsite'}
44 from datetime
import date
45 from datetime
import timedelta
52 from threading
import Timer
67 "ikog.py v 1.88 2007-11-23",
68 "Copyright (C) 2006-2007 S. J. Butler",
69 "Visit http://www.henspace.co.uk for more information.",
70 "This program is free software; you can redistribute it and/or modify",
71 "it under the terms of the GNU General Public Licence as published by",
72 "the Free Software Foundation; either version 2 of the License, or",
73 "(at your option) any later version.",
75 "This program is distributed in the hope that it will be useful,",
76 "but WITHOUT ANY WARRANTY; without even the implied warranty of",
77 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
78 "GNU General Public License for more details. The license is available",
79 "from http://www.gnu.org/licenses/gpl.txt"
84 " (_) | | __ ___ __ _",
85 " | | | |/ / / _ \ / _` |",
86 " | | | < | (_) | | (_| |",
87 " |_| |_|\_\ \___/ \__, |",
89 " (_) | |_ | | __ ___ ___ _ __ ___ ___ _ __",
90 " | | | __| | |/ / / _ \ / _ \ | '_ \ / __| / _ \ | '_ \ ",
91 " | | | |_ | < | __/ | __/ | |_) | \__ \ | (_) | | | | |",
92 " |_| \__| |_|\_\ \___| \___| | .__/ |___/ \___/ |_| |_|",
94 " __ _ _ __ ___ __ __ (_) _ __ __ _ ",
95 " / _` | | '__| / _ \ \ \ /\ / / | | | '_ \ / _` |",
96 " | (_| | | | | (_) | \ V V / | | | | | | | (_| | _",
97 " \__, | |_| \___/ \_/\_/ |_| |_| |_| \__, | (_)",
106 ruler
= "~".ljust(gMaxLen
- 1, "~")
107 divider
= "_".ljust(gMaxLen
- 1, "_")
109 print "Error found. Probably wrong version of Python"
115 def safeRawInput(prompt
):
117 entry
= raw_input(prompt
)
123 ### global compare function
124 def compareTodo(a
, b
):
125 return cmp(a
.getEffectivePriority(), b
.getEffectivePriority())
128 print gColor
.code("error") + "ERROR: " + msg
+ gColor
.code("normal")
131 def clearScreen(useSys
= False):
133 if os
.name
== "posix":
135 elif os
.name
in ("dos", "ce", "nt"):
141 ### XTEA algorithm public domain
146 def crypt(self
, key
,data
,iv
='\00\00\00\00\00\00\00\00',n
=32):
147 def keygen(key
,iv
,n
):
149 iv
= self
.xtea_encrypt(key
,iv
,n
)
152 xor
= [ chr(x^y
) for (x
,y
) in zip(map(ord,data
),keygen(key
,iv
,n
)) ]
155 def xtea_encrypt(self
, key
,block
,n
=32):
156 v0
,v1
= struct
.unpack("!2L",block
)
157 k
= struct
.unpack("!4L",key
)
158 sum,delta
,mask
= 0L,0x9e3779b9L
,0xffffffffL
159 for round in range(n
):
160 v0
= (v0
+ (((v1
<<4 ^ v1
>>5) + v1
) ^
(sum + k
[sum & 3]))) & mask
161 sum = (sum + delta
) & mask
162 v1
= (v1
+ (((v0
<<4 ^ v0
>>5) + v0
) ^
(sum + k
[sum>>11 & 3]))) & mask
163 return struct
.pack("!2L",v0
,v1
)
166 def __init__(self
, width
):
171 def addLine(self
, pos
):
173 self
.nLines
= self
.nLines
+ 1
178 def intelliLen(self
, text
):
179 return len(gColor
.stripCodes(text
))
181 def wrap(self
, text
):
183 formatted
= text
.replace("<br>", "\n").replace("<BR>", "\n")
184 lines
= formatted
.splitlines()
187 for thisline
in lines
:
189 words
= thisline
.split()
194 wlen
= self
.intelliLen(w
) + 1
195 if (self
.pos
+ wlen
) == self
.width
:
198 elif (self
.pos
+ wlen
) < self
.width
:
204 self
.pos
= self
.pos
+ wlen
+ 1
211 ### Color code class for handling color text output
215 codes
= [{"normal":"\x1b[0;37;40m",
216 "title":"\x1b[1;32;40m",
217 "heading":"\x1b[1;35;40m",
218 "bold":"\x1b[1;35;40m",
219 "important":"\x1b[1;31;40m",
220 "error":"\x1b[1;31;40m",
221 "reverse":"\x1b[0;7m",
222 "row0":"\x1b[0;35;40m",
223 "row1":"\x1b[0;36;40m"},
224 {"normal":"\x1b[0;37m",
225 "title":"\x1b[1;32m",
226 "heading":"\x1b[1;35m",
228 "important":"\x1b[1;31m",
229 "error":"\x1b[1;31m",
230 "reverse":"\x1b[0;7m",
232 "row1":"\x1b[0;36m"}]
234 def __init__(self
, set):
235 self
.codeSet
= self
.NONE
238 def stripCodes(self
, text
):
239 # strip out the ansi codes
240 ex
= re
.compile("\x1b\[[0-9;]*m")
241 return ex
.sub("", text
)
243 def setCodeSet(self
, set):
246 self
.codeSet
= self
.NONE
247 elif set < len(self
.codes
):
249 return (old
!= self
.codeSet
)
251 def isValidSet(self
, myset
):
252 if myset
< len(self
.codes
):
257 def colorSupported(self
):
258 return (os
.name
== "posix" or os
.name
== "mac")
260 def usingColor(self
):
261 return (self
.codeSet
<> self
.NONE
and self
.colorSupported())
263 def code(self
, type):
264 if self
.codeSet
== self
.NONE
or not self
.colorSupported():
267 return self
.codes
[self
.codeSet
][type]
269 def printCode(self
, type):
270 if self
.codeSet
!= self
.NONE
:
271 print self
.code(type),
273 def getCodeSet(self
):
276 ### Viewer class for paging through multiple lines
278 def __init__(self
, maxlines
):
279 self
.maxlines
= maxlines
281 def show(self
, list, pause
):
284 if count
>= self
.maxlines
or line
== pause
:
285 io
= safeRawInput("--- Press enter for more. Enter s to skip ---").strip()
287 if len(io
) > 0 and io
.upper()[0] == "S":
294 ### Handler for encryption
296 TYPE_OBSCURED
= "xtea_"
302 self
.encryptionType
= self
.TYPE_OBSCURED
304 def setType(self
, codeType
):
305 if codeType
== self
.TYPE_AES
and supportAes
== False:
306 self
.encryptionType
= self
.TYPE_OBSCURED
308 self
.encryptionType
= codeType
309 return self
.encryptionType
311 def setKey(self
, key
):
317 def enterKey(self
, prompt1
, prompt2
):
320 input1
= getpass
.getpass(prompt1
+ " >>>")
322 input2
= getpass
.getpass(prompt2
+ " >>>")
324 print "You must enter the same password. Start again"
332 def complexKey(self
):
333 return md5(self
.key
).digest()
335 def getSecurityClass(self
, encrypted
):
336 if encrypted
.startswith(self
.TYPE_OBSCURED
):
337 return "private xtea"
338 if encrypted
.startswith(self
.TYPE_AES
):
342 def obscure(self
, plainText
):
343 key
= self
.complexKey()
344 obscured
= Xtea().crypt(key
, plainText
, self
.SALT_64
)
345 return self
.TYPE_OBSCURED
+ obscured
.encode('hex_codec')
347 def unobscure(self
, obscured
):
349 data
= obscured
[len(self
.TYPE_OBSCURED
):]
350 data
= data
.decode('hex_codec')
351 key
= self
.complexKey()
352 plain
= Xtea().crypt(key
, data
, self
.SALT_64
)
355 def encryptAes(self
, plainText
):
356 if len(self
.key
) < 16:
357 key
= self
.complexKey()
360 obscured
= pyRijndael
.EncryptData(key
, plainText
)
361 return self
.TYPE_AES
+ obscured
.encode('hex_codec')
363 def decryptAes(self
, encrypted
):
365 data
= encrypted
[len(self
.TYPE_AES
):]
366 data
= data
.decode('hex_codec')
367 if len(self
.key
) < 16:
368 key
= self
.complexKey()
371 plain
= pyRijndael
.DecryptData(key
, data
)
374 def enterKeyAndEncrypt(self
, plainText
):
375 self
.enterKey("Enter the master password.", "Re-enter the master password")
376 return self
.encrypt(plainText
)
378 def encrypt(self
, plainText
):
379 if self
.encryptionType
== self
.TYPE_AES
:
380 return self
.encryptAes(plainText
)
382 return self
.obscure(plainText
)
384 def enterKeyAndDecrypt(self
, encryptedText
):
385 self
.enterKey("Enter your master password", "")
386 return self
.decrypt(encryptedText
)
388 def decrypt(self
, encryptedText
):
389 if encryptedText
.startswith(self
.TYPE_AES
):
391 return "You do not have the pyRinjdael module so the text cannot be decrypted."
393 return self
.decryptAes(encryptedText
)
395 return self
.unobscure(encryptedText
)
397 ### Handler for user input
399 def __init__(self
, prompt
):
402 def read(self
, entry
= ""):
404 entry
= safeRawInput(self
.prompt
)
405 entry
= entry
.strip()
410 if entry
.find(magicTag
) == 0:
411 printError("You cannot begin lines with the sequence " + magicTag
)
414 elif entry
.find(TodoItem
.ENCRYPTION_MARKER
) >= 0:
415 printError ("You cannot use the special sequence " + TodoItem
.ENCRYPTION_MARKER
)
427 return (command
, line
)
429 class EditorLauncher
:
430 WARNING_TEXT
= "# Do not enter secret or private information!"
434 def edit(self
, text
):
437 if os
.name
== "posix":
439 elif os
.name
== "nt":
443 printError("Sorry, but external editing not supported on " + os
.name
.upper())
446 fname
= self
.makeFile(text
, terminator
)
448 printError("Unable to create temporary file.")
450 success
= self
.run(ed
, fname
)
452 (success
, text
) = self
.readFile(fname
)
453 if text
== self
.orgText
:
454 print("No changes made.");
456 self
.scrubFile(fname
)
462 def scrubFile(self
, fname
):
466 printError("Failed to remove file " + fname
+ ". If you entered any private data you should delete this file yourself.")
468 def readFile(self
, fname
):
471 fh
= open(fname
, "rt")
476 thisLine
= self
.safeString(line
)
477 if thisLine
!= self
.WARNING_TEXT
:
480 text
= text
+ thisLine
486 printError("Error reading the edited text. " + str(e
))
487 return (success
, text
)
489 def safeString(self
, text
):
490 return text
.replace("\r","").replace("\n","")
492 def makeFile(self
, text
, terminator
):
494 (fh
, fname
) = tempfile
.mkstemp(".tmpikog","ikog")
495 fout
= os
.fdopen(fh
,"wt")
496 text
= text
.replace("<BR>", "<br>")
498 lines
= text
.split("<br>")
499 fout
.write(self
.WARNING_TEXT
+ terminator
)
500 for thisline
in lines
:
501 fout
.write(self
.safeString(thisline
) + terminator
)
505 def run(self
, program
, file):
506 progs
= program
.split(",")
508 success
= self
.runProgram(prog
.strip(), file)
513 def runProgram(self
, program
, file):
515 if os
.name
== "posix":
518 os
.spawnlp(os
.P_WAIT
, program
, progarg
, file)
524 elif os
.name
== "nt":
525 if file.find(" ") >= 0:
526 file = "\"" + file + "\""
527 for path
in os
.environ
["PATH"].split(os
.pathsep
):
529 prog
= os
.path
.join(path
, program
)
530 if prog
.find(" ") >= 0:
531 progarg
= "\"" + prog
+ "\""
534 os
.spawnl(os
.P_WAIT
, prog
, progarg
, file)
545 ### The main todo list
547 quickCard
= ["Quick reference card:",
548 "? ADD/A/+ text FILTER/FI [filter]",
549 "HELP/H IMMEDIATE/I/++ text TOP/T [N]",
550 "COLOR/COLOUR/C [N] KILL/K/X/- N NEXT/N",
551 "MONOCHROME/MONO CLEAR PREV/P",
552 "EXPORT REP/R N [text] GO/G N",
553 "IMPORT file MOD/M N [text] LIST/L [filter]",
554 "REVIEW/REV ON/OFF EXTEND/E N [text] LIST>/L> [filter]",
556 "V1 SUB/SU N /s1/s2/ :D",
558 "SAVE/S DOWN/D N @>",
559 "AUTOSAVE/AS ON|OFF UP/U N :D>",
560 "VER/VERSION NOTE/NOTES text :P>",
561 "CLEARSCREEN/CLS O/OPEN file SHOW N",
562 "SYS ON|OFF NEW file SETEDxx editor",
563 "!CMD command 2 ABBREV/AB @x @full",
564 "ABBREV/AB ? PAB ? PAB :px :pfull",
565 "SHORTCUT/SC N cmd SHORTCUT/SC ? =N",
566 "ARCHIVE/DONE N [text]",
572 "The program is designed to help manage tasks using techniques",
573 "such as Getting Things Done by David Allen. Check out",
574 "http://www.henspace.co.uk for more information and detailed help.",
575 "To use the program, simply enter the task at the prompt.",
576 "All of the commands are displayed in the next section.",
580 "Commands that have more than one method of entry are shown separated by /",
581 "e.g HELP/H means that you can enter either HELP or an H.",
582 "All commands can be entered in upper or lower case.",
583 "Items shown in square brackets are optional.",
584 "Items shown separated by the | symbol are alternatives. e.g ON|OFF means",
585 "you should type either ON or OFF.",
586 "Note that some commands refer to adding tasks to the top or bottom of the",
587 "list. However the task's position in the list is also determined by its.",
588 "priority. So, for example, adding a task to the top will still not allow",
589 "it to precede tasks that have been assigned a higher priority number. ",
593 "? : displays a quick reference card",
594 "HELP/H : displays this help.",
595 "VERSION/VER : display the version.",
596 "WEB : Go to the website for more information",
597 "CLEARSCREEN/CLS : Clear the screen",
598 "COLOR/COLOUR/C [N] : Use colour display (not Windows) N=1 for no background",
599 "MONOCHROME/MONO : Use monochrome display",
600 "EXPORT : Export the tasks only to filename.tasks.txt",
601 "IMPORT file : Import tasks from the file",
602 "REVIEW/REV ON|OFF : If on, hitting enter moves to the next task",
603 " : If off, enter re-displays the current task",
604 "V0 : Same as REVIEW OFF",
605 "V1 : Same as REVIEW ON",
606 "SAVE/S : Save the tasks",
607 "O/OPEN file : Open a new data file.",
608 "NEW file : Create a new data file.",
609 "AUTOSAVE/AS ON|OFF : Switch autosave on or off",
610 "SYS ON|OFF : Allow the program to use system calls.",
611 "!CMD command : Run a system command.",
612 "2 : Start a two minute timer (for GTD)",
613 "QUIT/Q : quit the program",
615 "TASK ENTRY AND EDITING COMMANDS",
616 "-------------------------------",
617 "For the editing commands that require a task number, you can",
618 "replace N by '^' or 'this' to refer to the current task.",
619 "ADD/A/+ the task : add a task to the bottom of the list.",
620 " : Entering any line that does not begin with",
621 " : a valid command and which is greater than 10",
622 " : characters long is also assumed to be an addition.",
623 "EDIT/ED [N] : Create task, or edit task N, using external editor.",
624 "SUB/SU N /s1/s2/ : Replace text s1 with s2 in task N. Use \/ if you",
625 " : need to include the / character.",
626 "NOTE/NOTES text : shorthand for ADD #0 @Notes text",
627 "IMMEDIATE/I/++ : add a task to the top of the list to do today.",
628 "REP/R N [text] : replace task N",
629 "MOD/M N [text] : modify task N.",
630 "EXTEND/E N [text] : add more text to task N",
631 "FIRST/F N : move task N to the top.",
632 "DOWN/D/ N : move task N down the queue",
633 "UP/U/ N : move task N up the queue",
635 "TASK REMOVAL COMMANDS",
636 "---------------------",
637 "KILL/K/X/- N : kill (delete) task N. You must define N",
638 "DONE N [text] : Remove task N and move to an archive file",
639 "ARCHIVE N [text] : Same as DONE",
640 "CLEAR : Remove all tasks",
644 "SHOW N : display encrypted text for task N",
645 "FILTER/FI [filter] : set a filter. Applies to all displays",
646 " : See list for details of the filter",
647 " : Setting the filter to nothing clears it.",
648 "TOP/T [N] : Go to top, list N tasks, and display the top task",
649 "NEXT/N : display the next task. Same as just hitting enter",
650 "PREV/P : display previous task",
651 "GO/G N : display task N",
652 "LIST/L [filter] : list tasks. Filter = context, project, priority, date",
653 " : or word. Contexts begin with @ and projects with :p",
654 " : Dates begin with :d, anything else is a search word.",
655 " : Precede term with - to exclude e.g. -@Computer",
656 " : e.g LIST @computer or LIST #5",
657 "@ : sorted list by Context.",
658 ":D : sorted list by Dates",
659 ":P : sorted list by Projects",
660 "LIST>/L> [filter] : standard list sent to an HTML report",
661 "@> : sorted list by Context sent to an HTML report",
662 ":D> : sorted list by Dates sent to an HTML report",
663 ":P> : sorted list by Projects sent to an HTML report",
664 " : The HTML reports are sent to todoFilename.html",
668 "The SETEDxxx commands allow you to use an external editor.",
669 "Note the editor you pick should be a simple text editor. If you pick",
670 "something that doesn't work, try the defaults again.",
671 "Because some systems may have different editors installed, you can set",
672 "more than one by separating the editors usng commas. The program will",
673 "use the first one it finds.",
674 "For Windows the default is edit, which works quite well in the terminal",
675 "but you could change it to notepad.",
676 "For Linux, the default is nano,pico,vim,emacs.",
677 "To use external editors you must switch on system calls using the SYS ON",
679 "SETEDNT command : Set the external editor for Windows (NT).",
680 "SETEDPOSIX command : Set the editor for posix systems.",
681 " : e.g. SETEDNT edit",
682 " : SETEDPOSIX nano,vim",
683 "SHORTCUT/SC ? : list shortcuts",
684 "SHORTCUT/SC N cmd : Set shortcut N to command cmd",
685 "=N : Run shortcut N",
687 "ABBREV/AB @x @full : Create new abbreviation. @x expands to @full",
688 "ABBREV/AB ? : List context abbreviations.",
689 "PAB :px :pfull : Project abbreviation. :px expands to :pfull",
690 "PAB ? : List project abbreviations.",
694 "When you enter a task, you can embed any number of contexts in the task.",
695 "You can also embed a project description by preceding it with :p",
696 "You can assign a priority by preceding a number by #. e.g. #9.",
697 "If you don't enter a number, a default of 5 is used. The higher the ",
698 "number the more important it is. Priorities range from 1 to 10.",
699 "Only the first # is used for the priority so you can use # as",
700 "a normal character as long as you precede it with a priority number.",
701 "You can define a date when the task must be done by preceding the date",
702 "with :d, i.e :dYYYY/MM/DD or :dMM/DD or :dDD. If you omit the year/month",
703 "they default to the current date. Adding a date automatically creates an",
704 "@Date context for the task.",
705 "So, for example, to add a new task to e-mail Joe, we could enter:",
706 "+ e-mail joe @computer",
707 "or to add a task to the decorating project, we could enter:",
708 "+ buy wallpaper :pdecorating",
709 "to enter a task with an importance of 9 we could enter:",
710 "+ book that holiday #9 @Internet",
712 "MODIFYING AND EXTENDING TASKS",
713 "-----------------------------",
714 "The modify command allows you to change part of an existing task.",
715 "So for example, imagine you have a task:",
716 "[05] Buy some food #9 @Internet Projects:Shopping",
717 "Enter the command M 5 and then type:",
719 "Because the only element we have entered is a new context, only",
720 "that part is modified, so we get.",
721 "[05] Buy some food #9 @Computer Projects:Shopping",
722 "Likewise, had we entered:",
723 "Buy some tea :pEating",
725 "[05] Buy some tea #9 @Internet Projects:Eating",
726 "The extend command is similar but it appends the entry. So had",
727 "we used the command E 5 instead of M 5 the result would have been",
728 "[05] Buy some food ... Buy some tea #9 @Internet Projects:Eating",
732 "Any word preceded by @ will be used as a context. Contexts are like",
733 "sub-categories or sub-lists. There are a number of pre-defined",
734 "abbreviations that you can use as well. The recognised abbreviations",
736 "@A = @Anywhere (this is the default)",
748 "@S = @Someday/maybe",
754 "An @Date context is created if you embed a date in the task.",
755 "Dates are embedded using the :dDATE format.",
756 "Valid DATE formats are yyyy-mm-dd, mm-dd or dd",
757 "You can also use : or / as the separators. So, for example:",
758 ":d2006/12/22 or :d2006-11-7 or :d9/28 are all valid entries.",
760 "If you set a date, then until that date is reached, the task is given",
761 "an effective priority of 0. Once the date is reached, the task's",
762 "priority is increased by 11, moving it to the of the list.",
764 "A date entry of :d0 can be used to clear a date entry.",
765 "A date entry of :d+X can be used to create a date entry of today + X days.",
766 "So :d+1 is tomorrow and :d+0 is today.",
770 "If you want to encrypt text you can use the <private> or <secret> tags or",
771 "their abbreviations <p> and <s>.",
772 "These tags will result in all text following the tag to be encrypted.",
773 "Note that any special commands, @contexts for example, are treated as plain",
774 "text in the encrypted portion.",
775 "To display the text you will need to use the SHOW command.",
777 "The <private> tag uses the inbuilt XTEA algorithm. This is supposedly a",
778 "relatively secure method but probably not suitable for very sensitive data.",
780 "The <secret> tag can only be used if you have the pyRijndael.py module.",
781 "This uses a 256 bit Rinjdael cipher. The module can be downloaded from ",
782 "http://jclement.ca/software/pyrijndael/",
783 "You can install this in your Python path or just place it alongside your",
785 "Note you cannot use the extend command with encrypted text.",
788 "MARKING TASKS AS COMPLETE",
789 "-------------------------",
790 "The normal way to mark a task as complete is just to remove it using the",
791 "KILL command. If you want to keep track of tasks you have finished, you",
792 "can use the ARCHIVE or DONE command. This gives the task an @Archived",
793 "context, changes the date to today and then moves it from the current",
794 "file to a file with archive.dat appended. The archive file is a valid",
795 "ikog file so you can use the OPEN command to view it, edit it and run",
796 "reports in the normal way. So assuming your current script is ikog.py,",
797 "to archive the current task you could enter:",
799 "ARCHIVE ^ I have finished this",
801 "This would move the task to a file called ikog.py.archive.dat",
804 "USING EXTERNAL DATA",
805 "-------------------",
806 "Normally the tasks are embedded in the main program file so all you have",
807 "to carry around with you is the ikog.py file. The advantage is that you",
808 "only have one file to look after; the disadvantage is that every time you",
809 "save a task you have to save the program as well. If you want, you can",
810 "keep your tasks in a separate file.",
811 "To do this, use the EXPORT function to create a file ikog.py.tasks.txt",
812 "Use the CLEAR command to remove the tasks from your main ikog.py program.",
813 "Rename the exported file from ikog.py.tasks.txt to ikog.py.dat",
814 "Ikog will now use this file for storing your tasks.",
817 "PASSING TASKS VIA THE COMMAND LINE",
818 "----------------------------------",
819 "It is possible to add tasks via the command line. The general format of",
820 "the command line is:",
821 " ikog.py filename commands",
822 "The filename is the name of the data file containing your tasks. You",
823 "can use . to represent the default internal tasks.",
824 "Commands is a set of normal ikog commands separated by the / ",
825 "character. Note there must be a space either side of the /.",
826 "So to add a task and then exit the program we could just enter:",
827 " ikog.py . + here is my task / QUIT",
828 "Note that we added the quit command to exit ikog.",
829 "You must make sure that you do not use any commands that require user",
830 "input. Deleting tasks via the command line is more complicated as you",
831 "need to find the task automatically. If you do try to delete this way,",
832 "use the filter command to find some unique text and then delete it. eg.",
833 " ikog.py . FI my_unique_text / KILL THIS / QUIT",
834 "Use THIS instead of ^ as a caret has a special meaning in Windows.",
835 "If you do intend automating ikog from the command line, you should add",
836 "a unique reference to each task so you can find it later using FILTER. eg.",
837 "+ this is my task ref_1256",
845 def __init__(self
, todoFile
, externalDataFile
):
852 self
.sysCalls
= False
854 self
.globalFilterText
= ""
855 self
.globalFilters
= []
856 self
.localFilterText
= ""
857 self
.localFilters
= []
858 # split the file into the source code and the todo list
859 self
.filename
= todoFile
861 self
.filename
= os
.readlink(todoFile
)
863 pass # probably windows
864 externalDataFile
= self
.makeFilename(externalDataFile
)
865 self
.externalData
= self
.findDataSource(externalDataFile
)
866 if self
.externalData
:
867 self
.filename
= externalDataFile
869 self
.splitFile(self
.filename
, False)
870 self
.exactPriority
= False
873 def setPAbbreviation(self
, line
):
875 elements
= line
.split(" ", 1)
876 if not elements
[0].lower().startswith(":p"):
877 self
.showError("Project abbreviations must begin with :p")
878 elif len(elements
) > 1:
879 if not elements
[1].lower().startswith(":p"):
880 abb
= ":p" + elements
[1].title()
882 abb
= ":p" +elements
[1][2:].title()
883 globalPAbbr
.addAbbreviation(elements
[0], abb
)
886 if not globalPAbbr
.removeAbbreviation(elements
[0]):
887 self
.showError("Could not find project abbreviation " + line
)
889 print "Project abbreviation ", line
, " removed."
893 def showPAbbreviations(self
):
894 print globalPAbbr
.toStringVerbose()
896 def setAbbreviation(self
, line
):
898 elements
= line
.split(" ", 1)
899 if not elements
[0].startswith("@"):
900 self
.showError("Abbreviations must begin with @")
901 elif len(elements
) > 1:
902 if not elements
[1].startswith("@"):
903 abb
= "@" + elements
[1].title()
905 abb
= "@" +elements
[1][1:].title()
906 globalAbbr
.addAbbreviation(elements
[0], abb
)
909 if not globalAbbr
.removeAbbreviation(elements
[0]):
910 self
.showError("Could not find abbreviation " + line
)
912 print "Abbreviation ", line
, " removed."
916 def showAbbreviations(self
):
917 print globalAbbr
.toStringVerbose()
919 def setShortcut(self
, line
, force
= False):
920 elements
= line
.split(" ", 1)
922 index
= int(elements
[0])
923 if len(elements
) > 1:
924 command
= elements
[1]
928 self
.showError("Did not understand the command. Format should be SHORTCUT N my command.")
931 if index
< 0 or index
> len(self
.shortcuts
):
932 self
.showError("The maximum number of shortcuts is " + str(len(self
.shortcuts
)) + ". Shortcuts ignored.")
935 if self
.shortcuts
[index
] != "" and not force
:
936 if safeRawInput("Do you want to change the current command '" + self
.shortcuts
[index
] + "'? Enter Yes to overwrite. >>>").upper() != "YES":
938 self
.shortcuts
[index
] = command
942 def showShortcuts(self
):
944 for s
in self
.shortcuts
:
949 print "=%1d %s" %(index
, msg
)
952 def setShortcuts(self
, settings
= []):
953 if len(settings
) > self
.MAX_SHORTCUTS
:
954 self
.showError("The maximum number of shortcuts is " + str(self
.MAX_SHORTCUTS
) + ". Shortcuts ignored.")
955 self
.shortcuts
= ["" for n
in range(self
.MAX_SHORTCUTS
)]
956 if len(settings
) > 0:
957 self
.shortcuts
[0:len(settings
)] = settings
959 def getShortcutIndex(self
, command
):
960 if len(command
) == 2 and command
[0:1].upper() == "=":
961 index
= ord(command
[1]) - ord("0")
962 if index
>= self
.MAX_SHORTCUTS
:
968 def getShortcut(self
, command
):
969 index
= self
.getShortcutIndex(command
)
971 return self
.shortcuts
[index
]
975 def safeSystemCall(self
, line
):
978 self
.showError("Nothing to do.")
979 elif words
[0].upper() == "RM" or words
[0].upper() == "RMDIR" or words
[0].upper() == "DEL":
980 self
.showError("Sorry, but deletion commands are not permitted.")
985 def processCfgLine(self
, line
):
986 params
= line
.split("=")
989 cmd
= params
[0].strip()
990 if cmd
== "cfgEditorNt":
992 cfgEditorNt
= params
[1].replace("\"", "").strip()
993 elif cmd
== "cfgEditorPosix":
994 global cfgEditorPosix
995 cfgEditorPosix
= params
[1].replace("\"", "").strip()
996 elif cmd
== "cfgShortcuts":
997 elements
= params
[1].strip()[1:-1].split(",")
1000 self
.setShortcut(str(index
) + " " + e
.strip()[1:-1], True)
1002 elif cmd
== "cfgAutoSave":
1003 if params
[1].upper().strip() == "TRUE":
1007 self
.setAutoSave(as_
, False)
1008 elif cmd
== "cfgReviewMode":
1009 if params
[1].upper().strip() == "TRUE":
1010 self
.setReview("ON")
1012 self
.setReview("OFF")
1013 elif cmd
== "cfgSysCalls":
1014 if params
[1].upper().strip() == "TRUE":
1015 self
.setSysCalls("ON")
1017 self
.setSysCalls("OFF")
1018 elif cmd
== "cfgColor":
1019 gColor
.setCodeSet(int(params
[1].strip()))
1020 elif cmd
== "cfgAbbreviations":
1021 abbrs
= eval(params
[1].strip())
1022 globalAbbr
.setAbbreviations(abbrs
)
1023 elif cmd
== "cfgPAbbreviations":
1024 abbrs
= eval(params
[1].strip())
1025 globalPAbbr
.setAbbreviations(abbrs
)
1027 self
.showError("Unrecognised command " + cmd
)
1029 def makeFilename(self
, name
):
1030 (root
, ext
) = os
.path
.splitext(name
)
1031 if ext
.upper() != ".DAT":
1032 name
= name
+ ".dat"
1034 name
= os
.path
.expanduser(name
)
1035 except Exception, e
:
1036 self
.showError("Failed to expand path. " + str(e
))
1039 def findDataSource(self
, filename
):
1043 self
.splitFile(filename
, False)
1044 print "Using external data file ", filename
1047 print "No external data file ", filename
, ", so using internal tasks."
1050 def setSysCalls(self
, mode
):
1051 oldCalls
= self
.sysCalls
1052 mode
= mode
.strip().upper()
1054 self
.sysCalls
= True
1055 print "Using system calls for clear screen"
1057 self
.sysCalls
= False
1058 print "No system calls for clear screen"
1060 self
.showError("Could not understand the sys command. Use SYS ON or OFF.")
1061 return (self
.sysCalls
!= oldCalls
)
1063 def setAutoSave(self
, as_
, save
):
1065 if self
.autoSave
== False:
1066 self
.autoSave
= True
1069 elif self
.autoSave
== True:
1070 self
.autoSave
= False
1074 print "Autosave is on."
1076 print "Autosave is off."
1079 def showError(self
, msg
):
1082 def pause(self
, prompt
= "Press enter to continue."):
1083 if safeRawInput(prompt
).strip() != "":
1084 print "Entry ignored!"
1086 def setReview(self
, mode
):
1087 oldReview
= self
.review
1088 mode
= mode
.strip().upper()
1091 print "In review mode. Enter advances to the next task"
1094 print "Review mode off. Enter re-displays the current task"
1096 self
.showError("Could not understand the review command. Use REVIEW ON or OFF.")
1097 return (self
.review
!= oldReview
)
1099 def sortByPriority(self
):
1100 self
.todo
.sort(key
=TodoItem
.getEffectivePriority
, reverse
= True)
1103 def run(self
, commandList
):
1105 print "AES encryption not available."
1106 print("\nEnter HELP for instructions.")
1110 self
.sortByPriority()
1113 truncateTask
= False
1115 self
.checkCurrentTask()
1117 self
.moveToVisible()
1120 self
.printItemTruncated(self
.currentTask
, "Current: ")
1122 self
.printItemVerbose(self
.currentTask
)
1125 truncateTask
= False
1130 if len(commandList
) >= 1:
1131 enteredLine
= commandList
[0]
1132 commandList
= commandList
[1:]
1134 (rawcommand
, line
) = InputParser(prompt
).read(enteredLine
)
1136 command
= rawcommand
.upper()
1137 if self
.getShortcutIndex(command
) >= 0:
1138 sc
= self
.getShortcut(command
)
1140 (rawcommand
, line
) = InputParser("").read(self
.getShortcut(command
))
1145 print "Shortcut: ", rawcommand
, " ", line
1146 command
= rawcommand
.upper()
1150 elif command
== "PAB":
1151 if line
.strip() == "?":
1152 self
.showPAbbreviations()
1153 elif self
.setPAbbreviation(line
):
1155 elif command
== "ABBREV" or command
== "AB":
1156 if line
.strip() == "?":
1157 self
.showAbbreviations()
1158 elif self
.setAbbreviation(line
):
1160 elif command
== "SHORTCUT" or command
== "SC":
1161 if line
.strip() == "?":
1162 self
.showShortcuts()
1164 if self
.setShortcut(line
):
1166 printCurrent
= False
1167 elif command
== "2":
1168 enteredLine
= self
.runTimer(2)
1169 printCurrent
= False
1170 elif command
== "CLS" or command
== "CLEARSCREEN":
1171 clearScreen(self
.sysCalls
)
1172 elif command
== "SETEDNT":
1176 elif command
== "SETEDPOSIX":
1177 global cfgEditorPosix
1178 cfgEditorPosix
= line
1180 elif command
== "SYS":
1181 if self
.setSysCalls(line
):
1183 elif command
== "!CMD":
1185 self
.safeSystemCall(line
)
1187 self
.showError("System calls are not allowed. Use SYS ON to enable them.")
1188 elif command
== "SHOW" or command
== "SH":
1190 self
.pause("Press enter to clear screen and continue. ")
1191 clearScreen(self
.sysCalls
)
1192 elif command
== "VERSION" or command
== "VER":
1194 elif command
== "SAVE" or command
== "S":
1196 print "There's no need to save now. If the prompt shows >>> "
1197 print "then there is nothing to save. You only need to save if the prompt "
1201 elif command
== "NEW":
1202 filename
= self
.makeFilename(line
)
1203 if self
.createFile(filename
):
1208 printCurrent
= False
1209 elif command
== "OPEN" or command
== "O":
1210 filename
= self
.makeFilename(line
)
1215 printCurrent
= False
1216 elif command
== "AUTOSAVE" or command
== "AS":
1218 self
.showError("You must enter ON or OFF for the autosave command")
1220 self
.setAutoSave(line
.upper() == "ON", True)
1221 elif command
== "REVIEW" or command
== "REV":
1222 if self
.setReview(line
):
1224 elif command
== "V0":
1225 if self
.setReview("OFF"):
1227 elif command
== "V1":
1228 if self
.setReview("ON"):
1230 elif command
== "?":
1231 self
.printHelp(self
.quickCard
)
1232 elif command
== "HELP" or command
== "H":
1233 self
.printHelp(self
.help)
1234 elif command
== "QUIT" or command
== "Q":
1238 printCurrent
= False
1239 elif command
== "WEB":
1241 webbrowser
.open("http://www.henspace.co.uk")
1242 except Exception, e
:
1243 self
.showError("Unable to launch browser. " + str(e
))
1244 elif command
== "COLOR" or command
== "COLOUR" or command
== "C":
1249 if not gColor
.isValidSet(set):
1250 self
.showError("Invalid colour set ignored.")
1251 elif gColor
.setCodeSet(set):
1253 elif command
== "MONOCHROME" or command
== "MONO":
1254 if gColor
.setCodeSet(gColor
.NONE
):
1256 elif command
== "EXPORT":
1258 elif command
== "IMPORT":
1259 if self
.importTasks(line
):
1261 elif command
== "CLEAR" and line
== "":
1264 elif command
== "FILTER" or command
== "FI" or command
== "=":
1265 self
.setFilterArray(False, line
)
1266 elif command
== "NEXT" or command
== "N":
1268 elif command
== "PREV" or command
== "P":
1270 elif command
== "TOP" or command
== "T" or command
== "0":
1271 self
.currentTask
= 0
1273 self
.setFilterArray(True, "")
1274 self
.showLocalFilter()
1275 self
.printShortList(line
)
1277 elif command
== "GO" or command
== "G":
1279 elif command
== "IMMEDIATE" or command
== "I" or command
== "++":
1280 newItem
= self
.createItem(":d+0 " + line
)
1281 if newItem
.hasHiddenTask():
1282 clearScreen(self
.sysCalls
)
1283 if newItem
.hasError():
1284 print "Errors were found:"
1285 print newItem
.getError()
1286 print "The task was not added."
1287 printCurrent
= False
1289 self
.todo
.insert(0, newItem
)
1290 self
.currentTask
= 0
1291 self
.sortByPriority()
1293 elif command
== "KILL" or command
== "K" or command
== "-" or command
== "X":
1294 if self
.removeTask(line
):
1296 elif command
== "ARCHIVE" or command
== "DONE":
1297 if self
.archiveTask(line
):
1299 elif command
== "REP" or command
=="R":
1300 if self
.modifyTask(line
, TodoItem
.REPLACE
):
1301 self
.sortByPriority()
1304 printCurrent
= False
1305 elif command
== "SUB" or command
== "SU":
1306 if self
.substituteText(line
):
1307 self
.sortByPriority()
1309 elif command
== "EDIT" or command
== "ED":
1310 if not self
.sysCalls
:
1311 self
.showError("External editing needs to use system calls. Use SYS ON to enable them.")
1313 self
.addTaskExternal()
1314 elif self
.modifyTask(line
, TodoItem
.MODIFY
, externalEditor
= True):
1315 self
.sortByPriority()
1318 printCurrent
= False
1319 elif command
== "MOD" or command
== "M":
1320 if self
.modifyTask(line
, TodoItem
.MODIFY
):
1321 self
.sortByPriority()
1324 printCurrent
= False
1325 elif command
== "EXTEND" or command
== "E":
1326 if self
.modifyTask(line
, TodoItem
.APPEND
):
1327 self
.sortByPriority()
1330 printCurrent
= False
1331 elif command
== "FIRST" or command
== "F":
1332 if self
.moveTask(line
, self
.MOVE_TOP
):
1333 self
.sortByPriority()
1335 self
.currentTask
= 0
1336 elif command
== "DOWN" or command
== "D":
1337 if self
.moveTask(line
, self
.MOVE_DOWN
):
1338 self
.sortByPriority()
1340 elif command
== "UP" or command
== "U":
1341 if self
.moveTask(line
, self
.MOVE_UP
):
1342 self
.sortByPriority()
1344 elif command
== "LIST" or command
== "L":
1346 self
.setFilterArray(True, line
)
1347 self
.showLocalFilter()
1348 self
.printList(False, "", "")
1349 self
.clearFilterArray(True)
1352 elif command
== "LIST>" or command
== "L>":
1354 self
.setFilterArray(True, line
)
1355 self
.showLocalFilter()
1356 self
.printList(False, "", "")
1357 self
.clearFilterArray(True)
1359 elif command
== "@":
1362 elif command
== ":P":
1363 self
.listByProject()
1365 elif command
== ":D":
1368 elif command
== "@>":
1369 self
.startHtml("Report by Context")
1372 elif command
== ":P>":
1373 self
.startHtml("Report by Project")
1374 self
.listByProject()
1376 elif command
== ":D>":
1377 self
.startHtml("Report by Date")
1380 elif command
== "ADD" or command
== "A" or command
== "+":
1382 elif command
== "NOTE" or command
== "NOTES":
1383 self
.addTask("#0 @Notes " + line
)
1384 elif (len(command
) + len(line
)) > 10:
1385 self
.addTask(rawcommand
+ " " + line
)
1386 elif len(command
) > 0:
1387 self
.showError("Didn't understand. (Make sure you have a space after the command or your entry is longer than 10 characters)")
1388 printCurrent
= False
1392 self
.timerActive
= False
1394 print "\n\x07Timer\x07 complete.\x07\n\x07Press enter to continue.\x07"
1396 def runTimer(self
, delay
):
1397 self
.timerActive
= True
1398 t
= Timer(delay
* 60 , self
.timeout
)
1400 s
= raw_input(str(delay
) + " minute timer running.\nAny entry will cancel the timer:\n>>>")
1401 if self
.timerActive
:
1403 print "Timer cancelled."
1406 print "Input discarded as timer has finished."
1409 def addTaskExternal(self
):
1410 exEdit
= EditorLauncher()
1411 entry
= exEdit
.edit("")
1415 self
.showError("Nothing to add")
1417 def addTask(self
, line
):
1418 newItem
= self
.createItem(line
)
1419 if newItem
.hasError():
1420 print "Errors were found:"
1421 print newItem
.getError()
1422 print "The task was not added."
1423 printCurrent
= False
1425 if newItem
.hasHiddenTask():
1426 clearScreen(self
.sysCalls
)
1427 self
.todo
.append(newItem
)
1428 self
.sortByPriority()
1431 def checkCurrentTask(self
):
1432 if self
.currentTask
> len(self
.todo
) - 1:
1433 self
.currentTask
= len(self
.todo
) - 1
1434 if self
.currentTask
< 0:
1435 self
.currentTask
= 0
1437 def writeArchive(self
, item
):
1439 filename
= self
.filename
+ ".archive.dat"
1441 if not os
.path
.exists(filename
):
1442 f
= open(filename
,"wb")
1443 f
.write("# " + notice
[0] + "\n")
1444 f
.write(magicTag
+ "DATA\n")
1446 f
= open(filename
,"a+b")
1448 f
.write(item
.toString())
1451 print "Tasks archived to " + filename
1453 except Exception, e
:
1454 self
.showError("Error trying to archive the tasks.\n" + str(e
))
1457 def exportTasks(self
):
1458 filename
= self
.filename
+ ".tasks.txt"
1460 f
= open(filename
,"wb")
1461 f
.write("# " + notice
[0] + "\n")
1462 f
.write(magicTag
+ "DATA\n")
1463 for item
in self
.todo
:
1464 f
.write(item
.toString())
1467 print "Tasks exported to " + filename
1468 except Exception, e
:
1469 self
.showError("Error trying to export the file.\n" + str(e
))
1471 def importTasks(self
, filename
):
1473 orgNTasks
= len(self
.todo
)
1475 self
.showError("You must supply the name of the file to import.")
1479 self
.splitFile(filename
, True)
1480 if len(self
.todo
) == orgNTasks
:
1481 self
.showError("Failed to find any tasks to import.")
1484 except Exception, e
:
1485 self
.showError("Error importing tasks. " + str(e
))
1488 def createFile(self
, filename
):
1490 if os
.path
.exists(filename
):
1491 self
.showError("Sorry but " + filename
+ " already exists.")
1494 f
= open(filename
, "wb")
1495 f
.write("#!/usr/bin/env python\n")
1496 f
.write("#" + ruler
+ "\n")
1499 except Exception, e
:
1500 self
.showError("Error trying to create the file " + filename
+ ". " + str(e
))
1503 def save(self
, filename
):
1504 if filename
!= "" or self
.autoSave
:
1505 self
.forceSave(filename
)
1508 print "Autosave is off, so changes not saved yet."
1510 def forceSave(self
, filename
):
1512 filename
= self
.filename
1513 tmpFilename
= filename
+ ".tmp"
1514 backupFilename
= filename
+ ".bak"
1517 f
= open(tmpFilename
,"wb")
1518 f
.write("#!/usr/bin/env python\n")
1519 f
.write("# -*- coding: utf-8 -*-\n")
1520 f
.write("#" + ruler
+ "\n")
1521 f
.write("# Run the script for details of the licence\n")
1522 f
.write("# or refer to the notice section later in the file.\n")
1523 f
.write("#" + ruler
+ "\n")
1524 f
.write(magicTag
+ "DATA\n")
1525 for item
in self
.todo
:
1526 f
.write(item
.toString())
1528 f
.write(magicTag
+ "CONFIG\n")
1529 f
.write("cfgColor = " + str(gColor
.getCodeSet()) + "\n")
1530 f
.write("cfgAutoSave = " + str(self
.autoSave
) + "\n")
1531 f
.write("cfgReviewMode = " + str(self
.review
) + "\n")
1532 f
.write("cfgSysCalls = " + str(self
.sysCalls
) + "\n")
1533 f
.write("cfgEditorNt = \"" + cfgEditorNt
+ "\"\n")
1534 f
.write("cfgEditorPosix = \"" + cfgEditorPosix
+ "\"\n")
1535 f
.write("cfgShortcuts = " + str(self
.shortcuts
) + "\n")
1536 f
.write("cfgAbbreviations = " +str(globalAbbr
.toString()) +"\n")
1537 f
.write("cfgPAbbreviations = " +str(globalPAbbr
.toString()) +"\n")
1538 f
.write(magicTag
+ "CODE\n")
1539 for codeline
in self
.code
:
1540 f
.write(codeline
.rstrip())
1545 except Exception, e
:
1546 self
.showError("Error trying to save the file.\n" + str(e
))
1549 os
.remove(backupFilename
)
1553 oldstat
= os
.stat(filename
)
1554 os
.rename(filename
, backupFilename
)
1555 os
.rename(tmpFilename
, filename
)
1556 os
.chmod(filename
, stat
.S_IMODE(oldstat
.st_mode
)) # ensure permissions carried over
1557 self
.filename
= filename
1559 print "Tasks saved."
1560 except Exception, e
:
1561 self
.showError("Error trying to rename the backups.\n" + str(e
))
1563 def moveTo(self
, indexStr
):
1565 index
= int(indexStr
, 10)
1566 if index
< 0 or index
> len(self
.todo
) - 1:
1567 self
.showError("Sorry but there is no task " + indexStr
)
1569 if not self
.isViewable(self
.todo
[index
]):
1570 print "Switching off your filter so that the task can be displayed."
1571 self
.clearFilterArray(False)
1572 self
.currentTask
= index
1574 self
.showError("Unable to understand the task " + indexStr
+ " you want to show.")
1577 def moveToVisible(self
):
1578 start
= self
.currentTask
1580 if start
< 0 or start
>= len(self
.todo
):
1582 while not self
.isViewable(self
.todo
[self
.currentTask
]):
1584 if self
.currentTask
== start
:
1585 print "Nothing matched your filter. Removing your filter so that the current task can be displayed."
1586 self
.clearFilterArray(False)
1589 def decrypt(self
, indexStr
):
1590 index
= self
.getRequiredTask(indexStr
)
1591 if index
< 0 or index
> len(self
.todo
) - 1:
1592 self
.showError("Sorry but there is no task " + indexStr
+ " to show.")
1593 elif self
.todo
[index
].hasHiddenTask():
1595 print WordWrapper(gMaxLen
).wrap(ec
.enterKeyAndDecrypt(self
.todo
[index
].getHiddenTask()))
1597 print "Task ", index
, " has no encrypted data."
1599 def moveTask(self
, indexStr
, where
):
1602 print "You must supply the number of the task to move."
1605 index
= self
.getRequiredTask(indexStr
)
1606 if index
< 0 or index
> len(self
.todo
) - 1:
1607 self
.showError("Sorry but there is no task " + indexStr
+ " to move.")
1608 elif where
== self
.MOVE_DOWN
:
1609 if index
<= len(self
.todo
) - 2:
1610 item
= self
.todo
[index
]
1611 self
.todo
[index
] = self
.todo
[index
+ 1]
1612 self
.todo
[index
+ 1] = item
1613 print "Task ", index
, " moved down."
1616 self
.showError("Task " + str(index
) + " is already at the bottom.")
1619 if where
== self
.MOVE_TOP
:
1620 self
.todo
.insert(0, self
.todo
.pop(index
))
1623 item
= self
.todo
[dest
]
1624 self
.todo
[dest
] = self
.todo
[index
]
1625 self
.todo
[index
] = item
1626 print "Task ", index
, " moved up."
1629 self
.showError("Task " + str(index
) + " is already at the top.")
1633 self
.showError("Unable to understand the task " + indexStr
+ " you want to move.")
1639 if safeRawInput("Are you really sure you want to remove everything? Yes or No? >>>").upper() != "YES":
1640 print("Nothing has been removed.")
1643 self
.currentTask
= 0
1647 def getRequiredTask(self
, indexStr
):
1648 if indexStr
== "^" or indexStr
.upper() == "THIS":
1649 index
= self
.currentTask
1652 index
= int(indexStr
, 10)
1657 def archiveTask(self
, indexStr
):
1659 line
= indexStr
.split(" ", 1)
1665 index
= self
.getRequiredTask(indexStr
)
1666 if index
< 0 or index
> len(self
.todo
) - 1:
1667 self
.showError("Sorry but there is no task " + indexStr
+ " to mark as done and archive.")
1669 if indexStr
== "^" or indexStr
.upper() == "THIS":
1672 print "Are you sure you want to archive: ' " + self
.todo
[index
].toStringSimple() + "'"
1673 if safeRawInput("Enter Yes to archive this task? >>>").upper() == "YES":
1676 newItem
= self
.createItem(":d+0")
1677 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1678 newItem
= self
.createItem(entry
+ " @Archived")
1679 self
.todo
[index
].copy(newItem
, TodoItem
.APPEND
)
1680 if self
.writeArchive(self
.todo
[index
]):
1681 self
.todo
[index
:index
+ 1] = []
1682 print "Task ", index
, " has been archived."
1685 print "Task ", index
, " marked as archived but not removed."
1687 print "Task ", index
, " has not been archived."
1690 def removeTask(self
, indexStr
):
1692 index
= self
.getRequiredTask(indexStr
)
1693 if index
< 0 or index
> len(self
.todo
) - 1:
1694 self
.showError("Sorry but there is no task " + indexStr
+ " to delete.")
1696 if indexStr
== "^" or indexStr
.upper() == "THIS":
1699 print "Are you sure you want to remove ' " + self
.todo
[index
].toStringSimple() + "'"
1700 if safeRawInput("Enter Yes to delete this task? >>>").upper() == "YES":
1703 self
.todo
[index
:index
+ 1] = []
1704 print "Task ", index
, " has been removed."
1706 print "Task ", index
, " has not been removed."
1709 def substituteText(self
, indexStr
):
1710 line
= indexStr
.split(" ", 1)
1715 self
.showError("You need to define the task and substitution phrases. e.g SUB 0 /old/new/")
1721 print "You must supply the number of the task to change."
1724 index
= self
.getRequiredTask(indexStr
)
1726 if index
< 0 or index
> len(self
.todo
) - 1:
1727 self
.showError("Sorry but there is no task " + indexStr
)
1729 text
= entry
.replace("/", "\n")
1730 text
= text
.replace("\\\n","/")
1731 phrases
= text
.split("\n")
1732 if len(phrases
) != 4:
1733 self
.showError("The format of the command is incorrect. The substitution phrases should be /s1/s2/ ")
1735 oldText
= self
.todo
[index
].getTask()
1736 newText
= oldText
.replace(phrases
[1], phrases
[2])
1737 if newText
== oldText
:
1738 self
.showError("Nothing has changed.")
1740 newItem
= self
.createItem(newText
)
1741 if newItem
.hasError():
1742 print "With the substitution the task had errors:"
1743 print newItem
.getError()
1744 print "Task ", index
, " is unchanged."
1746 if newItem
.hasHiddenTask():
1747 clearScreen(self
.sysCalls
)
1748 self
.showError("It isn't possible to create private or secret data by using the substitition command.")
1750 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1751 print "Task ", index
, " has been changed."
1756 def modifyTask(self
, indexStr
, replace
, externalEditor
= False):
1757 line
= indexStr
.split(" ", 1)
1766 print "You must supply the number of the task to change."
1769 index
= self
.getRequiredTask(indexStr
)
1771 if index
< 0 or index
> len(self
.todo
) - 1:
1772 self
.showError("Sorry but there is no task " + indexStr
)
1776 exEdit
= EditorLauncher()
1777 (key
, entry
) = self
.todo
[index
].toStringEditable()
1778 entry
= exEdit
.edit(entry
)
1780 if replace
== TodoItem
.REPLACE
:
1781 print "This task will completely replace the existing entry,"
1782 print "including any projects and actions."
1783 elif replace
== TodoItem
.MODIFY
:
1784 print "Only the elements you add will be replaced. So, for example,"
1785 print "if you don't enter any projects the original projects will remain."
1787 print "Elements you enter will be appended to the current task"
1788 entry
= safeRawInput("Enter new details >>>")
1790 if replace
== TodoItem
.APPEND
:
1791 newItem
= self
.createItem(entry
, password
="unused") # we will discard the encrypted part on extend
1792 elif externalEditor
:
1793 newItem
= self
.createItem(entry
, password
= key
)
1795 newItem
= self
.createItem(entry
)
1796 if newItem
.hasHiddenTask():
1797 clearScreen(self
.sysCalls
)
1798 if newItem
.hasError():
1799 print "The task had errors:"
1800 print newItem
.getError()
1801 print "Task ", index
, " is unchanged."
1803 if newItem
.hasHiddenTask() and replace
== TodoItem
.APPEND
:
1804 self
.showError("It isn't possible to extend the encrypted part of a task.\nThis part is ignored.")
1805 self
.todo
[index
].copy(newItem
, replace
)
1806 print "Task ", index
, " has been changed."
1809 print "Task ", index
, " has not been touched."
1813 if self
.currentTask
< len(self
.todo
) - 1:
1814 self
.currentTask
= self
.currentTask
+ 1
1816 def incTaskLoop(self
):
1817 if self
.currentTask
< len(self
.todo
) - 1:
1818 self
.currentTask
= self
.currentTask
+ 1
1820 self
.currentTask
= 0
1822 if self
.currentTask
> 0:
1823 self
.currentTask
= self
.currentTask
- 1
1825 def decTaskLoop(self
):
1826 if self
.currentTask
> 0:
1827 self
.currentTask
= self
.currentTask
- 1
1829 self
.currentTask
= len(self
.todo
) - 1
1831 def printItemTruncated(self
, index
, leader
):
1832 if len(self
.todo
) < 1:
1833 print leader
, "no tasks"
1835 scrnline
= leader
+ "[%02d] %s" % (index
, self
.todo
[index
].toStringSimple())
1836 if len(scrnline
) > gMaxLen
:
1837 print scrnline
[0:gMaxLen
- 3] + "..."
1842 def printItem(self
, index
, colorType
):
1843 if len(self
.todo
) < 1:
1844 self
.output("There are no tasks to be done.\n", 0)
1847 wrapper
= WordWrapper(gMaxLen
)
1848 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringSimple()))
1849 if colorType
== "row0":
1850 style
= "class=\"evenTask\""
1852 style
= "class=\"oddTask\""
1853 self
.output("<div %s>[%02d] %s</div>\n" % (style
, index
, self
.todo
[index
].toStringSimple()),
1854 gColor
.code(colorType
) + scrnline
+ gColor
.code("normal") + "\n" )
1855 nlines
= wrapper
.getNLines()
1858 def printItemVerbose(self
, index
):
1859 if len(self
.todo
) < 1:
1860 print "There are no tasks to be done."
1863 wrapper
= WordWrapper(gMaxLen
)
1864 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringVerbose()))
1867 def clearFilterArray(self
, local
):
1869 self
.localFilters
= []
1870 self
.localFilterText
= ""
1872 self
.globalFilters
= []
1873 self
.globalFilterText
= ""
1875 def setFilterArray(self
, local
, requiredFilter
):
1876 filters
= requiredFilter
.split()
1878 destination
= self
.localFilters
1880 destination
= self
.globalFilters
1883 for word
in filters
:
1884 if word
[0:1] == "-":
1889 if word
[0:2].upper() == ":D" and len(word
) > 2:
1890 filter = ":D" + TodoItem("").parseDate(word
[2:].strip(), False)
1891 elif word
[0:2].lower() == ":p":
1892 filter = globalPAbbr
.expandProject(word
)
1894 filter = globalAbbr
.expandAction(word
)
1896 filter = "-" + filter
1897 destination
.append(filter)
1898 if humanVersion
!= "":
1899 humanVersion
= humanVersion
+ " " + filter
1901 humanVersion
= filter
1903 for filter in self
.globalFilters
:
1904 destination
.append(filter)
1905 if humanVersion
!= "":
1906 humanVersion
= humanVersion
+ " " + filter
1908 humanVersion
= filter
1910 self
.localFilterText
= humanVersion
1912 self
.globalFilterText
= humanVersion
1914 def isViewable(self
, item
):
1915 if len(self
.globalFilters
) == 0 and len(self
.localFilters
) == 0:
1919 if len(self
.localFilters
) > 0:
1920 filterArray
= self
.localFilters
1922 filterArray
= self
.globalFilters
1923 if "or" in filterArray
or "OR" in filterArray
:
1927 for filter in filterArray
:
1928 if filter.upper() == "OR":
1934 if filter[0:1] == "+":
1937 if filter[0:1] == "-":
1943 if filter[0:1] == "#":
1944 priority
= int(filter[1:], 10)
1949 if self
.exactPriority
:
1950 if item
.hasPriority(priority
):
1952 elif item
.hasPriorityOrAbove(priority
):
1954 elif filter[0:2].upper() == ":D":
1955 if item
.hasDate(filter[2:]):
1957 elif filter[0:2].upper() == ":P":
1958 view
= item
.hasProject(filter)
1959 elif filter[0:1].upper() == "@":
1960 view
= item
.hasAction(filter)
1961 elif item
.hasWord(filter):
1964 view
= (view
!= True)
1972 if fast
or mustHave
:
1977 def listByAction(self
):
1978 index
= SearchIndex()
1979 for item
in self
.todo
:
1980 index
.addCollection(item
.getActions())
1982 (n
, value
) = index
.getFirstItem()
1986 if not gColor
.usingColor() and n
> 0:
1990 self
.setFilterArray(True, "+" + value
)
1991 self
.printList(div
, "<H2 class=\"hAction\">" + value
+ "</H2>\n", gColor
.code("title") + "\n" + value
+ "\n" + gColor
.code("title"))
1992 self
.clearFilterArray(True)
1993 (n
, value
) = index
.getNextItem(n
)
1996 def listByProject(self
):
1997 index
= SearchIndex()
1998 for item
in self
.todo
:
1999 index
.addCollection(item
.getProjects())
2001 (n
, value
) = index
.getFirstItem()
2005 if not gColor
.usingColor() and n
> 0:
2009 self
.setFilterArray(True, "+:p" + value
)
2010 self
.printList(div
, "<H2 class =\"hProject\">Project: " + value
+ "</H2>\n", gColor
.code("title") + "\nProject: " + value
+ gColor
.code("normal") + "\n")
2011 self
.clearFilterArray(True)
2012 (n
, value
) = index
.getNextItem(n
)
2015 def listByDate(self
):
2016 index
= SearchIndex()
2017 for item
in self
.todo
:
2018 index
.add(item
.getDate())
2020 (n
, value
) = index
.getFirstItem()
2024 if not gColor
.usingColor() and n
> 0:
2028 self
.setFilterArray(True, "+:D" + value
)
2029 self
.printList(div
, "<H2 class =\"hDate\">Date: " + value
+ "</H2>\n", gColor
.code("title") + "\nDate: " + value
+ gColor
.code("normal") + "\n")
2030 self
.clearFilterArray(True)
2031 (n
, value
) = index
.getNextItem(n
)
2035 def showFilter(self
):
2036 if self
.globalFilterText
!= "":
2037 self
.output("<H3 class =\"hFilter\">Filter = " + self
.globalFilterText
+ "</H3>\n",
2038 gColor
.code("bold") + "Filter = " + self
.globalFilterText
+ gColor
.code("normal") + "\n")
2040 def showLocalFilter(self
):
2041 if self
.localFilterText
!= "":
2042 self
.output("<H3 class =\"hFilter\">Filter = " + self
.localFilterText
+ "</H3>\n",
2043 gColor
.code("bold") + "Filter = " + self
.localFilterText
+ gColor
.code("normal") + "\n")
2045 def printList(self
, div
, outHtml
, outStd
):
2046 self
.doPrintList(-1, div
, outHtml
, outStd
)
2048 def printShortList(self
, line
):
2051 count
= int(line
, 10)
2053 self
.showError("Didn't understand the number of tasks you wanted listed.")
2054 self
.doPrintList(count
, False, "", "")
2056 def doPrintList(self
, limitItems
, div
, outHtml
, outStd
):
2064 self
.outputHtml("<div class=\"itemGroup\">\n")
2065 for item
in self
.todo
:
2066 if self
.isViewable(item
):
2070 self
.output(outHtml
, outStd
)
2071 if not gColor
.usingColor() and not first
:
2074 count
= count
+ self
.printItem(n
, color
)
2080 displayed
= displayed
+ 1
2082 if limitItems
>= 0 and displayed
>= limitItems
:
2084 if count
>= maxlines
:
2085 if self
.htmlFile
== "":
2086 msg
= safeRawInput("---press Enter for more. Enter s to skip: ")
2087 if len(msg
) > 0 and msg
.strip().upper()[0] == "S":
2091 self
.outputHtml("</div>\n")
2093 def printHelp(self
, lines
):
2094 ListViewer(24).show(lines
,"!PAUSE!")
2096 def splitFile(self
, filename
, dataOnly
):
2100 f
= open(filename
, 'r')
2102 if line
[0:2] == "#!":
2105 if line
.find(magicTag
+ "DATA") == 0:
2109 elif line
.find(magicTag
+ "CONFIG") == 0:
2113 elif line
.find(magicTag
+ "CODE") == 0:
2120 self
.code
.append(line
)
2122 self
.processCfgLine(line
)
2125 if len(line
) > 0 and line
[0] == "#":
2126 line
= line
[1:].strip()
2128 newItem
= self
.createItem(line
)
2130 self
.todo
.append(newItem
)
2135 def createItem(self
, line
, password
= ""):
2136 item
= TodoItem(line
, password
)
2139 def outputHtml(self
, html
):
2140 if self
.htmlFile
!= "":
2141 self
.htmlFile
.write(html
)
2143 def output(self
, html
, stdout
):
2144 if self
.htmlFile
!= "":
2145 #self.htmlFile.write(html.replace("\n", "<br>\n"))
2146 self
.htmlFile
.write(html
)
2153 def startHtml(self
, title
):
2154 htmlFilename
= self
.filename
+ ".html"
2156 self
.htmlFile
= open(htmlFilename
, "w")
2157 self
.htmlFile
.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n")
2158 self
.htmlFile
.write("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n")
2159 self
.htmlFile
.write("<html>\n<head>\n")
2160 self
.htmlFile
.write("<style>\n")
2161 self
.htmlFile
.write(".footer {text-align:center;}\n")
2162 self
.htmlFile
.write("</style>\n")
2163 self
.htmlFile
.write("<link rel=\"stylesheet\" href=\"ikog.css\" type=\"text/css\">\n")
2164 self
.htmlFile
.write("</head>\n<body>\n")
2165 self
.htmlFile
.write("<div class=\"header\">\n")
2166 self
.htmlFile
.write("<H1 class=\"hTitle\">iKog Todo List</H1>\n")
2167 self
.htmlFile
.write("<H2 class=\"hSubTitle\">" + title
+ " printed " + date
.today().isoformat() + "</H1>\n")
2168 self
.htmlFile
.write("</div>\n")
2169 self
.htmlFile
.write("<div class=\"taskArea\">\n")
2171 print "Failed to create output file:", htmlFilename
2175 name
= self
.htmlFile
.name
2178 self
.htmlFile
.write("</div>\n")
2179 self
.htmlFile
.write("<div class=\"footer\">\n")
2180 self
.htmlFile
.write("--- end of todo list ---<br>\n")
2181 self
.htmlFile
.write("Created using " + notice
[0] + "\n<br>" + notice
[1] + "<br>\n")
2182 self
.htmlFile
.write("</div>\n")
2184 self
.htmlFile
.write("</body>\n</html>\n")
2185 self
.htmlFile
.close()
2187 print "HTML file " + name
+ " created."
2189 except Exception, e
:
2190 self
.showError("Error writing to file. " + str(e
))
2194 safeName
= os
.path
.abspath(name
).replace("\\","/")
2195 safeName
= "file://" + urllib
.quote(safeName
," /:")
2196 webbrowser
.open(safeName
)
2197 except Exception, e
:
2198 self
.showError("Unable to launch html output. " + str(e
))
2200 class Abbreviations
:
2201 def __init__(self
, project
= False):
2202 self
.default(project
)
2204 def default(self
,project
):
2208 self
.abbrevs
= {"@A":"@Anywhere","@C":"@Computer",
2209 "@D":"@Desk", "@E": "@Errands",
2210 "@H":"@Home", "@I":"@Internet","@L":"@Lunch", "@M":"@Meeting", "@N":"@Next",
2211 "@P":"@Phone", "@Pw":"@Password", "@S":"@Someday/Maybe",
2212 "@O":"@Other", "@W4":"@Waiting_For", "@W":"@Work"}
2215 def setAbbreviations(self
, abbr
):
2216 self
.abbrevs
.update(abbr
)
2218 def addAbbreviation(self
, key
, word
):
2219 self
.abbrevs
.update({key
.title():word
})
2221 def removeAbbreviation(self
, key
):
2223 if self
.abbrevs
.has_key(key
):
2224 del self
.abbrevs
[key
]
2228 def expandAction(self
, action
):
2229 if action
[0:1] != "@":
2232 action
= action
.title()
2233 if self
.abbrevs
.has_key(action
):
2234 return self
.abbrevs
[action
]
2237 def expandProject(self
, project
):
2238 if not project
.lower().startswith(":p"):
2240 project
= project
.title()
2241 if self
.abbrevs
.has_key(project
):
2242 return self
.abbrevs
[project
]
2246 return str(self
.abbrevs
)
2248 def toStringVerbose(self
):
2251 for key
in self
.abbrevs
:
2252 output
= output
+ key
.ljust(5) + " = " + self
.abbrevs
[key
].ljust(30)
2255 output
= output
+ "\n"
2257 output
= output
+ "\n"
2265 if ent
!= "" and not ent
in self
.items
:
2266 self
.items
.append(ent
)
2268 def addCollection(self
, collection
):
2269 for ent
in collection
:
2270 if ent
!= "" and not ent
in self
.items
:
2271 self
.items
.append(ent
)
2275 def getFirstItem(self
):
2276 if len(self
.items
) > 0:
2277 return (0, self
.items
[0])
2281 def getNextItem(self
, count
):
2283 if count
> len(self
.items
) - 1:
2286 return (count
, self
.items
[count
])
2290 ENCRYPTION_MARKER
= "{}--xx"
2294 NOT_DUE_PRIORITY
= 0
2295 DEFAULT_PRIORITY
= 5
2296 OVERDUE_PRIORITY
= 11
2297 MEETING_PRIORITY
= 10
2298 def __init__(self
,line
, password
= ""):
2301 self
.hiddenTask
= ""
2305 self
.created
= date
.today().isoformat()
2307 self
.autoAction
= False
2308 self
.autoProject
= False
2309 self
.nullDate
= False
2310 self
.parse(line
, password
)
2312 def makeSafeDate(self
, year
, month
, day
):
2319 newDate
= date(year
, month
, day
)
2326 def parseDate(self
, dateStr
, quiet
):
2327 dateStr
= dateStr
.replace("/","-")
2328 dateStr
= dateStr
.replace(":","-")
2329 entry
= dateStr
.split("-")
2333 elif dateStr
== "0":
2334 self
.nullDate
= True
2339 if dateStr
[0:1] == "+":
2340 days
= int(dateStr
[1:].strip(), 10)
2341 when
= now
+ timedelta(days
)
2344 year
= int(entry
[0], 10)
2345 month
= int(entry
[1], 10)
2346 day
= int(entry
[2], 10)
2349 month
= int(entry
[0], 10)
2350 day
= int(entry
[1], 10)
2354 day
= int(entry
[0], 10)
2362 when
= self
.makeSafeDate(year
, month
, day
)
2364 self
.nullDate
= False
2370 self
.addError("Could not decode the date. Use :dYYYY/MM/DD")
2373 return when
.isoformat()
2375 def parse(self
, line
, password
):
2377 words
= line
.split(" ")
2381 ecmLen
= len(self
.ENCRYPTION_MARKER
)
2382 for word
in words
[start
:]:
2383 wordUC
= word
.strip().upper()
2386 taskToHide
= taskToHide
+ word
.strip() + " "
2387 elif word
[0:ecmLen
] == self
.ENCRYPTION_MARKER
:
2388 self
.hiddenTask
= word
[ecmLen
:]
2389 elif wordUC
.startswith("<PRIVATE>") or wordUC
.startswith("<SECRET>") or wordUC
.startswith("<S>") or wordUC
.startswith("<P>"):
2392 pos
= word
.index(">")
2393 taskToHide
= taskToHide
+ word
[pos
+ 1:].strip() + " "
2396 elif word
[0] == "@" and len(word
) > 1:
2397 if wordUC
== "@DATE":
2398 self
.addError("@Date contexts should not be entered. Use :dYYYY-MM-DD")
2400 act
= globalAbbr
.expandAction(word
.strip())
2401 if not act
in self
.actions
:
2402 self
.actions
.append(act
)
2403 elif word
[0:1] == "#" and len(word
) > 1 and self
.priority
== -1:
2405 self
.priority
= int(word
[1:].strip(), 10)
2406 if self
.priority
< 1:
2408 elif self
.priority
> 10:
2411 self
.addError("Did not understand priority.")
2413 elif wordUC
[0:2] == ":P" and len(word
) > 2:
2414 proj
= globalPAbbr
.expandProject(word
.strip())[2:].title()
2415 if not proj
in self
.projects
:
2416 self
.projects
.append(proj
)
2417 elif wordUC
[0:8] == ":CREATED" and len(word
) > 8:
2418 self
.created
= word
[8:].strip()
2419 elif wordUC
[0:2] == ":D" and len(word
) > 2:
2420 self
.when
= self
.parseDate(word
[2:].strip(), False)
2422 self
.task
= self
.task
+ word
.strip() + " "
2423 if taskToHide
!= "":
2426 if ec
.setType(ec
.TYPE_AES
) != ec
.TYPE_AES
:
2427 self
.addError("AES encryption is not available.")
2430 ec
.setType(ec
.TYPE_OBSCURED
)
2431 if taskToHide
!= "":
2433 self
.hiddenTask
= ec
.enterKeyAndEncrypt(taskToHide
)
2436 self
.hiddenTask
= ec
.encrypt(taskToHide
)
2438 if len(self
.actions
) == 0:
2439 self
.actions
.append("@Anywhere")
2440 self
.autoAction
= True
2441 if len(self
.projects
) == 0:
2442 self
.projects
.append("None")
2443 self
.autoProject
= True
2445 def addError(self
, err
):
2446 if len(self
.error
) > 0:
2447 self
.error
= self
.error
+ "\n"
2448 self
.error
= self
.error
+ err
2451 return self
.error
!= ""
2458 def hasWord(self
, word
):
2459 return (self
.task
.upper().find(word
.upper()) >= 0)
2461 def hasAction(self
, loc
):
2462 if self
.when
!= "" and loc
.upper() == "@DATE":
2465 return loc
.title() in self
.actions
2467 def copy(self
, todoItem
, replace
):
2468 if replace
== TodoItem
.REPLACE
or len(todoItem
.task
.strip()) > 0:
2469 if replace
== TodoItem
.APPEND
:
2470 self
.task
= self
.task
+ " ..." + todoItem
.task
2472 self
.task
= todoItem
.task
2473 if replace
== TodoItem
.REPLACE
or todoItem
.autoAction
== False:
2474 if replace
!= TodoItem
.APPEND
:
2476 for loc
in todoItem
.actions
:
2477 if not loc
in self
.actions
:
2478 self
.actions
.append(loc
)
2479 if replace
== TodoItem
.REPLACE
or todoItem
.autoProject
== False:
2480 if replace
!= TodoItem
.APPEND
:
2482 for proj
in todoItem
.projects
:
2483 if not proj
in self
.projects
:
2484 self
.projects
.append(proj
)
2485 if replace
== TodoItem
.REPLACE
or (todoItem
.when
!= "" or todoItem
.nullDate
== True):
2486 self
.when
= todoItem
.when
2487 if todoItem
.priority
>= 0:
2488 self
.priority
= todoItem
.priority
2490 if replace
== TodoItem
.REPLACE
or len(todoItem
.hiddenTask
.strip()) > 0:
2491 if replace
== TodoItem
.APPEND
:
2494 self
.hiddenTask
= todoItem
.hiddenTask
2496 def hasHiddenTask(self
):
2497 return self
.hiddenTask
!= ""
2500 return len(self
.task
.strip()) > 0
2502 def hasProject(self
, proj
):
2503 if proj
[0:2].upper() == ":P":
2505 return proj
.title() in self
.projects
2507 def hasDate(self
, dt
):
2508 dt
= self
.parseDate(dt
, True)
2512 return self
.when
== dt
2514 def hasPriorityOrAbove(self
, priority
):
2515 return (self
.getEffectivePriority() >= priority
)
2517 def hasPriority(self
, priority
):
2518 return (self
.getEffectivePriority() == priority
)
2520 def getHiddenTask(self
):
2521 return self
.hiddenTask
2526 def getActions(self
):
2530 return self
.actions
+ ["@Date"]
2532 def getProjects(self
):
2533 return self
.projects
2538 def getPriority(self
):
2539 if self
.priority
< 0:
2540 return self
.DEFAULT_PRIORITY
2542 return self
.priority
2544 def getEffectivePriority(self
):
2545 userP
= self
.getPriority()
2547 if self
.when
<= date
.today().isoformat():
2548 userP
= self
.OVERDUE_PRIORITY
+ userP
2549 if self
.hasAction("@Meeting"):
2550 userP
= userP
+ self
.MEETING_PRIORITY
2552 userP
= self
.NOT_DUE_PRIORITY
2558 entry
= entry
+ " " + self
.task
2559 for action
in self
.actions
:
2560 entry
= entry
+ " " + action
2561 for project
in self
.projects
:
2562 entry
= entry
+ " :p" + project
2563 entry
= entry
+ " :created" + self
.created
2565 entry
= entry
+ " :d" + self
.when
2566 if self
.priority
>= 0:
2567 entry
= entry
+ " #" + str(self
.priority
)
2568 if self
.hiddenTask
!= "":
2569 entry
= entry
+ " " + self
.ENCRYPTION_MARKER
+ self
.hiddenTask
2572 def toStringEditable(self
, includeHidden
= False):
2576 entry
= entry
+ ":d" + self
.when
+ " "
2577 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2578 if len(self
.actions
) > 0:
2579 for action
in self
.actions
:
2580 entry
= entry
+ " " + action
2581 if len(self
.projects
) > 0:
2582 for project
in self
.projects
:
2584 if project
!= "None":
2585 entry
= entry
+ " :p" + project
2586 if self
.hiddenTask
!= "" and includeHidden
:
2588 entry
= entry
+ " <" + Encryptor().getSecurityClass(self
.hiddenTask
)[0:1] + ">"
2589 entry
= entry
+ ec
.enterKeyAndDecrypt(self
.hiddenTask
)
2590 password
= ec
.getKey()
2591 return (password
, entry
.strip())
2593 def toStringSimple(self
):
2596 entry
= entry
+ "@Date " + self
.when
+ " "
2597 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2598 if self
.hiddenTask
!= "":
2599 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2600 if len(self
.actions
) > 0:
2601 for action
in self
.actions
:
2602 entry
= entry
+ " " + action
2603 if len(self
.projects
) > 0:
2605 for project
in self
.projects
:
2607 if project
!= "None":
2609 entry
= entry
+ " Projects: " + project
2612 entry
= entry
+ ", " + project
2613 #entry = entry + " [" + self.created + "]"
2616 def toStringVerbose(self
):
2617 entry
= gColor
.code("title") + self
.task
2618 if self
.hiddenTask
!= "":
2619 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2620 entry
= entry
+ gColor
.code("bold") + "\nPriority: %02d" % (self
.getPriority())
2621 if len(self
.actions
) or self
.when
!= "" > 0:
2622 entry
= entry
+ gColor
.code("heading") + "\nContext: "
2624 entry
= entry
+ gColor
.code("important") + "@Date " + self
.when
2625 entry
= entry
+ gColor
.code("normal")
2626 for action
in self
.actions
:
2627 entry
= entry
+ " " + action
;
2628 if len(self
.projects
) > 0:
2630 for project
in self
.projects
:
2631 if project
!= "None":
2633 entry
= entry
+ gColor
.code("heading") + "\nProjects: " + gColor
.code("normal");
2634 entry
= entry
+ project
2637 entry
= entry
+ ", " + project
2638 entry
= entry
+ gColor
.code("normal") + "\nCreated: [" + self
.created
+ "]"
2642 #for line in notice:
2645 pythonVer
= platform
.python_version()
2646 ver
= pythonVer
.split(".")
2647 if int(ver
[0]) < gReqPythonMajor
or (int(ver
[0]) == gReqPythonMajor
and int(ver
[1]) < gReqPythonMinor
):
2648 print "\nSorry but this program requires Python ", \
2649 str(gReqPythonMajor
) + "." + str(gReqPythonMinor
), \
2650 "\nYour current version is ", \
2651 str(ver
[0]) + "." + str(ver
[1]), \
2652 "\nTo run the program you will need to install the current version of Python."
2655 # signal.signal(signal.SIGINT, signalHandler)
2656 gColor
= ColorCoder(cfgColor
)
2657 globalAbbr
= Abbreviations()
2658 globalPAbbr
= Abbreviations(project
=True)
2660 if len(sys
.argv
) > 2:
2662 reopen
= sys
.argv
[1]
2664 reopen
= sys
.argv
[0] + ".dat"
2665 for word
in sys
.argv
[2:]:
2667 commandList
.append(command
)
2670 command
= command
+ word
+ " "
2671 commandList
.append(command
)
2672 elif len(sys
.argv
) > 1:
2673 reopen
= sys
.argv
[1]
2675 reopen
= sys
.argv
[0] + ".dat"
2678 todoList
= TodoList(sys
.argv
[0], reopen
)
2679 reopen
= todoList
.run(commandList
)