# HG changeset patch # User Sietse Brouwer # Date 1590145227 -7200 # Fri May 22 13:00:27 2020 +0200 # Node ID ad995b7166e82f6ff907009579502e2b0e160a96 # Parent 7b79f3a173bd4c467e00507b51544a9be3433c80 Keep config items in an external file; add a parser for that file diff --git a/check.py b/check.py --- a/check.py +++ b/check.py @@ -2,8 +2,11 @@ import curses import re +import sys + +from collections import defaultdict from enum import Enum, unique -from pprint import pformat +from pprint import pformat, pprint TASKS = [ @@ -27,6 +30,103 @@ 'Plan day: put TODOs into worklog, distribute over hours', ] +def parse_config_list(config_list, filename): + """Parse a list of config file lines into a dict of sections, one list of + items per section. + + >>> pprint(dict(parse_config_list([ + ... '[morning]\\n', + ... 'one\\n', + ... 'two\\n', + ... '', + ... '[evening]', + ... 'a', + ... '', # Empty lines within sections are preserved + ... 'b', + ... '', # empty lines between sections are ignored + ... '[default]', + ... 'evening' + ... ], 'myfilename'))) + ... + {'default': ['a', '', 'b'], + 'evening': ['a', '', 'b'], + 'morning': ['one', 'two']} + """ + + no_newlines = (x.rstrip('\r\n') for x in config_list) + + def is_section(line: str) -> bool: + return 3 <= len(line) and line[0] == '[' and line[-1] == ']' + + class State: + name = 'start' + section = None # Name of the current section + first_section = None + empty_lines_stack = [] + + result = defaultdict(list) + for i, line in enumerate(no_newlines): + if len(line.lstrip()) and line.lstrip()[0] == '#': + # Ignore comment lines + continue + + elif State.name == 'start': + if len(line) == 0: + continue + elif is_section(line): + # start a new section list + State.section = line[1:-1] + State.name = 'section' + continue + else: + raise ValueError(f"{filename}:{i}:Every checklist in the config must have " + "a header like '[my checklist name]'.") + + elif State.name == 'section': + if len(line) == 0: + # Save the empty lines: they are saved within a checklist, but + # discarded outside one. + State.empty_lines_stack.append(line) + continue + elif is_section(line): + # Start a new section + State.empty_lines_stack.clear() + State.section = line[1:-1] + continue + else: + # New checklist item + result[State.section].extend(State.empty_lines_stack) + State.empty_lines_stack.clear() + result[State.section].append(line) + continue + + if len(result) == 0: + raise ValueError('Input contains no checklists') + + if 'default' not in result: + return result + + if not len(result['default']): + raise ValueError("[default] section contains no section name") + + default_section = result['default'][-1] + + if default_section not in result: + raise ValueError(f"Could not find specified default section " + "[{result['default']}].") + + # replace "default: "default_section's_name" + # with "default": [default, section's, list] + result['default'] = result[default_section] + return result + + +def parse_config_file(filename): + with open(filename) as f: + config_list = f.readlines() + + return parse_config_list(config_list, filename=filename) + @unique class Debug(Enum): Show = 1 @@ -135,9 +235,9 @@ pass -def run(t, styles): +def run(t, styles, tasks): state = { - 'tasks': [(False, t) for t in TASKS], + 'tasks': [(False, t) for t in tasks], 'current_task_id': 0, 'running': True, 'debug': Debug.Hidden, @@ -151,10 +251,25 @@ if not state['running']: break +def inner_main(args): + checklists = parse_config_file('/home/sietse/.config/checklists') + desired_checklist = args[1] if 2 <= len(args) else 'default' + if desired_checklist not in checklists: + print("Checklist not found: {desired_checklist}.") + print("Available checklists: {', '.join(checklists.keys())}") + sys.exit(1) + tasks = checklists[desired_checklist] + with TerminalAndStyles() as (t, styles): + run(t, styles, tasks) + def main(): - with TerminalAndStyles() as (t, styles): - run(t, styles) + import doctest + failure_count, test_count = doctest.testmod(sys.modules[__name__]) + if failure_count: + sys.exit(1) + inner_main(sys.argv) + if __name__ == '__main__': main()