refactoring: "->', color. prefix in swatches

This commit is contained in:
Johann Dreo 2023-08-25 19:17:24 +02:00
commit cadc9d5140

View file

@ -93,18 +93,18 @@ class task:
self.show_only = task.keys() self.show_only = task.keys()
sid = str(task["id"]) sid = str(task["id"])
if ':' in task["description"]: if ":" in task["description"]:
short, desc = task["description"].split(":") short, desc = task["description"].split(":")
title = rich.text.Text(sid, style='id') + rich.text.Text(":", style="default") + rich.text.Text(short.strip(), style='short_description') title = rich.text.Text(sid, style="color.id") + rich.text.Text(":", style="default") + rich.text.Text(short.strip(), style="color.description.short")
desc = rich.text.Text("\n".join(textwrap.wrap(desc.strip(), self.wrap_width)), style='long_description') desc = rich.text.Text("\n".join(textwrap.wrap(desc.strip(), self.wrap_width)), style="color.description.long")
elif len(task["description"]) <= self.wrap_width - 8: elif len(task["description"]) <= self.wrap_width - 8:
d = task["description"].strip() d = task["description"].strip()
title = rich.text.Text(sid, style='id') + rich.text.Text(":", style="default") + rich.text.Text(d, style='short_description') title = rich.text.Text(sid, style="color.id") + rich.text.Text(":", style="default") + rich.text.Text(d, style="color.description.short")
desc = None desc = None
else: else:
desc = task["description"] desc = task["description"]
desc = rich.text.Text("\n".join(textwrap.wrap(desc.strip(), self.wrap_width)), style='description') desc = rich.text.Text("\n".join(textwrap.wrap(desc.strip(), self.wrap_width)), style="color.description")
title = rich.text.Text(sid, style='id') title = rich.text.Text(sid, style="color.id")
segments = [] segments = []
for key in self.show_only: for key in self.show_only:
@ -112,22 +112,22 @@ class task:
val = task[key] val = task[key]
segment = f"{key}: " segment = f"{key}: "
if type(val) == str: if type(val) == str:
segments.append(rich.text.Text(segment+val, style=key)) segments.append(rich.text.Text(segment+val, style=f"color.{key}"))
elif type(val) == list: elif type(val) == list:
# FIXME Columns does not fit. # FIXME Columns does not fit.
# g = Columns([f"+{t}" for t in val], expand = False) # g = Columns([f"+{t}" for t in val], expand = False)
lst = [] lst = []
for t in val: for t in val:
lst.append( \ lst.append( \
rich.text.Text(self.tag_icons[0], style="tags_ends") + \ rich.text.Text(self.tag_icons[0], style="color.tags.ends") + \
rich.text.Text(t, style=key) + \ rich.text.Text(t, style=f"color.{key}") + \
rich.text.Text(self.tag_icons[1], style="tags_ends") \ rich.text.Text(self.tag_icons[1], style="color.tags.ends") \
) )
g = rich.console.Group(*lst, fit = True) g = rich.console.Group(*lst, fit = True)
# g = rich.console.Group(*[rich.text.Text(f"{self.tag_icons[0]}{t}{self.tag_icons[1]}", style=key) for t in val], fit = True) # g = rich.console.Group(*[rich.text.Text(f"{self.tag_icons[0]}{t}{self.tag_icons[1]}", style=f"color.{key}") for t in val], fit = True)
segments.append(g) segments.append(g)
else: else:
segments.append(rich.text.Text(segment+str(val), style=key)) segments.append(rich.text.Text(segment+str(val), style=f"color.{key}"))
# FIXME Columns does not fit. # FIXME Columns does not fit.
# cols = Columns(segments) # cols = Columns(segments)
@ -145,7 +145,7 @@ class task:
sid = str(task["id"]) sid = str(task["id"])
if sid in self.touched: if sid in self.touched:
panel = rich.panel.Panel(body, title = title, panel = rich.panel.Panel(body, title = title,
title_align="left", expand = False, padding = (0,1), border_style = 'touched', box = rich.box.DOUBLE_EDGE) title_align="left", expand = False, padding = (0,1), border_style = "color.touched", box = rich.box.DOUBLE_EDGE)
else: else:
panel = rich.panel.Panel(body, title = title, panel = rich.panel.Panel(body, title = title,
title_align="left", expand = False, padding = (0,1)) title_align="left", expand = False, padding = (0,1))
@ -160,15 +160,15 @@ class task:
def __call__(self, task): def __call__(self, task):
title, body = self._make(task) title, body = self._make(task)
t = rich.text.Text(self.title_ends[0], style="short_description_ends") + \ t = rich.text.Text(self.title_ends[0], style="color.description.short.ends") + \
title + \ title + \
rich.text.Text(self.title_ends[1], style="short_description_ends") rich.text.Text(self.title_ends[1], style="color.description.short.ends")
sid = str(task["id"]) sid = str(task["id"])
if sid in self.touched: if sid in self.touched:
b = rich.panel.Panel(body, box = rich.box.SIMPLE_HEAD, style='touched') b = rich.panel.Panel(body, box = rich.box.SIMPLE_HEAD, style="color.touched")
else: else:
b = rich.panel.Panel(body, box = rich.box.SIMPLE_HEAD, style='description') b = rich.panel.Panel(body, box = rich.box.SIMPLE_HEAD, style="color.description")
sheet = rich.console.Group(t,b) sheet = rich.console.Group(t,b)
return sheet return sheet
@ -219,11 +219,11 @@ class stack:
def __call__(self, tasks): def __call__(self, tasks):
def p_value(task): def p_value(task):
p_values = {'H': 0, 'M': 1, 'L': 2, '': 3} p_values = {"H": 0, "M": 1, "L": 2, "": 3}
if self.field in task: if self.field in task:
return p_values[task[self.field]] return p_values[task[self.field]]
else: else:
return p_values[''] return p_values[""]
return sorted(tasks, key = p_value, reverse = self.reverse) return sorted(tasks, key = p_value, reverse = self.reverse)
class RawTable(Stacker): class RawTable(Stacker):
@ -234,51 +234,59 @@ class stack:
def __call__(self, tasks): def __call__(self, tasks):
keys = self.tasker.show_only keys = self.tasker.show_only
table = rich.table.Table(box = None, show_header = False, show_lines = True, expand = True, row_styles=['row_odd', 'row_even']) table = rich.table.Table(box = None, show_header = False, show_lines = True, expand = True, row_styles=["color.row.odd", "color.row.even"])
table.add_column('H') table.add_column("H")
for k in keys: for k in keys:
table.add_column(k) table.add_column(k)
for task in self.sorter(tasks): for task in self.sorter(tasks):
taskers = self.tasker(task) taskers = self.tasker(task)
if str(task['id']) in self.tasker.touched: if str(task["id"]) in self.tasker.touched:
row = [rich.text.Text('', style = 'touched')] row = [rich.text.Text("", style = "color.touched")]
else: else:
row = [''] row = [""]
for k in keys: for k in keys:
if k in task: if k in task:
val = taskers[k] val = taskers[k]
##### String keys #####
if type(val) == str: if type(val) == str:
if k == 'description' and ':' in val: # Description is a special case.
short, desc = val.split(':') if k == "description" and ":" in val:
short, desc = val.split(":")
# FIXME groups add a newline or hide what follows, no option to avoid it. # FIXME groups add a newline or hide what follows, no option to avoid it.
# row.append( rich.console.Group( # row.append( rich.console.Group(
# rich.text.Text(short+':', style='short_description', end='\n'), # rich.text.Text(short+":", style="color.description.short", end="\n"),
# rich.text.Text(desc, style='description', end='\n') # rich.text.Text(desc, style="color.description", end="\n")
# )) # ))
# FIXME style leaks on all texts: # FIXME style leaks on all texts:
row.append( rich.text.Text(short, style='short_description', end='') + \ # (Note that "default" is a special color for Rich.)
rich.text.Text(':', style='default', end='') + \ row.append( rich.text.Text(short, style="color.description.short", end="") + \
rich.text.Text(desc, style='long_description', end='') ) rich.text.Text(":", style="default", end="") + \
rich.text.Text(desc, style="color.description.long", end="") )
# Strings, but not description.
else: else:
row.append( rich.text.Text(val, style=k) ) row.append( rich.text.Text(val, style=f"color.{k}") )
##### List keys. #####
elif type(val) == list: elif type(val) == list:
if k == 'tags': # Tags are a special case.
if k == "tags":
tags = rich.text.Text("") tags = rich.text.Text("")
for t in val: for t in val:
# FIXME use Columns if/when it does not expand. # FIXME use Columns if/when it does not expand.
tags += \ tags += \
rich.text.Text(self.tag_icons[0], style="tags_ends") + \ rich.text.Text(self.tag_icons[0], style="color.tags.ends") + \
rich.text.Text(t, style=k) + \ rich.text.Text(t, style=f"color.{k}") + \
rich.text.Text(self.tag_icons[1], style="tags_ends") + \ rich.text.Text(self.tag_icons[1], style="color.tags.ends") + \
" " " "
row.append( tags ) row.append( tags )
# List, but not tags.
else: else:
row.append( rich.text.Text(" ".join(val), style=k) ) row.append( rich.text.Text(" ".join(val), style=f"color.{k}") )
##### Other type of keys. #####
else: else:
row.append( rich.text.Text(str(val), style=k) ) row.append( rich.text.Text(str(val), style=f"color.{k}") )
else: else:
row.append("") row.append("")
table.add_row(*[t for t in row]) table.add_row(*[t for t in row])
@ -317,7 +325,7 @@ class sections:
groups = self.group(tasks) groups = self.group(tasks)
for key in self.order(groups): for key in self.order(groups):
if key in groups: if key in groups:
sections.append( rich.panel.Panel(self.stacker(groups[key]), title = rich.text.Text(str(key).upper(), style=key), title_align = "left", expand = True)) sections.append( rich.panel.Panel(self.stacker(groups[key]), title = rich.text.Text(str(key).upper(), style=f"color.{key}"), title_align = "left", expand = True))
return rich.console.Group(*sections) return rich.console.Group(*sections)
class Horizontal(Sectioner): class Horizontal(Sectioner):
@ -336,7 +344,7 @@ class sections:
row = [] row = []
for k in keys: for k in keys:
row.append( rich.panel.Panel(self.stacker(groups[k]), title = rich.text.Text(k.upper(), style=k), title_align = "left", expand = True, border_style="title")) row.append( rich.panel.Panel(self.stacker(groups[k]), title = rich.text.Text(k.upper(), style=f"color.{k}"), title_align = "left", expand = True, border_style="color.title"))
table.add_row(*row) table.add_row(*row)
return table return table
@ -402,12 +410,12 @@ class group:
return groups return groups
def call_taskwarrior(args:list[str] = ['export'], taskfile = ".task") -> str: def call_taskwarrior(args:list[str] = ["export"], taskfile = ".task") -> str:
# Local file. # Local file.
env = os.environ.copy() env = os.environ.copy()
env["TASKDATA"] = taskfile env["TASKDATA"] = taskfile
cmd = ['task'] + args cmd = ["task"] + args
try: try:
p = subprocess.Popen( " ".join(cmd), p = subprocess.Popen( " ".join(cmd),
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -421,13 +429,13 @@ def call_taskwarrior(args:list[str] = ['export'], taskfile = ".task") -> str:
print("ERROR:", exc.returncode, exc.output, err) print("ERROR:", exc.returncode, exc.output, err)
sys.exit(exc.returncode) sys.exit(exc.returncode)
else: else:
return out.decode('utf-8') return out.decode("utf-8")
def get_data(taskfile, filter = None): def get_data(taskfile, filter = None):
if not filter: if not filter:
filter = [] filter = []
out = call_taskwarrior(filter+['export'], taskfile) out = call_taskwarrior(filter+["export"], taskfile)
try: try:
jdata = json.loads(out) jdata = json.loads(out)
except json.decoder.JSONDecodeError as exc: except json.decoder.JSONDecodeError as exc:
@ -437,91 +445,98 @@ def get_data(taskfile, filter = None):
def parse_touched(out): def parse_touched(out):
return re.findall('(?:Modifying|Created|Starting|Stopping)+ task ([0-9]+)', out) return re.findall("(?:Modifying|Created|Starting|Stopping)+ task ([0-9]+)", out)
def get_swatches(name = None): def get_swatches(name = None):
swatches = { swatches = {
"none": { "none": {
'touched': '', "color.touched": "",
'id': '', "color.id": "",
'title': '', "color.title": "",
'description': '', "color.description": "",
'short_description': '', "color.description.short": "",
'short_description_ends': '', "color.description.short.ends": "",
'long_description': '', "color.description.long": "",
'entry': '', "color.entry": "",
'modified': '', "color.end": "",
'started': '', "color.modified": "",
'status': '', "color.started": "",
'uuid': '', "color.status": "",
'tags': '', "color.uuid": "",
'tags_ends': '', "color.tags": "",
'urgency': '', "color.tags.ends": "",
'row_odd': '', "color.urgency": "",
'row_even' : '', "color.row.odd": "",
"color.row.even" : "",
"color.priority": "",
}, },
"nojhan": { "nojhan": {
'touched': '#4E9A06', "color.touched": "#4E9A06",
'id': 'color(214)', "color.id": "color(214)",
'title': '', "color.title": "",
'description': 'color(231)', "color.description": "color(231)",
'short_description': 'color(231)', "color.description.short": "color(231)",
'short_description_ends': '', "color.description.short.ends": "",
'long_description': 'default', "color.description.long": "default",
'entry': '', "color.entry": "",
'modified': 'color(240)', "color.end": "",
'started': '', "color.modified": "color(240)",
'status': 'bold italic white', "color.started": "",
'uuid': '', "color.status": "bold italic white",
'tags': 'color(33)', "color.uuid": "",
'tags_ends': 'color(26)', "color.tags": "color(33)",
'urgency': 'color(219)', "color.tags.ends": "color(26)",
'row_odd': 'on #262121', "color.urgency": "color(219)",
'row_even' : 'on #2d2929', "color.row.odd": "on #262121",
'priority': 'color(105)', "color.row.even" : "on #2d2929",
"color.priority": "color(105)",
}, },
"chalky": { "chalky": {
'touched': 'color(0) on color(15)', "color.touched": "color(0) on color(15)",
'id': 'bold color(160) on white', "color.id": "bold color(160) on white",
'title': '', "color.title": "",
'description': 'black on white', "color.description": "black on white",
'short_description': 'bold black on white', "color.description.short": "bold black on white",
'short_description_ends': 'white', "color.description.short.ends": "white",
'long_description': 'black on white', "color.description.long": "black on white",
'entry': '', "color.entry": "",
'modified': 'color(240)', "color.end": "",
'started': '', "color.modified": "color(240)",
'status': 'bold italic white', "color.started": "",
'uuid': '', "color.status": "bold italic white",
'tags': 'color(166) on white', "color.uuid": "",
'tags_ends': 'white', "color.tags": "color(166) on white",
'urgency': 'color(219)', "color.tags.ends": "white",
'row_odd': '', "color.urgency": "color(219)",
'row_even' : '', "color.row.odd": "",
"color.row.even" : "",
"color.priority": "",
}, },
"carbon": { "carbon": {
'touched': 'color(15) on color(0)', "color.touched": "color(15) on color(0)",
'id': 'bold color(196) on color(236)', "color.id": "bold color(196) on color(236)",
'title': '', "color.title": "",
'description': 'white on color(236)', "color.description": "white on color(236)",
'short_description': 'bold white on color(236)', "color.description.short": "bold white on color(236)",
'short_description_ends': 'color(236)', "color.description.short.ends": "color(236)",
'long_description': 'white on color(236)', "color.description.long": "white on color(236)",
'entry': '', "color.entry": "",
'modified': '', "color.end": "",
'started': '', "color.modified": "",
'status': 'bold italic white', "color.started": "",
'uuid': '', "color.status": "bold italic white",
'tags': 'bold black on color(88)', "color.uuid": "",
'tags_ends': 'color(88)', "color.tags": "bold black on color(88)",
'urgency': 'color(219)', "color.tags.ends": "color(88)",
'row_odd': '', "color.urgency": "color(219)",
'row_even' : '', "color.row.odd": "",
"color.row.even" : "",
"color.priority": "",
}, },
} }
@ -535,24 +550,24 @@ def get_icons(name=None):
icons = { icons = {
'none' : { "none" : {
'tag': ['', ''], "tag": ["", ""],
'short': ['', ''], "short": ["", ""],
}, },
'ascii' : { "ascii" : {
'tag': ['+', ''], "tag": ["+", ""],
'short': ['', ''], "short": ["", ""],
}, },
'emojis' : { "emojis" : {
'tag': ['🏷️ ', ''], "tag": ["🏷️ ", ""],
'short': ['\n', ''], "short": ["\n", ""],
}, },
'power' : { "power" : {
'tag': ['', ''], "tag": ["", ""],
'short': ['\n', ''], "short": ["\n", ""],
}, },
} }
@ -586,24 +601,38 @@ def get_layouts(kind = None, name = None):
elif not kind and not name: elif not kind and not name:
return available return available
else: else:
raise KeyError("cannot get layouts with 'name' only") raise KeyError("cannot get layouts with `name` only")
def tw_to_rich(color):
# color123 -> color(123)
color = re.sub(r"color([0-9]{0,3})", r"color(\1)", color)
# rgb123 -> #123 and rgb123abc -> #123abc
color = re.sub(r"rgb(([\da-f]{3}){1,2})", r"#\1", color)
# rgb123 -> rgb112233
color = re.sub(r"#([\da-f])([\da-f])([\da-f])", r"#\1\1\2\2\3\3", color)
return color
# We cannot use tomllib because strings are not quoted. # We cannot use tomllib because strings are not quoted.
# We cannot use configparser because there is no section and because of the 'include' command. # We cannot use configparser because there is no section and because of the "include" command.
# FIXME handle possible values when possible. # FIXME handle possible values when possible.
def parse_config(filename, current): def parse_config(filename, current):
config = current config = current
with open(filename, 'r') as fd: with open(filename, "r") as fd:
for i,line in enumerate(fd.readlines()): for i,line in enumerate(fd.readlines()):
if line.strip() and line.strip()[0] != '#': if line.strip() and line.strip()[0] != "#": # Starting comment.
if '=' in line: if "=" in line:
key,value = line.split('=') key,value = line.split("=")
if '#' in value: if "#" in value: # Ending comments.
value = value.split('#')[0] value = value.split("#")[0]
if "color" in key: # Colors conversion.
value = tw_to_rich(value.strip())
if ".uda." in key:
key = re.sub(r"color\.uda\.", r"color.", key)
config[key.strip()] = value.strip() config[key.strip()] = value.strip()
elif "include" in line: elif "include" in line:
_,path = line.split() _,path = line.split()
config['includes'].append(path.strip()) config["includes"].append(path.strip())
else: else:
print(f"Cannot parse line {i} of config file `{filename}`, I'll ignore it.") print(f"Cannot parse line {i} of config file `{filename}`, I'll ignore it.")
return config return config
@ -640,7 +669,7 @@ def find_config(fname, current):
config = parse_config(p, config) config = parse_config(p, config)
# Second, user. # Second, user.
p = pathlib.Path(os.path.expanduser('~')) / pathlib.Path(fname) p = pathlib.Path(os.path.expanduser("~")) / pathlib.Path(fname)
if p.exists(): if p.exists():
config = parse_config(p, config) config = parse_config(p, config)
@ -685,7 +714,6 @@ def parse_filter(cmd):
return filter return filter
return None return None
if __name__ == "__main__": if __name__ == "__main__":
default_conf = { default_conf = {
@ -709,7 +737,7 @@ if __name__ == "__main__":
"list.filtered": "false", "list.filtered": "false",
} }
# First, taskwarrior's config... # First, taskwarrior"s config...
config = find_config(".taskrc", default_conf) config = find_config(".taskrc", default_conf)
# ... overwritten by TWD config. # ... overwritten by TWD config.
config = find_config(".twdrc", config) config = find_config(".twdrc", config)
@ -756,14 +784,16 @@ if __name__ == "__main__":
swatch = rich.theme.Theme(get_swatches(config["design.swatch"])) swatch = rich.theme.Theme(get_swatches(config["design.swatch"]))
layouts = get_layouts() layouts = get_layouts()
##### Tasks #####
if config["layout.task"] == "Card": 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']) 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": elif config["layout.task"] == "Sheet":
icons = get_icons(config["design.icons"]) 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']) tasker = layouts["task"]["Sheet"](show_only, touched = touched, wrap_width = int(config["widget.card.wrap"]), tag_icons = icons["tag"], title_ends = icons["short"])
else: else:
tasker = layouts['task'][config["layout.task"]](show_only, touched = touched) tasker = layouts["task"][config["layout.task"]](show_only, touched = touched)
##### Stack #####
if config["layout.stack.sort"]: if config["layout.stack.sort"]:
if config["layout.stack.sort"] == "priority": if config["layout.stack.sort"] == "priority":
sorter = stack.sort.Priority(as_bool(config["layout.stack.sort.reverse"])) sorter = stack.sort.Priority(as_bool(config["layout.stack.sort.reverse"]))
@ -775,13 +805,14 @@ if __name__ == "__main__":
sorter = None sorter = None
if config["layout.stack"] == "RawTable": if config["layout.stack"] == "RawTable":
stacker = layouts['stack']["RawTable"](tasker, sorter = sorter, tag_icons = get_icons(config["design.icons"])['tag']) stacker = layouts["stack"]["RawTable"](tasker, sorter = sorter, tag_icons = get_icons(config["design.icons"])["tag"])
else: else:
stacker = layouts['stack'][config["layout.stack"]](tasker, sorter = sorter) stacker = layouts["stack"][config["layout.stack"]](tasker, sorter = sorter)
##### Sections #####
if config["layout.sections.group"]: if config["layout.sections.group"]:
values = config["layout.sections.group.show"].split(list_separator) values = config["layout.sections.group.show"].split(list_separator)
if values == ['']: if values == [""]:
values = [] values = []
if config["layout.sections.group"].lower() == "status": if config["layout.sections.group"].lower() == "status":
group_by = group.Status() group_by = group.Status()
@ -800,9 +831,10 @@ if __name__ == "__main__":
group_by = group.Status() group_by = group.Status()
g_sort_on = group.sort.OnValues(["pending","started","completed"]) g_sort_on = group.sort.OnValues(["pending","started","completed"])
##### Subsections #####
if config["layout.subsections.group"]: if config["layout.subsections.group"]:
values = config["layout.subsections.group.show"].split(list_separator) values = config["layout.subsections.group.show"].split(list_separator)
if values == ['']: if values == [""]:
values = [] values = []
if config["layout.subsections.group"].lower() == "status": if config["layout.subsections.group"].lower() == "status":
subgroup_by = group.Status() subgroup_by = group.Status()
@ -822,10 +854,10 @@ if __name__ == "__main__":
g_subsort_on = None g_subsort_on = None
if config["layout.subsections"] and config["layout.subsections.group"]: if config["layout.subsections"] and config["layout.subsections.group"]:
subsectioner = layouts['sections'][config["layout.subsections"]](stacker, g_subsort_on, subgroup_by) subsectioner = layouts["sections"][config["layout.subsections"]](stacker, g_subsort_on, subgroup_by)
sectioner = layouts['sections'][config["layout.sections"]](subsectioner, g_sort_on, group_by) sectioner = layouts["sections"][config["layout.sections"]](subsectioner, g_sort_on, group_by)
else: else:
sectioner = layouts['sections'][config["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 = Console(theme = swatch)
# console.rule("taskwarrior-deluxe") # console.rule("taskwarrior-deluxe")