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 # create new website design template @Anywhere :pDesign :created2009-01-06 :d2009-01-06
9 # rss feeds for labels @Anywhere :pDjango :created2009-01-01
10 # meta blog all, this will contain posts across all blogs @Anywhere :pDjango :created2009-01-03
11 # flexible blog, +/- blog/labels, multi filters. @Anywhere :pDjango :created2009-01-03
12 # blog view permission @Anywhere :pDjango :created2009-01-03
13 # encrypted documents support, index.enc. @Anywhere :pCore :created2009-01-03
14 # login page with yahoo/gmail logo along with openid textinput @Anywhere :pDesign :created2009-01-07
15 # utilities to find ancestory in wiki pages @Anywhere :pCore :created2009-01-07
16 # user info store indexed on openid @Anywhere :pCore :created2009-01-07
17 # user groups, multi user in one group @Anywhere :pCore :created2009-01-07
18 # permissions based on groups and individuals, on blog/wiki/document basis @Anywhere :pCore :created2009-01-07
25 cfgEditorPosix
= "nano,pico,vim,emacs"
26 cfgShortcuts
= ['', '', '', '', '', '', '', '', '', '']
27 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'}
28 cfgPAbbreviations
= {':Pw': ':pWebsite'}
33 from datetime
import date
34 from datetime
import timedelta
41 from threading
import Timer
56 "ikog.py v 1.88 2007-11-23",
57 "Copyright (C) 2006-2007 S. J. Butler",
58 "Visit http://www.henspace.co.uk for more information.",
59 "This program is free software; you can redistribute it and/or modify",
60 "it under the terms of the GNU General Public Licence as published by",
61 "the Free Software Foundation; either version 2 of the License, or",
62 "(at your option) any later version.",
64 "This program is distributed in the hope that it will be useful,",
65 "but WITHOUT ANY WARRANTY; without even the implied warranty of",
66 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
67 "GNU General Public License for more details. The license is available",
68 "from http://www.gnu.org/licenses/gpl.txt"
73 " (_) | | __ ___ __ _",
74 " | | | |/ / / _ \ / _` |",
75 " | | | < | (_) | | (_| |",
76 " |_| |_|\_\ \___/ \__, |",
78 " (_) | |_ | | __ ___ ___ _ __ ___ ___ _ __",
79 " | | | __| | |/ / / _ \ / _ \ | '_ \ / __| / _ \ | '_ \ ",
80 " | | | |_ | < | __/ | __/ | |_) | \__ \ | (_) | | | | |",
81 " |_| \__| |_|\_\ \___| \___| | .__/ |___/ \___/ |_| |_|",
83 " __ _ _ __ ___ __ __ (_) _ __ __ _ ",
84 " / _` | | '__| / _ \ \ \ /\ / / | | | '_ \ / _` |",
85 " | (_| | | | | (_) | \ V V / | | | | | | | (_| | _",
86 " \__, | |_| \___/ \_/\_/ |_| |_| |_| \__, | (_)",
95 ruler
= "~".ljust(gMaxLen
- 1, "~")
96 divider
= "_".ljust(gMaxLen
- 1, "_")
98 print "Error found. Probably wrong version of Python"
104 def safeRawInput(prompt
):
106 entry
= raw_input(prompt
)
112 ### global compare function
113 def compareTodo(a
, b
):
114 return cmp(a
.getEffectivePriority(), b
.getEffectivePriority())
117 print gColor
.code("error") + "ERROR: " + msg
+ gColor
.code("normal")
120 def clearScreen(useSys
= False):
122 if os
.name
== "posix":
124 elif os
.name
in ("dos", "ce", "nt"):
130 ### XTEA algorithm public domain
135 def crypt(self
, key
,data
,iv
='\00\00\00\00\00\00\00\00',n
=32):
136 def keygen(key
,iv
,n
):
138 iv
= self
.xtea_encrypt(key
,iv
,n
)
141 xor
= [ chr(x^y
) for (x
,y
) in zip(map(ord,data
),keygen(key
,iv
,n
)) ]
144 def xtea_encrypt(self
, key
,block
,n
=32):
145 v0
,v1
= struct
.unpack("!2L",block
)
146 k
= struct
.unpack("!4L",key
)
147 sum,delta
,mask
= 0L,0x9e3779b9L
,0xffffffffL
148 for round in range(n
):
149 v0
= (v0
+ (((v1
<<4 ^ v1
>>5) + v1
) ^
(sum + k
[sum & 3]))) & mask
150 sum = (sum + delta
) & mask
151 v1
= (v1
+ (((v0
<<4 ^ v0
>>5) + v0
) ^
(sum + k
[sum>>11 & 3]))) & mask
152 return struct
.pack("!2L",v0
,v1
)
155 def __init__(self
, width
):
160 def addLine(self
, pos
):
162 self
.nLines
= self
.nLines
+ 1
167 def intelliLen(self
, text
):
168 return len(gColor
.stripCodes(text
))
170 def wrap(self
, text
):
172 formatted
= text
.replace("<br>", "\n").replace("<BR>", "\n")
173 lines
= formatted
.splitlines()
176 for thisline
in lines
:
178 words
= thisline
.split()
183 wlen
= self
.intelliLen(w
) + 1
184 if (self
.pos
+ wlen
) == self
.width
:
187 elif (self
.pos
+ wlen
) < self
.width
:
193 self
.pos
= self
.pos
+ wlen
+ 1
200 ### Color code class for handling color text output
204 codes
= [{"normal":"\x1b[0;37;40m",
205 "title":"\x1b[1;32;40m",
206 "heading":"\x1b[1;35;40m",
207 "bold":"\x1b[1;35;40m",
208 "important":"\x1b[1;31;40m",
209 "error":"\x1b[1;31;40m",
210 "reverse":"\x1b[0;7m",
211 "row0":"\x1b[0;35;40m",
212 "row1":"\x1b[0;36;40m"},
213 {"normal":"\x1b[0;37m",
214 "title":"\x1b[1;32m",
215 "heading":"\x1b[1;35m",
217 "important":"\x1b[1;31m",
218 "error":"\x1b[1;31m",
219 "reverse":"\x1b[0;7m",
221 "row1":"\x1b[0;36m"}]
223 def __init__(self
, set):
224 self
.codeSet
= self
.NONE
227 def stripCodes(self
, text
):
228 # strip out the ansi codes
229 ex
= re
.compile("\x1b\[[0-9;]*m")
230 return ex
.sub("", text
)
232 def setCodeSet(self
, set):
235 self
.codeSet
= self
.NONE
236 elif set < len(self
.codes
):
238 return (old
!= self
.codeSet
)
240 def isValidSet(self
, myset
):
241 if myset
< len(self
.codes
):
246 def colorSupported(self
):
247 return (os
.name
== "posix" or os
.name
== "mac")
249 def usingColor(self
):
250 return (self
.codeSet
<> self
.NONE
and self
.colorSupported())
252 def code(self
, type):
253 if self
.codeSet
== self
.NONE
or not self
.colorSupported():
256 return self
.codes
[self
.codeSet
][type]
258 def printCode(self
, type):
259 if self
.codeSet
!= self
.NONE
:
260 print self
.code(type),
262 def getCodeSet(self
):
265 ### Viewer class for paging through multiple lines
267 def __init__(self
, maxlines
):
268 self
.maxlines
= maxlines
270 def show(self
, list, pause
):
273 if count
>= self
.maxlines
or line
== pause
:
274 io
= safeRawInput("--- Press enter for more. Enter s to skip ---").strip()
276 if len(io
) > 0 and io
.upper()[0] == "S":
283 ### Handler for encryption
285 TYPE_OBSCURED
= "xtea_"
291 self
.encryptionType
= self
.TYPE_OBSCURED
293 def setType(self
, codeType
):
294 if codeType
== self
.TYPE_AES
and supportAes
== False:
295 self
.encryptionType
= self
.TYPE_OBSCURED
297 self
.encryptionType
= codeType
298 return self
.encryptionType
300 def setKey(self
, key
):
306 def enterKey(self
, prompt1
, prompt2
):
309 input1
= getpass
.getpass(prompt1
+ " >>>")
311 input2
= getpass
.getpass(prompt2
+ " >>>")
313 print "You must enter the same password. Start again"
321 def complexKey(self
):
322 return md5(self
.key
).digest()
324 def getSecurityClass(self
, encrypted
):
325 if encrypted
.startswith(self
.TYPE_OBSCURED
):
326 return "private xtea"
327 if encrypted
.startswith(self
.TYPE_AES
):
331 def obscure(self
, plainText
):
332 key
= self
.complexKey()
333 obscured
= Xtea().crypt(key
, plainText
, self
.SALT_64
)
334 return self
.TYPE_OBSCURED
+ obscured
.encode('hex_codec')
336 def unobscure(self
, obscured
):
338 data
= obscured
[len(self
.TYPE_OBSCURED
):]
339 data
= data
.decode('hex_codec')
340 key
= self
.complexKey()
341 plain
= Xtea().crypt(key
, data
, self
.SALT_64
)
344 def encryptAes(self
, plainText
):
345 if len(self
.key
) < 16:
346 key
= self
.complexKey()
349 obscured
= pyRijndael
.EncryptData(key
, plainText
)
350 return self
.TYPE_AES
+ obscured
.encode('hex_codec')
352 def decryptAes(self
, encrypted
):
354 data
= encrypted
[len(self
.TYPE_AES
):]
355 data
= data
.decode('hex_codec')
356 if len(self
.key
) < 16:
357 key
= self
.complexKey()
360 plain
= pyRijndael
.DecryptData(key
, data
)
363 def enterKeyAndEncrypt(self
, plainText
):
364 self
.enterKey("Enter the master password.", "Re-enter the master password")
365 return self
.encrypt(plainText
)
367 def encrypt(self
, plainText
):
368 if self
.encryptionType
== self
.TYPE_AES
:
369 return self
.encryptAes(plainText
)
371 return self
.obscure(plainText
)
373 def enterKeyAndDecrypt(self
, encryptedText
):
374 self
.enterKey("Enter your master password", "")
375 return self
.decrypt(encryptedText
)
377 def decrypt(self
, encryptedText
):
378 if encryptedText
.startswith(self
.TYPE_AES
):
380 return "You do not have the pyRinjdael module so the text cannot be decrypted."
382 return self
.decryptAes(encryptedText
)
384 return self
.unobscure(encryptedText
)
386 ### Handler for user input
388 def __init__(self
, prompt
):
391 def read(self
, entry
= ""):
393 entry
= safeRawInput(self
.prompt
)
394 entry
= entry
.strip()
399 if entry
.find(magicTag
) == 0:
400 printError("You cannot begin lines with the sequence " + magicTag
)
403 elif entry
.find(TodoItem
.ENCRYPTION_MARKER
) >= 0:
404 printError ("You cannot use the special sequence " + TodoItem
.ENCRYPTION_MARKER
)
416 return (command
, line
)
418 class EditorLauncher
:
419 WARNING_TEXT
= "# Do not enter secret or private information!"
423 def edit(self
, text
):
426 if os
.name
== "posix":
428 elif os
.name
== "nt":
432 printError("Sorry, but external editing not supported on " + os
.name
.upper())
435 fname
= self
.makeFile(text
, terminator
)
437 printError("Unable to create temporary file.")
439 success
= self
.run(ed
, fname
)
441 (success
, text
) = self
.readFile(fname
)
442 if text
== self
.orgText
:
443 print("No changes made.");
445 self
.scrubFile(fname
)
451 def scrubFile(self
, fname
):
455 printError("Failed to remove file " + fname
+ ". If you entered any private data you should delete this file yourself.")
457 def readFile(self
, fname
):
460 fh
= open(fname
, "rt")
465 thisLine
= self
.safeString(line
)
466 if thisLine
!= self
.WARNING_TEXT
:
469 text
= text
+ thisLine
475 printError("Error reading the edited text. " + str(e
))
476 return (success
, text
)
478 def safeString(self
, text
):
479 return text
.replace("\r","").replace("\n","")
481 def makeFile(self
, text
, terminator
):
483 (fh
, fname
) = tempfile
.mkstemp(".tmpikog","ikog")
484 fout
= os
.fdopen(fh
,"wt")
485 text
= text
.replace("<BR>", "<br>")
487 lines
= text
.split("<br>")
488 fout
.write(self
.WARNING_TEXT
+ terminator
)
489 for thisline
in lines
:
490 fout
.write(self
.safeString(thisline
) + terminator
)
494 def run(self
, program
, file):
495 progs
= program
.split(",")
497 success
= self
.runProgram(prog
.strip(), file)
502 def runProgram(self
, program
, file):
504 if os
.name
== "posix":
507 os
.spawnlp(os
.P_WAIT
, program
, progarg
, file)
513 elif os
.name
== "nt":
514 if file.find(" ") >= 0:
515 file = "\"" + file + "\""
516 for path
in os
.environ
["PATH"].split(os
.pathsep
):
518 prog
= os
.path
.join(path
, program
)
519 if prog
.find(" ") >= 0:
520 progarg
= "\"" + prog
+ "\""
523 os
.spawnl(os
.P_WAIT
, prog
, progarg
, file)
534 ### The main todo list
536 quickCard
= ["Quick reference card:",
537 "? ADD/A/+ text FILTER/FI [filter]",
538 "HELP/H IMMEDIATE/I/++ text TOP/T [N]",
539 "COLOR/COLOUR/C [N] KILL/K/X/- N NEXT/N",
540 "MONOCHROME/MONO CLEAR PREV/P",
541 "EXPORT REP/R N [text] GO/G N",
542 "IMPORT file MOD/M N [text] LIST/L [filter]",
543 "REVIEW/REV ON/OFF EXTEND/E N [text] LIST>/L> [filter]",
545 "V1 SUB/SU N /s1/s2/ :D",
547 "SAVE/S DOWN/D N @>",
548 "AUTOSAVE/AS ON|OFF UP/U N :D>",
549 "VER/VERSION NOTE/NOTES text :P>",
550 "CLEARSCREEN/CLS O/OPEN file SHOW N",
551 "SYS ON|OFF NEW file SETEDxx editor",
552 "!CMD command 2 ABBREV/AB @x @full",
553 "ABBREV/AB ? PAB ? PAB :px :pfull",
554 "SHORTCUT/SC N cmd SHORTCUT/SC ? =N",
555 "ARCHIVE/DONE N [text]",
561 "The program is designed to help manage tasks using techniques",
562 "such as Getting Things Done by David Allen. Check out",
563 "http://www.henspace.co.uk for more information and detailed help.",
564 "To use the program, simply enter the task at the prompt.",
565 "All of the commands are displayed in the next section.",
569 "Commands that have more than one method of entry are shown separated by /",
570 "e.g HELP/H means that you can enter either HELP or an H.",
571 "All commands can be entered in upper or lower case.",
572 "Items shown in square brackets are optional.",
573 "Items shown separated by the | symbol are alternatives. e.g ON|OFF means",
574 "you should type either ON or OFF.",
575 "Note that some commands refer to adding tasks to the top or bottom of the",
576 "list. However the task's position in the list is also determined by its.",
577 "priority. So, for example, adding a task to the top will still not allow",
578 "it to precede tasks that have been assigned a higher priority number. ",
582 "? : displays a quick reference card",
583 "HELP/H : displays this help.",
584 "VERSION/VER : display the version.",
585 "WEB : Go to the website for more information",
586 "CLEARSCREEN/CLS : Clear the screen",
587 "COLOR/COLOUR/C [N] : Use colour display (not Windows) N=1 for no background",
588 "MONOCHROME/MONO : Use monochrome display",
589 "EXPORT : Export the tasks only to filename.tasks.txt",
590 "IMPORT file : Import tasks from the file",
591 "REVIEW/REV ON|OFF : If on, hitting enter moves to the next task",
592 " : If off, enter re-displays the current task",
593 "V0 : Same as REVIEW OFF",
594 "V1 : Same as REVIEW ON",
595 "SAVE/S : Save the tasks",
596 "O/OPEN file : Open a new data file.",
597 "NEW file : Create a new data file.",
598 "AUTOSAVE/AS ON|OFF : Switch autosave on or off",
599 "SYS ON|OFF : Allow the program to use system calls.",
600 "!CMD command : Run a system command.",
601 "2 : Start a two minute timer (for GTD)",
602 "QUIT/Q : quit the program",
604 "TASK ENTRY AND EDITING COMMANDS",
605 "-------------------------------",
606 "For the editing commands that require a task number, you can",
607 "replace N by '^' or 'this' to refer to the current task.",
608 "ADD/A/+ the task : add a task to the bottom of the list.",
609 " : Entering any line that does not begin with",
610 " : a valid command and which is greater than 10",
611 " : characters long is also assumed to be an addition.",
612 "EDIT/ED [N] : Create task, or edit task N, using external editor.",
613 "SUB/SU N /s1/s2/ : Replace text s1 with s2 in task N. Use \/ if you",
614 " : need to include the / character.",
615 "NOTE/NOTES text : shorthand for ADD #0 @Notes text",
616 "IMMEDIATE/I/++ : add a task to the top of the list to do today.",
617 "REP/R N [text] : replace task N",
618 "MOD/M N [text] : modify task N.",
619 "EXTEND/E N [text] : add more text to task N",
620 "FIRST/F N : move task N to the top.",
621 "DOWN/D/ N : move task N down the queue",
622 "UP/U/ N : move task N up the queue",
624 "TASK REMOVAL COMMANDS",
625 "---------------------",
626 "KILL/K/X/- N : kill (delete) task N. You must define N",
627 "DONE N [text] : Remove task N and move to an archive file",
628 "ARCHIVE N [text] : Same as DONE",
629 "CLEAR : Remove all tasks",
633 "SHOW N : display encrypted text for task N",
634 "FILTER/FI [filter] : set a filter. Applies to all displays",
635 " : See list for details of the filter",
636 " : Setting the filter to nothing clears it.",
637 "TOP/T [N] : Go to top, list N tasks, and display the top task",
638 "NEXT/N : display the next task. Same as just hitting enter",
639 "PREV/P : display previous task",
640 "GO/G N : display task N",
641 "LIST/L [filter] : list tasks. Filter = context, project, priority, date",
642 " : or word. Contexts begin with @ and projects with :p",
643 " : Dates begin with :d, anything else is a search word.",
644 " : Precede term with - to exclude e.g. -@Computer",
645 " : e.g LIST @computer or LIST #5",
646 "@ : sorted list by Context.",
647 ":D : sorted list by Dates",
648 ":P : sorted list by Projects",
649 "LIST>/L> [filter] : standard list sent to an HTML report",
650 "@> : sorted list by Context sent to an HTML report",
651 ":D> : sorted list by Dates sent to an HTML report",
652 ":P> : sorted list by Projects sent to an HTML report",
653 " : The HTML reports are sent to todoFilename.html",
657 "The SETEDxxx commands allow you to use an external editor.",
658 "Note the editor you pick should be a simple text editor. If you pick",
659 "something that doesn't work, try the defaults again.",
660 "Because some systems may have different editors installed, you can set",
661 "more than one by separating the editors usng commas. The program will",
662 "use the first one it finds.",
663 "For Windows the default is edit, which works quite well in the terminal",
664 "but you could change it to notepad.",
665 "For Linux, the default is nano,pico,vim,emacs.",
666 "To use external editors you must switch on system calls using the SYS ON",
668 "SETEDNT command : Set the external editor for Windows (NT).",
669 "SETEDPOSIX command : Set the editor for posix systems.",
670 " : e.g. SETEDNT edit",
671 " : SETEDPOSIX nano,vim",
672 "SHORTCUT/SC ? : list shortcuts",
673 "SHORTCUT/SC N cmd : Set shortcut N to command cmd",
674 "=N : Run shortcut N",
676 "ABBREV/AB @x @full : Create new abbreviation. @x expands to @full",
677 "ABBREV/AB ? : List context abbreviations.",
678 "PAB :px :pfull : Project abbreviation. :px expands to :pfull",
679 "PAB ? : List project abbreviations.",
683 "When you enter a task, you can embed any number of contexts in the task.",
684 "You can also embed a project description by preceding it with :p",
685 "You can assign a priority by preceding a number by #. e.g. #9.",
686 "If you don't enter a number, a default of 5 is used. The higher the ",
687 "number the more important it is. Priorities range from 1 to 10.",
688 "Only the first # is used for the priority so you can use # as",
689 "a normal character as long as you precede it with a priority number.",
690 "You can define a date when the task must be done by preceding the date",
691 "with :d, i.e :dYYYY/MM/DD or :dMM/DD or :dDD. If you omit the year/month",
692 "they default to the current date. Adding a date automatically creates an",
693 "@Date context for the task.",
694 "So, for example, to add a new task to e-mail Joe, we could enter:",
695 "+ e-mail joe @computer",
696 "or to add a task to the decorating project, we could enter:",
697 "+ buy wallpaper :pdecorating",
698 "to enter a task with an importance of 9 we could enter:",
699 "+ book that holiday #9 @Internet",
701 "MODIFYING AND EXTENDING TASKS",
702 "-----------------------------",
703 "The modify command allows you to change part of an existing task.",
704 "So for example, imagine you have a task:",
705 "[05] Buy some food #9 @Internet Projects:Shopping",
706 "Enter the command M 5 and then type:",
708 "Because the only element we have entered is a new context, only",
709 "that part is modified, so we get.",
710 "[05] Buy some food #9 @Computer Projects:Shopping",
711 "Likewise, had we entered:",
712 "Buy some tea :pEating",
714 "[05] Buy some tea #9 @Internet Projects:Eating",
715 "The extend command is similar but it appends the entry. So had",
716 "we used the command E 5 instead of M 5 the result would have been",
717 "[05] Buy some food ... Buy some tea #9 @Internet Projects:Eating",
721 "Any word preceded by @ will be used as a context. Contexts are like",
722 "sub-categories or sub-lists. There are a number of pre-defined",
723 "abbreviations that you can use as well. The recognised abbreviations",
725 "@A = @Anywhere (this is the default)",
737 "@S = @Someday/maybe",
743 "An @Date context is created if you embed a date in the task.",
744 "Dates are embedded using the :dDATE format.",
745 "Valid DATE formats are yyyy-mm-dd, mm-dd or dd",
746 "You can also use : or / as the separators. So, for example:",
747 ":d2006/12/22 or :d2006-11-7 or :d9/28 are all valid entries.",
749 "If you set a date, then until that date is reached, the task is given",
750 "an effective priority of 0. Once the date is reached, the task's",
751 "priority is increased by 11, moving it to the of the list.",
753 "A date entry of :d0 can be used to clear a date entry.",
754 "A date entry of :d+X can be used to create a date entry of today + X days.",
755 "So :d+1 is tomorrow and :d+0 is today.",
759 "If you want to encrypt text you can use the <private> or <secret> tags or",
760 "their abbreviations <p> and <s>.",
761 "These tags will result in all text following the tag to be encrypted.",
762 "Note that any special commands, @contexts for example, are treated as plain",
763 "text in the encrypted portion.",
764 "To display the text you will need to use the SHOW command.",
766 "The <private> tag uses the inbuilt XTEA algorithm. This is supposedly a",
767 "relatively secure method but probably not suitable for very sensitive data.",
769 "The <secret> tag can only be used if you have the pyRijndael.py module.",
770 "This uses a 256 bit Rinjdael cipher. The module can be downloaded from ",
771 "http://jclement.ca/software/pyrijndael/",
772 "You can install this in your Python path or just place it alongside your",
774 "Note you cannot use the extend command with encrypted text.",
777 "MARKING TASKS AS COMPLETE",
778 "-------------------------",
779 "The normal way to mark a task as complete is just to remove it using the",
780 "KILL command. If you want to keep track of tasks you have finished, you",
781 "can use the ARCHIVE or DONE command. This gives the task an @Archived",
782 "context, changes the date to today and then moves it from the current",
783 "file to a file with archive.dat appended. The archive file is a valid",
784 "ikog file so you can use the OPEN command to view it, edit it and run",
785 "reports in the normal way. So assuming your current script is ikog.py,",
786 "to archive the current task you could enter:",
788 "ARCHIVE ^ I have finished this",
790 "This would move the task to a file called ikog.py.archive.dat",
793 "USING EXTERNAL DATA",
794 "-------------------",
795 "Normally the tasks are embedded in the main program file so all you have",
796 "to carry around with you is the ikog.py file. The advantage is that you",
797 "only have one file to look after; the disadvantage is that every time you",
798 "save a task you have to save the program as well. If you want, you can",
799 "keep your tasks in a separate file.",
800 "To do this, use the EXPORT function to create a file ikog.py.tasks.txt",
801 "Use the CLEAR command to remove the tasks from your main ikog.py program.",
802 "Rename the exported file from ikog.py.tasks.txt to ikog.py.dat",
803 "Ikog will now use this file for storing your tasks.",
806 "PASSING TASKS VIA THE COMMAND LINE",
807 "----------------------------------",
808 "It is possible to add tasks via the command line. The general format of",
809 "the command line is:",
810 " ikog.py filename commands",
811 "The filename is the name of the data file containing your tasks. You",
812 "can use . to represent the default internal tasks.",
813 "Commands is a set of normal ikog commands separated by the / ",
814 "character. Note there must be a space either side of the /.",
815 "So to add a task and then exit the program we could just enter:",
816 " ikog.py . + here is my task / QUIT",
817 "Note that we added the quit command to exit ikog.",
818 "You must make sure that you do not use any commands that require user",
819 "input. Deleting tasks via the command line is more complicated as you",
820 "need to find the task automatically. If you do try to delete this way,",
821 "use the filter command to find some unique text and then delete it. eg.",
822 " ikog.py . FI my_unique_text / KILL THIS / QUIT",
823 "Use THIS instead of ^ as a caret has a special meaning in Windows.",
824 "If you do intend automating ikog from the command line, you should add",
825 "a unique reference to each task so you can find it later using FILTER. eg.",
826 "+ this is my task ref_1256",
834 def __init__(self
, todoFile
, externalDataFile
):
841 self
.sysCalls
= False
843 self
.globalFilterText
= ""
844 self
.globalFilters
= []
845 self
.localFilterText
= ""
846 self
.localFilters
= []
847 # split the file into the source code and the todo list
848 self
.filename
= todoFile
850 self
.filename
= os
.readlink(todoFile
)
852 pass # probably windows
853 externalDataFile
= self
.makeFilename(externalDataFile
)
854 self
.externalData
= self
.findDataSource(externalDataFile
)
855 if self
.externalData
:
856 self
.filename
= externalDataFile
858 self
.splitFile(self
.filename
, False)
859 self
.exactPriority
= False
862 def setPAbbreviation(self
, line
):
864 elements
= line
.split(" ", 1)
865 if not elements
[0].lower().startswith(":p"):
866 self
.showError("Project abbreviations must begin with :p")
867 elif len(elements
) > 1:
868 if not elements
[1].lower().startswith(":p"):
869 abb
= ":p" + elements
[1].title()
871 abb
= ":p" +elements
[1][2:].title()
872 globalPAbbr
.addAbbreviation(elements
[0], abb
)
875 if not globalPAbbr
.removeAbbreviation(elements
[0]):
876 self
.showError("Could not find project abbreviation " + line
)
878 print "Project abbreviation ", line
, " removed."
882 def showPAbbreviations(self
):
883 print globalPAbbr
.toStringVerbose()
885 def setAbbreviation(self
, line
):
887 elements
= line
.split(" ", 1)
888 if not elements
[0].startswith("@"):
889 self
.showError("Abbreviations must begin with @")
890 elif len(elements
) > 1:
891 if not elements
[1].startswith("@"):
892 abb
= "@" + elements
[1].title()
894 abb
= "@" +elements
[1][1:].title()
895 globalAbbr
.addAbbreviation(elements
[0], abb
)
898 if not globalAbbr
.removeAbbreviation(elements
[0]):
899 self
.showError("Could not find abbreviation " + line
)
901 print "Abbreviation ", line
, " removed."
905 def showAbbreviations(self
):
906 print globalAbbr
.toStringVerbose()
908 def setShortcut(self
, line
, force
= False):
909 elements
= line
.split(" ", 1)
911 index
= int(elements
[0])
912 if len(elements
) > 1:
913 command
= elements
[1]
917 self
.showError("Did not understand the command. Format should be SHORTCUT N my command.")
920 if index
< 0 or index
> len(self
.shortcuts
):
921 self
.showError("The maximum number of shortcuts is " + str(len(self
.shortcuts
)) + ". Shortcuts ignored.")
924 if self
.shortcuts
[index
] != "" and not force
:
925 if safeRawInput("Do you want to change the current command '" + self
.shortcuts
[index
] + "'? Enter Yes to overwrite. >>>").upper() != "YES":
927 self
.shortcuts
[index
] = command
931 def showShortcuts(self
):
933 for s
in self
.shortcuts
:
938 print "=%1d %s" %(index
, msg
)
941 def setShortcuts(self
, settings
= []):
942 if len(settings
) > self
.MAX_SHORTCUTS
:
943 self
.showError("The maximum number of shortcuts is " + str(self
.MAX_SHORTCUTS
) + ". Shortcuts ignored.")
944 self
.shortcuts
= ["" for n
in range(self
.MAX_SHORTCUTS
)]
945 if len(settings
) > 0:
946 self
.shortcuts
[0:len(settings
)] = settings
948 def getShortcutIndex(self
, command
):
949 if len(command
) == 2 and command
[0:1].upper() == "=":
950 index
= ord(command
[1]) - ord("0")
951 if index
>= self
.MAX_SHORTCUTS
:
957 def getShortcut(self
, command
):
958 index
= self
.getShortcutIndex(command
)
960 return self
.shortcuts
[index
]
964 def safeSystemCall(self
, line
):
967 self
.showError("Nothing to do.")
968 elif words
[0].upper() == "RM" or words
[0].upper() == "RMDIR" or words
[0].upper() == "DEL":
969 self
.showError("Sorry, but deletion commands are not permitted.")
974 def processCfgLine(self
, line
):
975 params
= line
.split("=")
978 cmd
= params
[0].strip()
979 if cmd
== "cfgEditorNt":
981 cfgEditorNt
= params
[1].replace("\"", "").strip()
982 elif cmd
== "cfgEditorPosix":
983 global cfgEditorPosix
984 cfgEditorPosix
= params
[1].replace("\"", "").strip()
985 elif cmd
== "cfgShortcuts":
986 elements
= params
[1].strip()[1:-1].split(",")
989 self
.setShortcut(str(index
) + " " + e
.strip()[1:-1], True)
991 elif cmd
== "cfgAutoSave":
992 if params
[1].upper().strip() == "TRUE":
996 self
.setAutoSave(as_
, False)
997 elif cmd
== "cfgReviewMode":
998 if params
[1].upper().strip() == "TRUE":
1001 self
.setReview("OFF")
1002 elif cmd
== "cfgSysCalls":
1003 if params
[1].upper().strip() == "TRUE":
1004 self
.setSysCalls("ON")
1006 self
.setSysCalls("OFF")
1007 elif cmd
== "cfgColor":
1008 gColor
.setCodeSet(int(params
[1].strip()))
1009 elif cmd
== "cfgAbbreviations":
1010 abbrs
= eval(params
[1].strip())
1011 globalAbbr
.setAbbreviations(abbrs
)
1012 elif cmd
== "cfgPAbbreviations":
1013 abbrs
= eval(params
[1].strip())
1014 globalPAbbr
.setAbbreviations(abbrs
)
1016 self
.showError("Unrecognised command " + cmd
)
1018 def makeFilename(self
, name
):
1019 (root
, ext
) = os
.path
.splitext(name
)
1020 if ext
.upper() != ".DAT":
1021 name
= name
+ ".dat"
1023 name
= os
.path
.expanduser(name
)
1024 except Exception, e
:
1025 self
.showError("Failed to expand path. " + str(e
))
1028 def findDataSource(self
, filename
):
1032 self
.splitFile(filename
, False)
1033 print "Using external data file ", filename
1036 print "No external data file ", filename
, ", so using internal tasks."
1039 def setSysCalls(self
, mode
):
1040 oldCalls
= self
.sysCalls
1041 mode
= mode
.strip().upper()
1043 self
.sysCalls
= True
1044 print "Using system calls for clear screen"
1046 self
.sysCalls
= False
1047 print "No system calls for clear screen"
1049 self
.showError("Could not understand the sys command. Use SYS ON or OFF.")
1050 return (self
.sysCalls
!= oldCalls
)
1052 def setAutoSave(self
, as_
, save
):
1054 if self
.autoSave
== False:
1055 self
.autoSave
= True
1058 elif self
.autoSave
== True:
1059 self
.autoSave
= False
1063 print "Autosave is on."
1065 print "Autosave is off."
1068 def showError(self
, msg
):
1071 def pause(self
, prompt
= "Press enter to continue."):
1072 if safeRawInput(prompt
).strip() != "":
1073 print "Entry ignored!"
1075 def setReview(self
, mode
):
1076 oldReview
= self
.review
1077 mode
= mode
.strip().upper()
1080 print "In review mode. Enter advances to the next task"
1083 print "Review mode off. Enter re-displays the current task"
1085 self
.showError("Could not understand the review command. Use REVIEW ON or OFF.")
1086 return (self
.review
!= oldReview
)
1088 def sortByPriority(self
):
1089 self
.todo
.sort(key
=TodoItem
.getEffectivePriority
, reverse
= True)
1092 def run(self
, commandList
):
1094 print "AES encryption not available."
1095 print("\nEnter HELP for instructions.")
1099 self
.sortByPriority()
1102 truncateTask
= False
1104 self
.checkCurrentTask()
1106 self
.moveToVisible()
1109 self
.printItemTruncated(self
.currentTask
, "Current: ")
1111 self
.printItemVerbose(self
.currentTask
)
1114 truncateTask
= False
1119 if len(commandList
) >= 1:
1120 enteredLine
= commandList
[0]
1121 commandList
= commandList
[1:]
1123 (rawcommand
, line
) = InputParser(prompt
).read(enteredLine
)
1125 command
= rawcommand
.upper()
1126 if self
.getShortcutIndex(command
) >= 0:
1127 sc
= self
.getShortcut(command
)
1129 (rawcommand
, line
) = InputParser("").read(self
.getShortcut(command
))
1134 print "Shortcut: ", rawcommand
, " ", line
1135 command
= rawcommand
.upper()
1139 elif command
== "PAB":
1140 if line
.strip() == "?":
1141 self
.showPAbbreviations()
1142 elif self
.setPAbbreviation(line
):
1144 elif command
== "ABBREV" or command
== "AB":
1145 if line
.strip() == "?":
1146 self
.showAbbreviations()
1147 elif self
.setAbbreviation(line
):
1149 elif command
== "SHORTCUT" or command
== "SC":
1150 if line
.strip() == "?":
1151 self
.showShortcuts()
1153 if self
.setShortcut(line
):
1155 printCurrent
= False
1156 elif command
== "2":
1157 enteredLine
= self
.runTimer(2)
1158 printCurrent
= False
1159 elif command
== "CLS" or command
== "CLEARSCREEN":
1160 clearScreen(self
.sysCalls
)
1161 elif command
== "SETEDNT":
1165 elif command
== "SETEDPOSIX":
1166 global cfgEditorPosix
1167 cfgEditorPosix
= line
1169 elif command
== "SYS":
1170 if self
.setSysCalls(line
):
1172 elif command
== "!CMD":
1174 self
.safeSystemCall(line
)
1176 self
.showError("System calls are not allowed. Use SYS ON to enable them.")
1177 elif command
== "SHOW" or command
== "SH":
1179 self
.pause("Press enter to clear screen and continue. ")
1180 clearScreen(self
.sysCalls
)
1181 elif command
== "VERSION" or command
== "VER":
1183 elif command
== "SAVE" or command
== "S":
1185 print "There's no need to save now. If the prompt shows >>> "
1186 print "then there is nothing to save. You only need to save if the prompt "
1190 elif command
== "NEW":
1191 filename
= self
.makeFilename(line
)
1192 if self
.createFile(filename
):
1197 printCurrent
= False
1198 elif command
== "OPEN" or command
== "O":
1199 filename
= self
.makeFilename(line
)
1204 printCurrent
= False
1205 elif command
== "AUTOSAVE" or command
== "AS":
1207 self
.showError("You must enter ON or OFF for the autosave command")
1209 self
.setAutoSave(line
.upper() == "ON", True)
1210 elif command
== "REVIEW" or command
== "REV":
1211 if self
.setReview(line
):
1213 elif command
== "V0":
1214 if self
.setReview("OFF"):
1216 elif command
== "V1":
1217 if self
.setReview("ON"):
1219 elif command
== "?":
1220 self
.printHelp(self
.quickCard
)
1221 elif command
== "HELP" or command
== "H":
1222 self
.printHelp(self
.help)
1223 elif command
== "QUIT" or command
== "Q":
1227 printCurrent
= False
1228 elif command
== "WEB":
1230 webbrowser
.open("http://www.henspace.co.uk")
1231 except Exception, e
:
1232 self
.showError("Unable to launch browser. " + str(e
))
1233 elif command
== "COLOR" or command
== "COLOUR" or command
== "C":
1238 if not gColor
.isValidSet(set):
1239 self
.showError("Invalid colour set ignored.")
1240 elif gColor
.setCodeSet(set):
1242 elif command
== "MONOCHROME" or command
== "MONO":
1243 if gColor
.setCodeSet(gColor
.NONE
):
1245 elif command
== "EXPORT":
1247 elif command
== "IMPORT":
1248 if self
.importTasks(line
):
1250 elif command
== "CLEAR" and line
== "":
1253 elif command
== "FILTER" or command
== "FI" or command
== "=":
1254 self
.setFilterArray(False, line
)
1255 elif command
== "NEXT" or command
== "N":
1257 elif command
== "PREV" or command
== "P":
1259 elif command
== "TOP" or command
== "T" or command
== "0":
1260 self
.currentTask
= 0
1262 self
.setFilterArray(True, "")
1263 self
.showLocalFilter()
1264 self
.printShortList(line
)
1266 elif command
== "GO" or command
== "G":
1268 elif command
== "IMMEDIATE" or command
== "I" or command
== "++":
1269 newItem
= self
.createItem(":d+0 " + line
)
1270 if newItem
.hasHiddenTask():
1271 clearScreen(self
.sysCalls
)
1272 if newItem
.hasError():
1273 print "Errors were found:"
1274 print newItem
.getError()
1275 print "The task was not added."
1276 printCurrent
= False
1278 self
.todo
.insert(0, newItem
)
1279 self
.currentTask
= 0
1280 self
.sortByPriority()
1282 elif command
== "KILL" or command
== "K" or command
== "-" or command
== "X":
1283 if self
.removeTask(line
):
1285 elif command
== "ARCHIVE" or command
== "DONE":
1286 if self
.archiveTask(line
):
1288 elif command
== "REP" or command
=="R":
1289 if self
.modifyTask(line
, TodoItem
.REPLACE
):
1290 self
.sortByPriority()
1293 printCurrent
= False
1294 elif command
== "SUB" or command
== "SU":
1295 if self
.substituteText(line
):
1296 self
.sortByPriority()
1298 elif command
== "EDIT" or command
== "ED":
1299 if not self
.sysCalls
:
1300 self
.showError("External editing needs to use system calls. Use SYS ON to enable them.")
1302 self
.addTaskExternal()
1303 elif self
.modifyTask(line
, TodoItem
.MODIFY
, externalEditor
= True):
1304 self
.sortByPriority()
1307 printCurrent
= False
1308 elif command
== "MOD" or command
== "M":
1309 if self
.modifyTask(line
, TodoItem
.MODIFY
):
1310 self
.sortByPriority()
1313 printCurrent
= False
1314 elif command
== "EXTEND" or command
== "E":
1315 if self
.modifyTask(line
, TodoItem
.APPEND
):
1316 self
.sortByPriority()
1319 printCurrent
= False
1320 elif command
== "FIRST" or command
== "F":
1321 if self
.moveTask(line
, self
.MOVE_TOP
):
1322 self
.sortByPriority()
1324 self
.currentTask
= 0
1325 elif command
== "DOWN" or command
== "D":
1326 if self
.moveTask(line
, self
.MOVE_DOWN
):
1327 self
.sortByPriority()
1329 elif command
== "UP" or command
== "U":
1330 if self
.moveTask(line
, self
.MOVE_UP
):
1331 self
.sortByPriority()
1333 elif command
== "LIST" or command
== "L":
1335 self
.setFilterArray(True, line
)
1336 self
.showLocalFilter()
1337 self
.printList(False, "", "")
1338 self
.clearFilterArray(True)
1341 elif command
== "LIST>" or command
== "L>":
1343 self
.setFilterArray(True, line
)
1344 self
.showLocalFilter()
1345 self
.printList(False, "", "")
1346 self
.clearFilterArray(True)
1348 elif command
== "@":
1351 elif command
== ":P":
1352 self
.listByProject()
1354 elif command
== ":D":
1357 elif command
== "@>":
1358 self
.startHtml("Report by Context")
1361 elif command
== ":P>":
1362 self
.startHtml("Report by Project")
1363 self
.listByProject()
1365 elif command
== ":D>":
1366 self
.startHtml("Report by Date")
1369 elif command
== "ADD" or command
== "A" or command
== "+":
1371 elif command
== "NOTE" or command
== "NOTES":
1372 self
.addTask("#0 @Notes " + line
)
1373 elif (len(command
) + len(line
)) > 10:
1374 self
.addTask(rawcommand
+ " " + line
)
1375 elif len(command
) > 0:
1376 self
.showError("Didn't understand. (Make sure you have a space after the command or your entry is longer than 10 characters)")
1377 printCurrent
= False
1381 self
.timerActive
= False
1383 print "\n\x07Timer\x07 complete.\x07\n\x07Press enter to continue.\x07"
1385 def runTimer(self
, delay
):
1386 self
.timerActive
= True
1387 t
= Timer(delay
* 60 , self
.timeout
)
1389 s
= raw_input(str(delay
) + " minute timer running.\nAny entry will cancel the timer:\n>>>")
1390 if self
.timerActive
:
1392 print "Timer cancelled."
1395 print "Input discarded as timer has finished."
1398 def addTaskExternal(self
):
1399 exEdit
= EditorLauncher()
1400 entry
= exEdit
.edit("")
1404 self
.showError("Nothing to add")
1406 def addTask(self
, line
):
1407 newItem
= self
.createItem(line
)
1408 if newItem
.hasError():
1409 print "Errors were found:"
1410 print newItem
.getError()
1411 print "The task was not added."
1412 printCurrent
= False
1414 if newItem
.hasHiddenTask():
1415 clearScreen(self
.sysCalls
)
1416 self
.todo
.append(newItem
)
1417 self
.sortByPriority()
1420 def checkCurrentTask(self
):
1421 if self
.currentTask
> len(self
.todo
) - 1:
1422 self
.currentTask
= len(self
.todo
) - 1
1423 if self
.currentTask
< 0:
1424 self
.currentTask
= 0
1426 def writeArchive(self
, item
):
1428 filename
= self
.filename
+ ".archive.dat"
1430 if not os
.path
.exists(filename
):
1431 f
= open(filename
,"wb")
1432 f
.write("# " + notice
[0] + "\n")
1433 f
.write(magicTag
+ "DATA\n")
1435 f
= open(filename
,"a+b")
1437 f
.write(item
.toString())
1440 print "Tasks archived to " + filename
1442 except Exception, e
:
1443 self
.showError("Error trying to archive the tasks.\n" + str(e
))
1446 def exportTasks(self
):
1447 filename
= self
.filename
+ ".tasks.txt"
1449 f
= open(filename
,"wb")
1450 f
.write("# " + notice
[0] + "\n")
1451 f
.write(magicTag
+ "DATA\n")
1452 for item
in self
.todo
:
1453 f
.write(item
.toString())
1456 print "Tasks exported to " + filename
1457 except Exception, e
:
1458 self
.showError("Error trying to export the file.\n" + str(e
))
1460 def importTasks(self
, filename
):
1462 orgNTasks
= len(self
.todo
)
1464 self
.showError("You must supply the name of the file to import.")
1468 self
.splitFile(filename
, True)
1469 if len(self
.todo
) == orgNTasks
:
1470 self
.showError("Failed to find any tasks to import.")
1473 except Exception, e
:
1474 self
.showError("Error importing tasks. " + str(e
))
1477 def createFile(self
, filename
):
1479 if os
.path
.exists(filename
):
1480 self
.showError("Sorry but " + filename
+ " already exists.")
1483 f
= open(filename
, "wb")
1484 f
.write("#!/usr/bin/env python\n")
1485 f
.write("#" + ruler
+ "\n")
1488 except Exception, e
:
1489 self
.showError("Error trying to create the file " + filename
+ ". " + str(e
))
1492 def save(self
, filename
):
1493 if filename
!= "" or self
.autoSave
:
1494 self
.forceSave(filename
)
1497 print "Autosave is off, so changes not saved yet."
1499 def forceSave(self
, filename
):
1501 filename
= self
.filename
1502 tmpFilename
= filename
+ ".tmp"
1503 backupFilename
= filename
+ ".bak"
1506 f
= open(tmpFilename
,"wb")
1507 f
.write("#!/usr/bin/env python\n")
1508 f
.write("# -*- coding: utf-8 -*-\n")
1509 f
.write("#" + ruler
+ "\n")
1510 f
.write("# Run the script for details of the licence\n")
1511 f
.write("# or refer to the notice section later in the file.\n")
1512 f
.write("#" + ruler
+ "\n")
1513 f
.write(magicTag
+ "DATA\n")
1514 for item
in self
.todo
:
1515 f
.write(item
.toString())
1517 f
.write(magicTag
+ "CONFIG\n")
1518 f
.write("cfgColor = " + str(gColor
.getCodeSet()) + "\n")
1519 f
.write("cfgAutoSave = " + str(self
.autoSave
) + "\n")
1520 f
.write("cfgReviewMode = " + str(self
.review
) + "\n")
1521 f
.write("cfgSysCalls = " + str(self
.sysCalls
) + "\n")
1522 f
.write("cfgEditorNt = \"" + cfgEditorNt
+ "\"\n")
1523 f
.write("cfgEditorPosix = \"" + cfgEditorPosix
+ "\"\n")
1524 f
.write("cfgShortcuts = " + str(self
.shortcuts
) + "\n")
1525 f
.write("cfgAbbreviations = " +str(globalAbbr
.toString()) +"\n")
1526 f
.write("cfgPAbbreviations = " +str(globalPAbbr
.toString()) +"\n")
1527 f
.write(magicTag
+ "CODE\n")
1528 for codeline
in self
.code
:
1529 f
.write(codeline
.rstrip())
1534 except Exception, e
:
1535 self
.showError("Error trying to save the file.\n" + str(e
))
1538 os
.remove(backupFilename
)
1542 oldstat
= os
.stat(filename
)
1543 os
.rename(filename
, backupFilename
)
1544 os
.rename(tmpFilename
, filename
)
1545 os
.chmod(filename
, stat
.S_IMODE(oldstat
.st_mode
)) # ensure permissions carried over
1546 self
.filename
= filename
1548 print "Tasks saved."
1549 except Exception, e
:
1550 self
.showError("Error trying to rename the backups.\n" + str(e
))
1552 def moveTo(self
, indexStr
):
1554 index
= int(indexStr
, 10)
1555 if index
< 0 or index
> len(self
.todo
) - 1:
1556 self
.showError("Sorry but there is no task " + indexStr
)
1558 if not self
.isViewable(self
.todo
[index
]):
1559 print "Switching off your filter so that the task can be displayed."
1560 self
.clearFilterArray(False)
1561 self
.currentTask
= index
1563 self
.showError("Unable to understand the task " + indexStr
+ " you want to show.")
1566 def moveToVisible(self
):
1567 start
= self
.currentTask
1569 if start
< 0 or start
>= len(self
.todo
):
1571 while not self
.isViewable(self
.todo
[self
.currentTask
]):
1573 if self
.currentTask
== start
:
1574 print "Nothing matched your filter. Removing your filter so that the current task can be displayed."
1575 self
.clearFilterArray(False)
1578 def decrypt(self
, indexStr
):
1579 index
= self
.getRequiredTask(indexStr
)
1580 if index
< 0 or index
> len(self
.todo
) - 1:
1581 self
.showError("Sorry but there is no task " + indexStr
+ " to show.")
1582 elif self
.todo
[index
].hasHiddenTask():
1584 print WordWrapper(gMaxLen
).wrap(ec
.enterKeyAndDecrypt(self
.todo
[index
].getHiddenTask()))
1586 print "Task ", index
, " has no encrypted data."
1588 def moveTask(self
, indexStr
, where
):
1591 print "You must supply the number of the task to move."
1594 index
= self
.getRequiredTask(indexStr
)
1595 if index
< 0 or index
> len(self
.todo
) - 1:
1596 self
.showError("Sorry but there is no task " + indexStr
+ " to move.")
1597 elif where
== self
.MOVE_DOWN
:
1598 if index
<= len(self
.todo
) - 2:
1599 item
= self
.todo
[index
]
1600 self
.todo
[index
] = self
.todo
[index
+ 1]
1601 self
.todo
[index
+ 1] = item
1602 print "Task ", index
, " moved down."
1605 self
.showError("Task " + str(index
) + " is already at the bottom.")
1608 if where
== self
.MOVE_TOP
:
1609 self
.todo
.insert(0, self
.todo
.pop(index
))
1612 item
= self
.todo
[dest
]
1613 self
.todo
[dest
] = self
.todo
[index
]
1614 self
.todo
[index
] = item
1615 print "Task ", index
, " moved up."
1618 self
.showError("Task " + str(index
) + " is already at the top.")
1622 self
.showError("Unable to understand the task " + indexStr
+ " you want to move.")
1628 if safeRawInput("Are you really sure you want to remove everything? Yes or No? >>>").upper() != "YES":
1629 print("Nothing has been removed.")
1632 self
.currentTask
= 0
1636 def getRequiredTask(self
, indexStr
):
1637 if indexStr
== "^" or indexStr
.upper() == "THIS":
1638 index
= self
.currentTask
1641 index
= int(indexStr
, 10)
1646 def archiveTask(self
, indexStr
):
1648 line
= indexStr
.split(" ", 1)
1654 index
= self
.getRequiredTask(indexStr
)
1655 if index
< 0 or index
> len(self
.todo
) - 1:
1656 self
.showError("Sorry but there is no task " + indexStr
+ " to mark as done and archive.")
1658 if indexStr
== "^" or indexStr
.upper() == "THIS":
1661 print "Are you sure you want to archive: ' " + self
.todo
[index
].toStringSimple() + "'"
1662 if safeRawInput("Enter Yes to archive this task? >>>").upper() == "YES":
1665 newItem
= self
.createItem(":d+0")
1666 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1667 newItem
= self
.createItem(entry
+ " @Archived")
1668 self
.todo
[index
].copy(newItem
, TodoItem
.APPEND
)
1669 if self
.writeArchive(self
.todo
[index
]):
1670 self
.todo
[index
:index
+ 1] = []
1671 print "Task ", index
, " has been archived."
1674 print "Task ", index
, " marked as archived but not removed."
1676 print "Task ", index
, " has not been archived."
1679 def removeTask(self
, indexStr
):
1681 index
= self
.getRequiredTask(indexStr
)
1682 if index
< 0 or index
> len(self
.todo
) - 1:
1683 self
.showError("Sorry but there is no task " + indexStr
+ " to delete.")
1685 if indexStr
== "^" or indexStr
.upper() == "THIS":
1688 print "Are you sure you want to remove ' " + self
.todo
[index
].toStringSimple() + "'"
1689 if safeRawInput("Enter Yes to delete this task? >>>").upper() == "YES":
1692 self
.todo
[index
:index
+ 1] = []
1693 print "Task ", index
, " has been removed."
1695 print "Task ", index
, " has not been removed."
1698 def substituteText(self
, indexStr
):
1699 line
= indexStr
.split(" ", 1)
1704 self
.showError("You need to define the task and substitution phrases. e.g SUB 0 /old/new/")
1710 print "You must supply the number of the task to change."
1713 index
= self
.getRequiredTask(indexStr
)
1715 if index
< 0 or index
> len(self
.todo
) - 1:
1716 self
.showError("Sorry but there is no task " + indexStr
)
1718 text
= entry
.replace("/", "\n")
1719 text
= text
.replace("\\\n","/")
1720 phrases
= text
.split("\n")
1721 if len(phrases
) != 4:
1722 self
.showError("The format of the command is incorrect. The substitution phrases should be /s1/s2/ ")
1724 oldText
= self
.todo
[index
].getTask()
1725 newText
= oldText
.replace(phrases
[1], phrases
[2])
1726 if newText
== oldText
:
1727 self
.showError("Nothing has changed.")
1729 newItem
= self
.createItem(newText
)
1730 if newItem
.hasError():
1731 print "With the substitution the task had errors:"
1732 print newItem
.getError()
1733 print "Task ", index
, " is unchanged."
1735 if newItem
.hasHiddenTask():
1736 clearScreen(self
.sysCalls
)
1737 self
.showError("It isn't possible to create private or secret data by using the substitition command.")
1739 self
.todo
[index
].copy(newItem
, TodoItem
.MODIFY
)
1740 print "Task ", index
, " has been changed."
1745 def modifyTask(self
, indexStr
, replace
, externalEditor
= False):
1746 line
= indexStr
.split(" ", 1)
1755 print "You must supply the number of the task to change."
1758 index
= self
.getRequiredTask(indexStr
)
1760 if index
< 0 or index
> len(self
.todo
) - 1:
1761 self
.showError("Sorry but there is no task " + indexStr
)
1765 exEdit
= EditorLauncher()
1766 (key
, entry
) = self
.todo
[index
].toStringEditable()
1767 entry
= exEdit
.edit(entry
)
1769 if replace
== TodoItem
.REPLACE
:
1770 print "This task will completely replace the existing entry,"
1771 print "including any projects and actions."
1772 elif replace
== TodoItem
.MODIFY
:
1773 print "Only the elements you add will be replaced. So, for example,"
1774 print "if you don't enter any projects the original projects will remain."
1776 print "Elements you enter will be appended to the current task"
1777 entry
= safeRawInput("Enter new details >>>")
1779 if replace
== TodoItem
.APPEND
:
1780 newItem
= self
.createItem(entry
, password
="unused") # we will discard the encrypted part on extend
1781 elif externalEditor
:
1782 newItem
= self
.createItem(entry
, password
= key
)
1784 newItem
= self
.createItem(entry
)
1785 if newItem
.hasHiddenTask():
1786 clearScreen(self
.sysCalls
)
1787 if newItem
.hasError():
1788 print "The task had errors:"
1789 print newItem
.getError()
1790 print "Task ", index
, " is unchanged."
1792 if newItem
.hasHiddenTask() and replace
== TodoItem
.APPEND
:
1793 self
.showError("It isn't possible to extend the encrypted part of a task.\nThis part is ignored.")
1794 self
.todo
[index
].copy(newItem
, replace
)
1795 print "Task ", index
, " has been changed."
1798 print "Task ", index
, " has not been touched."
1802 if self
.currentTask
< len(self
.todo
) - 1:
1803 self
.currentTask
= self
.currentTask
+ 1
1805 def incTaskLoop(self
):
1806 if self
.currentTask
< len(self
.todo
) - 1:
1807 self
.currentTask
= self
.currentTask
+ 1
1809 self
.currentTask
= 0
1811 if self
.currentTask
> 0:
1812 self
.currentTask
= self
.currentTask
- 1
1814 def decTaskLoop(self
):
1815 if self
.currentTask
> 0:
1816 self
.currentTask
= self
.currentTask
- 1
1818 self
.currentTask
= len(self
.todo
) - 1
1820 def printItemTruncated(self
, index
, leader
):
1821 if len(self
.todo
) < 1:
1822 print leader
, "no tasks"
1824 scrnline
= leader
+ "[%02d] %s" % (index
, self
.todo
[index
].toStringSimple())
1825 if len(scrnline
) > gMaxLen
:
1826 print scrnline
[0:gMaxLen
- 3] + "..."
1831 def printItem(self
, index
, colorType
):
1832 if len(self
.todo
) < 1:
1833 self
.output("There are no tasks to be done.\n", 0)
1836 wrapper
= WordWrapper(gMaxLen
)
1837 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringSimple()))
1838 if colorType
== "row0":
1839 style
= "class=\"evenTask\""
1841 style
= "class=\"oddTask\""
1842 self
.output("<div %s>[%02d] %s</div>\n" % (style
, index
, self
.todo
[index
].toStringSimple()),
1843 gColor
.code(colorType
) + scrnline
+ gColor
.code("normal") + "\n" )
1844 nlines
= wrapper
.getNLines()
1847 def printItemVerbose(self
, index
):
1848 if len(self
.todo
) < 1:
1849 print "There are no tasks to be done."
1852 wrapper
= WordWrapper(gMaxLen
)
1853 scrnline
= wrapper
.wrap("[%02d] %s" % (index
, self
.todo
[index
].toStringVerbose()))
1856 def clearFilterArray(self
, local
):
1858 self
.localFilters
= []
1859 self
.localFilterText
= ""
1861 self
.globalFilters
= []
1862 self
.globalFilterText
= ""
1864 def setFilterArray(self
, local
, requiredFilter
):
1865 filters
= requiredFilter
.split()
1867 destination
= self
.localFilters
1869 destination
= self
.globalFilters
1872 for word
in filters
:
1873 if word
[0:1] == "-":
1878 if word
[0:2].upper() == ":D" and len(word
) > 2:
1879 filter = ":D" + TodoItem("").parseDate(word
[2:].strip(), False)
1880 elif word
[0:2].lower() == ":p":
1881 filter = globalPAbbr
.expandProject(word
)
1883 filter = globalAbbr
.expandAction(word
)
1885 filter = "-" + filter
1886 destination
.append(filter)
1887 if humanVersion
!= "":
1888 humanVersion
= humanVersion
+ " " + filter
1890 humanVersion
= filter
1892 for filter in self
.globalFilters
:
1893 destination
.append(filter)
1894 if humanVersion
!= "":
1895 humanVersion
= humanVersion
+ " " + filter
1897 humanVersion
= filter
1899 self
.localFilterText
= humanVersion
1901 self
.globalFilterText
= humanVersion
1903 def isViewable(self
, item
):
1904 if len(self
.globalFilters
) == 0 and len(self
.localFilters
) == 0:
1908 if len(self
.localFilters
) > 0:
1909 filterArray
= self
.localFilters
1911 filterArray
= self
.globalFilters
1912 if "or" in filterArray
or "OR" in filterArray
:
1916 for filter in filterArray
:
1917 if filter.upper() == "OR":
1923 if filter[0:1] == "+":
1926 if filter[0:1] == "-":
1932 if filter[0:1] == "#":
1933 priority
= int(filter[1:], 10)
1938 if self
.exactPriority
:
1939 if item
.hasPriority(priority
):
1941 elif item
.hasPriorityOrAbove(priority
):
1943 elif filter[0:2].upper() == ":D":
1944 if item
.hasDate(filter[2:]):
1946 elif filter[0:2].upper() == ":P":
1947 view
= item
.hasProject(filter)
1948 elif filter[0:1].upper() == "@":
1949 view
= item
.hasAction(filter)
1950 elif item
.hasWord(filter):
1953 view
= (view
!= True)
1961 if fast
or mustHave
:
1966 def listByAction(self
):
1967 index
= SearchIndex()
1968 for item
in self
.todo
:
1969 index
.addCollection(item
.getActions())
1971 (n
, value
) = index
.getFirstItem()
1975 if not gColor
.usingColor() and n
> 0:
1979 self
.setFilterArray(True, "+" + value
)
1980 self
.printList(div
, "<H2 class=\"hAction\">" + value
+ "</H2>\n", gColor
.code("title") + "\n" + value
+ "\n" + gColor
.code("title"))
1981 self
.clearFilterArray(True)
1982 (n
, value
) = index
.getNextItem(n
)
1985 def listByProject(self
):
1986 index
= SearchIndex()
1987 for item
in self
.todo
:
1988 index
.addCollection(item
.getProjects())
1990 (n
, value
) = index
.getFirstItem()
1994 if not gColor
.usingColor() and n
> 0:
1998 self
.setFilterArray(True, "+:p" + value
)
1999 self
.printList(div
, "<H2 class =\"hProject\">Project: " + value
+ "</H2>\n", gColor
.code("title") + "\nProject: " + value
+ gColor
.code("normal") + "\n")
2000 self
.clearFilterArray(True)
2001 (n
, value
) = index
.getNextItem(n
)
2004 def listByDate(self
):
2005 index
= SearchIndex()
2006 for item
in self
.todo
:
2007 index
.add(item
.getDate())
2009 (n
, value
) = index
.getFirstItem()
2013 if not gColor
.usingColor() and n
> 0:
2017 self
.setFilterArray(True, "+:D" + value
)
2018 self
.printList(div
, "<H2 class =\"hDate\">Date: " + value
+ "</H2>\n", gColor
.code("title") + "\nDate: " + value
+ gColor
.code("normal") + "\n")
2019 self
.clearFilterArray(True)
2020 (n
, value
) = index
.getNextItem(n
)
2024 def showFilter(self
):
2025 if self
.globalFilterText
!= "":
2026 self
.output("<H3 class =\"hFilter\">Filter = " + self
.globalFilterText
+ "</H3>\n",
2027 gColor
.code("bold") + "Filter = " + self
.globalFilterText
+ gColor
.code("normal") + "\n")
2029 def showLocalFilter(self
):
2030 if self
.localFilterText
!= "":
2031 self
.output("<H3 class =\"hFilter\">Filter = " + self
.localFilterText
+ "</H3>\n",
2032 gColor
.code("bold") + "Filter = " + self
.localFilterText
+ gColor
.code("normal") + "\n")
2034 def printList(self
, div
, outHtml
, outStd
):
2035 self
.doPrintList(-1, div
, outHtml
, outStd
)
2037 def printShortList(self
, line
):
2040 count
= int(line
, 10)
2042 self
.showError("Didn't understand the number of tasks you wanted listed.")
2043 self
.doPrintList(count
, False, "", "")
2045 def doPrintList(self
, limitItems
, div
, outHtml
, outStd
):
2053 self
.outputHtml("<div class=\"itemGroup\">\n")
2054 for item
in self
.todo
:
2055 if self
.isViewable(item
):
2059 self
.output(outHtml
, outStd
)
2060 if not gColor
.usingColor() and not first
:
2063 count
= count
+ self
.printItem(n
, color
)
2069 displayed
= displayed
+ 1
2071 if limitItems
>= 0 and displayed
>= limitItems
:
2073 if count
>= maxlines
:
2074 if self
.htmlFile
== "":
2075 msg
= safeRawInput("---press Enter for more. Enter s to skip: ")
2076 if len(msg
) > 0 and msg
.strip().upper()[0] == "S":
2080 self
.outputHtml("</div>\n")
2082 def printHelp(self
, lines
):
2083 ListViewer(24).show(lines
,"!PAUSE!")
2085 def splitFile(self
, filename
, dataOnly
):
2089 f
= open(filename
, 'r')
2091 if line
[0:2] == "#!":
2094 if line
.find(magicTag
+ "DATA") == 0:
2098 elif line
.find(magicTag
+ "CONFIG") == 0:
2102 elif line
.find(magicTag
+ "CODE") == 0:
2109 self
.code
.append(line
)
2111 self
.processCfgLine(line
)
2114 if len(line
) > 0 and line
[0] == "#":
2115 line
= line
[1:].strip()
2117 newItem
= self
.createItem(line
)
2119 self
.todo
.append(newItem
)
2124 def createItem(self
, line
, password
= ""):
2125 item
= TodoItem(line
, password
)
2128 def outputHtml(self
, html
):
2129 if self
.htmlFile
!= "":
2130 self
.htmlFile
.write(html
)
2132 def output(self
, html
, stdout
):
2133 if self
.htmlFile
!= "":
2134 #self.htmlFile.write(html.replace("\n", "<br>\n"))
2135 self
.htmlFile
.write(html
)
2142 def startHtml(self
, title
):
2143 htmlFilename
= self
.filename
+ ".html"
2145 self
.htmlFile
= open(htmlFilename
, "w")
2146 self
.htmlFile
.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n")
2147 self
.htmlFile
.write("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n")
2148 self
.htmlFile
.write("<html>\n<head>\n")
2149 self
.htmlFile
.write("<style>\n")
2150 self
.htmlFile
.write(".footer {text-align:center;}\n")
2151 self
.htmlFile
.write("</style>\n")
2152 self
.htmlFile
.write("<link rel=\"stylesheet\" href=\"ikog.css\" type=\"text/css\">\n")
2153 self
.htmlFile
.write("</head>\n<body>\n")
2154 self
.htmlFile
.write("<div class=\"header\">\n")
2155 self
.htmlFile
.write("<H1 class=\"hTitle\">iKog Todo List</H1>\n")
2156 self
.htmlFile
.write("<H2 class=\"hSubTitle\">" + title
+ " printed " + date
.today().isoformat() + "</H1>\n")
2157 self
.htmlFile
.write("</div>\n")
2158 self
.htmlFile
.write("<div class=\"taskArea\">\n")
2160 print "Failed to create output file:", htmlFilename
2164 name
= self
.htmlFile
.name
2167 self
.htmlFile
.write("</div>\n")
2168 self
.htmlFile
.write("<div class=\"footer\">\n")
2169 self
.htmlFile
.write("--- end of todo list ---<br>\n")
2170 self
.htmlFile
.write("Created using " + notice
[0] + "\n<br>" + notice
[1] + "<br>\n")
2171 self
.htmlFile
.write("</div>\n")
2173 self
.htmlFile
.write("</body>\n</html>\n")
2174 self
.htmlFile
.close()
2176 print "HTML file " + name
+ " created."
2178 except Exception, e
:
2179 self
.showError("Error writing to file. " + str(e
))
2183 safeName
= os
.path
.abspath(name
).replace("\\","/")
2184 safeName
= "file://" + urllib
.quote(safeName
," /:")
2185 webbrowser
.open(safeName
)
2186 except Exception, e
:
2187 self
.showError("Unable to launch html output. " + str(e
))
2189 class Abbreviations
:
2190 def __init__(self
, project
= False):
2191 self
.default(project
)
2193 def default(self
,project
):
2197 self
.abbrevs
= {"@A":"@Anywhere","@C":"@Computer",
2198 "@D":"@Desk", "@E": "@Errands",
2199 "@H":"@Home", "@I":"@Internet","@L":"@Lunch", "@M":"@Meeting", "@N":"@Next",
2200 "@P":"@Phone", "@Pw":"@Password", "@S":"@Someday/Maybe",
2201 "@O":"@Other", "@W4":"@Waiting_For", "@W":"@Work"}
2204 def setAbbreviations(self
, abbr
):
2205 self
.abbrevs
.update(abbr
)
2207 def addAbbreviation(self
, key
, word
):
2208 self
.abbrevs
.update({key
.title():word
})
2210 def removeAbbreviation(self
, key
):
2212 if self
.abbrevs
.has_key(key
):
2213 del self
.abbrevs
[key
]
2217 def expandAction(self
, action
):
2218 if action
[0:1] != "@":
2221 action
= action
.title()
2222 if self
.abbrevs
.has_key(action
):
2223 return self
.abbrevs
[action
]
2226 def expandProject(self
, project
):
2227 if not project
.lower().startswith(":p"):
2229 project
= project
.title()
2230 if self
.abbrevs
.has_key(project
):
2231 return self
.abbrevs
[project
]
2235 return str(self
.abbrevs
)
2237 def toStringVerbose(self
):
2240 for key
in self
.abbrevs
:
2241 output
= output
+ key
.ljust(5) + " = " + self
.abbrevs
[key
].ljust(30)
2244 output
= output
+ "\n"
2246 output
= output
+ "\n"
2254 if ent
!= "" and not ent
in self
.items
:
2255 self
.items
.append(ent
)
2257 def addCollection(self
, collection
):
2258 for ent
in collection
:
2259 if ent
!= "" and not ent
in self
.items
:
2260 self
.items
.append(ent
)
2264 def getFirstItem(self
):
2265 if len(self
.items
) > 0:
2266 return (0, self
.items
[0])
2270 def getNextItem(self
, count
):
2272 if count
> len(self
.items
) - 1:
2275 return (count
, self
.items
[count
])
2279 ENCRYPTION_MARKER
= "{}--xx"
2283 NOT_DUE_PRIORITY
= 0
2284 DEFAULT_PRIORITY
= 5
2285 OVERDUE_PRIORITY
= 11
2286 MEETING_PRIORITY
= 10
2287 def __init__(self
,line
, password
= ""):
2290 self
.hiddenTask
= ""
2294 self
.created
= date
.today().isoformat()
2296 self
.autoAction
= False
2297 self
.autoProject
= False
2298 self
.nullDate
= False
2299 self
.parse(line
, password
)
2301 def makeSafeDate(self
, year
, month
, day
):
2308 newDate
= date(year
, month
, day
)
2315 def parseDate(self
, dateStr
, quiet
):
2316 dateStr
= dateStr
.replace("/","-")
2317 dateStr
= dateStr
.replace(":","-")
2318 entry
= dateStr
.split("-")
2322 elif dateStr
== "0":
2323 self
.nullDate
= True
2328 if dateStr
[0:1] == "+":
2329 days
= int(dateStr
[1:].strip(), 10)
2330 when
= now
+ timedelta(days
)
2333 year
= int(entry
[0], 10)
2334 month
= int(entry
[1], 10)
2335 day
= int(entry
[2], 10)
2338 month
= int(entry
[0], 10)
2339 day
= int(entry
[1], 10)
2343 day
= int(entry
[0], 10)
2351 when
= self
.makeSafeDate(year
, month
, day
)
2353 self
.nullDate
= False
2359 self
.addError("Could not decode the date. Use :dYYYY/MM/DD")
2362 return when
.isoformat()
2364 def parse(self
, line
, password
):
2366 words
= line
.split(" ")
2370 ecmLen
= len(self
.ENCRYPTION_MARKER
)
2371 for word
in words
[start
:]:
2372 wordUC
= word
.strip().upper()
2375 taskToHide
= taskToHide
+ word
.strip() + " "
2376 elif word
[0:ecmLen
] == self
.ENCRYPTION_MARKER
:
2377 self
.hiddenTask
= word
[ecmLen
:]
2378 elif wordUC
.startswith("<PRIVATE>") or wordUC
.startswith("<SECRET>") or wordUC
.startswith("<S>") or wordUC
.startswith("<P>"):
2381 pos
= word
.index(">")
2382 taskToHide
= taskToHide
+ word
[pos
+ 1:].strip() + " "
2385 elif word
[0] == "@" and len(word
) > 1:
2386 if wordUC
== "@DATE":
2387 self
.addError("@Date contexts should not be entered. Use :dYYYY-MM-DD")
2389 act
= globalAbbr
.expandAction(word
.strip())
2390 if not act
in self
.actions
:
2391 self
.actions
.append(act
)
2392 elif word
[0:1] == "#" and len(word
) > 1 and self
.priority
== -1:
2394 self
.priority
= int(word
[1:].strip(), 10)
2395 if self
.priority
< 1:
2397 elif self
.priority
> 10:
2400 self
.addError("Did not understand priority.")
2402 elif wordUC
[0:2] == ":P" and len(word
) > 2:
2403 proj
= globalPAbbr
.expandProject(word
.strip())[2:].title()
2404 if not proj
in self
.projects
:
2405 self
.projects
.append(proj
)
2406 elif wordUC
[0:8] == ":CREATED" and len(word
) > 8:
2407 self
.created
= word
[8:].strip()
2408 elif wordUC
[0:2] == ":D" and len(word
) > 2:
2409 self
.when
= self
.parseDate(word
[2:].strip(), False)
2411 self
.task
= self
.task
+ word
.strip() + " "
2412 if taskToHide
!= "":
2415 if ec
.setType(ec
.TYPE_AES
) != ec
.TYPE_AES
:
2416 self
.addError("AES encryption is not available.")
2419 ec
.setType(ec
.TYPE_OBSCURED
)
2420 if taskToHide
!= "":
2422 self
.hiddenTask
= ec
.enterKeyAndEncrypt(taskToHide
)
2425 self
.hiddenTask
= ec
.encrypt(taskToHide
)
2427 if len(self
.actions
) == 0:
2428 self
.actions
.append("@Anywhere")
2429 self
.autoAction
= True
2430 if len(self
.projects
) == 0:
2431 self
.projects
.append("None")
2432 self
.autoProject
= True
2434 def addError(self
, err
):
2435 if len(self
.error
) > 0:
2436 self
.error
= self
.error
+ "\n"
2437 self
.error
= self
.error
+ err
2440 return self
.error
!= ""
2447 def hasWord(self
, word
):
2448 return (self
.task
.upper().find(word
.upper()) >= 0)
2450 def hasAction(self
, loc
):
2451 if self
.when
!= "" and loc
.upper() == "@DATE":
2454 return loc
.title() in self
.actions
2456 def copy(self
, todoItem
, replace
):
2457 if replace
== TodoItem
.REPLACE
or len(todoItem
.task
.strip()) > 0:
2458 if replace
== TodoItem
.APPEND
:
2459 self
.task
= self
.task
+ " ..." + todoItem
.task
2461 self
.task
= todoItem
.task
2462 if replace
== TodoItem
.REPLACE
or todoItem
.autoAction
== False:
2463 if replace
!= TodoItem
.APPEND
:
2465 for loc
in todoItem
.actions
:
2466 if not loc
in self
.actions
:
2467 self
.actions
.append(loc
)
2468 if replace
== TodoItem
.REPLACE
or todoItem
.autoProject
== False:
2469 if replace
!= TodoItem
.APPEND
:
2471 for proj
in todoItem
.projects
:
2472 if not proj
in self
.projects
:
2473 self
.projects
.append(proj
)
2474 if replace
== TodoItem
.REPLACE
or (todoItem
.when
!= "" or todoItem
.nullDate
== True):
2475 self
.when
= todoItem
.when
2476 if todoItem
.priority
>= 0:
2477 self
.priority
= todoItem
.priority
2479 if replace
== TodoItem
.REPLACE
or len(todoItem
.hiddenTask
.strip()) > 0:
2480 if replace
== TodoItem
.APPEND
:
2483 self
.hiddenTask
= todoItem
.hiddenTask
2485 def hasHiddenTask(self
):
2486 return self
.hiddenTask
!= ""
2489 return len(self
.task
.strip()) > 0
2491 def hasProject(self
, proj
):
2492 if proj
[0:2].upper() == ":P":
2494 return proj
.title() in self
.projects
2496 def hasDate(self
, dt
):
2497 dt
= self
.parseDate(dt
, True)
2501 return self
.when
== dt
2503 def hasPriorityOrAbove(self
, priority
):
2504 return (self
.getEffectivePriority() >= priority
)
2506 def hasPriority(self
, priority
):
2507 return (self
.getEffectivePriority() == priority
)
2509 def getHiddenTask(self
):
2510 return self
.hiddenTask
2515 def getActions(self
):
2519 return self
.actions
+ ["@Date"]
2521 def getProjects(self
):
2522 return self
.projects
2527 def getPriority(self
):
2528 if self
.priority
< 0:
2529 return self
.DEFAULT_PRIORITY
2531 return self
.priority
2533 def getEffectivePriority(self
):
2534 userP
= self
.getPriority()
2536 if self
.when
<= date
.today().isoformat():
2537 userP
= self
.OVERDUE_PRIORITY
+ userP
2538 if self
.hasAction("@Meeting"):
2539 userP
= userP
+ self
.MEETING_PRIORITY
2541 userP
= self
.NOT_DUE_PRIORITY
2547 entry
= entry
+ " " + self
.task
2548 for action
in self
.actions
:
2549 entry
= entry
+ " " + action
2550 for project
in self
.projects
:
2551 entry
= entry
+ " :p" + project
2552 entry
= entry
+ " :created" + self
.created
2554 entry
= entry
+ " :d" + self
.when
2555 if self
.priority
>= 0:
2556 entry
= entry
+ " #" + str(self
.priority
)
2557 if self
.hiddenTask
!= "":
2558 entry
= entry
+ " " + self
.ENCRYPTION_MARKER
+ self
.hiddenTask
2561 def toStringEditable(self
, includeHidden
= False):
2565 entry
= entry
+ ":d" + self
.when
+ " "
2566 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2567 if len(self
.actions
) > 0:
2568 for action
in self
.actions
:
2569 entry
= entry
+ " " + action
2570 if len(self
.projects
) > 0:
2571 for project
in self
.projects
:
2573 if project
!= "None":
2574 entry
= entry
+ " :p" + project
2575 if self
.hiddenTask
!= "" and includeHidden
:
2577 entry
= entry
+ " <" + Encryptor().getSecurityClass(self
.hiddenTask
)[0:1] + ">"
2578 entry
= entry
+ ec
.enterKeyAndDecrypt(self
.hiddenTask
)
2579 password
= ec
.getKey()
2580 return (password
, entry
.strip())
2582 def toStringSimple(self
):
2585 entry
= entry
+ "@Date " + self
.when
+ " "
2586 entry
= entry
+ "%s #%d" % (self
.task
, self
.getPriority())
2587 if self
.hiddenTask
!= "":
2588 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2589 if len(self
.actions
) > 0:
2590 for action
in self
.actions
:
2591 entry
= entry
+ " " + action
2592 if len(self
.projects
) > 0:
2594 for project
in self
.projects
:
2596 if project
!= "None":
2598 entry
= entry
+ " Projects: " + project
2601 entry
= entry
+ ", " + project
2602 #entry = entry + " [" + self.created + "]"
2605 def toStringVerbose(self
):
2606 entry
= gColor
.code("title") + self
.task
2607 if self
.hiddenTask
!= "":
2608 entry
= entry
+ " <*** " + Encryptor().getSecurityClass(self
.hiddenTask
) + " ***> "
2609 entry
= entry
+ gColor
.code("bold") + "\nPriority: %02d" % (self
.getPriority())
2610 if len(self
.actions
) or self
.when
!= "" > 0:
2611 entry
= entry
+ gColor
.code("heading") + "\nContext: "
2613 entry
= entry
+ gColor
.code("important") + "@Date " + self
.when
2614 entry
= entry
+ gColor
.code("normal")
2615 for action
in self
.actions
:
2616 entry
= entry
+ " " + action
;
2617 if len(self
.projects
) > 0:
2619 for project
in self
.projects
:
2620 if project
!= "None":
2622 entry
= entry
+ gColor
.code("heading") + "\nProjects: " + gColor
.code("normal");
2623 entry
= entry
+ project
2626 entry
= entry
+ ", " + project
2627 entry
= entry
+ gColor
.code("normal") + "\nCreated: [" + self
.created
+ "]"
2631 #for line in notice:
2634 pythonVer
= platform
.python_version()
2635 ver
= pythonVer
.split(".")
2636 if int(ver
[0]) < gReqPythonMajor
or (int(ver
[0]) == gReqPythonMajor
and int(ver
[1]) < gReqPythonMinor
):
2637 print "\nSorry but this program requires Python ", \
2638 str(gReqPythonMajor
) + "." + str(gReqPythonMinor
), \
2639 "\nYour current version is ", \
2640 str(ver
[0]) + "." + str(ver
[1]), \
2641 "\nTo run the program you will need to install the current version of Python."
2644 # signal.signal(signal.SIGINT, signalHandler)
2645 gColor
= ColorCoder(cfgColor
)
2646 globalAbbr
= Abbreviations()
2647 globalPAbbr
= Abbreviations(project
=True)
2649 if len(sys
.argv
) > 2:
2651 reopen
= sys
.argv
[1]
2653 reopen
= sys
.argv
[0] + ".dat"
2654 for word
in sys
.argv
[2:]:
2656 commandList
.append(command
)
2659 command
= command
+ word
+ " "
2660 commandList
.append(command
)
2661 elif len(sys
.argv
) > 1:
2662 reopen
= sys
.argv
[1]
2664 reopen
= sys
.argv
[0] + ".dat"
2667 todoList
= TodoList(sys
.argv
[0], reopen
)
2668 reopen
= todoList
.run(commandList
)