Skip to content

Commit

Permalink
feat: add 'edit' command
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalinDe committed Dec 3, 2023
1 parent d699871 commit 053e50d
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 24 deletions.
61 changes: 56 additions & 5 deletions pusteblume/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
_RESERVED_CHARS = "[]"


def _name(string):
def name(string):
"""Assert string matches 'name' pattern.
:param str string: string
Expand All @@ -56,7 +56,7 @@ def _name(string):
return string


def _tag(string):
def tag(string):
"""Assert string matches 'tag' pattern.
:param str string: string
Expand All @@ -67,7 +67,7 @@ def _tag(string):
:rtype: str
"""
if match := re.match(r"\[(.+?)\]", string):
return _name(match.group(1))
return name(match.group(1))
raise argparse.ArgumentTypeError(
pusteblume.errors.ERRORS["cli"]["invalid_tag"].format(string=string),
)
Expand Down Expand Up @@ -106,6 +106,42 @@ def split(argv):
return args


def parse_input(input_, type_, choices=()):
"""Parse input.
:param str input_: input
:param str type_: type
:param list choices: choices
:returns: parsed input
:rtype: argparse.Namespace
"""
parser = argparse.ArgumentParser(
prog="",
add_help=False,
)
if type_ == "name":
parser.add_argument(
type_,
type=name,
)
input_ = [input_]
if type_ == "tag":
parser.add_argument(
type_,
nargs="*",
type=tag,
)
input_ = [tag + "]" for tag in input_.split("]") if tag]
if type_ == "choice":
parser.add_argument(
type_,
choices=choices,
)
input_ = [input_]
return parser.parse_args(input_)


def init_argument_parser():
"""Initialize argument parser.
Expand Down Expand Up @@ -143,12 +179,12 @@ def _init_subparsers(parser):
"help": pusteblume.messages.MESSAGES["cli"]["help"]["start"],
"arguments": {
"name": {
"type": _name,
"type": name,
"help": pusteblume.messages.MESSAGES["cli"]["help"]["name"],
},
"tags": {
"nargs": "*",
"type": _tag,
"type": tag,
"help": pusteblume.messages.MESSAGES["cli"]["help"]["tags"],
},
},
Expand All @@ -164,6 +200,21 @@ def _init_subparsers(parser):
"arguments": {},
"func": pusteblume.tasks.status,
},
"edit": {
"help": pusteblume.messages.MESSAGES["cli"]["help"]["edit"],
"arguments": {
"name": {
"type": name,
"help": pusteblume.messages.MESSAGES["cli"]["help"]["name"],
},
"tags": {
"nargs": "*",
"type": tag,
"help": pusteblume.messages.MESSAGES["cli"]["help"]["tags"],
},
},
"func": pusteblume.tasks.edit,
},
}
subparsers = parser.add_subparsers()
for subcommand in subcommands:
Expand Down
9 changes: 9 additions & 0 deletions pusteblume/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
MESSAGES = {
"tasks": {
"no_running_task": "no running task",
"edit": {
"task": "Editing task '{task}'...",
"tasks": "Which task do you want to edit?",
"no_task": "'{task}' does not exist",
"attribute": "Which attribute do you want to edit?",
"new_attr": "What is the new value of {attribute}?",
"new_value": "The new value of {attribute} is {value}",
},
},
"config": {
"default": "generated default configuration file {config_file}",
Expand All @@ -39,6 +47,7 @@
"start": "start task",
"stop": "stop task",
"status": "show currently running task if any",
"edit": "edit task",
"name": "task name, e.g. 'debug command-line interface'",
"tags": "tag(s), e.g. '[v1.2]'",
},
Expand Down
173 changes: 155 additions & 18 deletions pusteblume/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

# third party imports
# library specific imports
import pusteblume.cli
import pusteblume.messages


Expand Down Expand Up @@ -246,6 +247,66 @@ def _get_currently_running_task(config):
)


def _get_task(config, name, tags):
"""Get task by name and (if applicable) tags.
:param configparser.ConfigParser config: configuration
:param str name: name
:param tuple tags: tags
:returns: task(s)
:rtype: generator
"""
for task_id, start_time, end_time in _execute(
config,
"SELECT id,start_time,end_time FROM task WHERE name = ?",
(name,),
):
if _get_related_tags(config, task_id) == tags:
yield (
task_id,
Task(
name,
tags,
(start_time, end_time),
),
)
else:
yield (task_id, Task(name, (), (start_time, end_time)))


def _input(prompt):
"""Wrapper around built-in input function."""
return input(prompt)


def _insert_tag(config, tag, task_id):
"""Insert tag.
:param configparser.ConfigParser config: configuration
:param str tag: tag
:param int task_id: task ID
"""
rows = _execute(
config,
"SELECT id FROM tag WHERE name = ?",
(tag,),
)
if not rows:
((tag_id,),) = _execute(
config,
"INSERT INTO tag(name) VALUES(?) RETURNING id",
(tag,),
)
else:
((tag_id,),) = rows
_execute(
config,
"INSERT INTO added_to(task_id,tag_id) VALUES(?,?)",
(task_id, tag_id),
)


def start(config, name=None, tags=tuple()):
"""Start task.
Expand All @@ -264,24 +325,7 @@ def start(config, name=None, tags=tuple()):
(name, start_time),
)
for tag in tags:
rows = _execute(
config,
"SELECT id FROM tag WHERE name = ?",
(tag,),
)
if not rows:
((tag_id,),) = _execute(
config,
"INSERT INTO tag(name) VALUES(?) RETURNING id",
(tag,),
)
else:
((tag_id,),) = rows
_execute(
config,
"INSERT INTO added_to(task_id,tag_id) VALUES(?,?)",
(task_id, tag_id),
)
_insert_tag(config, tag, task_id)
return os.linesep.join(
(output, Task(name, tags, (start_time, None)).pprinted_short)
)
Expand Down Expand Up @@ -355,3 +399,96 @@ def status(config):
_get_related_tags(config, task_id),
(start_time, None),
).pprinted_short


def _select(config, prompt, choices):
"""Let the user choose from a list.
:param configparser.ConfigParser config: configuration
:param str prompt: prompt
:param list choices: choices
:returns: choice
:rtype: int
"""
return pusteblume.cli.parse_input(
_input(
os.linesep.join(
(
prompt,
*(
f"{i}. {choice}"
for (i, choice) in enumerate(choices, start=1)
),
),
),
),
"choice",
choices=[str(i) for i in range(1, len(choices) + 1)],
).choice


def edit(config, name=None, tags=tuple()):
"""Edit task.
:param configparser.ConfigParser config: configuration
:param str name: task name
:param tuple tags: tag(s)
:returns: output
:rtype: str
"""
tasks = list(_get_task(config, name, tags))
if not tasks:
return pusteblume.messages.MESSAGES["tasks"]["edit"]["no_task"].format(
task=Task(name, tags, (None, None)).pprinted_short,
)
if len(tasks) > 1:
task_id, task = tasks[
_select(
config,
pusteblume.messages.MESSAGES["tasks"]["edit"]["tasks"],
(task.pprinted_medium for _, task in tasks),
) - 1
]
else:
task_id, task = tasks[0]
print(
pusteblume.messages.MESSAGES["tasks"]["edit"]["task"].format(
task=task.pprinted_medium,
)
)
attrs = {
"name": pusteblume.cli.name,
"tag": pusteblume.cli.tag,
}
attr = list(attrs.keys())[
int(
_select(
config,
pusteblume.messages.MESSAGES["tasks"]["edit"]["attribute"],
list(attrs.keys()),
)
) - 1
]
new_value = _input(
pusteblume.messages.MESSAGES["tasks"]["edit"]["new_attr"].format(
attribute=attr,
),
)
parsed_input = pusteblume.cli.parse_input(new_value, attr)
if attr == "name":
_execute(
config,
f"UPDATE task SET {attr} = ? WHERE id = ?",
(parsed_input.name, task_id),
)
if attr == "tag":
for tag in parsed_input.tag:
_insert_tag(config, tag, task_id)
print(
pusteblume.messages.MESSAGES["tasks"]["edit"]["new_value"].format(
attribute=attr,
value=new_value,
),
)
13 changes: 13 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,16 @@ def test_status(self):
"""
args = ["status"]
self.assertListEqual(pusteblume.cli.split(args), args)

def test_edit(self):
"""Test splitting 'edit' subcommand.
Trying: split 'edit' subcommand
Expecting: 'edit' subcommand and its command-line arguments
"""
args = ["edit", "write test cases"]
# name
self.assertListEqual(pusteblume.cli.split(args), args)
# name and tags
args = [*args, "[pusteblume]", "[v1.2]"]
self.assertListEqual(pusteblume.cli.split(args), args)
Loading

0 comments on commit 053e50d

Please sign in to comment.