# -*- coding: utf-8 -*- """ ToDoExtension.py Todo-Extension for WikidPad (http://www.wikidpad.net) Written by Christian Ziemski http://www.ziemski.net/wikidpad/todo_extension.html <<< hacked a bit by Björn Johansson, a search for the phrase "changed here" reveals all changes. Now, if the todo is untagged but placed on a page where the page name is a date, this date gets added as a tag (only if the todo has not already been dated in the normal way). Perhaps this could/should be extended to other categories than dates ie it could be detected if the todo is placed under a page that is called SomeDay for example. fixed by Christian a bit, so not all "changed here" from Björn are still there... >>> Why all that? ------------- I was looking for a good To-Do/Notes/PIM application for a long time and found WikidPad. Loosely based on the following extensions I wrote my own todo-extension: http://wikidpad.python-hosting.com/wiki/GettingThingsDone The starting point. NextActions by Eric Neumann Enhanced by icosahedral to organize by context instead of by project name. http://wikidpad.python-hosting.com/wiki/SortedTodos JaysenNaidoo version to sort the page according to tags (todo.:) Mike Crowe added dynamic tags I added date calculations, date sorting, a separate calendar page and a configuration section. (The formulae for date calculation from week number I found somewhere in 'the internet'.) Description: ------------ If a wikiWord beginning with "ToDo" is created or selected (viewed) it automatically collects all 'todo' entries from all other pages and inserts them here (after a placemark). Of course you can write own text before that placemark without being changed. If the wikiWord has additional characters after the "ToDo" like "ToDoPrivate" those characters "Private" are used as filter (case insensitive). If there are spaces or underlines between "ToDo" and the following characters (e.g "ToDo_Private") these are ignored and so still the filter is "Private". (This may be better readable.) Only Todos containing that filter string (in its name or in the full entry) are collected. (Behavior depends on configuration option "filterTodos" below.) Note: If the optional Calendar feature is enabled below, it is "ToDoCalendar" and "ToDoCalendarPrivate" in the example above.) The todos are sorted by 'tag' the following way: 1.) Completely untagged e.g. 'todo: shopping' 2.) Tagged with special tags (at End-Of-Tag!) e.g. 'todo.High: pay taxes' Sort order and display see config section below 3.) Tagged with a date (at End-Of-Tag!) Sorted by date (earliest first in list) Marked as 'GONE', 'TODAY', 'n hours left', 'n days left' dependent on dayrange below Displayed in different formats, dependent the same way. The following date formats are recognized: "yyyy-mm-dd" # standard date e.g. todo.2006-10-31: Halloween 2006 "mm/dd/yyyy" # GB/US date "dd.mm.yyyy" # European date "yyyy-mm-*" # complete month mm (every day) of year yyyy e.g. todo.2010-07-*: One month for summer vacations "*-mm-*" # complete month mm (every day); every year e.g. todo.*-12-*: Buy Christmas gifts "yyyy-Wnn-d" # DayOfWeek in given week+year (Mon=1,Sun=7) e.g. todo.2007-W03-2: Tuesday of 3rd week in 2007 "*-W*-d" # DayOfWeek in every week+year (Mon=1,Sun=7) e.g. todo.*-W*-1: go to work on Monday "yyyy-Wnn" # complete week in a given year e.g. todo.2008-W12: complete week 12 in 2008 "*-Wnn" # complete week, every year e.g. todo.*-W52: prepare end of year "CWnn" # same as above: Calendar Week "KWnn" # same as above: Calendar Week (German: Kalender Woche) "*-mm-dd" # a date every year e.g. todo.*-10-31: Halloween, in every year "*-*-dd" # a day every month, every year e.g. todo.*-*-01: Every month, the 1st day "*-*-*" # every day "*-mm-dd+d" # every DayOfWeek before(-)/after(+) dd'th day of month mm "*-*-dd+d" # every DayOfWeek before(-)/after(+) dd'th day every month e.g. todo.*-*-31-1: Monthly report (last Monday of the month) "*-E+ddd" # Easter +- ddd days The dates with "*" are optimistic ones: if such a date is in the past it is assumed as "for next week/month/year" and not as "GONE"! If there are subtags like the string "Peter" in "todo.Peter.2008-09-16: Birthday" the subtag is listed in parentheses on the todo page. 4.) otherwise tagged e.g. 'todo.family: holidays' Subtags are possible: e.g. 'todo.business.Next: talk to boss' 'todo.family.party.2009-06-14: invite friends' 'todo.business.office: tidy up' The following WikiDocumentAttributes will be added to that generated page (Only if there isn't already a custom [icon: ...] set, see configuration below): [icon: spanner] [color: magenta] [bookmarked=true] Note: Within the WikidPad help wiki this hook/extension does nothing. """ ################################################################################################## """ History: -------- 2006-10-30 first version 2006-11-02 bug fixes 2006-11-09 Fix to work with pages named like [no wiki word] as well (beta) 2006-11-10 Fix to work with pages named like [no WikiWord] as well (beta) 2006-11-11 Use internal function to check WikiWord 2006-11-16 several small mods (config, filter, color) 2007-01-31 fixes to run in WikidPad up to 1.8 and in the new 1.9 as well 2007-06-16 fixes tags with sub-text and date; text without trailing LF at ToDo page 2007-06-18 fix for problem with multiple-tabs (todos on wrong page inserted) 2007-07-27 fix for displayMessage in 1.9 (up to 1.9beta7) 2007-07-27 above fix removed because no longer necessary due to 1.9beta8 2007-07-31 if (onlyRealTodos == 0): that other category (like done, question ...) is shown in list 2007-08-03 only a fix for above 2007-09-08 new config option extendedLinks for links with search capability (only in editor window(?)) (test for Christopher) 2007-10-02 new config: setBookmark added // list of todos sorted within every tag block 2007-10-09 new config added: realDatesLast for sorting and infoPopupTimeDelta for suppressing frequent popups 2007-10-11 time calculation changed a bit (tip from Jouni) 2007-10-17 display weekday for dates // optional split of real-date-todos and normal ones into separate pages (wish from Jouni) 2007-10-19 little config changes, some cleaning up 2007-10-20 fixes // display week number for dates 2007-10-22 display possible tags in entries on ToDoCalendar page too 2007-12-06 fix for a bug if onlyRealTodos==True and todos with embedded ':' in their text 2008-01-22 some checking for valid dates 2008-02-20 Begin of rewrite, thanks to Marko Mahnic. 2008-02-23 new outDateFormat configuration option 2008-02-24 fix: new outDateFormat configuration option doesn't work on Windows automatically. Fallback implemented. 2008-02-24 added a configuration file ToDoExtension.cfg for local changes 2008-11-23 added new date formats "*-Wnn" for 'Monday of every week nn, every year' and "*-W*-d" for 'every d weekday (Monday=1, Tuesday=2...)'; fixes in date format CWnn and KWnn (suggested by Jouni Miettunen) 2009-01-24 fix: optional filter for todo page (changed configuration option) / add: config option for deleting attributes from todo entry (suggested by Kurt Woodham) 2009-02-02 Works with WikidPad 2.0alpha too now. Some fixes (e.g. bug when realDatesSeparate=True) 2009-02-03 Better error message for invalid dates, but still needs more work 2009-02-05 removed unused srePersistent / modified version check / new config option: showVersion 2009-03-11 several internal changes / fixed wrong categorized special multi-tags 2009-03-12 more fixes regarding sections 2009-03-15 cleanup, debug, new config handling & syntax 2009-04-05 no longer initialize on every todo page again (thanks to Michael) / wiki-local configuration page added 2009-04-06 bugfix for empty ToDoConfiguration page problem 2009-04-22 made timestamp in placemark configurable with todoTimestamp*... (be careful) 2009-04-26 bugfix: if tagstring was in title todo might not be listed / special handling for multiple tags removed / new sortTagsCaseInsensitive 2009-05-05 bugfixes: multiple tags could be sorted into one todo by mistake (old one ...) / fixed regression from 2009-04-26 2009-05-11 bugfix: Closing a ToDo page and opening another one could lead to errors 2009-07-26 bugfix: Better checking for and handling of invalid date entries (problem reported by Jens S.) 2010-01-05 bugfix: thisweek was wrong, now using week number as of ISO 8601:1988 (%V instead of %W) (problem reported by Jens S.) 2010-01-09 bugfix: regression on Windows platform fixed (reported by Andy M., solution suggested by Jens S.). More fixes in week calculation. 2010-01-10 New date formats (suggested by Jens S.) 2010-01-25 bugfix: another little bug in the date calculation fixed (reported by Jens S.) 2010-03-31 bugfix: grouping error of similar tags Low/VeryLow fixed (reported by Andy M.). Date check for "*-*-dd+d" fixed. 2010-03-31 added: showInfoOnPage (suggested by Andy M.) + pgInfoFrame, pgInfoPrefix. Asterisks in headlines disabled 2010-04-10 fix: temporary hack to make it work with new WikidPad 2.1 alpha 2010-12-03 fix: todos with date format "*-W*-d" sometimes not sorted correctly (reported by Jens S.) 2010-12-07 added: date format '*-E+-ddd' to calculate by Easter (by Jens Sp.) 2011-05-15 added Bjorn's hack, description see above and inline 2011-06-05 improved/fixed handling of 'done's. new config option 'ignoreDones' 2011-06-06 little optical improvement by Björn """ version = "2011-06-06" ################################################################################################## # todo: how to avoid double initialization of the complete hook? -> Michael # see: 04.02.2009 09:26: Re: [wikidPad] Bug? Extensions are "called" twice on initialization # http://groups.yahoo.com/group/wikidPad/message/4894 # todo: see inline ones below # todo: add field to cfg{} to indicate what can be modified where # todo: online help or help page or ... # todo: remove more complexity wherever possible # todo: auto refresh or so ################################################################################################## WIKIDPAD_PLUGIN = (("hooks", 1),) def startup(pWiki): # startup() is called rather early during initialization. # This is done here to avoid later checking of existence of # the member variable "externalPlugin_ToDo_todoext". # Member variables starting with "externalPlugin_" will never be used for WikidPad core. pWiki.externalPlugin_ToDo_todoext = None # Alternatively (it depends on code in Todo.__init__() if this works or not): # (Some necessary set-up may be missing due to early startup()) # (In this extension the first version above seems to be the safe/possible one.) # #pWiki.externalPlugin_ToDo_todoext = Todo(pWiki) # Only a single hook for this original function from WikidPadHooks.py is installed herein def openedWikiWord(docPagePresenter, wikiWord): """ Called when a new or existing wiki word was opened successfully. wikiWord -- name of the wiki word to create """ if (wikiWord[:4] == "ToDo"): # get the already initialized instance of this extension or "None" on first call if wpversion < "1.9": wikiname = docPagePresenter.wikiName todoext = docPagePresenter.externalPlugin_ToDo_todoext else: wikiname = docPagePresenter.getMainControl().wikiName todoext = docPagePresenter.getMainControl().externalPlugin_ToDo_todoext # to avoid modification of help-wiki if wikiname == "WikidPadHelp": return # Only initialize the extension once ... # (Note: Following block could be omitted if second alternative above for startup() would be used) if todoext is None: # if not yet instantiated todoext = Todo(docPagePresenter) # do it now if wpversion < "1.9": docPagePresenter.externalPlugin_ToDo_todoext = todoext else: docPagePresenter.getMainControl().externalPlugin_ToDo_todoext = todoext # ... but update to current docPagePresenter (reference to wxPanel) on every call # this way: # todoext.wikidpad = docPagePresenter # or that way: todoext.setDocPagePresenter(docPagePresenter) todoext.ProcessTodoPage(wikiWord) # then do the work #======================================================================================== CFG_FILE="ToDoExtension.cfg" import os, sys import locale import wx import pprint from re import compile, match, search #added search, changed here ! from time import strftime, strptime, mktime, localtime, time from datetime import date, timedelta #datetime from string import join import inspect # Get WikidPad's version. More infos in Consts.py # VERSION_TUPLE = ("wikidPad", 2, 0, 1, 4) # VERSION_STRING = "wikidPad 2.0alpha01_04" try: from WikidPadStarter import VERSION_TUPLE, VERSION_STRING (vbr,vmaj,vmin,vsam,vpat) = VERSION_TUPLE wpversion = "%d.%d" % (vmaj,vmin) wpversionstr = VERSION_STRING except: # Attention: This determination of the version is not foolproof! # Coded this way to remain compatible back to WikidPad 1.8 from WikidPadStarter import VERSION_STRING # e.g. "wikidPad 1.8beta6", "wikidPad 2.0alpha01_03" wpversionstr = VERSION_STRING wpversion = VERSION_STRING[9:12] # e.g. "1.8" #--- some functions ---------------------------------------------------------------------- # # from http://www.merlyn.demon.co.uk/weekcalc.htm (as of 2006), fixed in 2010 # def LeapYr(y): if (y % 4): return False; if (y % 100): return True return not(y % 400) def Jan1DoWG(y): y = y - 1 return (int(1.25 * y) - int(y / 100) + int(y / 400) + 1) % 7 def DoYLtoMonth(J,L): if (J < 32): return 1 return 1 + int((303 + 5 * (J - 59 - L)) / 153) def DoYMLtoDate(J,M,L): if (M < 3): return J - (31 * (M - 1)) return J - (int((153 * M - 2) / 5) - 32 + L) def RevWN(Y,W,D): Yr = Y Leap = LeapYr(Yr) X = (Jan1DoWG(Yr) + 2) % 7 J = 7 * W + D - X - 4 Mth = int(DoYLtoMonth(J, Leap)) Dte = int(DoYMLtoDate(J, Mth, Leap)) if (Mth == 13): Yr = Yr + 1 Mth = 1 if (Dte < 1): Yr = Yr - 1 Mth = 12 Dte = Dte + 31 return [Yr, Mth, Dte] #------------ def LastDayOfMonth(y, m): nextm = date(y, m, 1) + timedelta(days=31) thatlast = date(nextm.year, nextm.month, 1) - timedelta(days=1) return thatlast.day def easter(year): """ Calculates the date of Easter Sunday According to Anonymous Gregorian algorithm from http://en.wikipedia.org/wiki/Computus#Anonymous_Gregorian_algorithm """ # Expression Y = 1961 Y = 2009 y = year # a = Y mod 19 a = 4 a = 14 a = y % 19 # b = floor (Y / 100) b = 19 b = 20 b = y/100 # c = Y mod 100 c = 61 c = 9 c = y % 100 # d = floor (b / 4) d = 4 d = 5 d = b/4 # e = b mod 4 e = 3 e = 0 e = b % 4 # f = floor ((b + 8) / 25) f = 1 f = 1 f = (b+8)/25 # g = floor ((b - f + 1) / 3) g = 6 g = 6 g = (b-f+1)/3 # h = (19a + b - d - g + 15) mod 30 h = 10 h = 20 h = (19*a+b-d-g+15) % 30 # i = floor (c / 4) i = 15 i = 2 i = c/4 # k = c mod 4 k = 1 k = 1 k = c % 4 # L = (32 + 2e + 2i - h - k) mod 7 L = 1 L = 1 L = (32 + 2*e + 2*i - h - k) % 7 # m = floor ((a + 11h + 22L) / 451) m = 0 m = 0 m = (a +11*h + 22*L)/451 # month = floor ((h + L - 7m + 114) / 31) month = 4 (April) month = 4 (April) month = (h + L - 7*m + 114)/31 # day = ((h + L - 7m + 114) mod 31) + 1 day = 2 day = 12 day = ((h + L - 7*m + 114) % 31) + 1 return (year,month,day) #----------------------------------------------------------------------------------------- class Todo: def __init__(self, wikidpad): self.wikidpad = wikidpad #print "=== wikidpad =", wikidpad self.config_set_defaults() self.cfg_file = os.path.join(os.path.dirname(os.path.realpath(os.sys.argv[0])), "user_extensions", CFG_FILE) #todo: easier? perhaps the current path is stored somewhere already?! if os.path.exists(self.cfg_file): try: cfg = open(self.cfg_file) self.config_read(cfg.readlines(), self.cfg_file, globalCfg=True) cfg.close() except: pass if self.cfg["isConfigPageActive"]: if wpversion < "1.9": if self.wikidpad.getWikiData().isDefinedWikiWord(self.cfg["configPage"]): config = self.wikidpad.getWikiData().getContent(self.cfg["configPage"]).split("\n") self.config_read(config, self.cfg["configPage"], globalCfg=False) elif wpversion < "2.0": if self.wikidpad.getWikiDocument().isDefinedWikiWord(self.cfg["configPage"]): config = self.wikidpad.getWikiDocument().getWikiData().getContent(self.cfg["configPage"]).split("\n") self.config_read(config, self.cfg["configPage"], globalCfg=False) else: if self.wikidpad.getWikiDocument().isDefinedWikiLink(self.cfg["configPage"]): config = self.wikidpad.getWikiDocument().getWikiData().getContent(self.cfg["configPage"]).split("\n") self.config_read(config, self.cfg["configPage"], globalCfg=False) if self.cfg["debug"]: pprint.pprint(self.cfg) def config_read(self, config, cfg_file, globalCfg=False): #print "*v*" #print "cfg_file=", cfg_file #pprint.pprint(config) #print "*^*" conf_count = 0 for cfgline in config: # todo: multi-line entries #print "cfgline =", cfgline cfgline = cfgline.strip() if cfgline.startswith("+"): continue # skip header line(s) if cfgline.startswith("#"): continue # skip comments #todo: improve! if cfgline.startswith(";"): continue # skip semicolon comments #todo: improve! if cfgline == "": continue # skip empty lines #todo: improve! if cfgline.startswith("self."): # check for old/invalid entries (e.g. self.tags) self.wikidpad.displayMessage("Warning!", "Old syntax in configuration '" + cfg_file + "'\n\nRemove the 'self.' from " + cfgline[0:15] + "...") cfgline = cfgline[5:] #print "cfgline =", cfgline if cfgline.count("=") == 0: continue cfgentry = cfgline[0 : cfgline.index("=")].strip() # todo: more robust! cfgvalue = cfgline[cfgline.index("=")+1:].strip() # todo: more robust! #if cfgvalue.count("#") > 0: #todo: that is not reliable because of possible ' in string value! # cfgvalue = cfgvalue[0:cfgvalue.index("#")].strip() #print "cfgentry = cfgvalue >>%s=%s<< " %(cfgentry, cfgvalue) if not globalCfg: if cfgentry in ['isConfigPageActive', 'configPage']: continue if not self.cfg.has_key(cfgentry): # check for unknown entries self.wikidpad.displayMessage("Warning!", "Error: Unknown entry in configuration '" + cfg_file + "'\n\n" + cfgentry + " ignored.") continue if cfgentry == "predefinedTags": # is usually multiline... self.wikidpad.displayMessage("Problem!", "Multiline config entries are not yet implemented\nin '" + cfg_file + "'\n\n" + cfgentry + " = " + cfgvalue[0:10] + "...\n\nRest of configuration ignored!") break if cfgvalue.startswith("[") and not cfgvalue.endswith("]"): self.wikidpad.displayMessage("Problem!", "Multiline config entries are not yet implemented\nin '" + cfg_file + "'\n\n" + cfgentry + " = " + cfgvalue[0:10] + "...\n\nRest of configuration ignored!") break try: exec "cfgvalue_tmp=" + cfgvalue #todo: choose a more secure way in favour of exec! And even more error handling self.cfg[cfgentry] = cfgvalue_tmp conf_count += 1 except: self.wikidpad.displayMessage("Error!", "Syntax error in configuration:\n\n" + cfg_file + "\n\n" + cfgline + "\n\n" + str(sys.exc_info()[1])) # make sure that UNTAGGED is always there if [t[0] for t in self.cfg["predefinedTags"]].count(u'UNTAGGED') == 0: self.cfg["predefinedTags"].insert(0, (u'UNTAGGED', '++++ Not yet tagged') ) #print self.cfg["placemark"] #print type(self.cfg["placemark"]) return conf_count def config_set_defaults(self): """ Default configuration values ---------------------------- ATTENTION: Don't change them here since they will be overwritten by the next update of this program. Instead maintain your own local configuration in file "user_extensions/ToDoExtension.cfg". Simply set config values in that file using the normal Python syntax: 'name = value' Please note: Multiline entries aren't supported yet. So 'predefinedTags' can't be changed that way. """ #------------------------------------------------------------------------------------------ self.cfgDefaults = [ ('debug', False, """Print out some debugging info on the console"""), ('configPage', 'ToDoConfiguration', """Name of the optional wiki page for wiki-local configuration"""), ('isConfigPageActive', True, """Activation switch for 'configPage'"""), # (Note: these two can't be changed on this very wiki page!) ('predefinedTags', [ (u'UNTAGGED', '++++ Not yet tagged'), # tag 'UNTAGGED' is MANDANTORY. Don't touch! (u'High', '+++ HIGH!'), (u'Next', '++++ Next Actions'), (u'ThisWeek', '++++ This Week'), (u'SomeDay', '++++ SomeDay / Maybe'), (u'TimeToTime', '++++ From time to time'), (u'Low', '++++ Tagged as LOW'), (u'VeryLow', '++++ Tagged as Very LOW'), ], """predefinedTags contains (Tag, TagHeader). TagHeaders are the descriptive headings that will be shown for each category. Tag 'UNTAGGED' is a special one (mandantory!) to collect the untagged todo's"""), ('onlyRealTodos', True, """True = ignore the todos like 'done', 'wait', 'question' etc. False = all from wikidPads todo-like category"""), ('ignoreDones', False, """False = treat 'done' like 'wait', 'question' etc. True = don't show the dones, except on the filter page "ToDoDone" (only effective if 'onlyRealTodos' is False).. """), ('realDatesLast', False, """ False: sorting is: untagged - known tags - dates - newly tagged (the default) True: sorting is: untagged - known tags - newly tagged - dates (test for Jouni)"""), ('realDatesSeparate', False, """False: all todos in one page: ToDo True: todos with real dates on a page 'ToDoCalendar', all others on the normal page 'ToDo'"""), ('filterTodos', 1, """0 = no filtering: ignore characters after 'ToDo' 1 = case insensitive filter string after 'ToDo', only in todo's name 2 = case insensitive filter string after 'ToDo', in complete todo entry (line)"""), ('sortTagsCaseInsensitive', True, ''), # Please note: Changing this placemark may double the todo page on the first call # if there was generated content with the old placemark before ('placemark', '++++ ________auto-collected todos________', """Fixed part of the divider between manual edited text and automatically generated todos"""), # Be very careful when modifying these three values! All three MUST match! ('todoTimestampFormat', '(@ %s)', """String after placemark with timestamp of this todo generation. %s will be replaced by date..."""), ('todoTimestampDateFormat', '%Y-%m-%d %H:%M', """Format of timestamp used after placemark"""), ('todoTimestampRegex', '\(@ ([0-9]{4}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9])\)', """MUST match todoTimestampDateFormat and todoTimestampFormat! And the date+time itself MUST be grouped!"""), ('underline', '_______', ''), ('spacerline', '----', ''), ('bullet', ' * ', ''), ('colorStringNormal', '[color: black]', """Colors for normal ToDo entries in the tree"""), ('colorStringNext', '[color: orange]', """Colors for 'next' ToDo entries in the tree"""), ('colorStringToday', '[color: red]', """Colors for 'todays' ToDo entries in the tree"""), ('colorStringMissed', '[color: magenta]', """Colors for 'missed' ToDo entries in the tree"""), ('notagHeading', '++++ ', """Heading for unknown tagged entries"""), ('dateHeading', '++++ ', """Heading for date entries far away (distance see below)"""), ('dateHeadingX', '+++ ', """Heading for date entries in near future or missed in past"""), ('missedString', ' ___ (+GONE+)', """Additional info"""), ('nowString', ' ___ !!! TODAY !!!', """Additional info"""), ('outDateFormat', '', """If this is set = '' the local date format is used, else set it to '%m/%d/%Y', '%d.%m.%Y' or '%Y-%m-%d' for example. :Note: On Windows the determination of the locale doesn't work yet. So you have to set it manually here. : Or it defaults to '%Y-%m-%d'. Please use only %m, %d for month and day and %y, %Y for 2-/4-digit year (Otherwise the optional separation of date driven todos may fail.)"""), ('showWeekday', True, """show weekday after dates"""), ('weekdayFormat', ' (%s,', """%s is replaced by the weekday as in the config value 'weekdays', Together with the optional week number (see below) it gives e.g. ' (Fri, W42)'"""), ('weekdays', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], """ Names for the weekdays ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] # English, 2 chars ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'] # German ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] # English, 3 chars"""), ('showWeeknumber', True, """Show the weeknumber after dates"""), ('weeknumberFormat', ' W%02d)', """ %02d is replaced by the weeknumber (2 digits with leading 0-padded)"""), ('dayrange', 7, """Date entries in that range are marked as 'more important' than the ones in 'far' future"""), ('deleteAttributes', 1, """0 = let possible attributes [...:...] in a todo entry alone 1 = delete possible attributes [...:...] from a todo entry to avoid multiple attribute errors"""), ('extendedLinks', False, """This feature works, but doesn't look nice, really! So better let it disabled... True = create links with search capability on target page (only working in editor window!?) False = create normal links"""), ('iconString', '[icon: spanner]', """Set the icon of the todo list"""), ('setBookmark', True, """Set the ToDo page [bookmarked=true] so it is listed in the Bookmark popup (Shift-Ctrl-B)"""), ('showInfoPopup', True, """Show summary info popup about todos for today / missed ones"""), ('showInfoOnPage', True,"""Show summary info about todos for today / missed ones on todo page"""), ('pgInfoFrame', '<>',"""Frame around the summary info about todos for today / missed ones on todo page. %s will be replaced by the text."""), ('pgInfoPrefix', ' ',"""Prefix for every entry of the summary info about todos for today / missed ones on todo page"""), ('infoPopupTimeDelta', 2, """Number of minutes where no new popup is shown after last visit of ToDo page"""), ('showVersion', False, """Show version of WikidPad and extension (mainly for debugging)"""), ('showConfigLink', True, """Show a link to the configuration page (only if 'isConfigPageActive' == True)"""), ] self.cfg = {} self.cfgDoc = {} for ce in self.cfgDefaults: self.cfg[ce[0]] = ce[1] self.cfgDoc[ce[0]] = join(["# " + ce1.strip().lstrip(":") for ce1 in ce[2].split("\n")], "\n") #pprint.pprint(self.cfg) #pprint.pprint(self.cfgDoc) def delAttribs(self, s): sattr = compile("\[[^:]+? *: *[^:]+?\]") result = sattr.search(s) while result: s = s[0:result.start()] + s[result.end():] result = sattr.search(s) return s def setDocPagePresenter(self, docPagePresenter): # update the docPagePresenter for this run #print "=== setDocPagePresenter =", docPagePresenter self.wikidpad = docPagePresenter def ProcessTodoPage(self, wikiWord): global wpversion missedTodos = 0 todosForToday = 0 todosForNextDays = 0 colorStringFlag = 1 # set color string per default, checked later below myNow = strftime(self.cfg["todoTimestampDateFormat"], localtime(time())) myToday = date.isoformat(date.today()) # strftime("%Y-%m-%d", localtime(time())) # => YYYY-MM-DD thismonth = int(myToday[5:7]) thisday = int(myToday[8:10]) #(xxthisyear,thismonth,thisday,xxthishour,xxthisminute,xxthisecond,xxthiswday,xxthisdoy,xxthisdst) = localtime(time()) # xx* not used yet (thisyear, thisweek, thiswday) = date.today().isocalendar() # Note: week and weekday are ISO ones (starting with "01", Monday=1=First day of week) warndate = strftime("%Y-%m-%d", localtime(time() + self.cfg["dayrange"]*24*60*60)) if self.cfg["outDateFormat"] == "": try: locale.setlocale(locale.LC_ALL, '') self.cfg["outDateFormat"] = locale.nl_langinfo(locale.D_FMT) except: self.cfg["outDateFormat"] = "%Y-%m-%d" ''' this definition was moved to here from below, changed here ! ''' dateChecks = [ ( compile("[0-9]{4}-[0-1][0-9]-[0-3][0-9]$"), "yyyy-mm-dd" ), # specific date; ISO ( compile("[0-1][0-9]/[0-3][0-9]/[0-9]{4}$"), "mm/dd/yyyy" ), # specific date; US ( compile("[0-3][0-9].[0-1][0-9].[0-9]{4}$"), "dd.mm.yyyy" ), # specific date; Germany etc. ( compile("[0-9]{4}-W[0-9]{2}-[0-7]$"), "yyyy-Wnn-d" ), # on given DayOfWeek in week nn; "d" is Mon(1)-Sun(7) ( compile("[0-9]{4}-W[0-9]{2}$"), "yyyy-Wnn" ), # complete week nn of year yyyy ( compile("\*-W[0-9]{2}$"), "*-Wnn" ), # complete week nn; every year ( compile("CW[0-9]{2}$"), "CWnn" ), # same as above: Calendar Week. todo: is "CW" the correct name in English? ( compile("KW[0-9]{2}$"), "KWnn" ), # same as above: German: Kalender-Woche ( compile("[0-9]{4}-[0-1][0-9]-\*$"), "yyyy-mm-*" ), # complete month mm (every day) of year yyyy ( compile("\*-[0-1][0-9]-\*$"), "*-mm-*" ), # complete month mm (every day); every year ( compile("\*-W\*-[0-7]$"), "*-W*-d" ), # on given DayOfWeek in every week; "d" is Mon(1)-Sun(7) ( compile("\*-[0-1][0-9]-[0-3][0-9]$"), "*-mm-dd" ), # every dd'th day of month mm ( compile("\*-\*-[0-3][0-9]$"), "*-*-dd" ), # every dd'th day; every month ( compile("\*-\*-\*$"), "*-*-*" ), # every day ( compile("\*-[0-1][0-9]-[0-3][0-9][-+][0-7]$"), "*-mm-dd+d" ), # every DayOfWeek before(-)/after(+) dd'th day of month mm ( compile("\*-\*-[0-3][0-9][-+][0-7]$"), "*-*-dd+d" ), # every DayOfWeek before(-)/after(+) dd'th day every month ( compile("\*-E[-+][0-9]{3}$"), "*-E+ddd" ) # Easter +- ddd days #( compile("[0-9]{4}-[0-1][0-9]-[0-3][0-9]([-_][0-9]{2}[-.][0-9]{2})?$"), "yyyy-mm-dd" ), # specific date; ISO Optional time???? TODO: how to display? TODO: implement that really? Better not since it is going to be too complicated! ] if wpversion < "1.9": todosFull = self.wikidpad.wikiData.getTodos() editor = self.wikidpad.getActiveEditor() elif wpversion < "2.1": todosFull = self.wikidpad.getMainControl().wikiData.getTodos() editor = self.wikidpad.getSubControl("textedit") else: todosFullN = self.wikidpad.getMainControl().wikiData.getTodos() ''' This part os new, changed here''' for index,todo in enumerate(todosFullN): if match("\d{4}-\d{2}-\d{2}",todo[0]) and not [True for datecheck in dateChecks if search(datecheck[0],todo[1])]: todosFullN[index] = (todo[0], todo[1]+"."+todo[0], todo[2]) ''' until here...''' todosFull = [ (t[0], t[1] + ":" + t[2].strip()) for t in todosFullN ] editor = self.wikidpad.getSubControl("textedit") if self.cfg["debug"]: print "todosFull=", pprint.pprint(todosFull) if wpversion <= "1.9": langHelper = self.wikidpad.getFormatting() else: # Retrieve internal name of current wiki language wikiLang = self.wikidpad.getWikiDocument().getWikiDefaultWikiLanguage() # Get language helper (an instance of WikidPadParser._TheHelper) langHelper = wx.GetApp().createWikiLanguageHelper(wikiLang) # For the newly published 2.0alpha01_03 it is then: #langHelper.isCcWikiWord(...) srchstr = wikiWord[4:] if wikiWord == self.cfg["configPage"]: if self.cfg["isConfigPageActive"]: #self.wikidpad.saveCurrentDocPage(force = True) self.wikidpad.displayMessage("Info", "Please note: \nThis configuration page is evaluated on WikidPad startup\nand/or after closing and reopening(!) this page after changes!\n\nSide effect: The configuration file\n ...%s \nis re-read first as well." % self.cfg_file[-50:]) # First reset the defaults and then re-read the global configuration file self.config_set_defaults() if os.path.exists(self.cfg_file): try: cfg = open(self.cfg_file) self.config_read(cfg.readlines(), self.cfg_file, globalCfg=False) cfg.close() except: pass conf_count = 0 # then evaluate the local configuration page if wpversion < "1.9": if self.wikidpad.getWikiData().isDefinedWikiWord(self.cfg["configPage"]): config = self.wikidpad.getWikiData().getContent(self.cfg["configPage"]).split("\n") conf_count = self.config_read(config, self.cfg["configPage"], globalCfg=False) elif wpversion < "2.0": if self.wikidpad.getWikiDocument().isDefinedWikiWord(self.cfg["configPage"]): config = self.wikidpad.getWikiDocument().getWikiData().getContent(self.cfg["configPage"]).split("\n") conf_count = self.config_read(config, self.cfg["configPage"], globalCfg=False) else: if self.wikidpad.getWikiDocument().isDefinedWikiLink(self.cfg["configPage"]): config = self.wikidpad.getWikiDocument().getWikiData().getContent(self.cfg["configPage"]).split("\n") conf_count = self.config_read(config, self.cfg["configPage"], globalCfg=False) if conf_count == 0: rtn = self.wikidpad.stdDialog("yn", "Info", "There are no (active) configuration items on this page.\nShould it be filled with the current values?") #print "rtn=", rtn if rtn == None: self.wikidpad.displayMessage("Problem", "Please note: \n\nDue to a bug in WikidPad up to 1.9rc03 it isn't possible to read your choice.\nSo it defaults to 'yes' here.\n\nYou may want to have a look at\nhttp://groups-beta.google.com/group/wikidpad-devel/t/b3b6c2b2b03a88c7 ") rtn = "yes" if rtn == "yes": editor.GotoPos(editor.GetLength()) # goto end of page editor.AddText("\n\n") oldpos= editor.GetCurrentPos() editor.AddText("# The config values on this page are local to this wiki. (There are a few more not to be set here.)\n") editor.AddText("# They take precedence over the default ones from the source code and the ones from the configuration file 'user_extensions\%s'\n" % (CFG_FILE)) editor.AddText("# Tip: You should only uncomment (remove leading semicolon) configuration values you want to have changed here.\n\n") for ciEntry in self.cfgDefaults: ci = ciEntry[0] if ci in ['predefinedTags', 'isConfigPageActive', 'configPage']: continue if ci == 'showConfigPage' and not self.cfg['isConfigPageActive']: continue editor.AddText("# -- %s --\n" % ci) if self.cfgDoc[ci] != '': editor.AddText(self.cfgDoc[ci] + "\n") editor.AddText("; %s = %s" % (ci, repr(self.cfg[ci]).replace("\\\\", "\\"))) editor.AddText("\n\n") editor.GotoPos(oldpos) # goto begin of insertion again return isCalendar = False if srchstr.startswith("Calendar"): #todo: or better check for equality? srchstr = srchstr[8:] # remove that "Calendar" from srchstr to get the real one isCalendar = True srchstr = srchstr.strip(" _") # remove possible leading/trailing spaces and underscores from srchstr # optionally filter the todos if self.cfg["filterTodos"] == 0: todos = [todo for todo in todosFull] # without checking srchstr elif self.cfg["filterTodos"] == 1: todos = [todo for todo in todosFull if (srchstr.upper() in todo[1][0:todo[1].find(":")].upper()) ] # case insensitive search in todo's name elif self.cfg["filterTodos"] == 2: todos = [todo for todo in todosFull if (srchstr.upper() in todo[1].upper()) ] # case insensitive in complete todo entry (the todo line) if (self.cfg["filterTodos"] > 0) and (srchstr.upper() == "DONE"): pass else: if self.cfg["onlyRealTodos"]: todos = [todo for todo in todos if todo[1][0:4] == "todo" ] else: if self.cfg["ignoreDones"]: todos = [todo for todo in todos if todo[1][0:4] != "done" ] # prepare text page to insert the collected todos after placemark nowstamp = self.cfg["todoTimestampFormat"] % (myNow) # e.g. "(@ 2007-10-28 22:09)" tscheck = compile(self.cfg["todoTimestampRegex"]) st = editor.FindText(0, editor.GetLength(), self.cfg["placemark"], 0) if st != -1: # if placemark found # select text to be replaced with new todos, beginning just after placemark, before timestamp editor.SetSelection(st + len(self.cfg["placemark"]), editor.GetLength()) sel = editor.GetSelectedText()[0:len(nowstamp)+1] # get the timestamp from just after the placemark result = tscheck.search(sel) if result: lastCreation = mktime(strptime(result.groups()[0], self.cfg["todoTimestampDateFormat"])) #todo: easier!?!? else: lastCreation = 0.0 editor.ReplaceSelection(" %s %s" % (nowstamp, self.cfg["underline"])) # add the new timestamp if srchstr != "" and self.cfg["filterTodos"]: # optionally add filter info editor.AddText(" (filter: '%s')" % srchstr) editor.AddText("\n\n\n") else: # if no placemark found editor.GotoPos(editor.GetLength()) # add the placemark and todos at end of page lastCreation = 0.0 editor.AddText("\n\n%s %s %s" % (self.cfg["placemark"], nowstamp, self.cfg["underline"])) if srchstr != "" and self.cfg["filterTodos"]: editor.AddText(" (filter: '%s')" % srchstr) editor.AddText("\n\n\n") st = editor.FindText(0, editor.GetLength(), "[icon:", 0) if st != -1: # if there already is an icon:string: self.cfg["iconString"] = "" # don't add our own st = editor.FindText(0, editor.GetLength(), "[color:", 0) if st != -1: # if there already is an color:string: colorStringFlag = 0 # don't add our own self.tags = [ t + ("","") for t in self.cfg["predefinedTags"] ] # determine tags checkTags = [tag[0] for tag in self.tags] # first part if self.cfg["debug"]: print "line number", inspect.currentframe().f_back.f_lineno print "checkTags=" pprint.pprint(checkTags) newtags = [] for todo in todos: ## [(u'pagename', u'todo.tag1.tag2: text')... allTodo = todo[1].split(":") ## [u'todo.tag1.tag2', u' text'] ... words = allTodo[0].split(".",1) ## [u'todo', u'tag1.tag2'] ... ## (keep additionally dotted parts together)! o.k.? for word in words[1:]: ## u'tag1.tag2' newtags.append( (word) ) newtags = sorted(set(newtags)) if self.cfg["debug"]: print "newtags=" pprint.pprint(newtags) #--------------------------------------------------- newtagsdata = [] # check all tags for being a date of special format for newtag in newtags: for dateCheck in dateChecks: # any match? result = dateCheck[0].search(newtag) if result: break if not result: # not a date like the above, perhaps no date at all #newtagLast = newtag.split(".")[-1] if newtag.find(".") > -1: newtagsdata.append( (newtag, newtag, 0, newtag[0:newtag.rfind(".")]) ) else: newtagsdata.append( (newtag, newtag, 0, "") ) ##?? or newtag? else: t = newtag[0:-len(dateCheck[1])] if dateCheck[1] == "yyyy-mm-dd": y = result.group()[0:4] m = result.group()[5:7] d = result.group()[8:10] elif dateCheck[1] == "mm/dd/yyyy": m = result.group()[0:2] d = result.group()[3:5] y = result.group()[6:10] elif dateCheck[1] == "dd.mm.yyyy": d = result.group()[0:2] m = result.group()[3:5] y = result.group()[6:10] elif dateCheck[1] == "yyyy-Wnn-d": y = int(result.group()[0:4]) w = int(result.group()[6:8]) wday = int(result.group()[9:10]) y,m,d = RevWN(y, w, wday) # eval year, month, day from week [and day_of_week] elif dateCheck[1] == "yyyy-Wnn": y = int(result.group()[0:4]) w = int(result.group()[6:8]) if (y == thisyear) and (w == thisweek): wday = thiswday # current weekday for current week else: wday = 1 # Monday for future weeks # And last weekday of that (ISO-)week: Sunday for recent (gone) weeks if y < thisyear or (y == thisyear and w < thisweek): wday = 7 y,m,d = RevWN(y, w, wday) elif dateCheck[1] == "KWnn" or dateCheck[1] == "CWnn": w = int(result.group()[2:4]) wday = 1 # Monday for recent and future weeks y = thisyear if w == thisweek: wday = thiswday # current weekday for current week else: if w < thisweek: y = y + 1 y,m,d = RevWN(y, w, wday) elif dateCheck[1] == "*-Wnn": w = int(result.group()[3:5]) wday = 1 # Monday for recent and future weeks y = thisyear if w == thisweek: wday = thiswday # current weekday for current week else: if w < thisweek: y = y + 1 y,m,d = RevWN(y, w, wday) elif dateCheck[1] == "*-W*-d": dw = int(result.group()[5:6]) y,m,d = RevWN(thisyear, thisweek, dw) # eval year, month, day from week [and day] if dw < thiswday: if m < 12 or (m == 12 and d < 25): y,m,d = RevWN(thisyear, thisweek+1, dw) else: y,m,d = RevWN(thisyear+1, 1, dw) elif dateCheck[1] == "*-mm-dd": y = thisyear m = int(result.group()[2:4]) d = int(result.group()[5:7]) if m < thismonth or (m == thismonth and d < thisday): y = y + 1 elif dateCheck[1] == "yyyy-mm-*": y = int(result.group()[0:4]) m = int(result.group()[5:7]) d = 1 # for future months if (y == thisyear) and (m == thismonth): d = thisday # current day for current month else: if y < thisyear or (y == thisyear and m < thismonth): # for recent months d = LastDayOfMonth(y, m) elif dateCheck[1] == "*-mm-*": y = thisyear m = int(result.group()[2:4]) d = 1 # for recent and future months if m == thismonth: d = thisday # current day for current month else: if m < thismonth: y = y + 1 elif dateCheck[1] == "*-mm-dd+d": y = thisyear m = int(result.group()[2:4]) d = int(result.group()[5:7]) direction = result.group()[7:8] dwShould = int(result.group()[8:9]) date_error = False try: base = date(y,m,d) except ValueError: if d >= 29 and d <= 31: # silently assume last day of month d = LastDayOfMonth(y,m) else: date_error = True d = thisday m = thismonth base = date(y,m,d) if date_error: self.wikidpad.displayMessage("Error!", "Todo entry with invalid date: %s\n\nDate is set to 'TODAY' temporarily.\n\nPlease check." % (newtag)) else: dwIs = base.isoweekday() # Mon=1 and Sun=7 ddiff = dwShould - dwIs if direction == '+' and ddiff < 0: ddiff += 7 elif direction == '-' and ddiff > 0: ddiff -= 7 tagday = base + timedelta(days = ddiff) if tagday < date.today() or (tagday == date.today() and d == thisday): try: base = date(y + 1, m, d) except ValueError: if d >= 29 and d <= 31: # silently assume last day of month d = LastDayOfMonth(y,m) base = date(y + 1, m, d) dwIs = base.isoweekday() # Mon=1 and Sun=7 ddiff = dwShould - dwIs if direction == '+' and ddiff < 0: ddiff += 7 elif direction == '-' and ddiff > 0: ddiff -= 7 tagday = base + timedelta(days = ddiff) y = tagday.year m = tagday.month d = tagday.day elif dateCheck[1] == "*-*-dd+d": y = thisyear m = thismonth d = int(result.group()[4:6]) direction = result.group()[6:7] dwShould = int(result.group()[7:9]) date_error = False try: base = date(y,m,d) except ValueError: if d >= 29 and d <= 31: # silently assume last day of month d = LastDayOfMonth(y,m) else: date_error = True d = thisday d0 = d base = date(y,m,d) dwIs = base.isoweekday() # Mon=1 and Sun=7 ddiff = dwShould - dwIs if direction == '+' and ddiff < 0: ddiff += 7 elif direction == '-' and ddiff > 0: ddiff -= 7 tagday = base + timedelta(days = ddiff) if tagday < date.today() or (tagday == date.today() and d == thisday): #print "newtag=", newtag, "tagday=", tagday, d, m, y m += 1 if m > 12: m = 1 y += 1 try: base = date(y, m, d) except ValueError: if d >= 29 and d <= 31: # silently assume last day of month d = LastDayOfMonth(y, m) base = date(y, m, d) dwIs = base.isoweekday() # Mon=1 and Sun=7 ddiff = dwShould - dwIs if direction == '+' and ddiff < 0: ddiff += 7 elif direction == '-' and ddiff > 0: ddiff -= 7 tagday = base + timedelta(days = ddiff) y = tagday.year m = tagday.month d = tagday.day if date_error: self.wikidpad.displayMessage("Error!", "Todo entry with invalid date: %s\n\nDate is set to a valid one temporarily.\n\n(Based on %4d-%02d-%02d)\n\nPlease check." % (newtag,y,m,d0)) elif dateCheck[1] == "*-*-dd": y = thisyear m = thismonth d = int(result.group()[4:6]) if d < thisday: m = m + 1 if m > 12: m = 1 y = y + 1 elif dateCheck[1] == "*-*-*": y = thisyear m = thismonth d = thisday elif dateCheck[1] == "*-E+ddd": #import pdb; pdb.set_trace() diffstr = newtag[3:7] diffint = int(diffstr) diff = timedelta(days=diffint) t = newtag[0:-7] y = thisyear # and if date already gone: use next year ... for yc in (0,1): (ey,em,ed) = easter(y + yc) tagday = date(ey,em,ed) + diff y = tagday.year m = tagday.month d = tagday.day if not (m < thismonth or (m == thismonth and d < thisday)): break # check whether date is valid. If not: Show message and set to "today" for manual fixing try: xnewdate = str(y) + "-" + str(m).zfill(2) + "-" + str(d).zfill(2) xtest = strftime(self.cfg["outDateFormat"],strptime(xnewdate, "%Y-%m-%d")) xyear,xweek,xwday = date.isocalendar(date(int(y), int(m), int(d))) except ValueError: self.wikidpad.displayMessage("Error!", "Todo entry with invalid date: " + newtag + "\n\nDate is set to 'TODAY' temporarily.\n\nPlease fix it manually and try again.") y = thisyear m = thismonth d = thisday xnewdate = str(y) + "-" + str(m).zfill(2) + "-" + str(d).zfill(2) # with flag = 1 to mark as date entry newtagsdata.append( (xnewdate, newtag, 1, t[0:t.rfind(".")]) ) # todo: save date format for later display if self.cfg["debug"]: print "newtagsdata=" pprint.pprint(newtagsdata) newtagsdata = sorted(set(newtagsdata)) if self.cfg["debug"]: print "newtagsdata(unique)=" pprint.pprint(newtagsdata) print "self.tags=" pprint.pprint(self.tags) # ==> newtagsdata = [ ( "yyyy-mm-dd" , "some date string" , 1, "tag(s) before date") # for date tags # ( tagstring , tagstring , 0, "n-1 tag(s)") # for multiple tags # ( tagstring , tagstring , 0, "") # for simple tags #--------------------------------------------------- # search for tags not in defined list and automatically add them in correct order #if realDatesSeparate: # perhaps not necessary, but may reduce calculations below? # if isCalendar: # newtagsdata = [ t for t in sorted(newtagsdata) if t[2]==1 ] # else: # newtagsdata = [ t for t in sorted(newtagsdata) if t[2]==0 ] #else: # pre-sort if self.cfg["sortTagsCaseInsensitive"]: mytuples = [(x[0].lower(), x) for x in newtagsdata] mytuples.sort() newtagsdata = [x[1] for x in mytuples] else: newtagsdata.sort() if self.cfg["realDatesLast"]: newtagsdataX1 = [ t for t in newtagsdata if t[2]==0 ] newtagsdataX2 = [ t for t in newtagsdata if t[2]==1 ] newtagsdata = newtagsdataX1[:] newtagsdata.extend(newtagsdataX2) if self.cfg["debug"]: print "newtagsdata(sorted)=" pprint.pprint(newtagsdata) print "checkTags=" pprint.pprint(checkTags) for sortednewtag in newtagsdata: if self.cfg["debug"]: print "sortednewtag=", pprint.pprint(sortednewtag) if not sortednewtag[1] in checkTags: # only if not already in tag list if self.cfg["debug"]: print "... not in checkTags" if sortednewtag[2] == 0: # if not a recognized date newtagLast = sortednewtag[0].split(".")[-1] if self.cfg["debug"]: print "... not a recognized date" print "newtagLast=", newtagLast if newtagLast in checkTags: tagidx = checkTags.index(newtagLast) if self.cfg["debug"]: print "is in checkTags", tagidx, self.tags[tagidx][1] print "newtagLast=", newtagLast, tagidx, len(checkTags), checkTags if tagidx < len(checkTags)-1: while checkTags[tagidx+1].endswith("." + newtagLast): tagidx += 1 if self.cfg["debug"]: print ">>>>>>>> checkTags[tagidx]=", checkTags[tagidx] #if checkTags[tagidx] > newtagLast: #todo: what was that intended for??? Is already sorted... # if self.cfg["debug"]: # print ">>>> break" # break self.tags.insert(tagidx + 1, (sortednewtag[0], self.tags[tagidx][1], "", sortednewtag[3]) ) checkTags.insert(tagidx + 1, sortednewtag[1]) if self.cfg["debug"]: print ">>>>>>>> checkTags=", checkTags else: self.tags.append( (sortednewtag[0], self.cfg["notagHeading"], sortednewtag[1], "") ) checkTags.append(sortednewtag[1]) else: # if a date if self.cfg["debug"]: print "... it's a recognized date" # "2007-10-19" ==> (2007, 42, 5) ==> "Friday Week42" weekDay = "" weekNumber = "" # validity of dates has been checked earlier. So there should be no problem here... xDate = date(int(sortednewtag[0][0:4]), int(sortednewtag[0][5:7]), int(sortednewtag[0][8:10])) xyear,xweek,xwday = date.isocalendar(xDate) if self.cfg["showWeekday"]: weekDay = self.cfg["weekdayFormat"] % self.cfg["weekdays"][xwday-1] # because ISO-day begins with 1 if self.cfg["showWeeknumber"]: weekNumber = self.cfg["weeknumberFormat"] % xweek outDate = strftime(self.cfg["outDateFormat"],strptime(sortednewtag[0], "%Y-%m-%d")) if sortednewtag[0] <= warndate: if sortednewtag[0] < myToday: missedTodos += 1 self.tags.append( (sortednewtag[1], self.cfg["dateHeadingX"], outDate + weekDay + weekNumber + self.cfg["missedString"], sortednewtag[3] ) ) elif sortednewtag[0] == myToday: todosForToday += 1 self.tags.append( (sortednewtag[1], self.cfg["dateHeadingX"], outDate + weekDay + weekNumber + self.cfg["nowString"], sortednewtag[3] ) ) else: todosForNextDays += 1 n = (mktime(strptime(sortednewtag[0],"%Y-%m-%d")) - time()) / (60*60*24) plural = "" if n <= 1: if int(n*24) > 1: plural = "s" self.tags.append( (sortednewtag[1], self.cfg["dateHeadingX"], outDate + weekDay + weekNumber + " ___ " + str(int(n*24)) + " hour" + plural + " left", sortednewtag[3]) ) else: if int(n) > 1: plural = "s" self.tags.append( (sortednewtag[1], self.cfg["dateHeadingX"], outDate + weekDay + weekNumber + " ___ " + str(int(n)) + " day" + plural + " left", sortednewtag[3]) ) else: self.tags.append( (sortednewtag[1], self.cfg["dateHeading"], outDate + weekDay + weekNumber, sortednewtag[3] ) ) checkTags.append(sortednewtag[1]) # ==> self.tags = [ ( "some date string", "heading" , "YYYY-MM-DD ....", "tags before date-string") # for new dates # ==> self.tags = [ ( "some string", "heading" , "some string" , "tags before last one") # for other ones ?? if self.cfg["debug"]: print "checkTags=" pprint.pprint(checkTags) print "self.tags=" pprint.pprint(self.tags) print "sorted(todos)=" pprint.pprint(sorted(todos)) #--------------------------------------------------- # iterate thru all tags now and insert them on page msgPop = "" msgPag = "" if self.cfg["showInfoPopup"]: if missedTodos: msgPop = "%2d x missed todo.\n" % missedTodos if todosForToday: msgPop += "\n%2d x todo for today! \n" % todosForToday if todosForNextDays: msgPop += "\n%2d x todo for the next %d days!" % (todosForNextDays, self.cfg["dayrange"]) if self.cfg["showInfoOnPage"]: if missedTodos: msgPag = self.cfg["pgInfoPrefix"] + " %2d x missed todo.\n" % missedTodos if todosForToday: msgPag += self.cfg["pgInfoPrefix"] + " %2d x todo for today! \n" % todosForToday if todosForNextDays: msgPag += self.cfg["pgInfoPrefix"] + " %2d x todo for the next %d days!" % (todosForNextDays, self.cfg["dayrange"]) lasttag1 = u"" lasttag2 = u"" newtext = u"" if self.cfg["showInfoOnPage"] and msgPag != "": if self.cfg["pgInfoFrame"].count("%s") == 1: newtext = self.cfg["pgInfoFrame"] % msgPag else: newtext = msgPag newtext += "\n" wroteHeader = False tagAdded = False #dcheck = compile("[0-9]{4}-[0-1][0-9]-[0-3][0-9]") # YYYY-MM-DD # trying to honor self.cfg["outDateFormat"] (e.g. "%Y-%m-%d"): # todo: more flexible dexpr = self.cfg["outDateFormat"].replace("%d","[0-3][0-9]").replace("%m","[0-1][0-9]").replace("%Y","[0-9]{4}").replace("%y","[0-9]{2}") dcheck = compile(dexpr) if not isCalendar or self.cfg["realDatesSeparate"]: for tag in self.tags: if self.cfg["debug"]: print "** tag : ", tag print " lasttag1 =", lasttag1 print " lasttag2 =", lasttag2 if self.cfg["realDatesSeparate"]: result = dcheck.search(tag[2]) if not isCalendar and result: continue if isCalendar and not result: continue tagContinued = False if self.cfg["debug"]: print "wroteHeader=", wroteHeader if tag[2] == "": ##if wroteHeader and (lasttag1 != "") and (tag[1] != "") and ((lasttag1 == tag[1]) or (tag[3] != "" and tag[0].startswith(tag[3]))): #todo: check #todo: check whether tag[1] could be empty at all : make it easier... if wroteHeader and (lasttag1 != "") and (tag[1] != "") and (lasttag1 == tag[1]) : tagContinued = True if self.cfg["debug"]: print "tagContinued set by 1" else: lasttag1 = tag[1] else: if lasttag2 == tag[2]: tagContinued = True if self.cfg["debug"]: print "tagContinued set by 2" #lasttag1 = u"" else: lasttag2 = tag[2] if self.cfg["debug"]: print "tagContinued=", tagContinued # write SPACER to the tag list: if tagAdded and not tagContinued: newtext += '\n' + self.cfg["spacerline"] tagAdded = False if not tagContinued: wroteHeader = False # write header only once per tag # handle untagged todos if tag[0] == u'UNTAGGED': if self.cfg["debug"]: print "... is UNTAGGED" for todo in sorted(todos): if self.cfg["debug"]: print "==> todo untagged", todo tcs = todo[1].split(":")[0] # only the tag(s) tcsp = tcs.find(".") # only the part after "todo." foundTag = False if tcsp > -1: # if tag there tcs = tcs[tcsp+1:] for t in self.tags: # check if this todo's tag is already known => will be listed under that tag later if t[0] == tcs: foundTag = True break if not foundTag: # if this todo is untagged if not wroteHeader: newtext += '\n' + tag[1] + '\n' wroteHeader = True s = todo[1] if self.cfg["deleteAttributes"]: s = self.delAttribs(s) # handle "unreal" todos (like 'done' ...) s1 = "" if (not self.cfg["onlyRealTodos"]) and (s[0:4] != "todo"): s1 = " {%s}" % s[0:s.find(':')] # create links ww1 = todo[0] #if not self.wikidpad.getFormatting().isCcWikiWord(ww1): if not langHelper.isCcWikiWord(ww1): ww1 = "[%s]" % ww1 if self.cfg["extendedLinks"]: newtext += "%s%s#%s : %s%s\n" % (self.cfg["bullet"], ww1, s.replace(" ","# "), s[s.find(':')+1:], s1) else: newtext += "%s%s: %s%s\n" % (self.cfg["bullet"], ww1, s[s.find(':')+1:], s1) tagAdded = True else: # handle tagged todos if self.cfg["debug"]: print "... is tagged" for todo in sorted(todos): if self.cfg["debug"]: print "todo=", todo tcs = todo[1][todo[1].find(".")+1:todo[1].find(":")] # the tag(s) alone if tag[0] == tcs: # if current tag matches current todo's tag if not wroteHeader and not tagContinued: if tag[2] == "": ntxt = tag[1] # normal tag of known type elif tag[2] != tag[0]: ntxt = tag[1] + tag[2] # tag with date and add. info else: ntxt = tag[1] + tag[0] # tag of unknown type and std. dates YYYY-MM-DD without info #todo: correct descr.??? #print "ntxt=", ntxt if ntxt.find("*") != -1: # if contains asterisks disable formatting meaning ntxt = "%s<< %s >>" % (tag[1], ntxt[len(tag[1]):]) # or: #ntxt = ntxt.replace("*","\*") newtext += '\n' + ntxt + '\n' wroteHeader = True s = todo[1] if self.cfg["deleteAttributes"]: s = self.delAttribs(s) # optionally handle "unreal" todos (like 'done' ...) if (not self.cfg["onlyRealTodos"]) and (s[0:4] != "todo"): st,dummy = s.split(':', 1) st,dummy = st.split('.', 1) s1 = " {" + st + "}" else: s1 = "" # create the links ww1 = todo[0] if not langHelper.isCcWikiWord(ww1): ww1 = "[%s]" % ww1 # possible tags before date string if tag[3] != "": ti = " (%s)" % tag[3] else: ti = "" if self.cfg["extendedLinks"]: newtext += "%s%s#%s : %s %s%s\n" % (self.cfg["bullet"], ww1, s.replace(" ","# "), ti, s[s.find(':')+1:], s1) else: newtext += "%s%s:%s %s%s\n" % (self.cfg["bullet"], ww1, ti, s[s.find(':')+1:], s1) tagAdded = True # and finally the footer newtext += '\n' + self.cfg["spacerline"] if self.cfg["realDatesSeparate"]: if isCalendar: newtext += self.cfg["spacerline"] + "\nDue to configuration option there may be the main todo page: ToDo ..." else: newtext += self.cfg["spacerline"] + "\nDue to configuration option there may be an additional page: ToDoCalendar ..." newtext += '\n' + 2 * self.cfg["spacerline"] if self.cfg["iconString"] != "" or colorStringFlag: newtext += '\n\n' if self.cfg["iconString"] != "": newtext += self.cfg["iconString"] + "\n" if colorStringFlag: if missedTodos: newtext += self.cfg["colorStringMissed"] elif todosForToday: newtext += self.cfg["colorStringToday"] elif todosForNextDays: newtext += self.cfg["colorStringNext"] else: newtext += self.cfg["colorStringNormal"] if self.cfg["setBookmark"]: newtext += '\n[bookmarked=true]\n' if self.cfg["showVersion"]: newtext += "\n*** Created by Todo-Extension version %s in %s ***\n" % (version, wpversionstr) if self.cfg["isConfigPageActive"] and self.cfg["showConfigLink"]: newtext += "*** Configuration can be changed locally on page %s ***\n" % (self.cfg["configPage"]) if not self.cfg["realDatesSeparate"] and isCalendar and not tagAdded: newtext = "Due to configuration option there are no entries on this page.\nHave a look at the main todo page: ToDo" editor.AddText(newtext) # put all the new text into the editor page editor.GotoPos(0) # and navigate to BeginOfFile #todo: better than nothing, but perhaps can be optimized if self.cfg["showInfoPopup"] and msgPop != "": if (self.cfg["realDatesSeparate"] and isCalendar) or (not self.cfg["realDatesSeparate"] and not isCalendar): if lastCreation < (time() - self.cfg["infoPopupTimeDelta"] * 60): self.wikidpad.displayMessage("Info from %s" % wikiWord, msgPop)