feat: add 'filter' and 'find'
- Allow configuration of hint marks. - Add a 'matching' style to themes.
This commit is contained in:
parent
94118b0c2c
commit
9428e7e326
1 changed files with 86 additions and 23 deletions
109
klyban.py
109
klyban.py
|
|
@ -130,11 +130,12 @@ def check_id(context, param, value):
|
||||||
)
|
)
|
||||||
@click.option('-i', '--input' , help="CSV data file.", default='.klyban.csv', type=click.Path(writable=True, readable=True, allow_dash=True), 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)
|
||||||
# Display options.
|
# Display options.
|
||||||
@click.option('-H','--show-headers', is_flag=True, help="Show the headers.")
|
@click.option('-h','--show-headers', is_flag=True, help="Show the headers.")
|
||||||
@click.option('-S', '--show-keys' , default='ID,TITLE,DETAILS,TAGS', type=str , show_default=True, help="Comma-separated, ordered list of fields that should be shown (use 'all' for everything).")
|
@click.option('-s', '--show-keys' , default='ID,TITLE,DETAILS,TAGS', type=str , show_default=True, help="Comma-separated, ordered list of fields that should be shown (use 'all' for everything).")
|
||||||
@click.option('-G', '--highlight', type = int, default = None, help="Highlight a specific task.")
|
@click.option('-g', '--highlight', type = int, default = None, help="Highlight a specific task.")
|
||||||
@click.option('-L', '--layout', type = click.Choice(['vertical-compact', 'vertical-fancy']), default = 'vertical-compact', help="How to display tasks.") # TODO , 'horizontal-compact', 'horizontal-fancy'
|
@click.option('--highlight-mark', type = str, default = ':arrow_forward:', help="String used to highlight a specific task.")
|
||||||
@click.option('-T', '--theme', type = click.Choice(['none', 'user', 'BW', 'BY', 'RW', 'nojhan'], case_sensitive=False), default = 'none', help="How to display tasks.")
|
@click.option('-l', '--layout', type = click.Choice(['vertical-compact', 'vertical-fancy']), default = 'vertical-compact', help="How to display tasks.") # TODO , 'horizontal-compact', 'horizontal-fancy'
|
||||||
|
@click.option('-t', '--theme', type = click.Choice(['none', 'user', 'BW', 'BY', 'RW', 'nojhan'], case_sensitive=False), default = 'none', help="How to display tasks.")
|
||||||
# Low-level configuration options.
|
# Low-level configuration options.
|
||||||
@click.option('--status-key' , default='STATUS' , type=str, show_default=True, help="Header key defining the status of tasks.")
|
@click.option('--status-key' , default='STATUS' , type=str, show_default=True, help="Header key defining the status of tasks.")
|
||||||
@click.option('--status-list' , default='TODO,DOING,HOLD,DONE', type=str, show_default=True, help="Comma-separated, ordered list of possible values for the status of tasks.")
|
@click.option('--status-list' , default='TODO,DOING,HOLD,DONE', type=str, show_default=True, help="Comma-separated, ordered list of possible values for the status of tasks.")
|
||||||
|
|
@ -153,26 +154,37 @@ def cli(context, **kwargs):
|
||||||
# Ensure that context.obj exists and is a dict.
|
# Ensure that context.obj exists and is a dict.
|
||||||
context.ensure_object(dict)
|
context.ensure_object(dict)
|
||||||
|
|
||||||
context.obj['input'] = kwargs['input']
|
def store(context_key, kw_key = None):
|
||||||
|
if not kw_key:
|
||||||
|
kw_key = context_key
|
||||||
|
context.obj[context_key] = kwargs[kw_key]
|
||||||
|
|
||||||
context.obj['id_key'] = kwargs['id_key']
|
store('input')
|
||||||
context.obj['status_key'] = kwargs['status_key']
|
|
||||||
context.obj['title_key'] = kwargs['title_key']
|
|
||||||
context.obj['details_key'] = kwargs['details_key']
|
|
||||||
context.obj['tags_key'] = kwargs['tags_key']
|
|
||||||
context.obj['deadline_key'] = kwargs['deadline_key']
|
|
||||||
context.obj['touched_key'] = kwargs['touched_key']
|
|
||||||
|
|
||||||
context.obj['show_headers'] = kwargs['show_headers']
|
store('debug')
|
||||||
context.obj['highlight'] = kwargs['highlight']
|
|
||||||
context.obj['layout'] = kwargs['layout']
|
store('id_key')
|
||||||
|
store('status_key')
|
||||||
|
store('title_key')
|
||||||
|
store('details_key')
|
||||||
|
store('tags_key')
|
||||||
|
store('deadline_key')
|
||||||
|
store('touched_key')
|
||||||
|
|
||||||
|
store('show_headers')
|
||||||
|
store('highlight')
|
||||||
|
store('highlight_mark')
|
||||||
|
|
||||||
|
store('layout')
|
||||||
context.obj['layouts'] = {
|
context.obj['layouts'] = {
|
||||||
'vertical-compact': VerticalCompact,
|
'vertical-compact': VerticalCompact,
|
||||||
'vertical-fancy': VerticalFancy,
|
'vertical-fancy': VerticalFancy,
|
||||||
}
|
}
|
||||||
|
|
||||||
context.obj['themes'] = {
|
context.obj['themes'] = {
|
||||||
'none': Theme({
|
'none': Theme({
|
||||||
'H': '',
|
'H': '',
|
||||||
|
'matching': 'italic',
|
||||||
context.obj['id_key']: '',
|
context.obj['id_key']: '',
|
||||||
context.obj['status_key']: '',
|
context.obj['status_key']: '',
|
||||||
context.obj['title_key']: '',
|
context.obj['title_key']: '',
|
||||||
|
|
@ -185,6 +197,7 @@ def cli(context, **kwargs):
|
||||||
}),
|
}),
|
||||||
'BW': Theme({
|
'BW': Theme({
|
||||||
'H': 'white',
|
'H': 'white',
|
||||||
|
'matching': 'italic',
|
||||||
context.obj['id_key']: 'bold black',
|
context.obj['id_key']: 'bold black',
|
||||||
context.obj['status_key']: 'bold',
|
context.obj['status_key']: 'bold',
|
||||||
context.obj['title_key']: 'bold white',
|
context.obj['title_key']: 'bold white',
|
||||||
|
|
@ -197,6 +210,7 @@ def cli(context, **kwargs):
|
||||||
}),
|
}),
|
||||||
'BY': Theme({
|
'BY': Theme({
|
||||||
'H': 'color(220)',
|
'H': 'color(220)',
|
||||||
|
'matching': 'italic',
|
||||||
context.obj['id_key']: 'bold color(39)',
|
context.obj['id_key']: 'bold color(39)',
|
||||||
context.obj['status_key']: 'bold color(227)',
|
context.obj['status_key']: 'bold color(227)',
|
||||||
context.obj['title_key']: 'color(220)',
|
context.obj['title_key']: 'color(220)',
|
||||||
|
|
@ -209,6 +223,7 @@ def cli(context, **kwargs):
|
||||||
}),
|
}),
|
||||||
'RW': Theme({
|
'RW': Theme({
|
||||||
'H': 'red',
|
'H': 'red',
|
||||||
|
'matching': 'italic',
|
||||||
context.obj['id_key']: 'bold red',
|
context.obj['id_key']: 'bold red',
|
||||||
context.obj['status_key']: 'bold white',
|
context.obj['status_key']: 'bold white',
|
||||||
context.obj['title_key']: 'bold white',
|
context.obj['title_key']: 'bold white',
|
||||||
|
|
@ -221,6 +236,7 @@ def cli(context, **kwargs):
|
||||||
}),
|
}),
|
||||||
'nojhan': Theme({
|
'nojhan': Theme({
|
||||||
'H': '#4E9A06',
|
'H': '#4E9A06',
|
||||||
|
'matching': 'italic on #464141',
|
||||||
context.obj['id_key']: 'bold color(214)',
|
context.obj['id_key']: 'bold color(214)',
|
||||||
context.obj['status_key']: 'bold italic white',
|
context.obj['status_key']: 'bold italic white',
|
||||||
context.obj['title_key']: 'bold white',
|
context.obj['title_key']: 'bold white',
|
||||||
|
|
@ -251,8 +267,6 @@ def cli(context, **kwargs):
|
||||||
# Always show the 'Hint' column.
|
# Always show the 'Hint' column.
|
||||||
context.obj['show_keys'] = ['H'] + context.obj['show_keys']
|
context.obj['show_keys'] = ['H'] + context.obj['show_keys']
|
||||||
|
|
||||||
context.obj['debug'] = kwargs['debug']
|
|
||||||
|
|
||||||
# At the end, always load data, whatever the command will be.
|
# At the end, always load data, whatever the command will be.
|
||||||
context.obj['data'] = load_data(context)
|
context.obj['data'] = load_data(context)
|
||||||
|
|
||||||
|
|
@ -295,7 +309,7 @@ class Vertical(Layout):
|
||||||
sections = []
|
sections = []
|
||||||
|
|
||||||
if self.context.obj['highlight'] is not None:
|
if self.context.obj['highlight'] is not None:
|
||||||
df.loc[self.context.obj['highlight'], 'H'] = ':arrow_forward:'
|
df.loc[self.context.obj['highlight'], 'H'] = self.contex.obj['highlight_mark']
|
||||||
|
|
||||||
# Group by status.
|
# Group by status.
|
||||||
tables = df.groupby(self.context.obj['status_key'])
|
tables = df.groupby(self.context.obj['status_key'])
|
||||||
|
|
@ -335,7 +349,11 @@ class Vertical(Layout):
|
||||||
|
|
||||||
for i,row in t.iterrows():
|
for i,row in t.iterrows():
|
||||||
items = (str(row[k]) for k in self.context.obj['show_keys'])
|
items = (str(row[k]) for k in self.context.obj['show_keys'])
|
||||||
table.add_row(*items)
|
if row['H']:
|
||||||
|
row_style = 'matching'
|
||||||
|
else:
|
||||||
|
row_style = None
|
||||||
|
table.add_row(*items, style = row_style)
|
||||||
|
|
||||||
self.section_prefix(sections)
|
self.section_prefix(sections)
|
||||||
self.section(section, table, sections)
|
self.section(section, table, sections)
|
||||||
|
|
@ -415,7 +433,6 @@ def show(context, tid):
|
||||||
console.print(panel)
|
console.print(panel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument('TITLE', required=True, nargs=-1)
|
@click.argument('TITLE', required=True, nargs=-1)
|
||||||
@click.option('-d', '--details' , type=str, prompt=True)
|
@click.option('-d', '--details' , type=str, prompt=True)
|
||||||
|
|
@ -506,7 +523,6 @@ def remove(context, tid):
|
||||||
context.invoke(show)
|
context.invoke(show)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def change_status(context, tid, new_status):
|
def change_status(context, tid, new_status):
|
||||||
"""Edit the status of a task."""
|
"""Edit the status of a task."""
|
||||||
df = context.obj['data']
|
df = context.obj['data']
|
||||||
|
|
@ -523,7 +539,6 @@ def change_status(context, tid, new_status):
|
||||||
|
|
||||||
save_data(context, df)
|
save_data(context, df)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.argument('TID', required=True, type=int, is_eager=True, callback=check_id)
|
@click.argument('TID', required=True, type=int, is_eager=True, callback=check_id)
|
||||||
@click.argument('STATUS', required=True, type=str)
|
@click.argument('STATUS', required=True, type=str)
|
||||||
|
|
@ -605,5 +620,53 @@ def config(context):
|
||||||
click.echo(f"Data file: `{context.obj['input']}`")
|
click.echo(f"Data file: `{context.obj['input']}`")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('REGEX', required=True, type=str)
|
||||||
|
@click.pass_context
|
||||||
|
def filter(context, regex):
|
||||||
|
"""Only show the tasks for which showed columns do contains a string matching the given regexp.
|
||||||
|
|
||||||
|
Example: klyban filter '[Aa]nd'"""
|
||||||
|
|
||||||
|
df = context.obj['data']
|
||||||
|
|
||||||
|
# Bring back TID as a regular *string* column.
|
||||||
|
df = df.reset_index().fillna("").astype('string')
|
||||||
|
|
||||||
|
# Filter mask.
|
||||||
|
mask = np.column_stack([ df[col].str.contains(regex, na=False) for col in df[context.obj['show_keys']] ] )
|
||||||
|
|
||||||
|
# Update in context for `show` to see.
|
||||||
|
context.obj['data'] = df.loc[mask.any(axis=1)]
|
||||||
|
|
||||||
|
context.invoke(show)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.argument('REGEX', required=True, type=str)
|
||||||
|
@click.option('-m', '--mark', type = str, default = ':arrow_forward:', help="String used to highlight matching tasks.")
|
||||||
|
@click.pass_context
|
||||||
|
def find(context, regex, mark):
|
||||||
|
"""Point out tasks containing a string matching the given regexp in any of the showed columns.
|
||||||
|
|
||||||
|
Example: klyban find '[Aa]nd'"""
|
||||||
|
|
||||||
|
df = context.obj['data']
|
||||||
|
|
||||||
|
# Bring back TID as a regular *string* column.
|
||||||
|
df = df.reset_index().fillna("").astype('string')
|
||||||
|
|
||||||
|
# Filter mask.
|
||||||
|
mask = np.column_stack([ df[col].str.contains(regex, na=False) for col in df[context.obj['show_keys']] ] )
|
||||||
|
|
||||||
|
# Mark out matching tasks.
|
||||||
|
df.loc[mask.any(axis=1), 'H'] = mark
|
||||||
|
|
||||||
|
# Update in context for `show` to see.
|
||||||
|
context.obj['data'] = df
|
||||||
|
|
||||||
|
context.invoke(show)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
cli(obj={})
|
cli(obj={})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue