|
|
@ -12,12 +12,41 @@ from rich.console import Console |
|
|
|
from rich.theme import Theme |
|
|
|
|
|
|
|
STORAGE_FILE = os.path.join(os.path.expanduser('~'), '.workflowy_data.json') |
|
|
|
|
|
|
|
INBOX_ID = "f5cf6b0b-2931-4a59-9301-0a52813e51e3" |
|
|
|
PLANNER_ID = "d0162605-db2d-7f97-f451-4408da2bb5df" |
|
|
|
TASKS_ID = "4f5fca00-8141-3f33-ac7a-b732f37515f4" |
|
|
|
PLANNER_IDS = { |
|
|
|
"life": "d0162605-db2d-7f97-f451-4408da2bb5df", |
|
|
|
"admin": "3af78fcf-71d1-c020-26f1-a230b2403683", |
|
|
|
"art": "d33c5cd2-6183-776f-33e8-c0714c8c5d63", |
|
|
|
"health": "ab0ed672-7868-f38d-06c1-86c6464cd85b", |
|
|
|
"mind": "09e9a2c7-954d-9178-7bca-de54d6f6680d", |
|
|
|
"social": "2be5c4e5-ebfe-a462-3389-5b83aa86d016", |
|
|
|
"church": "c69aa1a1-26a3-2ab7-a125-fe6b61d108bd", |
|
|
|
"work": "f6306ec9-9243-a249-3af7-aa7d64963e2b" |
|
|
|
} |
|
|
|
|
|
|
|
solarized_theme = Theme({ |
|
|
|
"orange": "#cb4b16", |
|
|
|
"violet": "#6c71c4", |
|
|
|
"base03": "bright_black", |
|
|
|
"base02": "black", |
|
|
|
"base01": "bright_green", |
|
|
|
"base00": "bright_yellow", |
|
|
|
"base0": "bright_blue", |
|
|
|
"base1": "bright_cyan", |
|
|
|
"base2": "white", |
|
|
|
"base3": "bright_white", |
|
|
|
"orange": "bright_red", |
|
|
|
"violet": "bright_magenta", |
|
|
|
"underline base03": "underline bright_black", |
|
|
|
"underline base02": "underline black", |
|
|
|
"underline base01": "underline bright_green", |
|
|
|
"underline base00": "underline bright_yellow", |
|
|
|
"underline base0": "underline bright_blue", |
|
|
|
"underline base1": "underline bright_cyan", |
|
|
|
"underline base2": "underline white", |
|
|
|
"underline base3": "underline bright_white", |
|
|
|
"underline orange": "bright_red underline", |
|
|
|
"underline violet": "bright_magenta underline", |
|
|
|
}) |
|
|
|
|
|
|
|
console = Console(highlight=False, theme=solarized_theme) |
|
|
@ -239,16 +268,36 @@ def clip_to_workflowy(name, description, parent_id): # {{{ |
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def simplify_project(project_data): # {{{ |
|
|
|
simplified_project = { |
|
|
|
"name": project_data.get("nm", ""), |
|
|
|
"description": project_data.get("no", ""), # Assuming 'no' contains description/URL if present |
|
|
|
"children": [] |
|
|
|
} |
|
|
|
children = project_data.get("ch", []) |
|
|
|
for child in children: |
|
|
|
simplified_project["children"].append(simplify_project(child)) |
|
|
|
return simplified_project |
|
|
|
def simplify_project(project_data, full_data, follow_mirrors=False): # {{{ |
|
|
|
if project_data.get("metadata", {}).get("mirror", {}).get("originalId"): |
|
|
|
if follow_mirrors: |
|
|
|
originalId = project_data["metadata"]["mirror"]["originalId"] |
|
|
|
project_data, full_data = find_project_by_id(full_data, full_data, originalId) |
|
|
|
else: |
|
|
|
return None, full_data |
|
|
|
if project_data.get("metadata", {}).get("isReferencesRoot"): |
|
|
|
if project_data.get("ch"): |
|
|
|
if len(project_data["ch"]) > 0: |
|
|
|
project_data["nm"] = "backlinks" |
|
|
|
project_data["metadata"]["layoutMode"] = "h1" |
|
|
|
else: |
|
|
|
return None, full_data |
|
|
|
else: |
|
|
|
return None, full_data |
|
|
|
if project_data: |
|
|
|
simplified_project = { |
|
|
|
"name": project_data.get("nm", ""), |
|
|
|
"id": project_data.get("id", ""), |
|
|
|
"children": [], |
|
|
|
"format": project_data.get("metadata", {}).get("layoutMode", None) |
|
|
|
} |
|
|
|
children = project_data.get("ch", []) |
|
|
|
for child in children: |
|
|
|
simplified_child, full_data = simplify_project(child, full_data, follow_mirrors=follow_mirrors) |
|
|
|
if simplified_child: |
|
|
|
simplified_project["children"].append(simplified_child) |
|
|
|
return simplified_project, full_data |
|
|
|
return None, full_data |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
@ -256,8 +305,14 @@ def simplify_project(project_data): # {{{ |
|
|
|
def flatten(children): # {{{ |
|
|
|
flattened = [] |
|
|
|
for child in children: |
|
|
|
try: |
|
|
|
grand_children = child.get("children", []) |
|
|
|
except Exception as e: |
|
|
|
print(f"child: {child} {e}") |
|
|
|
grand_children = [] |
|
|
|
child["children"] = [] |
|
|
|
flattened.append(child) |
|
|
|
flattened.extend(flatten(child.get("children", []))) |
|
|
|
flattened.extend(flatten(grand_children)) |
|
|
|
return flattened |
|
|
|
# }}} |
|
|
|
|
|
|
@ -269,32 +324,90 @@ def flatten_project(project_data): # {{{ |
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def filter_project_any(project_data, filters): # {{{ |
|
|
|
filtered_project = { |
|
|
|
"name": project_data["name"], |
|
|
|
"children": [] |
|
|
|
} |
|
|
|
children = project_data.get("children", []) |
|
|
|
for child in children: |
|
|
|
include = False |
|
|
|
for filter_text in filters: |
|
|
|
if filter_text.lower() in child["name"].lower(): |
|
|
|
include = True |
|
|
|
break |
|
|
|
if include: |
|
|
|
filtered_project["children"].append(filter_project_any(child, filters)) |
|
|
|
return filtered_project |
|
|
|
# def filter_project_any(project_data, filters): # {{{ |
|
|
|
# filtered_project = { |
|
|
|
# "name": project_data["name"], |
|
|
|
# "id": project_data.get("id", ""), |
|
|
|
# # "description": project_data["description"], |
|
|
|
# "children": [], |
|
|
|
# "format": project_data["format"] |
|
|
|
# } |
|
|
|
# children = project_data.get("children", []) |
|
|
|
# for child in children: |
|
|
|
# include = False |
|
|
|
# for filter_text in filters: |
|
|
|
# if filter_text.lower() in child["name"].lower(): |
|
|
|
# include = True |
|
|
|
# break |
|
|
|
# if include: |
|
|
|
# filtered_project["children"].append(filter_project_any(child, filters)) |
|
|
|
# return filtered_project |
|
|
|
|
|
|
|
def filter_project_any(project_data, filters, include_headers=False): |
|
|
|
include = False |
|
|
|
if include_headers: |
|
|
|
if project_data["format"] == "h1" or project_data["format"] == "h2": |
|
|
|
include = True |
|
|
|
for filter_text in filters: |
|
|
|
if filter_text in project_data["name"]: |
|
|
|
include = True |
|
|
|
break |
|
|
|
|
|
|
|
children = [] |
|
|
|
for child in project_data.get("children", []): |
|
|
|
child = filter_project_any(child, filters, include_headers=include_headers) |
|
|
|
if child: |
|
|
|
children.append(child) |
|
|
|
|
|
|
|
project_data["children"] = children |
|
|
|
|
|
|
|
if include or children: |
|
|
|
return project_data |
|
|
|
else: |
|
|
|
return None |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def filter_project_all(project_data, filters): # {{{ |
|
|
|
include = True |
|
|
|
for filter_text in filters: |
|
|
|
if filter_text not in project_data["name"]: |
|
|
|
include = False |
|
|
|
break |
|
|
|
|
|
|
|
children = [] |
|
|
|
for child in project_data.get("children", []): |
|
|
|
child = filter_project_all(child, filters) |
|
|
|
if child: |
|
|
|
children.append(child) |
|
|
|
|
|
|
|
project_data["children"] = children |
|
|
|
|
|
|
|
if include or children: |
|
|
|
return project_data |
|
|
|
else: |
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def strip(project_data, regex): # {{{ |
|
|
|
project_data["name"] = re.sub(regex, "", project_data["name"]) |
|
|
|
children = project_data.get("children", []) |
|
|
|
for child in children: |
|
|
|
strip(child, regex) |
|
|
|
return project_data |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def rstrip(project_data): # {{{ |
|
|
|
project_data["name"] = project_data["name"].rstrip() |
|
|
|
children = project_data.get("children", []) |
|
|
|
for child in children: |
|
|
|
rstrip(child) |
|
|
|
return project_data |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
@ -311,7 +424,16 @@ highlights = { |
|
|
|
"@a": "red", |
|
|
|
"@b": "blue", |
|
|
|
"@c": "green", |
|
|
|
"@d": "violet" |
|
|
|
"@d": "violet", |
|
|
|
"@done": "green", |
|
|
|
"@missed": "red", |
|
|
|
"@na": "blue", |
|
|
|
"@one": "base01", |
|
|
|
"@two": "base01", |
|
|
|
"@three": "base01", |
|
|
|
"@five": "base01", |
|
|
|
"@eight": "base01", |
|
|
|
"@thirteen": "base01", |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -326,45 +448,108 @@ def highlight(project_data): # {{{ |
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
colors1 = { |
|
|
|
"xnext": "blue", |
|
|
|
"xtimed": "orange", |
|
|
|
"xwhenever": "orange", |
|
|
|
"xwaiting": "cyan", |
|
|
|
"xmaybe": "violet", |
|
|
|
"xrepeat": "green", |
|
|
|
"xscheduled": "red", |
|
|
|
"xblocked": "red", |
|
|
|
"xvisualisation": "red", |
|
|
|
"xactive": "underline magenta", |
|
|
|
"xhold": "underline orange", |
|
|
|
"xsprint": "violet", |
|
|
|
"xstory": "cyan", |
|
|
|
"xfuture": "yellow", |
|
|
|
"xarchive": "underline base01", |
|
|
|
"xsomeday": "underline violet" |
|
|
|
} |
|
|
|
|
|
|
|
colors2 = { |
|
|
|
"@done": "strike base01", |
|
|
|
"@missed": "strike base01", |
|
|
|
"@na": "strike base01", |
|
|
|
} |
|
|
|
|
|
|
|
colors3 = { |
|
|
|
# "xactive": "underline", |
|
|
|
# "xhold": "underline", |
|
|
|
# "xarchive": "underline", |
|
|
|
# "xsomeday": "underline", |
|
|
|
# "@done": "strike", |
|
|
|
# "@missed": "strike", |
|
|
|
# "@na": "strike", |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def recolor(project_data, colors): |
|
|
|
for key, value in colors.items(): |
|
|
|
if key in project_data["name"]: |
|
|
|
if key.startswith("x"): |
|
|
|
project_data["name"] = f"[{value}]{project_data['name']} [/][base01]{key}[/]" |
|
|
|
else: |
|
|
|
project_data["name"] = f"[{value}]{project_data['name']}[/]" |
|
|
|
break |
|
|
|
children = project_data.get("children", []) |
|
|
|
for child in children: |
|
|
|
recolor(child, colors) |
|
|
|
return project_data |
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def print_pretty(data, indent=0, color="grey"): # {{{ |
|
|
|
for item in data["children"]: |
|
|
|
console.print(" " * indent + f"[white]•[/] [{color}]{item['name']}[/]") |
|
|
|
if item["children"]: |
|
|
|
print_pretty(item["children"], indent + 1) |
|
|
|
try: |
|
|
|
# href = f"https://workflowy.com/#/{data['id'].split('-')[4]}" |
|
|
|
for item in data["children"]: |
|
|
|
if item["format"] == "h1" or item["format"] == "h2": |
|
|
|
console.print("") |
|
|
|
console.print(" " * indent + f"[base3]•[/] [base3][underline]{item['name']}[/][/]") |
|
|
|
else: |
|
|
|
console.print(" " * indent + f"[base3]•[/] [{color}]{item['name']}[/]") |
|
|
|
if item["children"]: |
|
|
|
print_pretty(item, indent + 1, color) |
|
|
|
except Exception as e: |
|
|
|
console.log(f"data: {data} {e}") |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def find_project_by_id(project_data, target_id): # {{{ |
|
|
|
def find_project_by_id(project_data, full_data, target_id): # {{{ |
|
|
|
if project_data.get("id") == target_id: |
|
|
|
return project_data |
|
|
|
return project_data, full_data |
|
|
|
|
|
|
|
for child in project_data.get("ch", []): |
|
|
|
result = find_project_by_id(child, target_id) |
|
|
|
result, full_data = find_project_by_id(child, full_data, target_id) |
|
|
|
if result: |
|
|
|
return result |
|
|
|
return None |
|
|
|
return result, full_data |
|
|
|
return None, full_data |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
def show(parent_id, flat=False, filter_all=None, filter_any=None, color="grey"): # {{{ |
|
|
|
def show(parent_id, flat=False, filter_all=None, filter_any=None, color="grey", follow_mirrors=False, include_headers=False): # {{{ |
|
|
|
root_data = load_from_storage("root") |
|
|
|
project_data = find_project_by_id(root_data, parent_id) |
|
|
|
project_data = simplify_project(project_data) |
|
|
|
project_data, root_data = find_project_by_id(root_data, root_data, parent_id) |
|
|
|
project_data, root_data = simplify_project(project_data, root_data, follow_mirrors=follow_mirrors) |
|
|
|
if flat: |
|
|
|
project_data = flatten_project(project_data) |
|
|
|
if filter_all is not None: |
|
|
|
pass |
|
|
|
if filter_any is not None: |
|
|
|
project_data = filter_project_any(project_data, filter_any) |
|
|
|
project_data = filter_project_any(project_data, filter_any, include_headers=include_headers) |
|
|
|
project_data = rstrip(project_data) |
|
|
|
project_data = recolor(project_data, colors2) |
|
|
|
project_data = recolor(project_data, colors3) |
|
|
|
project_data = recolor(project_data, colors1) |
|
|
|
project_data = strip(project_data, r"<a href=\".*\">.*</a>") |
|
|
|
project_data = strip(project_data, r"<time .*</time>") |
|
|
|
project_data = strip(project_data, r"<span[^>]*>") |
|
|
|
project_data = strip(project_data, r"</span>") |
|
|
|
project_data = remove_double_spaces(project_data) |
|
|
|
project_data = strip(project_data, r"<[^>]*>") |
|
|
|
project_data = strip(project_data, r"</[^>]*>") |
|
|
|
project_data = highlight(project_data) |
|
|
|
console.print(f"\n[white][bold]{project_data['name']}[/][/]\n") |
|
|
|
project_data = remove_double_spaces(project_data) |
|
|
|
console.print(f"\n[base3][bold]{project_data['name']}[/][/]\n") |
|
|
|
print_pretty(project_data, color=color) |
|
|
|
console.print("") |
|
|
|
return True |
|
|
@ -386,19 +571,18 @@ def main(): # {{{ |
|
|
|
|
|
|
|
subparsers = parser.add_subparsers(dest="command", required=True) |
|
|
|
|
|
|
|
subparsers.add_parser("dump", help="Dump storage") |
|
|
|
|
|
|
|
inbox_parser = subparsers.add_parser("inbox", help="Inbox commands") |
|
|
|
inbox_parser.add_argument("name", help="Item text", nargs="*") |
|
|
|
|
|
|
|
subparsers.add_parser("vis", help="Visualisations") |
|
|
|
subparsers.add_parser("active", help="Active projects") |
|
|
|
subparsers.add_parser("hold", help="Hold projects") |
|
|
|
subparsers.add_parser("sprint", help="Sprint goals") |
|
|
|
subparsers.add_parser("today", help="Today's tasks") |
|
|
|
subparsers.add_parser("habit", help="Habits") |
|
|
|
subparsers.add_parser("story", help="Stories") |
|
|
|
subparsers.add_parser("future", help="Futures") |
|
|
|
tasks_parser = subparsers.add_parser("tasks", help="Tasks commands") |
|
|
|
tasks_parser.add_argument("filter", help="Filter text", nargs="*", default=None) |
|
|
|
|
|
|
|
subparsers.add_parser("dump", help="Dump storage") |
|
|
|
|
|
|
|
for planner_name, planner_id in PLANNER_IDS.items(): |
|
|
|
planner_parser = subparsers.add_parser(planner_name, help=f"{planner_name.capitalize()} commands") |
|
|
|
planner_parser.add_argument("filter", help="Filter text", nargs="*", default=None) |
|
|
|
|
|
|
|
args = parser.parse_args() |
|
|
|
|
|
|
@ -416,32 +600,24 @@ def main(): # {{{ |
|
|
|
else: |
|
|
|
show(INBOX_ID) |
|
|
|
|
|
|
|
if args.command == "vis": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xvisualisation"], color="red") |
|
|
|
|
|
|
|
if args.command == "active": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xactive"], color="magenta") |
|
|
|
|
|
|
|
if args.command == "hold": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xhold"], color="orange") |
|
|
|
|
|
|
|
if args.command == "sprint": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xsprint"], color="violet") |
|
|
|
|
|
|
|
if args.command == "today": |
|
|
|
today = get_today() |
|
|
|
show(PLANNER_ID, flat=True, filter_any=[today], color="blue") |
|
|
|
|
|
|
|
if args.command == "habits": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xtimed", "xwhenever"], color="orange") |
|
|
|
|
|
|
|
if args.command == "story": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xstory"], color="cyan") |
|
|
|
if args.command == "tasks": |
|
|
|
if args.filter: |
|
|
|
if args.filter == "today": |
|
|
|
show(TASKS_ID, filter_any=[get_today()], flat=True, follow_mirrors=True, include_headers=True) |
|
|
|
else: |
|
|
|
show(TASKS_ID, filter_any=args.filter, flat=True, follow_mirrors=True, include_headers=True) |
|
|
|
else: |
|
|
|
show(TASKS_ID, follow_mirrors=True) |
|
|
|
|
|
|
|
if args.command == "future": |
|
|
|
show(PLANNER_ID, flat=True, filter_any=["xfuture"], color="yellow") |
|
|
|
for planner_name, PLANNER_ID in PLANNER_IDS.items(): |
|
|
|
if args.command == planner_name: |
|
|
|
if args.filter: |
|
|
|
show(PLANNER_ID, filter_any=args.filter) |
|
|
|
else: |
|
|
|
show(PLANNER_ID) |
|
|
|
|
|
|
|
# }}} |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
main() |
|
|
|