From 68915c9d2fd17f7ede3332eba5277b353696c02a Mon Sep 17 00:00:00 2001 From: nojhan Date: Fri, 28 Jul 2023 12:08:36 +0200 Subject: [PATCH] feat: add `status` - real .klyban.csv - real .klyban.conf --- .klyban.conf | 15 ++++++++++ .klyban.csv | 14 ++++------ klyban.py | 79 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 .klyban.conf diff --git a/.klyban.conf b/.klyban.conf new file mode 100644 index 0000000..e1622f4 --- /dev/null +++ b/.klyban.conf @@ -0,0 +1,15 @@ + +[options] +status_key = STATUS +id_key = ID +title_key = TITLE +details_key = DETAILS +tags_key = TAGS +deadline_key = DEADLINE +show_keys = ID,TITLE,DETAILS,DEADLINE,TAGS + +[options.add] +details = "" +tags = "" +deadline = "" +status = TODO diff --git a/.klyban.csv b/.klyban.csv index 70580c7..41d9523 100644 --- a/.klyban.csv +++ b/.klyban.csv @@ -1,9 +1,5 @@ -"ID","STATUS","TITLE","DETAILS","TAGS","DEADLINE" -1,"DOING","print card","pretty print fixed-width cards given a content","klyban","" -2,"TODO","print table","pretty print set of cards on each column","klyban","" -3,"TODO","nested prints","print cards within cards","klyban","" -4,"TODO","a test","","","" -5,"TODO","another test","","","" -6,"TODO","another test","","","" -7,"TODO","anothering test","","","" -8,"TODO","anothering test","","","" +"ID","STATUS","TITLE","DETAILS","TAGS","DEADLINE","TOUCHED" +0,"TODO","Use click-option-group","To help sort options in categories in help.","","","2023-07-28T12:04:02.615501" +1,"TODO","Use click-aliases","To allow for aliases (TBC: user-defined in config file?)","","","2023-07-28T12:05:04.229519" +2,"TODO","edit existing","When calling edit, populate defaults with existing data.","","","2023-07-28T12:07:08.177802" +3,"TODO","sanity checks","Check data consistency in load_data and save_data.","","","2023-07-28T12:08:10.272349" diff --git a/klyban.py b/klyban.py index 74c2e18..9f8bed4 100644 --- a/klyban.py +++ b/klyban.py @@ -38,14 +38,18 @@ def load_data(context): context.obj['deadline_key'], context.obj['touched_key'], ]) + df = df.set_index(context.obj['id_key']) save_data(context, df) - # set index on TID. - df = df.astype({context.obj['id_key']:int}).set_index(context.obj['id_key']) - if context.obj['debug']: - print("Loaded:") - print(df) - return df + else: + # set index on ID. + df = df.astype({context.obj['id_key']:int}).set_index(context.obj['id_key']) + + finally: + if context.obj['debug']: + print("Loaded:") + print(df) + return df def save_data(context, df): @@ -54,7 +58,7 @@ def save_data(context, df): print(df) # FIXME double check that there are actually data. - # Bring back TID as a regular column. + # Bring back ID as a regular column. df = df.reset_index() # Automagically manages standard input if input=="-", thanks to allow_dash=True. @@ -87,7 +91,7 @@ def configure(context, param, filename): callback = configure, is_eager = True, expose_value = False, - help = 'Read option defaults from the specified configuration file', + help = 'Read option defaults from the specified configuration file.', show_default = True, ) @click.option('-i', '--input' , help="CSV data file.", default='.klyban.csv', type=click.Path(writable=True, readable=True, allow_dash=True), show_default=True) @@ -176,7 +180,10 @@ def show(context): def add(context, title, status, details, tags, deadline): """Add a new task.""" df = load_data(context) - next_id = df.index.max() + 1 + if df.index.empty: + next_id = 0 + else: + next_id = df.index.max() + 1 df.loc[next_id] = pd.Series({ context.obj['status_key']: status, context.obj['title_key']: " ".join(title), @@ -246,17 +253,49 @@ def delete(context, tid): context.invoke(show) + +def change_status(context, tid, new_status): + """Edit the status of a task.""" + df = load_data(context) + + row = df.loc[tid] + if row.empty: + error("ID_NOT_FOUND", "{} = {} not found in `{}`".format(context.obj['id_key'], tid, context.obj['input'])) + + if new_status not in context.obj['status_list']: + error("UNKNOWN_STATUS", "Unknown status `{}`".format(new_status)) + else: + df.loc[tid, context.obj['status_key']] = new_status + df.loc[tid, context.obj['touched_key']] = datetime.datetime.now().isoformat() + + save_data(context, df) + + @cli.command() -@click.argument('TID', required=True, type=int) +@click.argument('TID', required=True, type=int, is_eager=True, callback=check_id) +@click.argument('STATUS', required=True, type=str) +@click.pass_context +def status(context, tid, status): + """Explicitely change the status of a task. + + Use status names configured with --status-list.""" + + change_status(context, tid, status) + + context.invoke(show) + + +@cli.command() +@click.argument('TID', required=True, type=int, is_eager=True, callback=check_id) @click.pass_context def promote(context, tid): - """Upgrade the status of task `TID` to the next one. + """Upgrade the status of a task to the next one. Use status names configured with --status-list.""" df = load_data(context) - row = df.loc[ df[context.obj['id_key']] == tid ] + row = df.loc[tid] if row.empty: error("ID_NOT_FOUND", "{} = {} not found in `{}`".format(context.obj['id_key'], tid, context.obj['input'])) @@ -269,25 +308,22 @@ def promote(context, tid): if i >= len(context.obj['status_list'])-1: error("UNKNOWN_STATUS", "Cannot promote task {}, already at the last status.".format(tid)) else: - df.loc[df[context.obj['id_key']] == tid, context.obj['status_key']] = context.obj['status_list'][i+1] - df.loc[df[context.obj['id_key']] == tid, context.obj['touched_key']] = datetime.datetime.now().isoformat() - - save_data(context, df) + change_status(context, tid, context.obj['status_list'][i+1]) context.invoke(show) @cli.command() -@click.argument('TID', required=True, type=int) +@click.argument('TID', required=True, type=int, is_eager=True, callback=check_id) @click.pass_context def demote(context, tid): - """Downgrade the status of task `TID` to the previous one. + """Downgrade the status of a task to the previous one. Use status names configured with --status-list.""" df = load_data(context) - row = df.loc[ df[context.obj['id_key']] == tid ] + row = df.loc[tid] if row.empty: error("ID_NOT_FOUND", "{} = {} not found in `{}`".format(context.obj['id_key'], tid, context.obj['input'])) @@ -300,10 +336,7 @@ def demote(context, tid): if i == 0: error("UNKNOWN_STATUS", "Cannot demote task {}, already at the first status.".format(tid)) else: - df.loc[df[context.obj['id_key']] == tid, context.obj['status_key']] = context.obj['status_list'][i-1] - df.loc[df[context.obj['id_key']] == tid, context.obj['touched_key']] = datetime.datetime.now().isoformat() - - save_data(context, df) + change_status(context, tid, context.obj['status_list'][i-1]) context.invoke(show)