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
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()
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
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()
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()
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)
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)
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
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()
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
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")
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")
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()
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
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
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()
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()
def productivity(self): productivity_summary = self.db.get_productivity_summary() Printer().add_mode_header("Productivity Summary").print() productivity_summary.print()
def projects(self): projects_summary = self.db.get_projects_summary() Printer().add_mode_header("Projects Summary").print() projects_summary.print()
def gather(self): Printer().newline().add_header(self.name).newline().print() return prompt(f"{self.name} :: ", vi_mode=True)
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
def areas_help(areas): p = Printer() for area_code, area in areas.items(): p.add(f"[{area_code}] {area['name']}") p.newline() return p