Beispiel #1
0
    def validate(self, input):
        if self.help_generator is not None and input is self.HELP_CHAR:
            Printer().newline().print()
            self.help_generator().print()
            return False

        if input not in self.categories:
            Printer().newline().add(f"Invalid {self.category_type} :: ",
                                    Highlight(input)).print()
            return False

        return True
Beispiel #2
0
 def print(self):
     self.simple_summary.print()
     p = Printer()
     p.newline()
     p.add_header("Total Time Logged Per Project")
     p.newline()
     graph = (
         SummaryGraph(self.project_time_map)
         .set_max_width(utils.max_width(self.config.get_max_graph_width()))
         .set_key_transform_function(self.config.get_project_name)
         .sort_graph(reverse=True)
         .generate()
     )
     for row in graph:
         p.add_nowrap(row)
     p.newline()
     p.add_header("Total Time Logged Per Area Per Project")
     p.newline()
     multigraph = (
         SummaryMultiGraph(self.project_area_time_map, 30)
         .set_header_transform_function(self.config.get_project_name)
         .generate()
     )
     for row in multigraph:
         p.add_nowrap(row)
     p.print()
Beispiel #3
0
def projects_help(projects, project_description_func):
    p = Printer()
    for project in projects:
        p.add("[{}]".format(project))
        p.add(project_description_func(project))
        p.newline()
    return p
Beispiel #4
0
def print_rendered_string(area_code, area, date_to_display, object_name,
                          duration, purpose):
    p = Printer()
    e = get_rendered_string(area_code, area, date_to_display, object_name,
                            duration, purpose)
    p.add(*e)
    p.print()
Beispiel #5
0
 def summary(self, target):
     summary = self.db.get_summary(target=target, detailed=True)
     if target is None:
         Printer().add_mode_header("Summary").print()
     else:
         if target not in self.config.get_areas():
             p = Printer()
             p.add("ERROR: No area found for: [", Highlight(target), "]")
             p.newline()
             p.add("Valid areas are: ")
             for area in self.config.get_areas():
                 p.add(f"[{area}]")
             p.print()
             return
         Printer().add_mode_header(f"Summary [{target}]").print()
     summary.print()
Beispiel #6
0
 def __init__(self):
     parser = argparse.ArgumentParser(
         add_help=False,
         description=(
             "Færeld :: A time tracking utility for effort optimisation ",
             "and visualisation",
         ),
         usage="\n" + str(help.cli_help()),
     )
     parser.add_argument("-c",
                         "--config",
                         default="~/.andgeloman/faereld/config.yml")
     parser.add_argument("mode", help="Mode to run")
     parser.add_argument("target", nargs="?", help="Target for the mode")
     args = parser.parse_args()
     if not hasattr(self, args.mode.lower()):
         Printer().add(*parser.description).newline().add(
             Highlight(args.mode),
             " is an unrecognised mode.").newline().print()
         self.help(None)
         exit(1)
     else:
         print("\x1b[2J\x1b[H", end="")
         config = Configuration(args.config)
         controller = Controller(config)
         getattr(self, args.mode.lower())(controller, args.target)
Beispiel #7
0
 def __load_configuration(self, configuration_path):
     """ Load the configuration from the supplied path. If the file does
     not exist at this path, create it from the default config settings.
     """
     expanded_path = path.expanduser(configuration_path)
     if not path.exists(path.dirname(expanded_path)):
         makedirs(path.dirname(expanded_path))
     if not path.exists(expanded_path):
         self.__write_config_file(expanded_path, self.DEFAULT_CONFIG)
         p = Printer()
         p.add_header("Wilcume on Færeld")
         p.newline()
         p.add("This looks like it is your first time running Færeld.")
         p.newline()
         p.add("A config file has been created at ",
               Highlight(expanded_path), ".")
         p.newline()
         p.add(
             "This contains some default values to get you started, ",
             "but you should take a look to add your own areas and projects.",
         )
         p.newline()
         p.add(
             "For more information, please see the configuration documentation ",
             "at https://faereld.readthedocs.io/en/latest/usage/configuration.html",
         )
         p.newline()
         p.print()
     else:
         self.__load_configuration_values(expanded_path)
Beispiel #8
0
    def input_non_project_object(self, area):
        use_last_objects = self.config.get_area(area).get(
            "use_last_objects", False)
        last_objects = []
        if use_last_objects:
            last_objects = self.db.get_last_objects(
                area, self.config.get_num_last_objects())
            last_objects_dict = {
                "[{0}]".format(x): k[0]
                for x, k in enumerate(last_objects)
            }
            # Transform last objects into [x]: object tags
            if len(last_objects) > 0:
                p = Printer()
                last_objects_dict = {
                    "[{0}]".format(x): k
                    for x, k in enumerate(last_objects)
                }
                p.add("Last {0} {1} Objects :: ".format(
                    len(last_objects), area))
                p.newline()
                for k, v in sorted(last_objects_dict.items()):
                    p.add("{0} {1}".format(k, v))
                p.newline()
                p.print()
        object = prompt("Object :: ",
                        completer=WordCompleter(last_objects),
                        vi_mode=True)
        if use_last_objects:
            if object in last_objects_dict:
                return last_objects_dict[object]

        return object
Beispiel #9
0
    def print(self):
        self.simple_summary.print()
        p = Printer()
        p.newline()
        p.add_header(f"Summary For {self.area_name}")
        p.newline()

        minimum = None
        maximum = None
        average = datetime.timedelta()

        for entry in self.area_time_map[self.area]:
            if minimum is None or minimum > entry:
                minimum = entry
            if maximum is None or maximum < entry:
                maximum = entry
            average += entry

        average = average / len(self.area_time_map[self.area])

        p.add(
            f"The lowest recorded entry for {self.area_name} is ",
            f"{utils.format_time_delta(minimum)}.",
        )
        p.add(
            f"The highest recorded entry for {self.area_name} is ",
            f"{utils.format_time_delta(maximum)}.",
        )
        p.add(
            f"The average entry for {self.area_name} is ",
            f"{utils.format_time_delta(average)}.",
        )

        p.newline()
        p.add_header(f"Entry Time Distribution for {self.area_name}")
        p.newline()
        box = (BoxPlot(self.area_time_map).set_max_width(
            utils.max_width(self.config.get_max_graph_width())
        ).set_exclude_list(
            self.config.get_exclude_from_entry_time_distribution()).generate())
        for row in box:
            p.add_nowrap(row)
        p.newline()
        p.add_header("LAST {0} ENTRIES".format(len(self.last_entries)))
        p.newline()
        for entry in self.last_entries:
            if self.config.get_use_wending():
                start_date = entry.start.strftime("{daeg} {month} {gere}")
            else:
                start_date = entry.start.strftime("%d %b %Y")
            p.add(*utils.get_rendered_string(
                entry.area,
                self.config.get_area(entry.area),
                start_date,
                self.config.get_object_name(entry.area, entry.obj),
                utils.time_diff(entry.start, entry.end),
                entry.purpose,
            ))
        p.print()
Beispiel #10
0
    def gather_inputs(self):
        Printer().newline().add_header("Area").newline().add("[ {0} ]".format(
            " // ".join(self.config.get_areas().keys()))).newline().print()
        area = self.input_area()
        if area in self.config.get_project_areas():
            Printer().newline().add_header("Project").newline(
            ).add("[ {0} ]".format(" // ".join(
                sorted(self.config.get_projects().keys())))).newline().print()
            object = self.input_project_object()
        else:
            Printer().newline().add_header("Object").newline().print()
            object = self.input_non_project_object(area)
        Printer().newline().add_header("Duration").newline().print()
        from_date, to_date = self.input_duration()
        time_diff = utils.time_diff(from_date, to_date)
        print()
        if self.config.get_use_wending:
            date_display = from_date.strftime("{daeg} {month} {gere}")
        else:
            date_display = from_date.strftime("%d %b %Y")
        if area in self.config.get_project_areas():
            Printer().newline().add_header("Purpose").newline().print()
            purpose = self.input_purpose()
        else:
            purpose = None
        utils.print_rendered_string(
            area,
            self.config.get_areas()[area],
            date_display,
            self.config.get_object_name(area, object),
            time_diff,
            purpose,
        )
        confirmation = prompt("Is this correct? (y/n) :: ", vi_mode=True)
        if confirmation.lower() == "y":
            return {
                "area": area,
                "object": object,
                "start": from_date,
                "end": to_date,
                "purpose": purpose,
            }

        else:
            return None
Beispiel #11
0
 def insert(self):
     Printer().add_mode_header("Insert").print()
     summary = self.db.get_summary()
     summary.print()
     entry_inputs = None
     while entry_inputs is None:
         entry_inputs = self.gather_inputs()
     self.db.create_entry(**entry_inputs)
     print("Færeld entry added")
Beispiel #12
0
    def insert(self):
        Printer().add_mode_header("Insert").print()
        summary = self.db.get_summary()
        summary.print()
        entry = None
        while entry is None:
            entry = Entry(self.config)

            Printer().newline().add_header("Area").newline().add(
                f"[ {' // '.join(self.config.areas.keys())} ]").newline(
                ).print()
            entry.area = self.input_area()
            if entry.area in self.config.project_areas:
                Printer().newline().add_header("Project").newline().add(
                    f"[ {' // '.join(sorted(self.config.projects.keys()))} ]"
                ).newline().print()
                entry.object = self.input_project_object()
            else:
                Printer().newline().add_header("Object").newline().print()
                entry.object = self.input_non_project_object(entry.area)

            duration_input = DurationInput(name="Duration",
                                           use_wending=self.config.use_wending)
            entry.from_date, entry.to_date = duration_input.gather()

            if entry.area in self.config.project_areas:
                purpose = Input(name="Purpose")
                entry.purpose = purpose.gather()
            else:
                entry.purpose = None
            print()
            print(str(entry))
            confirmation = prompt("Is this correct? (Y/n) :: ", vi_mode=True)
            if confirmation in ["Y", "y", ""]:
                break
            else:
                entry = None
        self.db.create_entry(entry)
        print("Færeld entry added")
Beispiel #13
0
 def print(self):
     self.simple_summary.print()
     p = Printer()
     p.newline()
     p.add_header("Total Time Logged Per Area")
     p.newline()
     # Sum all the timedeltas for the summary graph
     summary_time_map = dict(
         map(
             lambda x: (x[0], sum(x[1], datetime.timedelta())),
             self.area_time_map.items(),
         )
     )
     graph = (
         SummaryGraph(summary_time_map)
         .set_max_width(utils.max_width(self.config.max_graph_width))
         .set_exclude_list(self.config.exclude_from_total_time)
         .generate()
     )
     for row in graph:
         p.add_nowrap(row)
     p.newline()
     p.add_header("ENTRY TIME DISTRIBUTION PER AREA")
     p.newline()
     box = (
         BoxPlot(self.area_time_map)
         .set_max_width(utils.max_width(self.config.max_graph_width))
         .set_exclude_list(self.config.exclude_from_entry_time_distribution)
         .generate()
     )
     for row in box:
         p.add_nowrap(row)
     p.newline()
     p.add_header("LAST {0} ENTRIES".format(len(self.last_entries)))
     p.newline()
     for entry in self.last_entries:
         if self.config.use_wending:
             start_date = entry.start.strftime("{daeg} {month} {gere}")
         else:
             start_date = entry.start.strftime("%d %b %Y")
         p.add(
             *utils.get_rendered_string(
                 entry.area,
                 self.config.get_area(entry.area),
                 start_date,
                 self.config.get_object_name(entry.area, entry.obj),
                 utils.time_diff(entry.start, entry.end),
                 entry.purpose,
             )
         )
     p.print()
Beispiel #14
0
 def gather(self):
     Printer().newline().add_header(self.name).newline().print()
     from_date, to_date = None, None
     while from_date is None and to_date is None:
         while from_date is None:
             from_input = prompt("From :: ", vi_mode=True)
             from_date = self._convert_input_date(from_input)
         while to_date is None:
             to_input = prompt("To :: ", vi_mode=True)
             to_date = self._convert_input_date(to_input)
         if from_date >= to_date:
             print(
                 f"Invalid Duration :: {utils.time_diff(from_date, to_date)}"
             )
             from_date, to_date = None, None
     return from_date, to_date
Beispiel #15
0
def cli_help():
    p = Printer()
    p.add_mode_header("Help")
    p.newline()
    p.add(" ━━━━┓┏━━━━\n━━━━ ┃┃ ━━━━\n ━━━━┫┣━━━━\n   ╺ ┃┃ ╸")
    p.newline()
    p.add(
        "Færeld (an Old English word meaning journey or ",
        "progession) is a time tracking utility built for ",
        "optimising and visualising the time spent on ",
        "projects and self-improvement.",
    )
    p.newline()
    p.add("faereld [-c CONFIG] MODE [TARGET]")
    p.newline()
    p.add("Færeld has 5 modes:")
    p.add("INSERT       Insert a time tracking record into Færeld")
    p.add("SUMMARY      Produce a summary of time spent on all areas")
    p.add("PROJECTS     Produce a summary of time spent on project specific areas")
    p.add(
        "PRODUCTIVITY Produce a summary of productivity aggregated over ",
        "hours and days of the week",
    )
    p.add("HELP         Print the help")
    p.newline()
    p.add_header("Configuration")
    p.newline()
    p.add(
        "Færeld's configuration file is stored, by ",
        "default, in ~/.andgeloman/faereld/config.yml. ",
        "In this file you can define your own areas and ",
        "projects, as well as tweak some settings for ",
        "things like the data path. For a full ",
        "explanation of these settings, please consult ",
        "https://faereld.readthedocs.io/en/latest/usage/configuration.html.",
    )
    p.newline()
    p.add("To use a different configuration file, use the -c flag ::")
    p.add("    faereld -c /path/to/config.yml MODE")
    p.newline()
    p.add("Source :: https://github.com/autophagy/faereld")
    p.add("Issue Tracker :: https://github.com/autophagy/faereld/issues")
    p.add("Documentation :: https://faereld.readthedocs.io/en/latest/")
    return p
Beispiel #16
0
    def print(self):
        self.simple_summary.print()
        p = Printer()

        def day_num_to_string(day_num):
            dates = {
                0: "MON",
                1: "TUE",
                2: "WED",
                3: "THU",
                4: "FRI",
                5: "SAT",
                6: "SUN",
            }
            return dates[day_num]

        def zero_pad_hour(hour):
            if len(str(hour)) == 1:
                return "0{}".format(str(hour))

            return str(hour)

        p.newline()
        p.add_header("Total Time Logged Per Day")
        p.newline()
        graph = (SummaryGraph(self.day_delta_map).set_max_width(
            utils.max_width(
                self.config.max_graph_width)).set_key_transform_function(
                    day_num_to_string).generate())
        for row in graph:
            p.add_nowrap(row)
        p.newline()
        p.add_header("Total time logged per hour")
        p.newline()
        graph = (SummaryGraph(self.hour_delta_map).set_max_width(
            utils.max_width(
                self.config.max_graph_width)).set_key_transform_function(
                    zero_pad_hour).generate())
        for row in graph:
            p.add_nowrap(row)
        p.print()
Beispiel #17
0
 def print(self):
     summary = "{0} DAYS // {1} ENTRIES // TOTAL {2}"
     p = Printer()
     p.add_header(
         summary.format(self.days, self.entries, self.formatted_time))
     p.print()
Beispiel #18
0
 def productivity(self):
     productivity_summary = self.db.get_productivity_summary()
     Printer().add_mode_header("Productivity Summary").print()
     productivity_summary.print()
Beispiel #19
0
 def projects(self):
     projects_summary = self.db.get_projects_summary()
     Printer().add_mode_header("Projects Summary").print()
     projects_summary.print()
Beispiel #20
0
 def gather(self):
     Printer().newline().add_header(self.name).newline().print()
     return prompt(f"{self.name} :: ", vi_mode=True)
Beispiel #21
0
def areas_help(areas):
    p = Printer()
    for area_code, area in areas.items():
        p.add("[{0}] {1}".format(area_code, area["name"]))
    p.newline()
    return p
Beispiel #22
0
def areas_help(areas):
    p = Printer()
    for area_code, area in areas.items():
        p.add(f"[{area_code}] {area['name']}")
    p.newline()
    return p