Anleitung 2: Dateibetrachter (quickcat)
Diese Anleitung erstellt quickcat, ein minimales cat-ähnliches Werkzeug, das eine
oder mehrere Dateien nach stdout ausgibt. Du lernst globale Optionen, den eingebauten
file_path-Validator und wiederholbare Optionen kennen.
Du lernst:
- globale Optionen auf der
Applicationzu definieren - den eingebauten
file_path-Validator zu verwenden - wiederholbare Optionen mit
multiple=Truezu akzeptieren - mehrere Eingabepfade im Handler zu kombinieren
Das vollständige Beispiel
Speichere die folgende Datei als quickcat.py:
from __future__ import annotations
from pathlib import Path
from quickli import Application, Argument, Option, file_path
app = Application(
name="quickcat",
description="A tiny cat-like CLI built with quickli.",
global_options=[
Option("verbose", short_name="v", is_flag=True, help_text="Enable verbose output."),
],
)
@app.entrypoint(
help_text="Print one or more text files to stdout.",
arguments=[
Argument("path", help_text="Primary file path.", validators=[file_path()]),
],
options=[
Option("encoding", short_name="e", default="utf-8", help_text="Text encoding."),
Option("number", short_name="n", help_text="Print line numbers.", is_flag=True),
Option(
"include",
short_name="i",
multiple=True,
validators=[file_path()],
help_text="Additional file paths to print after the primary file.",
),
],
)
def show(
path: Path,
encoding: str = "utf-8",
number: bool = False,
include: list[Path] | None = None,
verbose: bool = False,
) -> str:
input_paths = [path, *(include or [])]
rendered_chunks: list[str] = []
for input_path in input_paths:
text = input_path.read_text(encoding=encoding)
lines = text.splitlines()
if number:
lines = [f"{index:>4} {line}" for index, line in enumerate(lines, start=1)]
if verbose:
rendered_chunks.append(f"==> {input_path} <==")
rendered_chunks.append("\n".join(lines))
return "\n".join(chunk for chunk in rendered_chunks if chunk)
if __name__ == "__main__":
print(app.run())
Ausführen
# Eine Datei ausgeben
python quickcat.py README.md
# Mit Zeilennummern ausgeben
python quickcat.py README.md --number
# Mehrere Dateien ausgeben (verbose zeigt Dateinamen)
python quickcat.py README.md -i CONTRIBUTING.md --verbose
# Textkodierung ändern
python quickcat.py data.txt --encoding latin-1
Erklärung Zeile für Zeile
Globale Optionen
app = Application(
name="quickcat",
description="A tiny cat-like CLI built with quickli.",
global_options=[
Option("verbose", short_name="v", is_flag=True, help_text="Enable verbose output."),
],
)
Globale Optionen werden auf der Ebene der Application definiert. Sie können auf der
Kommandozeile vor oder nach dem Befehlsnamen stehen. Jeder Handler, der eine globale
Option verwenden möchte, muss den passenden Parameter deklarieren.
Der file_path-Validator
Argument("path", help_text="Primary file path.", validators=[file_path()]),
file_path() gibt eine Validator-Funktion zurück, die prüft, ob der Wert auf eine
existierende und lesbare Datei zeigt. Existiert der Pfad nicht, schlägt der Befehl mit
einer klaren Fehlermeldung fehl, bevor der Handler aufgerufen wird.
Validatoren werden nach der Konvertierung aufgerufen. Der Handler erhält daher ein
Path-Objekt und keinen rohen String.
Wiederholbare Optionen
Option(
"include",
short_name="i",
multiple=True,
validators=[file_path()],
help_text="Additional file paths to print after the primary file.",
),
multiple=True weist quickli an, jedes Vorkommen von --include in einer Liste zu
sammeln. Du kannst die Option mehrmals übergeben:
python quickcat.py main.py -i utils.py -i helpers.py
Der Handler erhält include als list[Path] | None. Wird die Option gar nicht
übergeben, ist include gleich None.
Automatische Typannotation
Die Handler-Signatur verwendet Path als Typ für path:
def show(
path: Path,
encoding: str = "utf-8",
number: bool = False,
include: list[Path] | None = None,
verbose: bool = False,
) -> str:
quickli verwendet Typannotationen zusammen mit registrierten Konvertern und Validatoren, um dem Handler bereits verwendbare Werte zu übergeben.
Die Ausgabe erstellen
input_paths = [path, *(include or [])]
rendered_chunks: list[str] = []
for input_path in input_paths:
text = input_path.read_text(encoding=encoding)
lines = text.splitlines()
if number:
lines = [f"{index:>4} {line}" for index, line in enumerate(lines, start=1)]
if verbose:
rendered_chunks.append(f"==> {input_path} <==")
rendered_chunks.append("\n".join(lines))
return "\n".join(chunk for chunk in rendered_chunks if chunk)
Der Handler sammelt für jede Datei einen Abschnitt und verbindet sie am Ende. Leere Strings, die von leeren Dateien erzeugt werden, werden herausgefiltert.
Was du als Nächstes ausprobieren kannst
- Füge mit
converter=inteine Option--max-lineshinzu, um die Ausgabelänge zu begrenzen. - Lies Anleitung 3: Verzeichnisauflistung, um optionale Argumente und Verzeichnisvalidierung kennenzulernen.
