diff --git a/.silojourn.ini b/.silojourn.ini index f29293c..2ec89e3 100644 --- a/.silojourn.ini +++ b/.silojourn.ini @@ -1,5 +1,6 @@ [filesystem] journal=/home/bagira/journal +task_tracker=/home/bagira/journal/task_tracker [tools] editor=/usr/bin/nano diff --git a/silojourn.py b/silojourn.py index adca19c..fdeb081 100755 --- a/silojourn.py +++ b/silojourn.py @@ -7,6 +7,8 @@ from dialog import Dialog from datetime import datetime import subprocess import re +import glob +import hashlib solution_description="Journaling system for daily needs." epilog="Designed and written by Chris Punches.\n SILO GROUP LLC, 2019. ALL RIGHTS RESERVED." @@ -22,6 +24,7 @@ class Config(): self.config.read( self.get_conf_location() ) self.journal_path = self.config['filesystem']['journal'] self.editor = self.config['tools']['editor'] + self.tracker = self.config['filesystem']['task_tracker'] def get_journal_path(self): return self.journal_path @@ -29,24 +32,60 @@ class Config(): def get_editor(self): return self.editor + def get_tracker(self): + return self.tracker + + +class Task(): + def __init__(self, filename, text, position): + self.filename = filename + self.topic = Journaler.topic_from_filename( self.filename ) + self.creation_date = Journaler.date_from_filename(self.filename) + self.position = position + self.text = text + self.hash = self._this_hash() + + def _this_hash(self): + return hashlib.md5( + "{}:{}:{}".format( + self.filename, + self.position, + self.text + ).encode('utf-8') + ).hexdigest()[:8] + class Journaler(): def __init__(self, config): self.config = config self.d = Dialog( dialog="dialog", autowidgetsize=True) + self.d.set_background_title("SILO JOURNALING SYSTEM") + @staticmethod + def topic_from_filename( filename ): + try: + topic = re.search( "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_(.+?)$", filename ).group(1) + except AttributeError: + topic = "NO_TOPIC" + return topic + + @staticmethod + def date_from_filename( filename ): + try: + date = re.search( "([0-9][0-9][0-9][-9]-[0-9][0-9]-[0-9][0-9])_.+?$", filename ).group(1) + except AttributeError: + date = "NO_TOPIC" + return date # generate the filepath for a specific entry def _get_entry_filename(self, topic, date): # TODO add sanity checks return "{0}/{1}_{2}".format( self.config.get_journal_path(), date, topic ) - # open a specific entry for a supplied topic and date def _open_entry(self, topic, date): subprocess.run( [ self.config.get_editor(), self._get_entry_filename( topic, date ) ] ) - # gemerate the current date in a standard format def _get_current_date(self): return datetime.today().strftime('%Y-%m-%d') @@ -56,19 +95,17 @@ class Journaler(): entry_list = os.listdir( self.config.get_journal_path() ) return entry_list - # get all topics for all dates def _get_all_topics(self): topics = list() for item in self._get_all_entries(): try: - found = re.search("[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_(.+?)$", item ).group(1) + found = re.search("[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_(.+?)$", item ).group(1) topics.append( found ) except AttributeError: pass return sorted( list( dict.fromkeys( topics ) ) ) - # get all topics for all dates def _get_all_dates(self): dates = list() @@ -80,7 +117,6 @@ class Journaler(): pass return sorted( list( dict.fromkeys( dates ) ) ) - # get all topics associated with a date def _get_topics(self, date): topics = list() @@ -93,7 +129,6 @@ class Journaler(): pass return sorted( list( dict.fromkeys( topics ) ) ) - # get all dates associated with a topic def _get_dates(self, topic): dates = list() @@ -106,7 +141,6 @@ class Journaler(): pass return sorted( list( dict.fromkeys( dates ) ) ) - # browse topics for a specific date def do_topic_browse_by_date(self, date): print( "topic browse by date: {}".format( date ) ) @@ -139,7 +173,6 @@ class Journaler(): else: self.do_browse_topics() - # browse dates for a specific topic def do_date_browse_by_topic(self, topic): dates = self._get_dates(topic) @@ -167,40 +200,119 @@ class Journaler(): else: self.do_browse_topics() + # return a list of tasks + def _get_todos(self): + for file in sorted( glob.glob(f"{self.config.get_journal_path()}**/*", recursive=False) ): + with open( file ) as _file: + for position, line in enumerate(_file.readlines()): + m = re.search("^TODO(.+?)$", line.rstrip().lstrip() ) + if m: + print("match found") + task = Task( + file, + m.group(1).rstrip().lstrip(), + position + ) + if not self._check_task_completion( task ): + yield task + + # check if task is completed + def _check_task_completion( self, task ): + # check if self.config.tracker file is there + # create it if not exists + # open self.config.tracker file + # iterate through lines + # find line that begins with hash + with open( self.config.tracker, 'w+' ) as tracker_file: + for line in tracker_file: + m = re.search( "^{0}\t.*$".format( task.hash ), line ) + print(line) + if m: + return True + return False + + def _mark_task_complete(self, task): + print( "marking task {} complete".format( task.hash ) ) + with open( self.config.tracker, 'a+' ) as tracker_file: + tracker_file.write( + "{0}\t{1}:{2}\t{3}\t{4}\n".format( + task.hash, + task.creation_date, + self._get_current_date(), + task.topic, + task.text + ) + ) def do_browse_todo_entries(self): - print( "not yet implemented" ) + choices = list() + for todo in self._get_todos(): + print(todo.text) + choices.append( + ( todo.hash, "({0}) {1}".format(todo.topic, todo.text) , False ) + ) + + code, hashes_marked_complete = self.d.checklist( + text="TODO Entries", + choices=choices, + backtitle="Select tasks to complete.", + width=self.d.maxsize()[1], + cancel_label="Exit", + ok_label="Complete" + ) + + if code == self.d.OK: + # it only returns the hashes + # we want to create a new list of tasks for tasks that should be completed + tasks_to_complete = list() + + # iterate through all tasks so nothing gets missed + for todo in self._get_todos(): + # in each iteration we want to iterate through the hashes marked complete and add tasks + # with a matching hash to that new list + for item in hashes_marked_complete: + if todo.hash == item: + tasks_to_complete.append(todo) + + for selection in tasks_to_complete: + self._mark_task_complete( selection ) + + def create_new_topic_ui(self): + input_code, input_topic = self.d.inputbox("Creating a new topic for today: {}".format(self._get_current_date()), + width=self.d.maxsize()[1]) + if input_code == self.d.OK: + self.do_new_topic(input_topic) + self.do_browse_topics() + else: + self.do_browse_topics() # browse topics for all dates def do_browse_topics(self): topics = self._get_all_topics() - menu_choices = list() - for i in range(len(topics)): - menu_choices.append( ( topics[i], "{} total entries.".format( len( self._get_dates( topics[i] ) ) ) ) ) + if len(topics) > 0: + menu_choices = list() + for i in range(len(topics)): + menu_choices.append( ( topics[i], "{} total entries.".format( len( self._get_dates( topics[i] ) ) ) ) ) + code, topic = self.d.menu( + "Select a topic whose dated entries you want to browse:", + choices=menu_choices, + width=self.d.maxsize()[1], + cancel_label="Exit", + ok_label="Select", + extra_button=True, + extra_label="New topic for today..." + ) - code, topic = self.d.menu( - "Select a topic whose dated entries you want to browse:", - choices=menu_choices, - width=self.d.maxsize()[1], - cancel_label="Back", - ok_label="Select", - extra_button=True, - extra_label="New topic for today..." - ) - - if code == self.d.OK: - self.do_date_browse_by_topic(topic) - elif code == self.d.EXTRA: - input_code, input_topic = self.d.inputbox( "Creating a new topic for today: {}".format( self._get_current_date() ), width=self.d.maxsize()[1] ) - if input_code == self.d.OK: - self.do_new_topic( input_topic ) - self.do_browse_topics() - else: - self.do_browse_topics() - + if code == self.d.OK: + self.do_date_browse_by_topic(topic) + elif code == self.d.EXTRA: + self.create_new_topic_ui() + else: + # no topics yet, create one + self.create_new_topic_ui() # browse dates for all topics def do_list_dates(self): @@ -219,7 +331,6 @@ class Journaler(): if code == self.d.OK: self.do_topic_browse_by_date( date ) - def do_new_topic(self, topic): self._open_entry( topic, self._get_current_date() )