Exemple #1
0
def cmd_repeat(tl, args):
    """marks the todo item as done and reenters it after the specified time
    
    :description: This command is for frequently occurring todo items, like e.g. a bi-weekly
                  status report.
    
    Required fields of :param:`args`:
    * item: the index number of the item from which something should be detached
    * date: the relative or absolute date when the item is due again 
    """
    with ColorRenderer() as cr:
        item = tl.get_item_by_index(args.item)
        # create a copy
        new_item = tl.add_item(item.text)
        # we have to create a new ID for the copied item
        new_item.remove_prop(conf.ID)
        tl.replace_or_add_prop(new_item, conf.ID, tl.create_tid(new_item))
        # set the due date of the new item to the specified date
        tl.replace_or_add_prop(new_item, conf.DUE, args.date,
                               to_date(args.date))
        # set old item to done
        item.set_to_done()
        suppress_if_quiet(
            u"Marked todo item as 'done' and reinserted:\n  {item}".format(
                item=cr.render(new_item)), args)
Exemple #2
0
def cmd_tasked(tl, args):
    """shows all open todo items that I am tasked with
    
    Required fields of :param:`args`:
    * initiator: for filtering the name used for denoting the initiator
    * all: if given, also the done todos are shown
    """
    with ColorRenderer() as cr:
        from_list = collections.defaultdict(list)
        for item in tl.list_items():
            if not args.all and (item.done or item.is_report):
                continue
            for initiator in item.delegated_from:
                from_list[initiator.lower()].append(item)
        if args.initiator:
            ini_list = [args.initiator.lower()]
        else:
            ini_list = sorted(from_list)
        nr = 0
        for initiator in ini_list:
            print("Tasks from {delegate}".format(
                delegate=cr.wrap_delegate(initiator, reset=True)))
            for item in sorted(from_list[initiator], cmp=tl.default_sort):
                print(" ", cr.render(item))
                nr += 1
        suppress_if_quiet(u"{nr} todo items displayed.".format(nr=nr), args)
Exemple #3
0
def cmd_edit(tl, args):
    """allows editing a given todo item
    
    :description: This action will open an editor. 
        If you're done editing, save the file and close the editor or cancel
        editing by pressing ``Ctrl+C``.
    
    * item: the index number of the item to edit
    """
    with ColorRenderer() as cr:
        if not args.item:
            open_editor(conf.todo_file)
            quit(0)
        item = tl.get_item_by_index(args.item)
        if not item:
            print("Could not find item '{item_id}'".format(item_id=args.item))
            return

        print(" ", cr.render(item))
        try:
            output = get_editor_input(item.text)
            # remove new lines
            edited_item = TodoItem(
                output.replace("\r\n", " ").replace("\n", " ").strip())
            tl.replace_item(item, edited_item)
            suppress_if_quiet(u"  {item}".format(item=cr.render(edited_item)),
                              args)
            edited_item.check()
        except KeyboardInterrupt:
            # editing has been aborted
            pass
Exemple #4
0
def cmd_delegated(tl, args):
    """shows all todo items that have been delegated and wait for input
    
    Required fields of :param:`args`:
    * delegate: for filtering the name used for denoting a delegate
    * all: if given, also the done todos are shown
    """
    with ColorRenderer() as cr:
        to_list = collections.defaultdict(list)
        for item in tl.list_items():
            if not args.all and (item.done or item.is_report):
                continue
            for delegate in item.delegated_to:
                to_list[delegate.lower()].append(item)
        if args.delegate:
            del_list = [args.delegate.lower()]
        else:
            del_list = sorted(to_list)
        nr = 0
        for delegate in del_list:
            print("Delegated to {delegate}".format(
                delegate=cr.wrap_delegate(delegate, reset=True)))
            for item in sorted(to_list[delegate], cmp=tl.default_sort):
                nr += 1
                print(" ", cr.render(item))
        suppress_if_quiet(u"{nr} todo items displayed.".format(nr=nr), args)
Exemple #5
0
def cmd_done(tl, args):
    """sets the status of one or more todo items to 'done'
    
    Required fields of :param:`args`:
    * items: the index number of the items to set to 'done'
    """
    with ColorRenderer() as cr:
        now = datetime.datetime.now()
        suppress_if_quiet(u"Marked following todo items as 'done':", args)
        for item in tl.get_items_by_index_list(args.items):
            tl.set_to_done(item)
            # if started property is set, remove it and update duration property
            if conf.STARTED in item.properties:
                start_time = item.properties[conf.STARTED]
                time_delta = now - start_time
                duration = 0
                try:
                    # try to parse existing duration property
                    duration = int(item.properties.get(conf.DURATION, 0))
                except:
                    pass
                # add delta time in minutes
                duration += int(time_delta.total_seconds() / 60)
                # remove started property
                tl.replace_or_add_prop(item, conf.STARTED, None)
                # update duration property
                tl.replace_or_add_prop(item, conf.DURATION, duration)

            suppress_if_quiet(u"  {item}".format(item=cr.render(item)), args)
Exemple #6
0
def cmd_unblock(tl, args):
    """unsetting the first item as a pre-requisite to the second item
    
    :description: This allows to remove dependencies between todo items. One item
        can be removed as a pre-requisite from another todo item. This command is only
        usable with id support activated.
    
    Required fields of :param:`args`:
    * item: the id of the todo item which is a pre-requisite
    * blocked: the id of the todo item which is blocked
    """
    with ColorRenderer() as cr:
        if not conf.id_support:
            print(u"ID support is deactivated. You cannot use this feature.")
            return
        item = tl.get_item_by_index(args.item)
        blocked = tl.get_item_by_index(args.blocked)
        if item.tid not in blocked.properties.get(conf.BLOCKEDBY, []):
            print(
                u"Todo item '{item_id}' is not a pre-requisite of '{blocked_id}'."
                .format(item_id=item, blocked_id=blocked))
            return
        tl.remove_prop(blocked, conf.BLOCKEDBY, item.tid)
        tl.clean_dependencies()
        tl.reindex()
        suppress_if_quiet(u"  {item}".format(item=cr.render(blocked)), args)
Exemple #7
0
def cmd_search(tl, args):
    """lists all current and archived todo items that match the search string
    
    Required fields of :param:`args`:
    * search_string: a search string
    * regex: if given, the search string is interpreted as a regular expression
    * ci: if given, the search string is interpreted as case insensitive
    """
    with ColorRenderer() as cr:
        # case insensitivity
        if args.ci:
            flags = re.UNICODE | re.IGNORECASE
        else:
            flags = re.UNICODE
        # no search string given
        if not args.search_string:
            args.search_string = "."
            args.regex = True
        # given as regular expression
        if args.regex:
            re_search = re.compile(args.search_string, flags)
        else:
            re_search = re.compile(re.escape(args.search_string), flags)

        # store for all matching items
        all_matches = []
        # first, look at current todo list
        for item in tl.list_items():
            if re_search.search(item.text):
                all_matches.append((conf.todo_file, item))

        # get file list of all archive files by replacing all %x-variables with '*' and
        # let glob do the hard work
        file_pattern = re_replace_archive_vars.sub(
            "*", conf.archive_filename_scheme)
        root_dir = os.path.dirname(conf.todo_file)
        file_list = glob.glob(os.path.join(root_dir, file_pattern))
        # add the file for items without done timestamp
        unsorted_file = os.path.join(root_dir, conf.archive_unsorted_filename)
        if os.path.exists(unsorted_file):
            file_list.append(unsorted_file)

        for arch_file in file_list:
            # create a new todo list for each archive file
            with TodoList(arch_file) as atl:
                for item in atl.todolist:
                    if re_search.search(item.text):
                        item.replace_or_add_prop(conf.ID, "(A)")
                        all_matches.append((arch_file, item))

        # sort by filename
        all_matches.sort(key=lambda x: x[0])
        # group by filename
        for filename, items in groupby(all_matches, lambda x: x[0]):
            print(u"File '{fn}':".format(fn=filename))
            for item in items:
                print(" ", cr.render(item[1]))
        suppress_if_quiet(
            u"{nr} matching todo items found".format(nr=len(all_matches)),
            args)
Exemple #8
0
def cmd_start(tl, args):
    """sets the 'started' property of an item or lists all started items.
    
    :description: If a todo item is picked to be worked on, this command
        allows setting the started time. Thus, the time it took to work
        on that item can be derived from 'started' and 'done' time.
    
    Required fields of :param:`args`:
    * item: the index number or id of the todo item which is started
    """
    with ColorRenderer() as cr:
        if not args.item:
            for item in tl.list_items(lambda x: True
                                      if conf.STARTED in x.properties and not (
                                          x.done or x.is_report) else False):
                print(u" ", cr.render(item))
        else:
            item = tl.get_item_by_index(args.item)
            if not item:
                print(u"No item found with number or ID '{item_id}'".format(
                    item_id=args.item))
                return
            if item.done:
                print(u"Todo item has already been set to 'done':")
                print(u" ", cr.render(item))
                return
            if conf.STARTED in item.properties:
                print(u"Todo item has already been started on {date}".format(
                    date=from_date(item.properties[conf.STARTED])))
                print(u" ", cr.render(item))
                return
            now = datetime.datetime.now()
            tl.replace_or_add_prop(item, conf.STARTED, from_date(now), now)
Exemple #9
0
def cmd_mark(tl, args):
    """lists all items with markers (e.g. '(!)')
    
    :description: Markers can be used to denote a todo item classification, e.g.
        an open question or an information ('(i)'). If no marker parameter
        is given, all found markers are listed.
    
    Required fields of :param:`args`:
    * marker: a single character that denotes the type of the marker to list.
    * all: if given, also the done todo and report items are shown
    """
    with ColorRenderer() as cr:
        marker_dict = collections.defaultdict(list)
        for item in tl.list_items(lambda x: True if args.all or not (
                x.done or x.is_report) else False):
            for marker in item.markers:
                marker_dict[marker].append(item)
        if args.marker:
            #show project if the given name (partially) matches the project identifier
            args_list = [
                name for name in sorted(marker_dict) if args.marker == name
            ]
        else:
            # show all sorted projects
            args_list = sorted(marker_dict)

        nr = 0
        for marker in args_list:
            print(
                cr.wrap_marker(u"({marker})".format(marker=marker),
                               reset=True))
            for item in sorted(marker_dict[marker], cmp=tl.default_sort):
                print(u" ", cr.render(item))
                nr += 1
        suppress_if_quiet(u"{nr} todo items displayed.".format(nr=nr), args)
Exemple #10
0
def cmd_remove(tl, args):
    """removes one or more items from the todo list
    
    Required fields of :param:`args`:
    * items: the index number of the items to remove
    * force: if given, confirmation is not requested
    """
    with ColorRenderer() as cr:
        item_list = tl.get_items_by_index_list(args.items)
        if not item_list:
            msg = "Could not find item(s) {item_ids}".format(
                item_ids=", ".join(args.items))
            print(msg)
            logger.info(msg)
            return
        if not args.force:
            print("Do you really want to remove the following item(s):")
            for item in item_list:
                print(" ", cr.render(item))
            if confirm_action("Please confirm (y/N): "):
                for item in item_list:
                    tl.remove_item(item)
            else:
                print("Removing aborted")
                return
        else:
            for item in item_list:
                tl.remove_item(item)
        msg = u"{nr} todo items ({item_ids}) have been removed.".format(
            nr=len(item_list),
            item_ids=",".join(
                [cr.wrap_id(item.tid, reset=True) for item in item_list]))
        suppress_if_quiet(msg, args)
        logger.info(msg)
Exemple #11
0
def cmd_prio(tl, args):
    """assigns given items a priority (absolute like 'A' or relative like '-')
    
    Required fields of :param:`args`:
    * items: the index numbers of the items to (re)prioritize
    * priority: the new priority ('A'..'Z' or '+'/'-') or 'x' (for removing)
    """
    with ColorRenderer() as cr:
        prio_items = tl.get_items_by_index_list(args.items)
        if not prio_items:
            print(u"Could not find items {item_ids}".format(
                item_ids=", ".join(args.items)))
            return
        new_prio = args.priority
        if not re_prio.match(new_prio):
            print(
                u"Priority '{prio}' can't be recognized (must be one of A to Z or +/-)"
                .format(prio=new_prio))
            return
        for item in prio_items:
            old_prio = item.priority
            if new_prio == "x":
                # remove priority
                suppress_if_quiet(u"  Removing priority:", args)
                tl.set_priority(item, None)
                suppress_if_quiet(u"  {item}".format(item=cr.render(item)),
                                  args)
            elif new_prio == "-":
                if old_prio in ("Z", None):
                    print(u"  Can't lower priority of following item:")
                    print(u" ", cr.render(item))
                else:
                    temp_prio = chr(ord(old_prio) + 1)
                    suppress_if_quiet(
                        u"  Lower priority from {old_prio} to {new_prio}:".
                        format(old_prio=old_prio, new_prio=temp_prio), args)
                    tl.set_priority(item, temp_prio)
                    suppress_if_quiet(u"  {item}".format(item=cr.render(item)),
                                      args)
            elif new_prio == "+":
                if old_prio in ("A", None):
                    print(u"  Can't raise priority of following item:")
                    print(u" ", cr.render(item))
                else:
                    temp_prio = chr(ord(old_prio) - 1)
                    suppress_if_quiet(
                        u"  Raise priority from {old_prio} to {new_prio}:".
                        format(old_prio=old_prio, new_prio=temp_prio), args)
                    tl.set_priority(item, temp_prio)
                    suppress_if_quiet(u"  {item}".format(item=cr.render(item)),
                                      args)
            else:
                suppress_if_quiet(
                    u"  Setting priority from {old_prio} to {new_prio}:".
                    format(old_prio=old_prio, new_prio=new_prio), args)
                tl.set_priority(item, new_prio)
                suppress_if_quiet(u"  {item}".format(item=cr.render(item)),
                                  args)
Exemple #12
0
def cmd_overdue(tl, args):
    """shows all todo items that are overdue
    
    Nor :param:`args` arguments are required.
    """
    with ColorRenderer() as cr:
        print("Overdue todo items:")
        nr = 0
        for item in tl.list_items(lambda x: not x.done and x.is_overdue()):
            print(" ", cr.render(item))
            nr += 1
        suppress_if_quiet(u"{nr} todo items displayed.".format(nr=nr), args)
Exemple #13
0
def cmd_reopen(tl, args):
    """reopens one or more items marked as 'done'
    
    Required fields of :param:`args`:
    * items: the index numbers of the items to reopen
    """
    with ColorRenderer() as cr:
        suppress_if_quiet(u"Set the following todo items to open again:", args)
        for item in tl.get_items_by_index_list(args.items):
            tl.reopen(item)
            tl.reindex()
            suppress_if_quiet(u"  {item}".format(item=cr.render(item)), args)
Exemple #14
0
def cmd_call(tl, args):
    """opens either an URL, a file or mail program depending on information that is attached to the todo item
    
    Required fields of :param:`args`:
    * item: the index number of the item that has either an URL or file attached
    """
    with ColorRenderer() as cr:
        item = tl.get_item_by_index(args.item)
        if not item:
            print(u"Could not find item '{item_id}'".format(item_id=args.item))
            return

        nr = 0
        actions = {}
        for toopen in item.urls:
            print(u"  [{nr: 3d}] Open web site {url}".format(nr=nr,
                                                             url=toopen))
            actions[nr] = (webbrowser.open_new_tab, toopen)
            nr += 1
        for file_name in item.properties.get(conf.FILE, []):
            if not os.path.exists(file_name):
                print(u"  [xxx] File {fn} does not exist".format(fn=file_name))
            else:
                print(
                    u"  [{nr: 3d}] Open file {fn} with default editor".format(
                        nr=nr, fn=file_name))
                actions[nr] = (os.startfile, file_name)
                nr += 1
        for email in item.properties.get(conf.MAILTO, []):
            print(
                u"  [{nr: 3d}] Write a mail to {email} with default mail program"
                .format(nr=nr, email=email))
            actions[nr] = (os.startfile, "mailto:" + email)
            nr += 1
        # simple case: only one action available
        if len(actions) == 1:
            actions[0][0](actions[0][1])
        elif len(actions) > 1:
            choice = raw_input(
                u"Please enter your choice (0-{max:d}): ".format(
                    max=len(actions) - 1)).strip()
            try:
                choice = int(choice)
            except:
                print(u"Not a valid option. Closing...")
                quit(-1)
            if int(choice) in actions:
                actions[choice][0](actions[choice][1])
        else:
            # nothing available
            print(u"No files / urls / email addresses found in task:")
            print(u" ", cr.render(item))
Exemple #15
0
def cmd_check(tl, args):  #@UnusedVariable
    """checks the todo list for syntactical validity
    
    Required fields of :param:`args`:
    """
    with ColorRenderer() as cr:
        nr = 0
        for item, warnings in tl.check_items():
            nr += 1
            print(u" ", cr.render(item))
            for warning in warnings:
                print(u" ", warning)
        print(u"{nr} warning(s) have been found".format(nr=(nr or "No")))
Exemple #16
0
def cmd_note(tl, args):
    """adding or editing a note to a todo item
    
    :description: Opens a text file that contains further notes for a specific
        item.
    
    Required fields of :param:`args`:
    * item: the id of the todo item that should be annotated
    """
    with ColorRenderer() as cr:
        if not conf.id_support:
            print(u"ID support is deactivated. You cannot use this feature.")
            return
        item = tl.get_item_by_index(args.item)
Exemple #17
0
def cmd_delay(tl, args):
    """delays the due date of one or more todo items
    
    Required fields of :param:`args`:
    * item: the index number of the item to delay
    * date: either a date or a string like 'tomorrow', default '1d' (delays for 1 day)
    * force: if given, confirmation is not requested
    """
    with ColorRenderer() as cr:
        item = tl.get_item_by_index(args.item)
        if not item:
            print(u"Could not find item '{item_id}'".format(item_id=args.item))
            return
        if item.due_date:
            new_date = to_date(args.date, item.due_date)
            if isinstance(new_date, basestring):
                # remove first character, as it is "?" with a non-parsable date
                print(u"The given relative date could not be parsed: {date}".
                      format(date=new_date[1:]))
            else:
                # ask for confirmation
                if not args.force:
                    print(" ", cr.render(item))
                    if not confirm_action(
                            u"Delaying the preceding item's date from {from_date} to {to_date} (y/N)?"
                            .format(from_date=from_date(item.due_date),
                                    to_date=from_date(new_date))):
                        return
                # do the actual replacement
                tl.replace_or_add_prop(item, conf.DUE, from_date(new_date),
                                       new_date)
        else:
            new_date = to_date(args.date)
            if not args.force:
                print(u" ", cr.render(item))
                if not confirm_action(
                        u"The preceding item has no due date set, set to {date} (y/N)?"
                        .format(date=from_date(new_date))):
                    return
                tl.replace_or_add_prop(item, conf.DUE, from_date(new_date),
                                       new_date)
        suppress_if_quiet(u"  {item}".format(item=cr.render(item)), args)
Exemple #18
0
def cmd_stats(tl, args):  #@UnusedVariable
    """displays some simple statistics about your todo list
    
    Required fields of :param:`args`:
    """
    # write # open / # done / # prioritized / # overdue items
    counter = collections.defaultdict(int)
    delegates = set()
    with ColorRenderer() as cr:
        for item in tl.list_items():
            counter["total"] += 1
            if item.done:
                counter["done"] += 1
            else:
                counter["open"] += 1
            if item.priority:
                counter["prioritized"] += 1
            if item.is_overdue() and not item.done:
                counter["overdue"] += 1
            if item.is_still_open_today() and not item.done:
                counter["today"] += 1
            if item.is_report:
                counter["report"] += 1
            delegates.update(item.delegated_to)
            delegates.update(item.delegated_from)
        print(u"Total number of items: {stat}".format(stat=counter["total"]))
        print(u"Open items           : {stat}".format(stat=counter["open"]))
        print(
            cr.wrap_prioritized(u"Prioritized items    : {stat}".format(
                stat=counter["prioritized"])))
        print(
            cr.wrap_overdue(u"Overdue items        : {stat}".format(
                stat=counter["overdue"])))
        print(
            cr.wrap_today(u"Items due today      : {stat}".format(
                stat=counter["today"])))
        print(
            cr.wrap_done(
                u"Done items           : {stat}".format(stat=counter["done"])))
        print(
            cr.wrap_report(u"Report items         : {stat}".format(
                stat=counter["report"])))
Exemple #19
0
def cmd_agenda(tl, args):
    """displays an agenda for a given date
    
    Required fields of :param:`args`:
    * date: either a date or a string like 'tomorrow' or '*', default 'today'
    """
    with ColorRenderer() as cr:
        agenda_items = []
        # if not set, get agenda for today
        list_all = False
        if not args.date:
            args.date = datetime.datetime.now()
        elif args.date == "*":
            list_all = True
        else:
            args.date = to_date(args.date)
            if isinstance(args.date, basestring):
                print(u"Could not parse date argument '{date_str}'".format(
                    date_str=args.date))
                quit(-1)
        for item in tl.list_items(lambda x: True if x.due_date else False):
            if is_same_day(args.date, item.due_date) or list_all:
                agenda_items.append(item)
        # default date used when no done date is specified
        na_date = datetime.datetime(1970, 1, 1)
        # sort filtered list by "due" date and whether they are already marked as "done"
        agenda_items.sort(
            key=lambda x: (x.done, x.due_date) or (x.done, na_date))
        # group report/done items by date
        for keys, groups in groupby(
                agenda_items, lambda x:
            ((x.due_date or na_date).year, (x.due_date or na_date).month,
             (x.due_date or na_date).day)):
            # filter out default dates again
            if (na_date.year, na_date.month, na_date.day) == keys:
                print(u"No done date attached")
            else:
                print(u"Agenda for {0:d}-{1:02d}-{2:02d}:".format(*keys))
            for item in groups:
                print(" ", cr.render(item))
        suppress_if_quiet(
            u"{nr} todo items displayed.".format(nr=len(agenda_items)), args)
Exemple #20
0
def cmd_detach(tl, args):
    """detaches a file from a given todo item
    
    Required fields of :param:`args`:
    * item: the index number of the item from which something should be detached
    """
    with ColorRenderer() as cr:
        item = tl.get_item_by_index(args.item)
        if not item:
            print(u"Could not find item '{item_id}'".format(item_id=args.item))
            return
        attmnt_list = []
        attmnt_list.extend(("url", url) for url in item.urls)
        for file_name in item.properties.get(conf.FILE, []):
            attmnt_list.append((conf.FILE, file_name))
        if len(attmnt_list) == 0:
            print(u"This item has no file or URLs attached")
            quit(0)
        elif len(attmnt_list) == 1:
            attmnt = attmnt_list[0]
        if len(attmnt_list) > 1:
            print(u"Please choose one of the following attachments to delete:")
            for nr, attmnt in enumerate(attmnt_list):
                print(u"  [{nr: 2d}] {attmnt}".format(nr=nr, attmnt=attmnt[1]))
            print(u"  [x] Abort operation")
            answer = raw_input(u"Your choice: ").lower().strip()
            if answer == "x":
                quit(0)
            try:
                attmnt_nr = int(answer)
                attmnt = attmnt_list[attmnt_nr]
            except:
                print(u"Not a valid input")
                quit(0)
        if attmnt[0] == conf.FILE:
            item = tl.replace_or_add_prop(item, conf.FILE, None, attmnt[1])
        else:
            item.text = u" ".join(item.text.replace(attmnt[1], "").split())
        suppress_if_quiet(u"  {item}".format(item=cr.render(item)), args)
        tl.dirty = True
Exemple #21
0
def cmd_attach(tl, args):
    """attaches a file to the given todo item
    
    Required fields of :param:`args`:
    * item: the index number of the item to which something should be attached
    * location: either a (relative) file name or a (fully qualified) URL
    """
    with ColorRenderer() as cr:
        item = tl.get_item_by_index(args.item)
        if not item:
            print(u"Could not find item '{item_id}'".format(item_id=args.item))
            return

        if re_urls.match(args.location):
            # we got an URL
            suppress_if_quiet(u"Attaching URL {url}".format(url=args.location),
                              args)
            item.text += u" {url}".format(url=args.location)
            item.urls.append(args.location.strip())
            tl.dirty = True
            tl.reindex()
        else:
            # get path relative to todo file
            try:
                path = os.path.relpath(args.location,
                                       os.path.dirname(conf.todo_file))
            except ValueError:
                # path is on other rive than reference location
                path = os.path.abspath(args.location)

            if not os.path.exists(path):
                print(u"File path '{fn}' does not exist".format(fn=path))
                quit(-1)

            suppress_if_quiet(u"Attaching file {fn}".format(fn=path), args)
            tl.replace_or_add_prop(item, conf.FILE, path)
            tl.reindex()
        suppress_if_quiet(u"  {item}".format(item=cr.render(item)), args)
Exemple #22
0
def cmd_list(tl, args):
    """lists all items that match the given expression
    
    :description: If no search query is given, all items are listed.
    
    Required fields of :param:`args`:
    * search_string: a search string
    * all: if given, also the done todo and report items are shown
    * regex: if given, the search string is interpreted as a regular expression
    * ci: if given, the search string is interpreted as case insensitive
    """
    with ColorRenderer() as cr:
        # case insensitivity
        if args.ci:
            flags = re.UNICODE | re.IGNORECASE
        else:
            flags = re.UNICODE
        # no search string given
        if not args.search_string:
            args.search_string = "."
            args.regex = True
        # given as regular expression
        if args.regex:
            re_search = re.compile(args.search_string, flags)
        else:
            re_search = re.compile(re.escape(args.search_string), flags)

        nr = 0
        for item in tl.list_items():
            if (not args.all) and (item.is_report or item.done):
                # if --all is not set, report and done items are suppressed
                #print(repr(item.properties))
                continue
            if re_search.search(item.text):
                nr += 1
                print(" ", cr.render(item))
        suppress_if_quiet(
            u"{nr_items} todo items displayed.".format(nr_items=nr), args)
Exemple #23
0
def cmd_context(tl, args):
    """lists all todo items per context
    
    Required fields of :param:`args`:
    * name: the name of the context to display
    * all: if given, also the done todo items are displayed
    * ci: if given, the context name is interpreted as case insensitive
    """
    # lists todo items per context (like list, only with internal grouping)
    with ColorRenderer() as cr:
        # case insensitivity
        if args.ci:
            flags = re.UNICODE | re.IGNORECASE
        else:
            flags = re.UNICODE
        if args.name:
            args.name = re.escape(args.name)
        else:
            args.name = "."
        re_search = re.compile(args.name, flags)

        context_dict = collections.defaultdict(list)
        for item in tl.list_items(lambda x: True if args.all or not (
                x.done or x.is_report) else False):
            for context in item.contexts:
                context_dict[context].append(item)
        #show context if the given name (partially) matches the context identifier
        args_list = [
            name for name in sorted(context_dict) if re_search.search(name)
        ]
        nr = 0
        for context in args_list:
            print(u"Context", cr.wrap_context(context, reset=True))
            for item in sorted(context_dict[context], cmp=tl.default_sort):
                print(u" ", cr.render(item))
                nr += 1
        suppress_if_quiet(u"{nr} todo items displayed.".format(nr=nr), args)
Exemple #24
0
def cmd_stop(tl, args):
    """stops working on a todo item without setting it to 'done'.
    
    :description: If a todo item is paused, the 'duration' property is 
        updated and the 'started' property is removed.
    
    Required fields of :param:`args`:
    * item: the index number or id of the todo item which should be stopped
    """
    with ColorRenderer() as cr:
        item = tl.get_item_by_index(args.item)
        if not item:
            print(u"No item found with number or ID '{item_id}'".format(
                item_id=args.item))
            return
        if conf.STARTED not in item.properties:
            print(u"Todo item has not been started yet")
            return
        start_time = item.properties[conf.STARTED]
        now = datetime.datetime.now()
        time_delta = now - start_time
        duration = 0
        try:
            # try to parse existing duration property
            duration = int(item.properties.get(conf.DURATION, 0))
        except:
            pass
        # add delta time in minutes
        duration += int(time_delta.total_seconds() / 60)
        # remove started property
        tl.remove_prop(item, conf.STARTED, None)
        # update duration property
        tl.replace_or_add_prop(item, conf.DURATION, duration)
        suppress_if_quiet(
            u"You have worked {dur} minutes on:\n  {item}".format(
                dur=duration, item=cr.render(item)), args)
Exemple #25
0
def cmd_add(tl, args):
    """adds a new todo item to the todo list
        
    :description: The source of the todo item can either be the command line or an editor.
    
    Required fields of :param:`args`:
    * text: the text of the todo item to add
    """
    with ColorRenderer() as cr:
        if not args.text:
            # no arguments given, open editor and let user enter data there
            output = get_editor_input("")
            item = tl.add_item(
                output.replace("\r\n", " ").replace("\n", " ").strip())
        elif isinstance(args.text, list):
            # string not enclosed in ""
            item = tl.add_item(" ".join(args.text))
        else:
            # single string
            item = tl.add_item(args.text)
        msg = u"Added {item}".format(item=cr.render(item))
        suppress_if_quiet(msg, args)
        logger.debug(msg)
        item.check()
Exemple #26
0
def cmd_report(tl, args):
    """shows a daily report of all done and report items
    
    :description: This command lists all done and report items for a given date
        or date range. If no arguments are given, the items of the last 7 days are
        displayed. 
    
    Required fields of :param:`args`:
    * from_date: either a date or a string like 'tomorrow' or '*'
    * to_date: either a date or a string like 'tomorrow'
    """
    with ColorRenderer() as cr:
        # default date used when no done date is specified
        na_date = datetime.datetime(1970, 1, 1, 0, 0, 0, 0)
        # today
        now = datetime.datetime.now().replace(hour=0,
                                              minute=0,
                                              second=0,
                                              microsecond=0)
        # check from and to date, make them datetime or None
        # what mode are we in?
        mode = None
        if args.from_date in ("*", "all"):
            mode, args.from_date, args.to_date = "ALL", na_date, now
        else:
            args.from_date = to_date(args.from_date)
            args.to_date = to_date(args.to_date)
            if isinstance(args.from_date, datetime.datetime):
                args.from_date = args.from_date.replace(hour=0,
                                                        minute=0,
                                                        second=0,
                                                        microsecond=0)
            else:
                logger.debug(
                    u"Cannot parse {date}".format(date=args.from_date))
                args.from_date = None
            if isinstance(args.to_date, datetime.datetime):
                args.to_date = args.to_date.replace(hour=0,
                                                    minute=0,
                                                    second=0,
                                                    microsecond=0)
            else:
                logger.debug(u"Cannot parse {date}".format(date=args.to_date))
                args.to_date = None

        if args.from_date and args.to_date and not mode:
            mode = "RANGE"
        elif args.from_date and not args.to_date:
            mode, args.to_date = "DAY", args.from_date
        elif not mode:
            # last 7 days
            mode, args.from_date, args.to_date = "LASTWEEK", now - datetime.timedelta(
                days=7), now

        # swap dates, if necessary
        if args.from_date > args.to_date:
            args.from_date, args.to_date = args.to_date, args.from_date
        # set end date to end of day
        args.to_date = args.to_date.replace(hour=23, minute=59, second=59)

        logger.debug(u"Report mode {0}: from {1} to {2}".format(
            mode, args.from_date, args.to_date))

        # get list of done and report items from current todo list
        report_list = list(tl.list_items(lambda x: (x.done or x.is_report)))

        # get list of done and report items from un-dated archive file
        root_dir = os.path.dirname(conf.todo_file)
        unsorted_fn = os.path.join(root_dir, conf.archive_unsorted_filename)
        if os.path.exists(unsorted_fn):
            res = TodoList(unsorted_fn)
            report_list.extend(res.todolist)

        # get all archive file names in list
        file_pattern = re_replace_archive_vars.sub(
            "*", conf.archive_filename_scheme)
        file_list = glob.glob(os.path.join(root_dir, file_pattern))

        # regex for finding all replaced parts in archive filename scheme
        re_find_date_str = re_replace_archive_vars.sub(
            "(.+)", conf.archive_filename_scheme).replace("\\", "\\\\")
        re_find_date = re.compile(re_find_date_str, re.UNICODE)
        # loop through all files and see, whether they match the given date range
        for fn in file_list:
            # get all replaced values in filename
            parts = re_find_date.findall(fn)[0]
            # get the variables responsible for this substitution (e.archived_items. "%Y", "%m", ...)
            tvars = re_replace_archive_vars.findall(
                conf.archive_filename_scheme)
            # create mapping, removing duplicates
            mapping = dict(zip(tvars, parts))
            # create date from mapping
            tdate = datetime.datetime.strptime(" ".join(mapping.values()),
                                               " ".join(mapping))

            # if filename matches date range
            if args.from_date <= tdate <= args.to_date:
                # load todo list
                res = TodoList(fn)
                # get items directly if they are done or report items
                archived_items = [
                    item for item in res.todolist
                    if item.done or item.is_report
                ]
                for item in archived_items:
                    # replace id with (A) to mark it as archived
                    item.replace_or_add_prop(conf.ID, "(A)")
                # append it to candidates
                report_list.extend(archived_items)

        # sort filtered list by "done" date
        report_list.sort(key=lambda x: x.done_date or na_date)

        nr = 0
        # group report/done items by date
        for keys, groups in groupby(
                report_list, lambda x:
            ((x.done_date or na_date).year, (x.done_date or na_date).month,
             (x.done_date or na_date).day)):
            # we are looking at that date right now
            temp_date = datetime.datetime(year=keys[0],
                                          month=keys[1],
                                          day=keys[2])
            # that date does not match the requested date range: skip
            if not args.from_date <= temp_date <= args.to_date:
                continue
            # filter out default dates again
            if is_same_day(na_date, temp_date):
                print(u"Report for unknown date:")
            else:
                print(u"Report for {date}:".format(
                    date=temp_date.strftime("%A, %Y-%m-%d")))
            # print the items, finally
            for item in groups:
                print(" ", cr.render(item))
                nr += 1

        suppress_if_quiet(u"{nr} todo items displayed.".format(nr=nr), args)