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