Repository: comic/evalutils

Issue:
Allow abbreviations for picking the templates
### Description

Evalutils allows you to pick a template using --kind or by entering it in the terminal. These option strings [Classification|Segmentation|Detection] need to be fully typed, but can be abbreviated. [C,S,B].

### What I Did

```
python -m evalutils init algorithm|evaluation myproject
```


Comments:

Patch:
diff --git a/evalutils/cli.py b/evalutils/cli.py
index fb8ce4d..7c160d9 100644
--- a/evalutils/cli.py
+++ b/evalutils/cli.py
@@ -1,5 +1,6 @@
 import re
 from pathlib import Path
+from typing import List
 
 import click
 from cookiecutter.exceptions import FailedHookException
@@ -39,6 +40,30 @@ def validate_python_module_name_fn(option):
     return validate_python_module_name_string
 
 
+class AbbreviatedChoice(click.Choice):
+    def __init__(self, choices: List[str]):
+        super().__init__(choices=choices, case_sensitive=True)
+        self._abbreviations = [e[0].upper() for e in choices]
+        self._choices_upper = list(map(str.upper, choices))
+        if len(set(self._abbreviations)) != len(choices):
+            raise ValueError(
+                "First letters of choices for AbbreviatedChoices should be unique!"
+            )
+
+    def get_metavar(self, param):
+        return "[{}]".format(
+            "|".join([f"({e[0]}){e[1:]}" for e in self.choices])
+        )
+
+    def convert(self, value, param, ctx):
+        value = value.upper()
+        if value in self._abbreviations:
+            value = self.choices[self._abbreviations.index(value)]
+        elif value in self._choices_upper:
+            value = self.choices[self._choices_upper.index(value)]
+        return super().convert(value=value, param=param, ctx=ctx)
+
+
 @init.command(
     name="evaluation", short_help="Initialise an evaluation project."
 )
@@ -47,8 +72,8 @@ def validate_python_module_name_fn(option):
 )
 @click.option(
     "--kind",
-    type=click.Choice(EVALUATION_CHOICES),
-    prompt=f"What kind of challenge is this? [{'|'.join(EVALUATION_CHOICES)}]",
+    type=AbbreviatedChoice(EVALUATION_CHOICES),
+    prompt=f"What kind of challenge is this?",
 )
 @click.option("--dev", is_flag=True)
 def init_evaluation(challenge_name, kind, dev):
@@ -137,8 +162,8 @@ def req_gpu_prompt(ctx, param, req_gpu_count):
 )
 @click.option(
     "--kind",
-    type=click.Choice(ALGORITHM_CHOICES),
-    prompt=f"What kind of algorithm is this? [{'|'.join(ALGORITHM_CHOICES)}]",
+    type=AbbreviatedChoice(ALGORITHM_CHOICES),
+    prompt=f"What kind of algorithm is this?",
 )
 @click.option("--diag-ticket", type=click.STRING, default="")
 @click.option(


Test patch:
diff --git a/tests/test_abbreviatedchoice.py b/tests/test_abbreviatedchoice.py
new file mode 100644
index 0000000..21a582c
--- /dev/null
+++ b/tests/test_abbreviatedchoice.py
@@ -0,0 +1,26 @@
+import pytest
+
+from evalutils.cli import AbbreviatedChoice
+
+
+def test_abbreviated_choice_not_unique():
+    with pytest.raises(ValueError):
+        AbbreviatedChoice(choices=["Same", "same", "other"])
+
+
+@pytest.mark.parametrize(
+    "value, expected",
+    (
+        ("s", "same"),
+        ("S", "same"),
+        ("Same", "same"),
+        ("same", "same"),
+        ("other", "oTHER"),
+        ("O", "oTHER"),
+        ("o", "oTHER"),
+        ("OtHER", "oTHER"),
+    ),
+)
+def test_abbreviated_choice_convert(value: str, expected: str):
+    ac = AbbreviatedChoice(choices=["same", "oTHER"])
+    assert ac.convert(value=value, param=None, ctx=None) == expected