Esempio n. 1
0
    def _do_lookup(self):
        """
            Searches for and displays one or more log entries.

            Arguments:  none.

            Returns:  True if successful; False if the user aborted.
           -------------------------------------------------------------
        """
        try:
            # Clear the screen.
            wl_resource.print_header(self)
            # If there are no entries in the log, tell the user and then
            #  return.
            if len(self.entries) == 0:
                io_utils.print_status(
                    "Error",
                    "There are no tasks in the log to search!",
                    line_length=self.line_length)
                return False
            # end if
            # Main menu.
            option_list = [
                "By Date/Time", "By Duration", "By Text Search",
                "By RE Pattern"
            ]
            prompt = "Please select a method by which to find entries:"
            search_opt = io_utils.menu(option_list,
                                       keystroke_list="#",
                                       prompt=prompt,
                                       line_length=self.line_length)
            # If the user quits, just return.
            if search_opt == 0:
                return False
            # end if
            # Otherwise call the appropriate lookup function.
            if search_opt == 1:
                results = wl_search.search_by_date(self)
            elif search_opt == 2:
                results = wl_search.search_by_duration(self)
            elif search_opt == 3:
                results = wl_search.search_by_text(self)
            else:  # search_opt == 4
                results = wl_search.search_by_re(self)
            # end if
            # If the search was unsuccessful, just return.
            if not results:
                return True
            # end if
            # Call the appropriate browse function, which will call other
            #  functions if needed.
            if type(results[0]) == datetime.date:
                wl_search.select_date(self, results)
                return True
            else:
                wl_search.select_entry(self, results)
                return True
            # end if
        except Exception as err:
            _z_exc("worklog.py/WorkLog/_do_lookup", err)
Esempio n. 2
0
    def _do_close(self):
        """
            Closes the work log object.

            The object will be overwritten by a new instance, or
             destroyed upon program exit.

            Arguments:  none.

            Returns:  nothing.
           -------------------------------------------------------------
        """
        try:
            if self.changed and io_utils.yes_no(
                    f"{self.filename} has changed.  Do you want to save it?",
                    line_length=self.line_length):
                if not self._do_save():
                    io_utils.print_status("Error",
                                          "Error saving file.",
                                          line_length=self.line_length)
                # end if
            else:
                io_utils.print_status("Status",
                                      f"{self.filename} closed.",
                                      line_length=self.line_length)
            return False
        except Exception as err:
            _z_exc("worklog.py/WorkLog/do_close", err)
Esempio n. 3
0
    def _init_entries(self, entry_list):
        """
            Initializes a set of log entries.

            Takes a list of dictionaries which have been read from a
             data file, uses them to initialize log entry objects and
             adds the log entry objects to the work log object.

            Arguments:
            - entry_list -- the list of OrderedDicts holding the log
               entries.

            Returns:  the number of entries that could not be created.
           -------------------------------------------------------------
        """
        try:
            failed = 0
            # Iterate through the list of entry dictionaries.
            for x, entry in enumerate(entry_list):
                # Try to intialize the entry.
                if not self._init_entry(entry):
                    # If it didn't work, increment number of failed
                    #  entries.
                    failed += 1
                    # Error message.
                    io_utils.print_status("error",
                                          f"Failed to create entry #{x}.",
                                          go=True,
                                          line_length=self.line_length)
                # end if
            # end for
            # Return number of failed entries.
            return failed
        except Exception as err:
            _z_exc("worklog.py/WorkLog/_init_entries", err)
def calc_duration_rel(wl_obj, end_time, start_time):
    """
        Determines a duration based on two times.

        Arguments:
        - wl_obj -- the work log object.
        - end_time -- the ending time.
        - start_time -- the starting time.

        Returns:  a timedelta object representing the difference between
         the two times, if that difference is positive; else None.
       -----------------------------------------------------------------
    """
    try:
        # Subtract the start time from the end time.  (Note that if a
        #  task extends across more than one day, the duration must be
        #  entered directly.)
        if end_time >= start_time:
            # Create timedeltas for subtraction.
            end_td = datetime.timedelta(
              hours=end_time.hour, minutes=end_time.minute)
            start_td = datetime.timedelta(
              hours=start_time.hour, minutes=start_time.minute)
            # Return the resulting timedelta.
            return end_td - start_td
        else:
            msg = "The end time cannot be before the start time."
            io_utils.print_status("Error", msg, line_length=wl_obj.line_length)
            return None
        # end if
    except Exception as err:
        _z_exc("wl_datetime/calc_duration_rel", err)
Esempio n. 5
0
    def _do_open(self):
        """
            Opens a log file and reads data into the log object.

            Arguments:  none.

            Returns:  True if successful; False if there was an error.
           -------------------------------------------------------------
        """
        try:
            # If we don't have a filename, get one.
            if self.filename == "":
                self.filename = io_utils.get_filename_open(filetype="csv")
            # end if
            # If self.filename is still empty, the user chose to go
            #  back, so just return.
            if self.filename == "":
                return False
            # end if
            # Open and read the file.
            entry_dict = io_utils.file_read(self.filename, filetype="csv")
            # If the file didn't open properly, let the user know before
            #  returning.
            if not entry_dict:
                io_utils.print_status("Error",
                                      f"{self.filename} could not be opened.")
                return False
            # end if
            # Pop the first entry in the list of dictionaries that was
            #  read from the file and use it to initialize the work log
            #  object.  Return False if the operation fails.
            if not self._init_worklog(entry_dict.pop(0)):
                return False
            # end if
            # Initialize the log entry objects.
            failed = self._init_entries(entry_dict)
            # Once all the entries have been added, sort the lists.
            self.sorts[TITLE_SORT].sort()
            self.sorts[DATE_SORT].sort()
            # Print final status.
            msg = f"{self.filename} opened.  {len(self.entries)} entries read."
            if failed:
                msg += (
                    f"  {self.total_entries - len(self.entries)} entries " +
                    "not read.  The file may be corrupted.")
            elif self.total_entries != len(self.entries):
                msg += (
                    f"  {self.total_entries} entries expected.  The file may "
                    + "have been edited outside the Work Log program.")
            io_utils.print_status("Status", msg, line_length=self.line_length)
            # Finally reset the total_entries attribute to the actual
            #  number of entries added.
            self.total_entries = len(self.entries)
            return True
        except Exception as err:
            _z_exc("worklog.py/WorkLog/_do_open", err)
Esempio n. 6
0
    def _do_save(self):
        """
            Saves the log file.

            Allows the user to continue working with the log object.

            Arguments:  none.

            Returns:  True if successful; False if there was an error.
           -------------------------------------------------------------
        """
        try:
            self.last_modified = datetime.datetime.now()
            # Initialize a temporary list to hold the log data.
            entry_list = []
            # Create a dummy entry object, which will hold data for the
            #  log object.
            new_entry = logentry.LogEntry()
            fn = new_entry.FIELDNAMES
            new_entry.info = {
                "total_entries": self.total_entries,
                "date_format": self.date_format,
                "time_format": self.time_format,
                "show_help": self.show_help,
                "last_modified": self.last_modified
            }
            # Start the list with the dummy entry object.
            entry_dict = new_entry.to_dict()
            entry_list.append(entry_dict)
            # Now add all of the entries.
            for entry in self.entries:
                # For each entry object, convert to a dictionary.
                entry_dict = entry.to_dict()
                entry_list.append(entry_dict)
            # end for
            # Pass everything to the io_utils function.
            success = io_utils.file_write(self.filename,
                                          "csv",
                                          entry_list,
                                          fieldnames=fn)
            if success:
                # Print status.
                io_utils.print_status("status",
                                      f"{self.filename} saved.",
                                      line_length=self.line_length)
                self.changed = False
                return True
            else:
                return False
            # end if
        except Exception as err:
            _z_exc("worklog.py/WorkLog/_do_save", err)
def set_endian(wl_obj):
    """
        Allows the user to set the preferred date format.

        Arguments:
        - wl_obj -- the work log object.

        Returns:  nothing
       -----------------------------------------------------------------
    """
    try:
        # Endian examples.
        end_list = [
          "July 15, 2010 (7/15/10)", "15 July, 2010 (15/7/10)",
          "2010 July 15 (10/7/15)"]
        # Print explanation.
        if wl_obj.date_format:
            msg = "The current date format is "
            if wl_obj.date_format == "M":
                msg += end_list[0]
            elif wl_obj.date_format == "L":
                msg += end_list[1]
            else:  # wl_obj.date_format == "B"
                msg += end_list[2]
            # end if
        else:
            msg = "The date format has not been set."
        # end if
        io_utils.print_status(
          "Status", msg, go=True, line_length=wl_obj.line_length)
        # If a date format is already set, allow the user to leave it.
        if wl_obj.date_format:
            q = True
        else:
            q = False
        # end if
        # Ask the user to set a date format.
        response = io_utils.menu(
          end_list, keystroke_list="#", confirm=True, quit_=q,
          prompt="Please select your preferred date format:")
        if response == 0:
            return
        elif response == 1:
            wl_obj.date_format = "M"
        elif response == 2:
            wl_obj.date_format = "L"
        else:  # response == 3
            wl_obj.date_format = "B"
        # end if
        return
    except Exception as err:
        _z_exc("wl_datetime/set_endian", err)
Esempio n. 8
0
def _find_entries_re(wl_obj, pattern, fields):
    """
        Finds entries matching a regular expression.

        Arguments:
        - wl_obj -- the work log object.
        - pattern -- the regular expression to search.
        - fields -- the field(s) to search.

        Returns:   a list of matching entries, or an empty list if no
         matches were found.
       -----------------------------------------------------------------
    """
    try:
        return_list = []
        # If the user has specified a case-insensitive search--
        # Make sure the pattern is valid.
        if re.search(r", re\.I", pattern):
            # Slice off the flag.1
            pattern = pattern[:-6]
            try:
                pattern = re.compile(pattern, re.I)
            except Exception:
                io_utils.print_status("Error",
                                      f"{pattern} failed to compile.",
                                      go=True)
                return []
            # end try
        else:
            try:
                pattern = re.compile(pattern)
            except Exception:
                io_utils.print_status("Error",
                                      f"{pattern} failed to compile.",
                                      go=True)
                return []
            # end try
        # end if
        for entry in wl_obj.entries:
            if (((fields in [TITLE, BOTH]) and
                 (re.search(pattern, entry.title)))
                    or ((fields in [NOTES, BOTH]) and
                        (re.search(pattern, entry.notes)))):
                return_list.append(entry)
            # end if
        # end for
        return return_list
    except Exception as err:
        _z_exc("wl_search.py/find_entries_re", err)
def set_screen_width(wl_obj):
    """
        Resets the width of the screen in characters.

        Arguments:
        - wl_obj -- the work log object.

        Modifies:  -- the work log object's line_length attribute.

        Returns -- nothing.
       -----------------------------------------------------------------
    """
    try:
        # Display current width.
        io_utils.print_status(
            "Status",
            f"The current width of the screen is {wl_obj.line_length} " +
            "characters.",
            go=True,
            line_length=wl_obj.line_length)
        msg = ("Please enter your desired screen width (must be at least 40 " +
               "characters), or press [ENTER] to go back:")
        # Loop until user enters a valid length or quits.
        while True:
            # Get the new line length (must be >= 40).
            response = io_utils.get_input(msg,
                                          typ="int",
                                          line_length=wl_obj.line_length,
                                          must_respond=False)
            # If the user quits, leave line_length unchanged.
            if not response:
                return
            elif (response < 40):
                io_utils.print_status("Error",
                                      "You did not enter a valid number.",
                                      line_length=wl_obj.line_length)
                if io_utils.yes_no("Try again?",
                                   line_length=wl_obj.line_length):
                    continue
                else:
                    return
                # end if
            else:
                wl_obj.line_length = response
                return
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_resource.py/set_screen_width", err)
Esempio n. 10
0
    def _add_entry(self, entry, recurring_entries):
        """
            Adds one entry.

            Arguments:
            - entry -- the entry to add.
            - recurring_entries -- the list of recurring entries, if
               there are any.

            Returns:  True if the user wants to add another task, else
             False.
           -------------------------------------------------------------
        """
        try:
            # Set the datetime attribute.
            entry.datetime = wl_add.add_datetime(entry)
            # Add the entry to the work log.
            self._do_sort(entry)
            self.entries.append(entry)
            # Now add the recurring entries, if any.
            self._add_recurring_entries(entry, recurring_entries)
            # And update.
            self.total_entries = len(self.entries)
            # Note the log object has changed.
            self.changed = True
            # Build status message.
            if recurring_entries:
                msg = f"{len(recurring_entries) + 1} entries added."
            else:
                msg = "Entry added."
            # end if
            # Update the total entries attribute.
            self.total_entries = len(self.entries)
            # Print that the entry was added.
            io_utils.print_status("Status", msg, line_length=self.line_length)
            # Finally, ask the user if they want to add another
            #  entry.  (Whether they are done will be the opposite
            #  of their answer.)
            return io_utils.yes_no("Do you want to add another task?",
                                   line_length=self.line_length)
        except Exception as err:
            _z_exc("worklog.py/WorkLog/do_add", err)
Esempio n. 11
0
    def _init_worklog(self, dict_entry):
        """
            Initializes the work log object from data from a file.

            The first log entry read from a data file does not contain
             information about a task; rather, its info attribute holds
             data on the work log itself.

            Arguments:
            - dict_entry -- the list representation of the first log
               entry as read from a data file.

            Returns:  True if data was successfully transferred to the
             work log object; else False.
           -------------------------------------------------------------
        """
        try:
            # Create a dummy log entry object to hold the data.
            new_entry = logentry.LogEntry()
            # Convert the log object information (stored in the info
            #  field of the first entry obuject) into a dictionary.
            new_entry.from_dict(dict_entry)
            # Set the log object's attributes from the dictionary.
            self.total_entries = new_entry.info["total_entries"]
            self.show_help = new_entry.info["show_help"]
            self.last_modified = new_entry.info["last_modified"]
            # But only set the format attributes if the user has not
            #  already set them.
            if not self.date_format:
                self.date_format = new_entry.info["date_format"]
            # end if
            if not self.time_format:
                self.time_format = new_entry.info["time_format"]
            # end if
            return True
        except Exception as err:
            io_utils.print_status("Error",
                                  f"Error reading log info:  {err}",
                                  line_length=self.line_length)
            return False
def set_time_format(wl_obj):
    """
        Allows the user to set the preferred time format.

        Arguments:
        - wl_obj -- the work log object.

        Returns:  nothing
       -----------------------------------------------------------------
    """
    try:
        # Build message.
        if not wl_obj.time_format:
            msg = "The time format has not been set."
        elif wl_obj.time_format == 12:
            msg = "The current time format is 12-hour (AM/PM)."
        else:  # wl_obj.time_format == 24
            msg = "The current time format is 24-hour (Military Time)."
        # end if
        # Print status.
        io_utils.print_status(
          "Status", msg, go=True, line_length=wl_obj.line_length)
        # Display menu and get response.
        response = io_utils.menu(
          ["12-hour Clock (AM/PM)", "24-hour Clock (Military Time)"],
          keystroke_list="#")
        # If the user chose to quit, just return without changing
        #  anything.
        if response == 0:
            return
        # Else set the time_format attribute to the user's choice.
        elif response == 1:
            wl_obj.time_format = 12
        else:  # response == 2
            wl_obj.time_format = 24
        # end if
        return
    except Exception as err:
        _z_exc("wl_datetime/set_time_format", err)
Esempio n. 13
0
    def _do_create(self):
        """
            Creates a new file to store data from the log object.

            If the user names an existing file, offers to open that
             file.

            Arguments:  none.

            Returns:  True if successful; False if there was an error or
             the user aborts the process.
           -------------------------------------------------------------
        """
        try:
            # Call the io_utils method to get a filename and create the file.
            self.filename, go_open = io_utils.file_create(filetype="csv")
            # If no filename was returned, the attempt was unsuccessful.
            #  Return False
            if self.filename == "":
                return False
            # If the open flag was set, call _do_open for the file.
            elif go_open:
                success = self._do_open()
                if success:
                    return True
                else:
                    return False
                # end if
            else:
                self.total_entries = 0
                # Print status.
                io_utils.print_status("Status",
                                      f"{self.filename} created.",
                                      line_length=self.line_length)
                return True
            # end if
        except Exception as err:
            _z_exc("worklog.py/WorkLog/do_create", err)
Esempio n. 14
0
def search_by_re(wl_obj):
    """
        Finds work log entries based on a regular expression.

        Arguments:
        - wl_obj -- the work log object.

        Returns:  a list of matching entries, if any are found; else an
         empty list.
       -----------------------------------------------------------------
    """
    try:
        # Run everything inside a loop in case the user wants to start
        #  over.
        while True:
            wl_obj.help.print_help(wl_obj.show_help,
                                   "RE Search",
                                   "_sh_re",
                                   line_length=wl_obj.line_length)
            # Get a regex string.
            re_string = io_utils.get_input(
                "Enter a regular expression (without quotation marks):")
            # If the user chose to toggle help, do that.
            if re.match(r"-h", re_string, re.I):
                wl_obj.show_help = not (wl_obj.show_help)
                continue
            # end if
            # If the user didn't enter anything...
            if not re_string:
                io_utils.print_status("Error",
                                      "You did not enter anything.",
                                      go=True,
                                      line_length=wl_obj.line_length)
                if not io_utils.yes_no("Try again?",
                                       line_length=wl_obj.line_length):
                    return []
                else:
                    continue
                # end if
            # end if
            # Option menu.
            search_mode = io_utils.menu(
                ["Title", "Notes", "Title and Notes"],
                keystroke_list="#",
                prompt="Which field(s) would you like to search?",
                line_length=wl_obj.line_length)
            # User quits, just exit.
            if search_mode == QUIT:
                return None
            # end if
            # Get the results of the find function.
            return_list = _find_entries_re(wl_obj, re_string, search_mode)
            # If nothing matched, tell the user.
            if len(return_list) == 0:
                io_utils.print_status("Status",
                                      "No matches found.",
                                      line_length=wl_obj.line_length)
            # end if
            return return_list
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_search.py/search_by_re", err)
Esempio n. 15
0
def search_by_text(wl_obj):
    """
        Finds work log entries based on a text string.

        Arguments:
        - wl_obj -- the work log object.

        Returns:  a list of matching entries, if any are found; else an
         empty list.
       -----------------------------------------------------------------
    """
    try:
        # Run everything inside a loop in case the user wants to start
        #  over.
        while True:
            # Option menu.
            search_fields = io_utils.menu(
                ["Title", "Notes", "Title and Notes"],
                keystroke_list="#",
                prompt="Which field(s) would you like to search?",
                line_length=wl_obj.line_length)
            # User quits, just exit.
            if search_fields == QUIT:
                return None
            # end if
            # Print the instructions.
            wl_obj.help.print_help(wl_obj.show_help,
                                   "Search Terms",
                                   "_sh_text",
                                   line_length=wl_obj.line_length)
            # Get a text string to search for.
            search_string = io_utils.get_input("Enter text to search for:")
            # If the user chose to toggle help, do that.
            if re.match(r"-h", search_string, re.I):
                wl_obj.show_help = not (wl_obj.show_help)
                continue
            # If the user didn't enter anything, either loop back or
            #  return.
            if not search_string:
                print("You did not enter anything.")
                if not io_utils.yes_no("Try again?",
                                       line_length=wl_obj.line_length):
                    return []
                else:
                    continue
                # end if
            # end if
            # If the search string includes wildcard characters, ask the
            #  user how to treat them.
            if re.search(r"[?*]", search_string):
                search_mode = io_utils.menu(
                    [
                        "Literal search (? and * will only match themselves)",
                        "Wildcard search (? and * will match any character)"
                    ],
                    prompt="Your search string contains one or more wildcard "
                    + "characters (? or *).  What kind of search would you " +
                    "like to perform?",
                    keystroke_list="#",
                    line_length=wl_obj.line_length)
                # If the user chooses to go back, start over.
                if search_mode == QUIT:
                    continue
                # end if
            # Set the default to literal search.
            else:
                search_mode = 1
            # Return the results of the find function.
            return_list = _find_entries_text(wl_obj, search_string,
                                             search_fields, search_mode)
            # If nothing matched, tell the user.
            if len(return_list) == 0:
                io_utils.print_status("Status",
                                      "No matches found.",
                                      line_length=wl_obj.line_length)
            # end if
            return return_list
        # end while
    except Exception as err:
        _z_exc("wl_search.py/search_by_text", err)
Esempio n. 16
0
def select_date(wl_obj, date_list):
    """
        Show a list (or part of a list) of dates that contain log
         entries.

        Arguments:
        - wl_obj -- the work log object.
        - date_list -- the list of dates.

        Returns:  nothing.
       -----------------------------------------------------------------
    """
    try:
        start = 0
        # Clear the screen.
        wl_resource.print_header(wl_obj)
        # Print the number of dates found.
        if len(date_list) == 1:
            msg = "Found 1 date with entries."
        else:
            msg = f"Found {len(date_list)} dates with entries."
        # end if
        io_utils.print_status("Status", msg, line_length=wl_obj.line_length)
        # Run in a loop until the user is done viewing/editing, then
        #  return.
        while True:
            # Call list browse.
            response = wl_viewedit.browse_list(wl_obj, date_list, start=start)
            # If the response isn't an integer, it's a command to move
            #  forward or back.  Move the start position, but only if it
            #  doesn't go beyond the bounds of the list.
            if type(response) == str:
                if (response.upper() == "P") and (start - 9 >= 0):
                    start -= 9
                elif ((response.upper() == "N")
                      and (start + 9 < len(date_list))):
                    start += 9
                # end if
                # Clear the screen before looping back.
                wl_resource.print_header(wl_obj)
                continue
            # end if
            # If the user quits, return.
            if response == QUIT:
                return
            # end if
            # If it's a non-zero integer, the user chose a date.  Find
            #  all entries on that date.
            start_date = datetime.datetime.combine(
                date_list[start + response - 1], datetime.time())
            end_date = start_date.replace(hour=23, minute=59)
            entry_list = _find_entries_date(wl_obj, start_date, end_date)
            # Let the user browse/edit those entries. (If the user
            #  edited the list, it will come back changed.)
            entry_list = wl_viewedit.browse_entries(wl_obj, entry_list)
            # If the user deleted all of the entries for a date, then
            #  that date is no longer valid and must be removed.
            if len(entry_list) == 0:
                del date_list[start + response - 1]
            # end if
            # Clear the screen before looping back.
            wl_resource.print_header(wl_obj)
        # end while
    except Exception as err:
        _z_exc("wl_search.py/select_date", err)
Esempio n. 17
0
def browse_list(wl_obj, browse_list, start=0):
    """
        Presents a list to the user and asks him/her to select one.

        Arguments:
        - wl_obj -- the work log object.
        - browse_list -- the list to present.
        - start -- the first item to display (default 0).

        Returns:  an integer representing an item to display, 0 if the
         user quits, or a string representing a navigational command.
       -----------------------------------------------------------------
    """
    try:
        # If the list is empty, automatically return as if the user had
        #  quit.
        if len(browse_list) == 0:
            return 0
        # end if
        # Clear the screen and print the matches to be listed.
        wl_resource.print_header(wl_obj)
        if len(browse_list) == 1:
            msg = "Showing match 1 of 1"
        else:
            msg = f"Showing matches {start + 1}-"
            if start + 9 <= len(browse_list):
                msg += f"{start + 9} of {len(browse_list)}"
            else:
                msg += f"{len(browse_list)} of {len(browse_list)}"
            # end if
        # end if
        io_utils.print_status("Status",
                              msg,
                              go=True,
                              line_length=wl_obj.line_length)
        # Build the options list.
        options = []
        # For dates, just append the date (as a string).
        if type(browse_list[0]) == datetime.date:
            for ndx in range(start, start + 9):
                # Stop if at the end of the list.
                if ndx == len(browse_list):
                    break
                # end if
                options.append(str(browse_list[ndx]))
            # end for
        # For entries, append the title, date and time (as strings).
        else:
            for ndx in range(start, start + 9):
                # Stop if at the end of the list.
                if ndx == len(browse_list):
                    break
                # end if
                # Gather the fields.
                title = browse_list[ndx].title
                date = wl_resource.format_string(wl_obj,
                                                 browse_list[ndx].date,
                                                 short=True)
                time = wl_resource.format_string(wl_obj, browse_list[ndx].time)
                # If the time had a leading zero stripped, replace it
                #  with a space.
                if time[1] == ":":
                    time = f" {time}"
                # end if
                max_title_len = wl_obj.line_length - 23
                # If the title is too long to fit in the column,
                #  truncate it.
                if len(title) > max_title_len:
                    title = title[:max_title_len - 1] + "… "
                else:
                    title += " "
                # end if
                rj = wl_obj.line_length - len(title) - 4
                option = title + f" {date} {time}".rjust(rj, ".")
                # Append the assembled string.
                options.append(option)
            # end for
        # end if
        if start > 0:
            prev = True
        else:
            prev = False
        # end if
        if start + 9 < len(browse_list):
            nxt = True
        else:
            nxt = False
        # end if
        # Now display the menu and return the user's choice.
        return io_utils.menu(options,
                             prompt="Select an entry to view:",
                             keystroke_list="#",
                             nav=True,
                             prev=prev,
                             nxt=nxt,
                             line_length=wl_obj.line_length)
    except Exception as err:
        _z_exc("wl_viewedit.py/browse_list", err)
Esempio n. 18
0
def select_entry(wl_obj, entry_list):
    """
        Allows the user to either browse a set of entries, or choose
         from a list.

        Arguments:
        - wl_obj -- the work log object.
        - entry_list -- the list of entries.

        Returns:  nothing.
       -----------------------------------------------------------------
    """
    try:
        # Clear the screen.
        wl_resource.print_header(wl_obj)
        # Print the number of dates found.
        if len(entry_list) == 1:
            msg = "Found 1 task."
        else:
            msg = f"Found {len(entry_list)} tasks."
        # end if
        io_utils.print_status("Status", msg, line_length=wl_obj.line_length)
        # Ask the user to browse or pick from list.
        response = io_utils.menu(
            [
                "Browse all matching tasks",
                "Choose from a list of matching tasks"
            ],
            keystroke_list="#",
            quit_=True,
            prompt="Please select how you want to see the matching tasks:",
            line_length=wl_obj.line_length)
        # If the user chooses to go back, just return.
        if response == QUIT:
            return
        # end if
        # If the user chooses to browse the matches, call the browse
        #  function, then return.
        if response == 1:
            entry_list = wl_viewedit.browse_entries(wl_obj, entry_list)
            return
        # If the user chooses to see a list, display it here.
        else:
            start = 0
            # Run in a loop until the user is done viewing/editing, then
            #  return.
            while True:
                # Call list browse.
                response = wl_viewedit.browse_list(wl_obj,
                                                   entry_list,
                                                   start=start)
                # If the response isn't an integer, it's a command to
                #  move forward or back.  Move the start position, but
                #  only if it doesn't go beyond the bounds of the list.
                if type(response) == str:
                    if (response.lower() == "p") and (start - 9 >= 0):
                        start -= 9
                    elif ((response.lower() == "n")
                          and (start + 9 < len(entry_list))):
                        start += 9
                    # end if
                    # Clear the screen before looping back.
                    wl_resource.print_header(wl_obj)
                    continue
                # end if
                # If the user quits, return.
                if response == QUIT:
                    return
                # end if
                # If it's a non-zero integer, the user chose an entry.
                #  Get the index number of the chosen entry in the list.
                ndx = start + response - 1
                # Browse entries, starting with the selected one.
                wl_viewedit.browse_entries(wl_obj, entry_list, ndx=ndx)
                # Clear the screen before looping back.
                wl_resource.print_header(wl_obj)
            # end while
        # end if
    except Exception as err:
        _z_exc("wl_search.py/select_entry", err)
Esempio n. 19
0
def _get_time(wl_obj, t_type, date, start=None):
    """
        Gets a time from the user.

        Arguments:
        - wl_obj -- the work log object.
        - t_type -- which date is being modified (single, start, end)
        - date -- the date object to which to append the time.

        Keyword Arguments:
        - start -- the starting date/time (for error checking).

        Returns:  1) an integer representing the return state: -1 if the
         user chose to go back, 0 if the user chose to abort completely,
         1 if the user successfully entered a date; 2) if the user
         enters a valid time, a datetime object with the original date
         combined with the time; otherwise, the original date object.
       -----------------------------------------------------------------
    """
    try:
        # Prompt.
        if t_type == "single":
            t_type = "conduct"
        # end if
        # Build prompt.
        prompt = (
            f"If you would like to {t_type} your search at a specific time" +
            f" during {wl_resource.format_string(wl_obj, date, short=True)}, "
            +
            "enter the time now, or just press [ENTER] to include the entire "
            + "day:")
        # Loop until a valid response is obtained.
        while True:
            # Get a time.
            response = io_utils.get_input(prompt, must_respond=False)
            # If the user didn't enter anything, just return the date.
            #  (Since the caller will set the time to 11:59pm, it will
            #  never be earlier than the starting time.)
            if not response:
                return 1, date
            # end if
            # Return None if the user wants to quit.
            if response.lower() == "-b":
                return -1, None
            # end if
            # Otherwise parse the input for a time.
            time = wl_datetime.parse_time_input(wl_obj, response)
            msg = f"{response} could not be interpreted as a valid time."
            # If a time was found, combine it with the date and return,
            #  unless--
            if time:
                # If this is the start date, or if this is the end date
                #  and it is different than the start date, then there
                #  is no need to check the time; just return the
                #  combined datetime object.
                if (start is None) or (date != start.date()):
                    return 1, datetime.datetime.combine(date, time)
                # But if the start and end dates are the same, the end
                #   time cannot precede the start time.
                else:
                    if time >= start.time:
                        return 1, datetime.datetime.combine(date, time)
                    else:
                        msg = (
                            f"The end time cannot be earlier than {start.time}"
                        )
                    # end if
                # end if
            # end if
            # Print an error message, ask the user if they want to try
            #  again, and if they say no, return None (else loop back).
            io_utils.print_status("Error", msg, line_length=wl_obj.line_length)
            retry = io_utils.yes_no("Try again?",
                                    quit_=True,
                                    line_length=wl_obj.line_length)
            if retry in ["-b", False]:
                return -1, None
            elif retry == "-q":
                return 0, None
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_search.py/_get_time", err)
Esempio n. 20
0
    def _do_add(self):
        """
            Adds one or more entry objects to the log object.

            Arguments:  none.

            Returns:  nothing.
           -------------------------------------------------------------
        """
        try:
            # List of object attributes to set (easier than directly
            #  iterating through the attributes).
            attr_list = [
                "title", "date", "time", "duration", "notes", "recurring"
            ]
            # Initialize list.
            recurring_entries = []
            # Like all of the _do methods, the entire method is inside a
            #  loop, allowing the user to add as many entries as they
            #  want.
            not_done = True
            while not_done:
                cancel = False
                # First, create a new log entry object.
                new_entry = logentry.LogEntry()
                # Go through the list and set values for each attribute.
                #  Because the user can choose to back up, we don't use
                #  a for loop to iterate through the attributes.
                attrib = 0
                # Loop runs until the last attribute is set.
                while attrib < len(attr_list):
                    # Get the attribute name.
                    attr = attr_list[attrib]
                    # The title attribute is the only one which does not
                    #  allow the user to go back (because it's the first
                    #  attribute set).
                    if attr == "title":
                        go = wl_add.add_title(self, new_entry)
                    # end if
                    # "date" must be convertible to a date object.
                    elif attr == "date":
                        go = wl_add.add_date(self, new_entry)
                        # If the user goes back a step, clear the
                        #  previous attribute.
                        if go == -1:
                            new_entry.title = None
                        # end if
                    # "time" must be convertible to a time object.
                    elif attr == "time":
                        go = wl_add.add_time(self, new_entry)
                        # If the user goes back a step...
                        if go == -1:
                            new_entry.date = None
                        # end if
                    # "duration" must be convertible to a timedelta
                    #  object.
                    elif attr == "duration":
                        go = wl_add.add_duration(self, new_entry)
                        # If the user goes back a step...
                        if go == -1:
                            new_entry.time = None
                        # end if
                    # "notes" can be any string, including empty.
                    elif attr == "notes":
                        go = wl_add.add_note(self, new_entry)
                        # If the user goes back a step...
                        if go == -1:
                            new_entry.duration = None
                        # end if
                    # "recurring" will either be True of False, and if
                    #  True will also return a list of recurrance date
                    #  objects.
                    elif attr == "recurring":
                        recurring_entries = []
                        go, recurring_entries = wl_add.add_recurrance(
                            self, new_entry)
                        # If the user goes back a step...
                        if go == -1:
                            new_entry.notes = None
                        # end if
                    # end if
                    if go == 0:
                        # Print that the entry was cancelled.
                        io_utils.print_status(
                            "Status",
                            "Addition of this task has been cancelled.",
                            line_length=self.line_length)
                        # If the user aborts and does not want to start
                        #  another task, return immediately.
                        if io_utils.yes_no("Do you want to add another task?",
                                           line_length=self.line_length):
                            cancel = True
                            break
                        else:
                            return
                        # end if
                    else:
                        attrib += go
                    # end if
                # end while
                if not cancel:
                    not_done = self._add_entry(new_entry, recurring_entries)
                # end if
            # end while
            return
        except Exception as err:
            _z_exc("worklog.py/WorkLog/do_add", err)
Esempio n. 21
0
def _get_date(wl_obj, d_type, start=None):
    """
        Gets a date from the user.

        Arguments:
        - wl_obj -- the work log object.
        - d_type -- which date to prompt for (single, start, end).

        Keyword Arguments:
        - start -- starting date (for error checking).

        Returns:  1) an integer representing the return state: -1 if the
         user chose to go back, 0 if the user chose to abort completely,
         1 if the user successfully entered a date; 2) a date object, or
         None if the user chose to quit or go back.
       -----------------------------------------------------------------
    """
    try:
        # Set the prompt.
        if d_type == "single":
            prompt = "Enter the date on which to search:"
        elif d_type == "start":
            prompt = "Enter the starting date for your search:"
        else:
            prompt = "Enter the ending date for your search:"
        # end if
        # Loop until a valid date is obtained or the user aborts.
        while True:
            # Get the date.
            response = io_utils.get_input(prompt)
            # Error prompt.
            msg = "You did nol enter anything."
            # If the user entered something, parse it.
            if response:
                date = wl_datetime.parse_date_input(wl_obj, response)
                if date:
                    if d_type != "end":
                        return 1, date
                    else:
                        if date >= start.date():
                            return 1, date
                        elif date and date < start.date():
                            # Alternate error message.
                            msg = ("The end date cannot be ealier than " +
                                   f"{start.date}")
                            io_utils.print_status(
                                "Error", msg, line_length=wl_obj.line_length)
                        # end if
                    # end if
                # end if
            retry = io_utils.yes_no("Try again?",
                                    quit_=True,
                                    line_length=wl_obj.line_length)
            # If the user decides not to try again, return.  (Not trying
            #  again here is treated as equivalent to going back.)
            if retry in ["-b", False]:
                return -1, None
            elif retry == "-q":
                return 0, None
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_search.py/_get_date", err)
Esempio n. 22
0
def search_by_duration(wl_obj):
    """
        Finds work log entries based on a duration or duration range.

        Arguments:
        - wl_obj -- the work log object.

        Returns:  a list of matching entries, if any are found; else an
         empty list.
       -----------------------------------------------------------------
    """
    try:
        # Run everything inside a loop in case the user wants to start
        #  over.
        while True:
            # Option menu.
            search_mode = io_utils.menu(
                ["A specific duration", "A range of durations"],
                keystroke_list="#",
                prompt="What would you like to search?",
                line_length=wl_obj.line_length)
            # User quits, just exit.
            if search_mode == QUIT:
                return None
            # end if
            # Both single duration and range require one duration, so
            #  get one now.
            if search_mode == DURATION:
                d_type = "single"
            else:
                d_type = "minimum"
            # end if
            go, min_duration = _get_duration(wl_obj, d_type)
            # If the user wants to go back, loop back to the beginning.
            if go == GO_BACK:
                continue
            # If the user aborts, return.
            elif go == QUIT:
                return []
            # end if
            # If the user wants to search a range, get a maximum
            #  duration.
            if search_mode == DURATION_RANGE:
                d_type = "maximum"
                go, max_duration = _get_duration(wl_obj, d_type, min_duration)
                # Again, loop back or return if the user chooses to go
                #  back or abort.
                if go == GO_BACK:
                    continue
                elif go == QUIT:
                    return []
                # end if
            # If the search is for a single duration, just set the
            #  maximum duration to equal the minimum.
            else:
                max_duration = min_duration
            # end if
            # Now find all entries that match.
            r_list = _find_entries_duration(wl_obj, min_duration, max_duration)
            # If no matches, tell the user.
            if len(r_list) == 0:
                io_utils.print_status("Status",
                                      "No matches found.",
                                      line_length=wl_obj.line_length)
            # end if
            return r_list
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_search.py/search_by_duration", err)
def parse_date_phrase(wl_obj, string):
    """
        Function that checks a string to see if it contains a valid word
         date.

        Arguments:
        - wl_obj -- the work log object.
        - string -- the user input.

        Returns:  a date object if successful, or None.
       -----------------------------------------------------------------
    """
    try:
        # First break the string into a list of words.
        word_list = re.findall(r"\b\w+\b", string)
        # If the user included the words "the" or "of", discard it.
        s = []
        for word in word_list:
            if word.lower() != "the" and word.lower() != "of":
                s.append(word)
            # end if
        # end for
        word_list = s
        # Check according to the length of the string.
        if len(word_list) == 1:
            # There are three valid one-word responses, plus the days of
            #  the week.
            if word_list[0].lower() == "today":
                return datetime.date.today()
            elif word_list[0].lower() == "yesterday":
                return datetime.date.today() - datetime.timedelta(days=1)
            elif word_list[0].lower() == "tomorrow":
                return datetime.date.today() + datetime.timedelta(days=1)
            else:
                valid = wl_resource.weekday(word_list[0])
                # If the string is a day of the week, return the date
                #  object that corresponds to that day of the current
                #  week.  Else return None.
                if valid:
                    return _create_date_from_weekday(valid, 0)
                else:
                    io_utils.print_status(
                      "Error",
                      f"{string} could not be interpreted as a valid date.",
                      line_length=wl_obj.line_length)
                    return None
                # end if
            # end if
        elif len(word_list) == 2:
            # A two-word response can be a calendar date without the
            #  year, or a phrase.  Check for a calendar date first.
            good = parse_date_calendar(wl_obj, word_list)
            if good:
                return good
            # end if
            # Two-word date phrases all start with "this", "next", or
            #  "last", followed by a day of the week.
            if word_list[0].lower() == "this":
                offset = 0
            elif word_list[0].lower() == "last":
                offset = -1
            elif word_list[0].lower() == "next":
                offset = 1
            else:
                io_utils.print_status(
                  "Error",
                  f"{string} could not be interpreted as a valid date.",
                  line_length=wl_obj.line_length)
                return None
            # end if
            valid = wl_resource.weekday(word_list[1])
            if valid:
                return _create_date_from_weekday(valid, offset)
            else:
                io_utils.print_status(
                  "Error",
                  f"{string} could not be interpreted as a valid date.",
                  line_length=wl_obj.line_length)
                return None
            # end if
        elif len(word_list) == 3:
            # Three word calendar dates are a full month, day and year
            #  (but not necessarily in that order).  Check for them.
            good = parse_date_calendar(wl_obj, word_list)
            if good:
                return good
            # end if
            # There are two set three-word date phrases.  It's simpler
            #  to search for them in the original string.
            if re.search(r"day after tomorrow", string, re.I):
                return datetime.date.today() + datetime.timedelta(days=2)
            elif re.search(r"day before yesterday", string, re.I):
                return datetime.date.today() - datetime.timedelta(days=2)
            # end if
            # Other three-word date phrases are a day of the week
            #  followed by either "before last" or "after next".
            valid = wl_resource.weekday(word_list[0])
            if valid:
                if (
                  word_list[1].lower() == "before" and
                  word_list[2].lower() == "last"):
                    return _create_date_from_weekday(valid, -2)
                elif (
                  word_list[1].lower() == "after" and
                  word_list[2].lower() == "next"):
                    return _create_date_from_weekday(valid, 2)
                else:
                    io_utils.print_status(
                      "Error",
                      f"{string} could not be interpreted as a valid date.",
                      line_length=wl_obj.line_length)
                    return None
                # end if
            else:
                io_utils.print_status(
                  "Error",
                  f"{string} could not be interpreted as a valid date.",
                  line_length=wl_obj.line_length)
                return None
            # end if
        else:
            io_utils.print_status(
              "Error", f"{string} could not be interpreted as a valid date.",
              line_length=wl_obj.line_length)
            return None
        # end if
    except Exception as err:
        _z_exc("wl_datetime/parse_date_phrase", err)
def parse_date_numeric(wl_obj, string):
    """
        Validates a numeric date in the current format.

        Allows the date format to be changed if the numeric combination
         is a valid date in a different format.

        Arguments:
        - wl_obj -- the work log object.
        - string -- the user input.

        Returns:  a date object if successful, or None.
       -----------------------------------------------------------------
    """
    try:
        # Format constants.
        BIG_FORMAT = "%Y %B %d"
        MID_FORMAT = "%B %d, %Y"
        LIT_FORMAT = "%d %B %Y"
        # First separate the elements of the date and convert them to
        #  integers.
        numbers = re.findall(r"\d+", string)
        for x, number in enumerate(numbers):
            numbers[x] = int(number)
        # end for
        # If there are only two elements, the year was omitted and
        #  defaults to the current year.  Where the year element is
        #  inserted depends on the format.
        if len(numbers) == 2:
            if wl_obj.date_format == "B":
                numbers.insert(0, datetime.date.today().year)
            else:
                numbers.append(datetime.date.today().year)
            # end if
        # end if
        # Now try to create a date object with the selected format.
        entry_date = _create_date(numbers, wl_obj.date_format)
        # If it's valid, return it.
        if entry_date:
            return entry_date
        # If it's not valid, try the other formats.
        valid_formats, valid_dates = _check_other_endians(
          numbers, wl_obj.date_format)
        # If neither of the other formats is valid, just return None.
        if valid_formats == []:
            io_utils.print_status(
              "Error", f"{string} could not be interpreted as a valid date.",
              line_length=wl_obj.line_length)
            return None
        # end if
        # Otherwise, inform the user of the successful format(s) and ask
        #  if he/she wants to change the selected format to match, or
        #  re-enter the date.
        msg = io_utils.print_block(
          f"The date {string} is not valid in the currently selected " +
          "format, but is valid in a different format.  You can choose to " +
          "change the date format or re-enter the date for this task.",
          ret_str=True)
        io_utils.print_status("Warning", msg, line_length=wl_obj.line_length)
        option_list = []
        for x, ndn in enumerate(valid_formats):
            if ndn == "B":
                option_list.append(valid_dates[x].strftime(BIG_FORMAT))
            elif ndn == "M":
                option_list.append(valid_dates[x].strftime(MID_FORMAT))
            else:
                option_list.append(valid_dates[x].strftime(LIT_FORMAT))
                # end if
            # end if
        # end for
        option_list.append("Re-enter the date.")
        # Give the user the option to change the format or try again.
        response = io_utils.menu(option_list, keystroke_list="#", quit_=False)
        # If the user chose to try again, return None.
        if response == len(option_list):
            input("Press [ENTER] to continue.")
            return None
        # Otherwise, reset the date format and return the date object.
        else:
            wl_obj.date_format = valid_formats[response - 1]
            return valid_dates[response - 1]
        # end if
    except Exception as err:
        _z_exc("wl_datetime/parse_date_numeric", err)
Esempio n. 25
0
def search_by_date(wl_obj):
    """
        Finds work log entries based on a date/time or date/time range.

        Arguments:
        - wl_obj -- the work log object.

        Returns:  for searches by date/time, a list of matching entries
         from the date-sorted index, if any are found, an empty list if
         no matches are found, or None if the user aborts; for a view of
         all dates, a list of unique date objects.
       -----------------------------------------------------------------
    """
    try:
        # Run everything inside a loop in case the user wants to start
        #  over.
        while True:
            # Option menu.
            search_mode = io_utils.menu([
                "A single date/time", "A range of dates/times",
                "View all dates"
            ],
                                        keystroke_list="#",
                                        prompt="How would you like to search?",
                                        line_length=wl_obj.line_length)
            # User quits, just exit.
            if search_mode == QUIT:
                return None
            # end if
            if search_mode in [DATE, DATE_RANGE]:
                # Both date/time searches require at least one date.
                if search_mode == DATE:
                    d_type = "single"
                else:
                    d_type = "start"
                # end if
                # Get the first (and for a single date/time search,
                #  only) date/time.
                go, date = _get_date(wl_obj, d_type)
                # If the user decided to go back here, loop back.
                if go == GO_BACK:
                    continue
                # end if
                # If the user decided to abort, confirm and then return.
                if go == QUIT and io_utils.confirm("abort the search"):
                    return None
                # end if
                # Now get a time, if the user wants one.
                go, date = _get_time(wl_obj, d_type, date)
                # If the user decided to go back here, loop back.
                if go == GO_BACK:
                    continue
                # end if
                # If the user decided to abort, confirm and then return.
                if go == QUIT and io_utils.confirm("abort the search"):
                    return None
                # end if
                # If the user declined to enter a time, set the time of
                #  the datetime object to midnight.
                if type(date) == datetime.date:
                    start_date = datetime.datetime.combine(
                        date, datetime.time())
                # Otherwise the datetime object is already set.
                else:
                    start_date = date
                # end if
                # If the user wants to search a single date/time...
                if search_mode == DATE:
                    # If the user wants to search a specific date AND
                    #  time, set the end of the range to equal the
                    #  start.
                    if type(date) == datetime.datetime:
                        end_date = start_date
                    # If the user wants to search a particular date but
                    #  not a particular time, set the end of the range
                    #  to 11:59pm on the same date as the start.
                    else:
                        end_date = start_date.replace(hour=23, minute=59)
                    # end if
                # If searching a range, get the end date from the user.
                else:
                    go, date = _get_date(wl_obj, "end", start=start_date)
                    # If the user decided to go back here, loop back.
                    if go == GO_BACK:
                        continue
                    # end if
                    # If the user decided to abort, confirm and then
                    #  return.
                    if go == QUIT and io_utils.confirm("abort the search"):
                        return None
                    # end if
                    # Now get a time, if the user wants one.
                    go, date = _get_time(wl_obj, "end", date)
                    # If the user decided to go back here, loop back.
                    if go == GO_BACK:
                        continue
                    # end if
                    # If the user decided to abort, confirm and then
                    #  return.
                    if go == QUIT and io_utils.confirm("abort the search"):
                        return None
                    # end if
                    # If the user declined to enter a time, set the time
                    #  of the datetime object to the end of the day.
                    if type(date) == datetime.date:
                        end_date = datetime.datetime.combine(
                            date, datetime.time.max)
                    # Otherwise just set the datetime object.
                    else:
                        end_date = date
                    # end if
                # end if
                # Return the entries to match the search terms.
                return_list = _find_entries_date(wl_obj, start_date, end_date)
                # If nothing was found, tell the user.
                if len(return_list) == 0:
                    io_utils.print_status("Status",
                                          "No matches found.",
                                          line_length=wl_obj.line_length)
                # end if
                return return_list
            # To view all dates, create a list of unique dates.
            else:
                return_list = []
                # Iterate through the sorted list.
                for entry in wl_obj.sorts[DATE_SORT]:
                    # Convert datetime to date and append all unique
                    #  dates.
                    if entry[SORT_KEY].date() not in return_list:
                        return_list.append(entry[SORT_KEY].date())
                    # end if
                # end for
                return return_list
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_search.py/search_by_date", err)
Esempio n. 26
0
def browse_entries(wl_obj, entry_list, ndx=0):
    """
        Allows the user to browse entries.

        Arguments:
        - wl_obj -- the work log object.
        - entry_list -- the list of entries to browse.

        Keyword Arguments:
        - ndx -- the index of the entry to initially display.

        Returns:  the entry list as modified.
       -----------------------------------------------------------------
    """
    try:
        # Loop.
        while True:
            # If the list is empty, automatically return.
            if len(entry_list) == 0:
                return entry_list
            # end if
            # Clear the screen.
            wl_resource.print_header(wl_obj)
            # Print status message.
            msg = f"Displaying task {(ndx + 1)} of {len(entry_list)}"
            io_utils.print_status("Status",
                                  msg,
                                  go=True,
                                  line_length=wl_obj.line_length)
            # Display the current entry.
            display_entry(wl_obj, entry_list[ndx])
            # Beneath the entry, display a menu.
            options = ["Edit", "Delete"]
            key_list = ["E", "D"]
            if ndx > 0:
                options.append("Previous")
                key_list.append("P")
            # end if
            if ndx < (len(entry_list) - 1):
                options.append("Next")
                key_list.append("N")
            # end if
            options.append("Back")
            key_list.append("B")
            response = io_utils.menu(options,
                                     keystroke=True,
                                     keystroke_list=key_list,
                                     lines=False,
                                     quit_=False,
                                     prompt=" ",
                                     line_length=wl_obj.line_length)
            # Convert the integer response back into the correct option.
            response = key_list[response - 1]
            # Take the appropriate action.
            if response == "B":
                # If the user wants to go back, return the entry list
                #  (as it may have been modified).
                return entry_list
            elif response == "P":
                # If the user wants to back up one entry, decrement the
                #  index and loop.
                ndx -= 1
                continue
            elif response == "N":
                # If the user wants to go to the next entry, increment
                #  the index and loop.
                ndx += 1
                continue
            elif response == "E":
                # If the user wants to edit the current entry, call the
                #  edit function and then loop.
                _edit_entry(wl_obj, entry_list[ndx], ndx, len(entry_list))
                continue
            else:
                # If the user wants to delete the entry, confirm, and if
                #  confirmed, call the delete function. and then loop.
                if io_utils.confirm("delete this entry"):
                    _delete_entry(wl_obj, entry_list, ndx)
                    # Delete the entry from the entry list.
                    # If there are no more entries, return the empty
                    #  list.
                    if len(entry_list) == 0:
                        return entry_list
                    # end if
                    # If the index is past the end of the list, reset
                    #  it.
                    if ndx <= len(entry_list):
                        ndx = len(entry_list) - 1
                    # end if
                # end if
                continue
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_viewedit.py/browse_entries", err)
Esempio n. 27
0
def _edit_entry(wl_obj, edit_entry, ndx, total):
    """
        Allows the user to edit a log entry.

        Arguments:
        - wl_obj -- the work log object.
        - edit_entry -- the entry to edit.
        - ndx -- the number of the entry being edited.
        - total -- the total number of entries being displayed.

        Returns:  nothing.
       -----------------------------------------------------------------
    """
    try:
        # This function mainly piggybacks on the add functions to alter
        #  the attributes of the log entry object.  All values are
        #  preserved via the info attribute of a working copy until
        #  saved by the user.
        changed = False
        # Create a working copy of the entry to be edited.
        new_entry = _copy_entry(edit_entry)
        # Store the original values in the info attribute.
        new_entry.info["title"] = new_entry.title
        new_entry.info["date"] = new_entry.date
        new_entry.info["time"] = new_entry.time
        new_entry.info["duration"] = new_entry.duration
        new_entry.info["notes"] = new_entry.notes
        new_entry.info["ndx"] = f"task {ndx + 1} of {total}"
        resort = False
        # Loop.
        while True:
            # Clear the screen and display program header.
            wl_resource.print_header(wl_obj)
            # Print status message.
            io_utils.print_status("Status",
                                  f"Editing {new_entry.info['ndx']}…",
                                  go=True,
                                  line_length=wl_obj.line_length)
            # Display the entry.
            display_entry(wl_obj, new_entry, edit=True)
            # Print instructions.
            wl_obj.help.print_help(wl_obj.show_help,
                                   "Editing",
                                   "_eh_edit",
                                   line_length=wl_obj.line_length)
            options = ["Title", "Date", "Time", "Duration", "Notes"]
            # User selects the field to edit.
            response = io_utils.menu(
                options,
                keystroke_list="#",
                prompt="Please select a field to edit.  When you are finished,"
                + " go back to save or discard your changes:",
                line_length=wl_obj.line_length,
                help_toggle=True)
            # If the user chose to toggle help, do that and loop back.
            if str(response).lower() == "-h":
                wl_obj.show_help = not wl_obj.show_help
                continue
            # end if
            # If the user chose to quit...
            if response == QUIT:
                # If the entry has been edited, prompt to save changes.
                if (changed and io_utils.yes_no(
                        "Do you want to save your changes?",
                        line_length=wl_obj.line_length)):
                    # Recalculate the datetime attribute, in case either
                    #  the date or time changed.
                    new_entry.datetime = wl_add.add_datetime(new_entry)
                    # Save the changed values to the original log entry
                    #  object.
                    _update_entry(wl_obj, new_entry, resort)
                    # Set the flag that the log object has changed.
                    wl_obj.changed = True
                # end if
                return
            # Edit title.
            elif response == TITLE:
                ch = wl_add.add_title(wl_obj, new_entry, edit=True)
                # If the title was edited, turn on the resort flag.
                if ch:
                    resort = True
                # end if
            # Edit date.
            elif response == DATE:
                ch = wl_add.add_date(wl_obj, new_entry, edit=True)
                # If the date was edited, turn on the resort flag.
                if ch:
                    resort = True
                # end if
            # Edit time.
            elif response == TIME:
                ch = wl_add.add_time(wl_obj, new_entry, edit=True)
                # If the time was edited, turn on the resort flag.
                if ch:
                    resort = True
                # end if
            # Edit duration.
            elif response == DURATION:
                ch = wl_add.add_duration(wl_obj, new_entry, edit=True)
            # Edit notes.
            else:
                ch = wl_add.add_note(wl_obj, new_entry, edit=True)
            # end if
            # If something was edited, turn on the changed flag.
            if ch:
                changed = True
            # end if
        # end while
    except Exception as err:
        _z_exc("wl_viewedit.py/_edit_entry", err)
def calc_duration_abs(wl_obj, string):
    """
        Parses a string and determines the duration it describes.

        Arguments:
        - wl_obj -- the work log object.
        - string -- the string to parse.

        Returns:  a timedelta object if a valid duration is found; else
         None.
       -----------------------------------------------------------------
    """
    try:
        # If the string is "max", return the maximum timedelta.
        if string.lower() == "max":
            return datetime.timedelta.max
        # end if
        # Otherwise the string must contain one or more number/word
        #  pairs:  a number (or number phrase) and a unit of measure.
        #  These should (but need not be) in descending order.
        #
        # First, convert any numbers.
        raw_list = wl_resource.numbers(string)
        # Then cull unneeded elements from the list.
        word_list = []
        for word in raw_list:
            if not ((word is None) or (str(word).lower() == "and")):
                word_list.append(word)
            # end if
        # end for
        # Now move through the string from left to right.
        minutes = None
        hours = None
        days = None
        ndx = 0
        while ndx < len(word_list):
            amt = None
            # An inner loop adds numbers together.  If there aren't any,
            #  amt will remain None.  (The function must differentiate
            #  here between None and 0.)
            while (
              (ndx < len(word_list) and type(word_list[ndx]) in [int, float])):
                if amt is None:
                    amt = word_list[ndx]
                else:
                    amt += word_list[ndx]
                # end if
                ndx += 1
            # end while
            # If there was no amount, check for an unspaced number/unit
            #  combination.
            if amt is None:
                if re.match(r"\d+m[inutes]?", word_list[ndx]):
                    minutes = int(re.match(r"\d+", word_list[ndx]).group())
                    ndx += 1
                    continue
                if re.match(r"\d+h[ours]?", word_list[ndx]):
                    hours = int(re.match(r"\d+", word_list[ndx]).group())
                    ndx += 1
                    continue
                if re.match(r"\d+d[ays]?", word_list[ndx]):
                    days = int(re.match(r"\d+", word_list[ndx]).group())
                    ndx += 1
                    continue
                # Otherwise just move to the next word.
                ndx += 1
                continue
            # end if
            # Determine the units (if not valid, just move to the next
            #  word).  But don't do this if the previous word was the
            #  last.
            if ndx < len(word_list):
                if re.match(r"m\w*", word_list[ndx]):
                    minutes = amt
                elif re.match(r"h\w*", word_list[ndx]):
                    hours = amt
                elif re.match(r"d\w*?", word_list[ndx]):
                    days = amt
                # end if
                ndx += 1
            # end if
        # end while
        # Having gone through the list, see if any times were found.  If
        #  not, return None.
        if (minutes is None) and (hours is None) and (days is None):
            return None
        # end if
        # Change non-present units to zeroes.
        if not minutes:
            minutes = 0
        # end if
        if not hours:
            hours = 0
        # end if
        if not days:
            days = 0
        # end if
        # Create a timedelta created from the times found.  Note that
        #  zero minutes is a valid duration, but negative durations are
        #  not valid.
        td = datetime.timedelta(days=days, hours=hours, minutes=minutes)
        # Check if the timedelta is negative.  If it is, print an error
        #  and return None.
        if td < datetime.timedelta():
            io_utils.print_status(
              "Error", "The duration cannot be negative.",
              line_length=wl_obj.line_length)
            return None
        # end if
        return td
    except Exception as err:
        _z_exc("wl_datetime/calc_duration_abs", err)
Esempio n. 29
0
def _get_duration(wl_obj, d_type, start=None):
    """
        Gets a duration from the user.

        Arguments:
        - wl_obj -- the work log object.
        - d_type -- the type of duration to get (single, min, max)

        Keyword Arguments:
        - start -- the min duration (for error checking).

        Returns:  1) an integer representing the return state: -1 if the
         user chose to go back, 0 if the user chose to abort completely,
         1 if the user successfully entered a duration; 2) if the user
         enters a valid duration, a timedelta object representing that
         duration; else None.
       -----------------------------------------------------------------
    """
    try:
        # Build prompt.
        if d_type == "single":
            p = ""
        else:
            p = f"{d_type} "
        # end if
        prompt = f"Enter the {p}duration to search for"
        if start:
            prompt += (
                ".  To search for ALL durations of " +
                f"{wl_resource.format_string(wl_obj, start)} or greater, " +
                "enter [max]:")
        else:
            prompt += ":"
        # Loop until a valid response is obtained.
        while True:
            # Get a duration.
            response = io_utils.get_input(prompt)
            if response == "-b":
                return -1, None
            elif response == "-q":
                return 0, None
            # end if
            # Parse the input for a duration.
            duration = wl_datetime.calc_duration_abs(wl_obj, response)
            # If no valid duration was entered, print error and prompt
            #  to try again.
            if duration is None:
                msg = (
                    f"{response} could not be interpreted as a valid duration."
                )
                io_utils.print_status("Error",
                                      msg,
                                      line_length=wl_obj.line_length)
                # Take a "no" as equivalent of the user aborting.
                if not io_utils.yes_no("Try again?",
                                       line_length=wl_obj.line_length):
                    return 0, None
                # If "yes", loop back.
                else:
                    continue
                # end if
            # end if
            return 1, duration
        # end while
    except Exception as err:
        _z_exc("wl_search.py/_get_duration", err)