Skip to content

Commit

Permalink
CLI and TUI
Browse files Browse the repository at this point in the history
  • Loading branch information
e3rd committed Mar 12, 2024
1 parent e64b513 commit 3417258
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 79 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/run-unittest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
# python-version: [3.11, 3.12] TODO
python-version: [3.12]
python-version: [3.11, 3.12]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ These imply the folders have the same structure. Deduplidog is tolerant towards

The program does not write anything to the disk, unless `execute=True` is set. Feel free to launch it just to inspect the recommended actions. Or set `bashify=True` to output bash commands you may launch after thorough examining.

# Launch

It works as a standalone program with both CLI and TUI interfaces. Just launch the `deduplidog` command.
Moreover, it works best when imported from a [Jupyter Notebook](https://jupyter.org/).

# Examples

It works great when launched from a [Jupyter Notebook](https://jupyter.org/).
Let's take a closer look to a use-case.

```python3
import logging
Expand Down
112 changes: 112 additions & 0 deletions deduplidog/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import sys
from dataclasses import fields
from typing import get_args

import click
from dataclass_click import dataclass_click
from textual import events
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Checkbox, Footer, Input, Label

from .interface_utils import Field
from .deduplidog import Deduplidog


class CheckboxApp(App[None]):
CSS_PATH = "form.tcss"

BINDINGS = [
("up", "go_up", "Go up"),
("down", "go_up", "Go down"),
("ctrl+s", "confirm", "Run"), # ctrl/alt+enter does not work; enter does not work with checkboxes
("escape", "exit", "Exit"),
]

def compose(self) -> ComposeResult:
yield Footer()
self.inputs = INPUTS
with VerticalScroll():
for input in self.inputs:
if isinstance(input, Input):
yield Label(input.placeholder)
yield input
yield Label(input._link.help)
yield Label("")

def on_mount(self):
self.inputs[0].focus()

def action_confirm(self):
self.exit(True)

def action_exit(self):
self.exit()

def on_key(self, event: events.Key) -> None:
try:
index = self.inputs.index(self.focused)
except ValueError: # probably some other element were focused
return
match event.key:
case "down":
self.inputs[(index + 1) % len(self.inputs)].focus()
case "up":
self.inputs[(index - 1) % len(self.inputs)].focus()
case letter if len(letter) == 1: # navigate by letters
for inp_ in self.inputs[index+1:] + self.inputs[:index]:
label = inp_.label if isinstance(inp_, Checkbox) else inp_.placeholder
if str(label).casefold().startswith(letter):
inp_.focus()
break


class RaiseOnMissingParam(click.Command):
def __call__(self, *args, **kwargs):
return super(RaiseOnMissingParam, self).__call__(*args, standalone_mode=False, **kwargs)


@click.command(cls=RaiseOnMissingParam)
@dataclass_click(Deduplidog)
def cli(dd: Deduplidog):
return dd


if __name__ == "__main__":
# CLI
try:
dd = cli()
if input("Continue? [Y/n] ").casefold() not in ("", "y"):
sys.exit()
except click.MissingParameter:
# User launched the program without parameters.
# This is not a problem, we have TUI instead.
dd = None

# TUI
dog_fields: list[Field] = []
for f in fields(Deduplidog):
try:
dog_fields.append(Field(f.name,
getattr(dd, f.name, f.default),
get_args(f.type)[0],
get_args(f.type)[1].kwargs["help"]))
except Exception as e:
# we want only documented fields, in case of an incorrenctly defined field, we do not let user to edit
continue
while True:
print("")
INPUTS = [f.get_widgets() for f in dog_fields]
if not CheckboxApp().run():
break
for form, field in zip(INPUTS, dog_fields):
field.value = form.value
try:
Deduplidog(**{f.name: f.convert() for f in dog_fields})
except Exception as e:
print("-"*100)
print(e)
input()
continue
if input("See more options? [Y/n] ").casefold() not in ("y", ""):
break
Loading

0 comments on commit 3417258

Please sign in to comment.