예제 #1
0
def main():
    habitapi = HabitAPI("<foo>", "<bar>")
    task_warrior = TaskWarrior(data_location='~/.task', create=False)
    task_warrior.config.update({
        "uda.habitrpg-id.type": "string",
        "uda.habitrpg-id.label": "HabitRPG ID"
    })

    #get data from habitrpg
    todos = habitapi.todos()
    #dailies = habitapi.dailies()
    #get local data from taskwarrior
    tw_todos = task_warrior.tasks.filter(tags__contains=["habitrpg"])
    print "== Taskwarriror Todos ===\n"
    for tw_todo in tw_todos:
        print tw_todo
    print "=========================\n"

    print "=== HabitRPG Todos ===\n"
    for habit_todo in todos:
        remote_name = habit_todo[u'text']
        #print "%s (%s)" % (remote_name,type(remote_name))
        local = tw_todos.filter(description__contains=remote_name)
        if local:
            print local
        # if local:
        #   print "Task %s already exists locally, checking for a habitrpg ID ..."
        #   print type(local)
        # else:
        #   print "Task %s does not exist locally creating it ..." % remote_name
    print "=======================\n"
예제 #2
0
def main():
    habitapi = HabitAPI("<foo>",
                        "<bar>")
    task_warrior = TaskWarrior(data_location='~/.task', create=False)
    task_warrior.config.update(
        {"uda.habitrpg-id.type":"string",
         "uda.habitrpg-id.label":"HabitRPG ID"}
        )

    #get data from habitrpg
    todos = habitapi.todos()
    #dailies = habitapi.dailies()
    #get local data from taskwarrior
    tw_todos = task_warrior.tasks.filter(tags__contains=["habitrpg"])
    print "== Taskwarriror Todos ===\n"
    for tw_todo in tw_todos:
        print tw_todo
    print "=========================\n"

    print "=== HabitRPG Todos ===\n"
    for habit_todo in todos:
        remote_name = habit_todo[u'text']
        #print "%s (%s)" % (remote_name,type(remote_name))
        local = tw_todos.filter(description__contains=remote_name)
        if local:
            print local
        # if local:
        #   print "Task %s already exists locally, checking for a habitrpg ID ..."
        #   print type(local)
        # else:
        #   print "Task %s does not exist locally creating it ..." % remote_name
    print "=======================\n"
예제 #3
0
 def _get_api(self, user_id=None, api_key=None):
     """Get the HabitRPG api object."""
     if not user_id and not api_key:
         if hasattr(self, 'api') and self.api:
             return self.api
     if not user_id:
         user_id = self.config["user_id"]
     if not api_key:
         api_key = self.config["api_key"]
     self.api = HabitAPI(user_id, api_key)
     return self.api
예제 #4
0
class HabitCLI(object):
    """A class incorporating everything necessary to interact with HabitRPG."""
    def __init__(self):
        """Initialize the CLI object."""
        self.config = read_config()
        self.api = self._get_api()
        self.user = self.get_user()

    def _get_api(self, user_id=None, api_key=None):
        """Get the HabitRPG api object."""
        if not user_id and not api_key:
            if hasattr(self, 'api') and self.api:
                return self.api
        if not user_id:
            user_id = self.config["user_id"]
        if not api_key:
            api_key = self.config["api_key"]
        self.api = HabitAPI(user_id, api_key)
        return self.api

    def get_user(self, refresh=False):
        """Get the user object from HabitRPG (if possible) or the cache."""
        if not refresh and hasattr(self, 'user') and self.user:
            return self.user
        else:
            try:
                self.user = self.api.user()
                save_user(self.user)
                self.user['cached'] = False
            except ConnectionError:
                self.user = load_user()
                self.user['cached'] = True

            if 'err' in self.user.keys():
                print "Error '%s': Is the configuration in %s correct?" % \
                    (self.user['err'], get_default_config_filename())
                sys.exit(1)

            # Replace user['todos'] with todo objects
            for index, todo in enumerate(self.user['todos']):
                self.user['todos'][index] = Todo(todo, hcli=self)

            # Add tag dictionaries to the user object
            tag_dict = defaultdict(lambda: "+missingtag")
            reverse_tag_dict = defaultdict(unicode)
            color_dict = defaultdict(lambda: lambda x: x)
            for tag in [tag for tag in self.user['tags']
                        if tag['name'] in self.config['tasks']]:
                tag_dict[tag['id']] = tag['name']
                reverse_tag_dict[tag['name']] = tag['id']

                if tag['name'] in self.config['taskcolors'].keys():
                    if self.config['taskcolors'][tag['name']] in colors.COLORS:
                        color = getattr(colors,
                                        self.config['taskcolors'][tag['name']])
                        color_dict[tag['name']] = color
                        color_dict[tag['id']] = color

            self.user['tag_dict'] = tag_dict
            self.user['reverse_tag_dict'] = reverse_tag_dict
            self.user['color_dict'] = color_dict
            return self.user

    def get_todo_str(self,
                     todo,
                     date=False,
                     completed_faint=False,
                     notes=False):
        """Get a nicely formatted and colored string describing a task."""
        todo_str = "%-*s" % (40, todo['text'])

        # If dates should be printed, add the planning and drop-dead dates
        if date:
            plan_date = ""
            plan_date_obj = todo.get_planning_date()
            if plan_date_obj:
                plan_date = pretty.date(plan_date_obj)

            due = ""
            if 'date' in todo.keys() and todo['date']:
                dt_obj = dateutil.parser.parse(todo['date'])
                due = pretty.date(dt_obj)

            todo_str += " Plan: %-*s Due:%-*s" % (15, plan_date, 15, due)

        # Underline the string if the task is urgent
        tags = [tag for tag in todo['tags'].keys() if todo['tags'][tag]]
        if 'urgent' in [self.user['tag_dict'][tag] for tag in tags]:
            todo_str = colors.underline(todo_str)

        # Make the string faint if it has been completed
        if completed_faint:
            if 'completed' in todo.keys() and todo['completed']:
                todo_str = colors.faint(todo_str)

        # Format the notes as an indented block of text
        if notes:
            wrapper = textwrap.TextWrapper(initial_indent=" "*4,
                                           subsequent_indent=""*4)
            todo_str += "\n" + wrapper.fill(todo['notes'])

        return todo_str

    def _print_change(self, response):
        """Print the stat change expressed in the response."""
        old_exp = self.user['stats']['exp']
        old_hp = self.user['stats']['hp']
        old_gp = self.user['stats']['gp']
        old_lvl = self.user['stats']['lvl']

        new_exp = response['exp']
        new_hp = response['hp']
        new_gp = response['gp']
        new_lvl = response['lvl']

        fragments = []
        if new_lvl > old_lvl:
            fragments.append("LEVEL UP! Say hello to level %d" % new_lvl)
        if new_hp < old_hp:
            fragments.append("%0.1f HP!" % (new_hp - old_hp))
        if new_exp > old_exp:
            fragments.append("%d XP!" % (new_exp - old_exp))
        if new_gp > old_gp:
            fragments.append("%0.1f GP!" % (new_gp - old_gp))
        if 'drop' in response['_tmp'].keys():
            drop_key = response['_tmp']['drop']['key']
            drop_type = response['_tmp']['drop']['type']
            fragments.append("%s %s dropped!" % (drop_key, drop_type))
        print "\n".join(fragments)

    @named('ls')
    def list_todos(self, raw=False, completed=False, list_tasks=False, *tags):
        """
        Print the incomplete tasks.
        """
        if self.user['cached']:
            print 'Cached'

        todos = [t for t in self.user['todos']
                 if 'completed' in t.keys()
                 and (completed or not t['completed'])]

        if tags:
            tag_ids = [self.user['reverse_tag_dict'][t.replace("+", "")]
                       for t in tags]
            todos = [todo for todo in todos if todo.has_tags(tag_ids)]

        # Print the raw json data
        if raw:
            for todo in todos:
                print todo
            return

        if list_tasks:
            for task in self.config['tasks']:
                print self.user['color_dict'][task](task),
            print
            print

        def group_plan_date(todo):
            """
            Extract a pretty date, with all past dates listed 'OVERDUE'.
            """
            plan_date = todo.get_planning_date()
            if plan_date:
                if plan_date.date() < datetime.datetime.now().date():
                    return "OVERDUE"
                else:
                    return pretty.date(plan_date.date())
            else:
                return "Unplanned"

        todos = self.sort_nicely(todos)

        for plan_date, grouped_todos in groupby(todos, group_plan_date):
            print "%s:" % plan_date

            for tag, tagtodos in groupby(grouped_todos,
                                         Todo.get_primary_tag):
                color = self.user['color_dict'][tag]
                for todo in tagtodos:
                    print "\t", color(self.get_todo_str(todo))

    @named('stats')
    def print_stat_bar(self):
        """Print the HP, MP, and XP bars, with some nice coloring."""
        current_hp = int(self.user['stats']['hp'])
        max_hp = int(self.user['stats']['maxHealth'])
        current_mp = int(self.user['stats']['mp'])
        max_mp = int(self.user['stats']['maxMP'])
        current_exp = int(self.user['stats']['exp'])
        level_exp = int(self.user['stats']['toNextLevel'])

        width = 60

        hp_percent = min(float(current_hp) / max_hp, 1.0)
        mp_percent = min(float(current_mp) / max_mp, 1.0)
        xp_percent = min(float(current_exp) / level_exp, 1.0)

        if hp_percent < 0.25:
            hp_color = colors.red
        elif hp_percent < 0.5:
            hp_color = colors.yellow
        else:
            hp_color = colors.green

        hp_bar = ("="*int(hp_percent*width)).ljust(width)
        mp_bar = ("="*int(mp_percent*width)).ljust(width)
        xp_bar = ("="*int(xp_percent*width)).ljust(width)

        print "HP: " + hp_color("[" + hp_bar + "]")
        print "MP: " + colors.blue("[" + mp_bar + "]")
        print "XP: [" + xp_bar + "]"
        if self.user['cached']:
            print "(Cached)"

    @named('add')
    def add_todo(self, todo, due_date="", plan_date="", *tags):
        """Add a todo with optional tags and due date in natural language."""

        new_todo = Todo(text=todo, hcli=self)

        for tag in tags:
            if tag.replace("+", "") in self.user['reverse_tag_dict'].keys():
                tag_id = self.user['reverse_tag_dict'][tag.replace("+", "")]
                new_todo['tags'][tag_id] = True
            else:
                valid_tags = self.user['reverse_tag_dict'].keys()
                raise NoSuchTagException(tag, valid_tags)

        if due_date:
            new_todo.set_due_date(parse_datetime(due_date))

        if plan_date:
            new_todo.set_planning_date(parse_datetime(plan_date))

        new_todo.create()

    @named('detail')
    def print_detailed_string(self, todo_string):
        """Print a detailed description of the described todo."""
        todo = self.match_todo_by_string(todo_string)['todo']
        print self.get_todo_str(todo, date=True, notes=True)

    def match_todo_by_string(self, todo_string):
        """
        Returns the best match from all the user's incomplete tasks.

        The returned object is a dictionary with keys 'todo', 'parent' and
        'check_index'.  'parent' and 'check_index' are only used if the matched
        item is a checklist item, in which case they contain the parent todo
        and the index number of the checklist item.
        """
        todos = [t for t in self.user['todos'] if 'completed' in t.keys()]

        incomplete_todos = []

        # Get all the incomplete todos and checklist items
        for todo in todos:
            if not todo['completed']:
                incomplete_todos.append({'todo': todo,
                                         'parent': None,
                                         'check_index': None})
                if 'checklist' in todo.keys():
                    for j, item in enumerate(todo['checklist']):
                        if not item['completed']:
                            incomplete_todos.append({'todo': item,
                                                     'parent': todo,
                                                     'check_index': j})

        processor = lambda x: x['todo']['text']

        selected_todo = process.extractOne(todo_string,
                                           incomplete_todos,
                                           processor=processor)[0]

        return selected_todo

    @named('addcheck')
    def add_checklist_item(self, check, parent_str):
        """Add a checklist item to a todo matched by natural language."""
        selected_todo = self.match_todo_by_string(parent_str)

        if not selected_todo:
            print "No match found."
        else:
            parent = selected_todo['todo']
            print parent['text']
            if confirm(resp=True):
                if 'checklist' not in parent.keys():
                    parent['checklist'] = []
                parent['checklist'].append({'text': check, 'completed': False})
                parent.update_db()
                print self.get_todo_str(parent, completed_faint=True)

    @named('plan')
    def update_todo_plan_date(self, todo, planned_date):
        """Set the planning date for a task, selected by natural language."""
        selected_todo = self.match_todo_by_string(todo)['todo']
        parsed_date = parse_datetime(planned_date)
        print "Change do-date of '%s' to %s?" % (selected_todo['text'],
                                                 pretty.date(parsed_date))
        if confirm(resp=True):
            selected_todo.set_planning_date(parsed_date, update=True)

    @named('delete')
    def delete_todo(self, *todos):
        """Delete a task."""
        todo_string = " ".join(todos)

        selected_todo = self.match_todo_by_string(todo_string)['todo']
        print "Delete '%s'?" % selected_todo['text']
        if confirm(resp=True):
            selected_todo.delete()

    @named('do')
    def complete_todo(self, *todos):
        """Complete a task selected by natural language with a confirmation."""
        todo_string = " ".join(todos)

        selected_todo = self.match_todo_by_string(todo_string)

        print selected_todo['todo']['text']
        if confirm(resp=True):
            # If it has a parent, it is a checklist item
            parent = selected_todo['parent']
            if parent:

                # Mark the checklist item as complete and repost
                check_index = selected_todo['check_index']
                parent['checklist'][check_index]['completed'] = True
                parent.update_db()
                # Print the remaining sections of the task
                print self.get_todo_str(parent, completed_faint=True)

            # Otherwise it is a normal to-do
            else:
                selected_todo['todo'].complete()
                self._print_change(selected_todo['todo'])

    def sort_nicely(self, todos):
        """Sort the todos by date and task."""
        def sort_primary_tag(todo):
            """
            Return the list index in self.config['tasks'] of the primary tag.
            """
            tag = todo.get_primary_tag()
            if tag:
                return self.config['tasks'].index(tag)
            else:
                return 99999

        def sort_plan_key(todo):
            """
            Extract the planning date for sorting, or a dummy far-future date.
            """
            plan_date = todo.get_planning_date()
            if plan_date:
                return plan_date
            else:
                localtz = get_localzone()
                far_future_date = datetime.datetime(2999, 12, 31)
                return localtz.localize(far_future_date)

        def sort_due_key(todo):
            """
            Extract the due date for sorting, or a dummy far-future date.
            """
            due_date = todo.get_due_date()
            if due_date:
                return due_date
            else:
                localtz = get_localzone()
                far_future_date = datetime.datetime(2999, 12, 31)
                return localtz.localize(far_future_date)

        # Sort tasks by the task tag
        todos.sort(key=sort_due_key)
        todos.sort(key=sort_primary_tag)
        # Sort tasks by the planned do-date
        todos.sort(key=sort_plan_key)

        return todos

    @named('gui')
    def launch_graphical_window(self, *tags):
        """
        Launch a graphical window to edit the tasks, optionally limited to
        those defined by the given tags.
        """
        todos = [t for t in self.user['todos']
                 if 'completed' in t.keys() and not t['completed']]
        if tags:
            tag_ids = [self.user['reverse_tag_dict'][t.replace("+", "")]
                       for t in tags]
            todos = [todo for todo in todos if todo.has_tags(tag_ids)]

        todos = self.sort_nicely(todos)
        habitcli.gui.make_gui(self, todos)
예제 #5
0
parser.add_argument('--skip-todos',
                    dest='skip_todos',
                    action='store_true',
                    help='Skip processing Todos')
parser.add_argument('--skip-dailies',
                    dest='skip_dailies',
                    action='store_true',
                    help='Skip processing Dailies')
parser.add_argument('--skip-habits',
                    dest='skip_habits',
                    action='store_true',
                    help='Skip processing Habits')
args = parser.parse_args()

# Get the tasks for the user in HabitRPG
api = HabitAPI(habit_uuid, habit_api_key)

# if we don't have trello tokens, we need to call OAuth.
if not trello_token or not trello_token_secret:
    access_token = create_oauth_token(None, None, trello_api_key,
                                      trello_api_secret, 'HabiTrello')
    trello_token = access_token['oauth_token']
    trello_token_secret = access_token['oauth_token_secret']

# Create our Trello API client
client = TrelloClient(api_key=trello_api_key,
                      api_secret=trello_api_secret,
                      token=trello_token,
                      token_secret=trello_token_secret)

habit = HabiTrello(api, client)