Skip to content

Commit

Permalink
Rewrite completions to work with click>=8
Browse files Browse the repository at this point in the history
They changed the api and the were still calling the functions using
the old api style.

Sadly, the new api doesnt make it easy to test. I have tried for a while
but couldnt get anywhere, so now I tested it manually.
  • Loading branch information
voidus committed Jun 6, 2022
1 parent 8ee6ef0 commit 1f5eaeb
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 242 deletions.
146 changes: 4 additions & 142 deletions tests/test_autocompletion.py
Original file line number Diff line number Diff line change
@@ -1,148 +1,10 @@
"""Unit tests for the 'autocompletion' module."""

import json
from argparse import Namespace

import pytest

from watson.autocompletion import (
get_frames,
get_project_or_task_completion,
get_projects,
get_rename_name,
get_rename_types,
get_tags,
)

from . import TEST_FIXTURE_DIR


AUTOCOMPLETION_FRAMES_PATH = TEST_FIXTURE_DIR / "autocompletion"
with open(str(AUTOCOMPLETION_FRAMES_PATH / "frames")) as fh:
N_FRAMES = len(json.load(fh))
N_PROJECTS = 5
N_TASKS = 3
N_VARIATIONS_OF_PROJECT3 = 2
N_FRAME_IDS_FOR_PREFIX = 2

ClickContext = Namespace


@pytest.mark.datafiles(AUTOCOMPLETION_FRAMES_PATH)
@pytest.mark.parametrize(
"func_to_test, rename_type, args",
[
(get_frames, None, []),
(get_project_or_task_completion, None, ["project1", "+tag1"]),
(get_project_or_task_completion, None, []),
(get_projects, None, []),
(get_rename_name, "project", []),
(get_rename_name, "tag", []),
(get_rename_types, None, []),
(get_tags, None, []),
],
)
def test_if_returned_values_are_distinct(
watson_df, func_to_test, rename_type, args
):
ctx = ClickContext(obj=watson_df, params={"rename_type": rename_type})
prefix = ""
ret_list = list(func_to_test(ctx, args, prefix))
assert sorted(ret_list) == sorted(set(ret_list))


@pytest.mark.datafiles(AUTOCOMPLETION_FRAMES_PATH)
@pytest.mark.parametrize(
"func_to_test, n_expected_returns, rename_type, args",
[
(get_frames, N_FRAMES, None, []),
(get_project_or_task_completion, N_TASKS, None, ["project1", "+"]),
(get_project_or_task_completion, N_PROJECTS, None, []),
(get_projects, N_PROJECTS, None, []),
(get_rename_name, N_PROJECTS, "project", []),
(get_rename_name, N_TASKS, "tag", []),
(get_rename_types, 2, None, []),
(get_tags, N_TASKS, None, []),
],
)
def test_if_empty_prefix_returns_everything(
watson_df, func_to_test, n_expected_returns, rename_type, args
):
prefix = ""
ctx = ClickContext(obj=watson_df, params={"rename_type": rename_type})
completed_vals = set(func_to_test(ctx, args, prefix))
assert len(completed_vals) == n_expected_returns


@pytest.mark.datafiles(AUTOCOMPLETION_FRAMES_PATH)
@pytest.mark.parametrize(
"func_to_test, rename_type, args",
[
(get_frames, None, []),
(get_project_or_task_completion, None, ["project1", "+"]),
(get_project_or_task_completion, None, ["project1", "+tag1", "+"]),
(get_project_or_task_completion, None, []),
(get_projects, None, []),
(get_rename_name, "project", []),
(get_rename_name, "tag", []),
(get_rename_types, None, []),
(get_tags, None, []),
],
)
def test_completion_of_nonexisting_prefix(
watson_df, func_to_test, rename_type, args
):
ctx = ClickContext(obj=watson_df, params={"rename_type": rename_type})
prefix = "NOT-EXISTING-PREFIX"
ret_list = list(func_to_test(ctx, args, prefix))
assert not ret_list


@pytest.mark.datafiles(AUTOCOMPLETION_FRAMES_PATH)
@pytest.mark.parametrize(
"func_to_test, prefix, n_expected_vals, rename_type, args",
[
(get_frames, "f4f7", N_FRAME_IDS_FOR_PREFIX, None, []),
(
get_project_or_task_completion,
"+tag",
N_TASKS,
None,
["project1", "+tag3"],
),
(get_project_or_task_completion, "+tag", N_TASKS, None, ["project1"]),
(
get_project_or_task_completion,
"project3",
N_VARIATIONS_OF_PROJECT3,
None,
[],
),
(get_projects, "project3", N_VARIATIONS_OF_PROJECT3, None, []),
(get_rename_name, "project3", N_VARIATIONS_OF_PROJECT3, "project", []),
(get_rename_name, "tag", N_TASKS, "tag", []),
(get_rename_types, "ta", 1, None, []),
(get_tags, "tag", N_TASKS, None, []),
],
)
def test_completion_of_existing_prefix(
watson_df, func_to_test, prefix, n_expected_vals, rename_type, args
):
ctx = ClickContext(obj=watson_df, params={"rename_type": rename_type})
ret_set = set(func_to_test(ctx, args, prefix))
assert len(ret_set) == n_expected_vals
assert all(cur_elem.startswith(prefix) for cur_elem in ret_set)


@pytest.mark.datafiles(AUTOCOMPLETION_FRAMES_PATH)
@pytest.mark.parametrize(
"func_to_test, prefix, expected_vals",
[
(get_rename_types, "", ["project", "tag"]),
(get_rename_types, "t", ["tag"]),
(get_rename_types, "p", ["project"]),
],
)
def test_for_known_completion_values(func_to_test, prefix, expected_vals):
ret_list = list(func_to_test(None, [], prefix))
assert ret_list == expected_vals
def test_completion():
pytest.xfail(
"There's no good way to test this since click8, see https://github.com/pallets/click/issues/1453"
)
164 changes: 78 additions & 86 deletions watson/autocompletion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .utils import create_watson, parse_tags
from .utils import create_watson


def _bypass_click_bug_to_ensure_watson(ctx):
Expand All @@ -8,111 +8,103 @@ def _bypass_click_bug_to_ensure_watson(ctx):
return ctx.obj


def get_project_or_task_completion(ctx, args, incomplete):
def get_project_tag_combined(ctx, param, incomplete):
"""Function to autocomplete either organisations or tasks, depending on the
shape of the current argument."""

assert isinstance(incomplete, str)

def get_incomplete_tag(args, incomplete):
"""Get incomplete tag from command line string."""
cmd_line = " ".join(args + [incomplete])
found_tags = parse_tags(cmd_line)
return found_tags[-1] if found_tags else ""

def fix_broken_tag_parsing(incomplete_tag):
"""
Remove spaces from parsed tag
The function `parse_tags` inserts a space after each character. In
order to obtain the actual command line part, the space needs to be
removed.
"""
return "".join(char for char in incomplete_tag.split(" "))

def prepend_plus(tag_suggestions):
"""
Prepend '+' to each tag suggestion.
For the `watson` targeted with the function
get_project_or_task_completion, a leading plus in front of a tag is
expected. The get_tags() suggestion generation does not include those
as it targets other subcommands.
In order to not destroy the current tag stub, the plus must be
pretended.
"""
for cur_suggestion in tag_suggestions:
yield "+{cur_suggestion}".format(cur_suggestion=cur_suggestion)

_bypass_click_bug_to_ensure_watson(ctx)

project_is_completed = any(
tok.startswith("+") for tok in args + [incomplete]
)
if project_is_completed:
incomplete_tag = get_incomplete_tag(args, incomplete)
fixed_incomplete_tag = fix_broken_tag_parsing(incomplete_tag)
tag_suggestions = get_tags(ctx, args, fixed_incomplete_tag)
return prepend_plus(tag_suggestions)
shape of the current argument."""

watson = _bypass_click_bug_to_ensure_watson(ctx)

if ctx.params["args"]:
# This isn't the first word, so we assume you're completing tags
given_tags = set(ctx.params["args"][1:])
return [
tag
for tag in [f"+{t}" for t in watson.tags]
if tag.startswith(incomplete) and tag not in given_tags
]

else:
return get_projects(ctx, args, incomplete)
return get_projects(ctx, param, incomplete)


def get_projects(ctx, args, incomplete):
def get_projects(ctx, param, incomplete):
"""Function to return all projects matching the prefix."""
watson = _bypass_click_bug_to_ensure_watson(ctx)
for cur_project in watson.projects:
if cur_project.startswith(incomplete):
yield cur_project
# breakpoint()
return [
project
for project in watson.projects
if project.startswith(incomplete) and project not in ctx.params.get("args", [])
]


def get_rename_name(ctx, args, incomplete):
def get_frames(ctx, param, incomplete):
"""
Function to return all projects or tasks matching the prefix
Depending on the specified rename_type, either a list of projects or a list
of tasks must be returned. This function takes care of this distinction and
returns the appropriate names.
Return all matching frame IDs
If the passed in type is unknown, e.g. due to a typo, an empty completion
is generated.
This function returns all frame IDs that match the given prefix in a
generator. If no ID matches the prefix, it returns the empty generator.
"""
watson = _bypass_click_bug_to_ensure_watson(ctx)

in_type = ctx.params["rename_type"]
if in_type == "project":
return get_projects(ctx, args, incomplete)
elif in_type == "tag":
return get_tags(ctx, args, incomplete)
return [frame.id for frame in watson.frames if frame.id.startswith(incomplete)]

return []

######
## tags and projects with -T/-p

def get_rename_types(ctx, args, incomplete):
"""Function to return all rename types matching the prefix."""
for cur_type in "project", "tag":
if cur_type.startswith(incomplete):
yield cur_type

def get_option_tags(ctx, param, incomplete):
watson = _bypass_click_bug_to_ensure_watson(ctx)
# breakpoint()
return [
tag
for tag in watson.tags
if tag.startswith(incomplete) and tag not in ctx.params["tags"]
]


def get_tags(ctx, args, incomplete):
"""Function to return all tags matching the prefix."""
def get_option_projects(ctx, param, incomplete):
watson = _bypass_click_bug_to_ensure_watson(ctx)
for cur_tag in watson.tags:
if cur_tag.startswith(incomplete):
yield cur_tag
# breakpoint()
return [
project
for project in watson.projects
if project.startswith(incomplete) and project not in ctx.params["projects"]
]


def get_frames(ctx, args, incomplete):
"""
Return all matching frame IDs
#########
## Rename

This function returns all frame IDs that match the given prefix in a
generator. If no ID matches the prefix, it returns the empty generator.
"""

def get_rename_types(ctx, param, incomplete):
"""Function to return all rename types matching the prefix."""
# breakpoint()
return [
rename_type
for rename_type in ["project", "tag"]
if rename_type.startswith(incomplete)
]


def get_rename_old_name(ctx, param, incomplete):
watson = _bypass_click_bug_to_ensure_watson(ctx)
items = {
"project": watson.projects,
"tag": watson.tags,
}[ctx.params["rename_type"]]
return [item for item in items if item.startswith(incomplete)]

for cur_frame in watson.frames:
yield_candidate = cur_frame.id
if yield_candidate.startswith(incomplete):
yield yield_candidate

def get_rename_new_name(ctx, param, incomplete):
watson = _bypass_click_bug_to_ensure_watson(ctx)
items = {
"project": watson.projects,
"tag": watson.tags,
}[ctx.params["rename_type"]]
return [
item
for item in items
if item.startswith(incomplete) and item != ctx.params["old_name"]
]
Loading

0 comments on commit 1f5eaeb

Please sign in to comment.