diff --git a/.task/backlog.data b/.task/backlog.data index 0c43b52..dd48577 100644 --- a/.task/backlog.data +++ b/.task/backlog.data @@ -47,3 +47,8 @@ {"description":"sorting: allow sorting within a stacker.","entry":"20230816T115710Z","modified":"20230816T193453Z","start":"20230816T193453Z","status":"pending","uuid":"30ec6b32-a32e-4595-a24d-02ac9002fd72","tags":["feat"]} {"description":"sorting: allow sorting within a stacker.","end":"20230816T193508Z","entry":"20230816T115710Z","modified":"20230816T193508Z","status":"completed","uuid":"30ec6b32-a32e-4595-a24d-02ac9002fd72","tags":["feat"]} {"description":"sort by urgency","end":"20230816T193637Z","entry":"20230811T170942Z","modified":"20230816T193637Z","priority":"H","status":"completed","uuid":"4f041f52-739a-4aca-94e6-164f43e61866","tags":["feat"]} +{"description":"semantic colors: allow coloring by values, not just fields.","entry":"20230817T065528Z","modified":"20230817T065528Z","status":"pending","uuid":"1a5aab4a-0c2b-444f-9259-6eddc29b9791","tags":["feat","themes"]} +{"description":"config: handle config files from taskwarrior first","entry":"20230811T171231Z","modified":"20230817T070119Z","status":"pending","uuid":"4d822c4a-d51e-4992-8a22-6e0167ac197a","tags":["feat"]} +{"description":"config: handle config files from taskwarrior first","entry":"20230811T171231Z","modified":"20230817T154131Z","start":"20230817T154131Z","status":"pending","uuid":"4d822c4a-d51e-4992-8a22-6e0167ac197a","tags":["feat"]} +{"description":"theme config: use taskwarrior theme config and extends from there.","entry":"20230819T190428Z","modified":"20230819T190428Z","status":"pending","uuid":"6b248843-1b11-487b-85e8-a96e668f6772","tags":["feat","themes"]} +{"description":"config: handle config files from taskwarrior first","end":"20230819T190559Z","entry":"20230811T171231Z","modified":"20230819T190559Z","status":"completed","uuid":"4d822c4a-d51e-4992-8a22-6e0167ac197a","tags":["feat"]} diff --git a/.task/completed.data b/.task/completed.data index fecd33e..c929e6e 100644 --- a/.task/completed.data +++ b/.task/completed.data @@ -1,3 +1,4 @@ +[description:"config: handle config files from taskwarrior first" end:"1692471959" entry:"1691773951" modified:"1692471959" status:"completed" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] [description:"sort by urgency" end:"1692214597" entry:"1691773782" modified:"1692214597" priority:"H" status:"completed" tags:"feat" tags_feat:"x" uuid:"4f041f52-739a-4aca-94e6-164f43e61866"] [description:"sorting: allow sorting within a stacker." end:"1692214508" entry:"1692187030" modified:"1692214508" status:"completed" tags:"feat" tags_feat:"x" uuid:"30ec6b32-a32e-4595-a24d-02ac9002fd72"] [description:"subgrouping: allow to add another sectionning, e.g. based on priority." end:"1692187048" entry:"1692122668" modified:"1692187048" status:"completed" tags:"feat" tags_feat:"x" uuid:"9dc6d43a-b316-4417-ad2a-e47b8a5ddc7a"] diff --git a/.task/pending.data b/.task/pending.data index 3bf1c5a..9cda1b7 100644 --- a/.task/pending.data +++ b/.task/pending.data @@ -1,9 +1,10 @@ [description:"find data updir: try to find .task on upper directories if not in the existing one" entry:"1691773581" modified:"1692121517" priority:"H" status:"pending" tags:"data,feat" tags_data:"x" tags_feat:"x" uuid:"b4ee0b55-0bb3-4a2f-b7c5-87cd70dd0ff0"] [description:"semantic tags : allow tags starting with a hash to refer to a github issue or PR" entry:"1691773642" modified:"1692121563" priority:"L" status:"pending" tags:"feat" tags_feat:"x" uuid:"06cf70a9-4f0e-4b32-b7bb-d1799803665c"] [description:"hide old done tasks: Hide tasks with last status that haven t been touched since a long time." entry:"1691773751" modified:"1692122496" priority:"M" status:"pending" tags:"feat" tags_feat:"x" uuid:"e18707b4-304d-41f3-8504-89476cd796f5"] -[description:"config file.s" entry:"1691773951" modified:"1691773951" status:"pending" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] [description:"fix escaped add: when passing add with escapable characters, bug ensues." entry:"1691774077" modified:"1691774077" status:"pending" tags:"bug" tags_bug:"x" uuid:"79e1ba11-15ae-489f-9868-ab6adea40a91"] [description:"more themes" entry:"1691864624" modified:"1691990020" start:"1691990020" status:"pending" tags:"themes" tags_themes:"x" uuid:"9ee183d4-3413-4efa-ba68-10d709669c05"] [description:"init command: add a command to initialize a local .task dir and\/or config file.s" entry:"1692121630" modified:"1692121630" priority:"M" status:"pending" tags:"feat" tags_feat:"x" uuid:"81fc6480-e8e7-4dd4-9f01-a00f34463135"] [description:"colored panels: allow selecting a different swatch for each &open;sub&close;panel" entry:"1692122434" modified:"1692122452" priority:"M" status:"pending" tags:"feat,themes" tags_feat:"x" tags_themes:"x" uuid:"ca17838d-958f-498b-bff5-a24576820ae7"] [description:"handle prompts: for some commands like delete, taskwarrior setup an interactive prompt, which needs to be handled." entry:"1692122614" modified:"1692122614" status:"pending" tags:"bug" tags_bug:"x" uuid:"e2480c4b-4c73-4d03-8568-82f14ade7b38"] +[description:"semantic colors: allow coloring by values, not just fields." entry:"1692255328" modified:"1692255328" status:"pending" tags:"feat,themes" tags_feat:"x" tags_themes:"x" uuid:"1a5aab4a-0c2b-444f-9259-6eddc29b9791"] +[description:"theme config: use taskwarrior theme config and extends from there." entry:"1692471868" modified:"1692471868" status:"pending" tags:"feat,themes" tags_feat:"x" tags_themes:"x" uuid:"6b248843-1b11-487b-85e8-a96e668f6772"] diff --git a/.task/undo.data b/.task/undo.data index 0eaf875..57ef44f 100644 --- a/.task/undo.data +++ b/.task/undo.data @@ -172,3 +172,21 @@ time 1692214597 old [description:"sort by urgency" entry:"1691773782" modified:"1692122485" priority:"H" status:"pending" tags:"feat" tags_feat:"x" uuid:"4f041f52-739a-4aca-94e6-164f43e61866"] new [description:"sort by urgency" end:"1692214597" entry:"1691773782" modified:"1692214597" priority:"H" status:"completed" tags:"feat" tags_feat:"x" uuid:"4f041f52-739a-4aca-94e6-164f43e61866"] --- +time 1692255328 +new [description:"semantic colors: allow coloring by values, not just fields." entry:"1692255328" modified:"1692255328" status:"pending" tags:"feat,themes" tags_feat:"x" tags_themes:"x" uuid:"1a5aab4a-0c2b-444f-9259-6eddc29b9791"] +--- +time 1692255679 +old [description:"config file.s" entry:"1691773951" modified:"1691773951" status:"pending" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] +new [description:"config: handle config files from taskwarrior first" entry:"1691773951" modified:"1692255679" status:"pending" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] +--- +time 1692286891 +old [description:"config: handle config files from taskwarrior first" entry:"1691773951" modified:"1692255679" status:"pending" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] +new [description:"config: handle config files from taskwarrior first" entry:"1691773951" modified:"1692286891" start:"1692286891" status:"pending" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] +--- +time 1692471868 +new [description:"theme config: use taskwarrior theme config and extends from there." entry:"1692471868" modified:"1692471868" status:"pending" tags:"feat,themes" tags_feat:"x" tags_themes:"x" uuid:"6b248843-1b11-487b-85e8-a96e668f6772"] +--- +time 1692471959 +old [description:"config: handle config files from taskwarrior first" entry:"1691773951" modified:"1692286891" start:"1692286891" status:"pending" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] +new [description:"config: handle config files from taskwarrior first" end:"1692471959" entry:"1691773951" modified:"1692471959" status:"completed" tags:"feat" tags_feat:"x" uuid:"4d822c4a-d51e-4992-8a22-6e0167ac197a"] +--- diff --git a/taskwarrior-deluxe.py b/taskwarrior-deluxe.py index 18f709f..69c4e81 100755 --- a/taskwarrior-deluxe.py +++ b/taskwarrior-deluxe.py @@ -377,7 +377,7 @@ class group: def call_taskwarrior(args:list[str] = ['export']) -> str: # Local file. env = os.environ.copy() - env["TASKDATA"] = asked.data + env["TASKDATA"] = ".task" # FIXME handle updir cmd = ['task'] + args try: @@ -558,67 +558,73 @@ def get_layouts(kind = None, name = None): else: raise KeyError("cannot get layouts with 'name' only") +# We cannot use tomllib because strings are not quoted. +# We cannot use configparser because there is no section and because of the 'include' command. +# FIXME handle possible values when possible. +def parse_config(filename, default): + config = default + with open(filename, 'r') as fd: + for i,line in enumerate(fd.readlines()): + if line.strip() and line.strip()[0] != '#': + if '=' in line: + key,value = line.split('=') + if '#' in value: + value = value.split('#')[0] + config[key.strip()] = value.strip() + elif "include" in line: + _,path = line.split() + config['includes'].append(path.strip()) + else: + print(f"Cannot parse line {i} of config file `{filename}`, I'll ignore it.") + return config + + +def as_bool(s): + if s.lower() in ["true", "yes", "y", "1"]: + return True + elif s.lower() in ["false", "no", "nope", "n", "0"]: + return False + else: + raise ValueError(f"Cannot interpret `{s}` as a boolean.") + if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="XXX", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) + default_conf = { + # taskwarrior + "report.list.columns":"id,priority,description,tags", - parser.add_argument("-s", "--show", metavar="columns", type=str, default="id,priority,description,tags", - help="Ordered list of columns to show.") + # taskwarrior-deluxe + "layout.task": "Raw", + "layout.stack": "RawTable", + "layout.stack.sort": "urgency", + "layout.stack.sort.reverse": "false", # urgency and priority are numeric. + "layout.subsections": "", + "layout.subsections.group": "", + "layout.sections": "Horizontal", + "layout.sections.group": "status", + "design.swatch": "none", + "design.icons": "none", + "widget.card.wrap": "25", + } + # TODO seek files up dans in config paths. + config_tw = parse_config(os.path.expanduser('~/.taskrc'), default_conf) + config = parse_config(os.path.expanduser('~/.twdrc'), config_tw) - config_grp = parser.add_argument_group('configuration options') + # for k in config: + # print(k,"=",config[k]) - config_grp.add_argument("-d", "--data", metavar="FILE", type=str, default=".task", nargs=1, - help="The input data file.") + list_separator = ',' - config_grp.add_argument("--list-separator", metavar="CHARACTER", type=str, default=",", nargs=1, - help="Separator used for lists that are passed as options arguments.") + cmd = sys.argv[1:] + # TODO add an init command to create config and task files. + #if cmd[0] == "init": - layouts_grp = parser.add_argument_group('layout options') - - layouts_grp.add_argument('-t', '--layout-task', metavar='NAME', type=str, default='Raw', - choices = get_layouts('task').keys(), help="Layout managing tasks.") - layouts_grp.add_argument('-k', '--layout-stack', metavar='NAME', type=str, default='RawTable', - choices = get_layouts('stack').keys(), help="Layout managing stacks.") - layouts_grp.add_argument('-c', '--layout-sections', metavar='NAME', type=str, default='Horizontal', - choices = get_layouts('sections').keys(), help="Layout managing sections.") - layouts_grp.add_argument('-C', '--layout-subsections', metavar='NAME', type=str, default=None, - choices = get_layouts('sections').keys(), help="Layout managing sub-sections.") - - layouts_grp.add_argument('-g', '--group-by', metavar="NAME", type=str, default="status", - help="Create sections by grouping on this field.") - - layouts_grp.add_argument('-G', '--subgroup-by', metavar="NAME", type=str, default=None, - help="Create sub-sections by grouping on this field.") - - layouts_grp.add_argument('-S', '--sort', metavar="NAME", type=str, default='urgency', - help="Field on which to sort tasks in a stack.") - - layouts_grp.add_argument('-R', '--reverse', action = 'store_true', - help="Reverse the sort of tasks in a stack.") - - layouts_grp.add_argument('-T', '--swatch', metavar='NAME', type=str, default='none', - choices = get_swatches().keys(), help="Color chart.") - - layouts_grp.add_argument('-I', '--icons', metavar='NAME', type=str, default='none', - choices = get_icons().keys(), help="Additional decorative characters.") - - layouts_grp.add_argument('--card-wrap', metavar="NB", type=int, default=25, - help="Number of character at which to wrap the description of Cards tasks.") - - # Capture whatever remains. - parser.add_argument('cmd', nargs="*") - - asked = parser.parse_args() - # print(asked) # First pass arguments to taskwarrior and let it do its magic. - out = call_taskwarrior(asked.cmd) + out = call_taskwarrior(cmd) if "Description" not in out: print(out.strip()) touched = parse_touched(out) @@ -628,68 +634,67 @@ if __name__ == "__main__": jdata = get_data() # print(json.dumps(jdata, indent=4)) - print(asked.show) - showed = asked.show.split(asked.list_separator) + showed = config["report.list.columns"].split(list_separator) if not showed: show_only = None else: show_only = showed - swatch = rich.theme.Theme(get_swatches(asked.swatch)) + swatch = rich.theme.Theme(get_swatches(config["design.swatch"])) layouts = get_layouts() - if asked.layout_task == "Card": - tasker = layouts['task']['Card'](show_only, touched = touched, wrap_width = asked.card_wrap, tag_icons = get_icons(asked.icons)['tag']) - elif asked.layout_task == "Sheet": - icons = get_icons(asked.icons) - tasker = layouts['task']['Sheet'](show_only, touched = touched, wrap_width = asked.card_wrap, tag_icons = icons['tag'], title_ends = icons['short']) + if config["layout.task"] == "Card": + tasker = layouts['task']['Card'](show_only, touched = touched, wrap_width = int(config["widget.card.wrap"]), tag_icons = get_icons(config["design.icons"])['tag']) + elif config["layout.task"] == "Sheet": + icons = get_icons(config["design.icons"]) + tasker = layouts['task']['Sheet'](show_only, touched = touched, wrap_width = int(config["widget.card.wrap"]), tag_icons = icons['tag'], title_ends = icons['short']) else: - tasker = layouts['task'][asked.layout_task](show_only, touched = touched) + tasker = layouts['task'][config["layout.task"]](show_only, touched = touched) - if asked.sort: - if asked.sort == "priority": - sorter = stack.sort.Priority(asked.reverse) - elif asked.sort == "urgency": - sorter = stack.sort.Priority(asked.reverse) + if config["layout.stack.sort"]: + if config["layout.stack.sort"] == "priority": + sorter = stack.sort.Priority(as_bool(config["layout.stack.sort.reverse"])) + elif config["layout.stack.sort"] == "urgency": + sorter = stack.sort.Priority(as_bool(config["layout.stack.sort.reverse"])) else: - sorter = stack.sort.Field(asked.sort, reverse = asked.reverse) + sorter = stack.sort.Field(config["layout.stack.sort"], reverse = as_bool(config["layout.stack.sort.reverse"])) else: sorter = None - stacker = layouts['stack'][asked.layout_stack](tasker, sorter = sorter) + stacker = layouts['stack'][config["layout.stack"]](tasker, sorter = sorter) - if asked.group_by: - if asked.group_by.lower() == "status": + if config["layout.sections.group"]: + if config["layout.sections.group"].lower() == "status": group_by = group.Status() g_sort_on = group.sort.OnValues(["pending","started","completed"]) - elif asked.group_by.lower() == "priority": + elif config["layout.sections.group"].lower() == "priority": group_by = group.Field("priority") g_sort_on = group.sort.OnValues(["H","M","L",""]) else: - group_by = group.Field(asked.group_by) + group_by = group.Field(config["layout.sections.group"]) g_sort_on = None else: group_by = group.Status() g_sort_on = group.sort.OnValues(["pending","started","completed"]) - if asked.subgroup_by: - if asked.subgroup_by.lower() == "status": + if config["layout.subsections.group"]: + if config["layout.subsections.group"].lower() == "status": subgroup_by = group.Status() g_subsort_on = group.sort.OnValues(["pending","started","completed"]) - if asked.subgroup_by.lower() == "priority": + if config["layout.subsections.group"].lower() == "priority": subgroup_by = group.Field("priority") g_subsort_on = group.sort.OnValues(["H","M","L",""]) else: - subgroup_by = group.Field(asked.subgroup_by) + subgroup_by = group.Field(config["layout.subsections.group"]) g_subsort_on = None else: subgroup_by = None g_subsort_on = None - if asked.layout_subsections and asked.subgroup_by: - subsectioner = layouts['sections'][asked.layout_subsections](stacker, g_subsort_on, subgroup_by) - sectioner = layouts['sections'][asked.layout_sections](subsectioner, g_sort_on, group_by) + if config["layout.subsections"] and config["layout.subsections.group"]: + subsectioner = layouts['sections'][config["layout.subsections"]](stacker, g_subsort_on, subgroup_by) + sectioner = layouts['sections'][config["layout.sections"]](subsectioner, g_sort_on, group_by) else: - sectioner = layouts['sections'][asked.layout_sections](stacker, g_sort_on, group_by) + sectioner = layouts['sections'][config["layout.sections"]](stacker, g_sort_on, group_by) console = Console(theme = swatch) # console.rule("taskwarrior-deluxe")