Guide 5: File Head (quickhead)
This guide builds quickhead, a minimal head-like tool that prints the first (or last)
lines of a file. It introduces numeric conversion with converter=int and the built-in
positive_number validator.
You will learn how to:
- convert raw option strings to integers with
converter=int - validate converted values with
positive_number() - add a tail mode flag to reverse direction
The full example
Save the following file as quickhead.py:
from __future__ import annotations
from pathlib import Path
from quickli import Application, Argument, Option, file_path, positive_number
app = Application(
name="quickhead",
description="A tiny head-like CLI built with quickli.",
)
@app.entrypoint(
help_text=(
"Display the first few lines of a file. If no value is given, the first 10 "
"lines are displayed."
),
arguments=[Argument("file", validators=[file_path()])],
options=[
Option(
"lines",
short_name="n",
converter=int,
validators=[positive_number()],
help_text="Number of lines to display.",
),
Option(
"tailmode",
short_name="t",
is_flag=True,
help_text="Display the last few lines instead of the first.",
),
],
)
def head(file: Path, lines: int = 10, tailmode: bool = False) -> str:
content = file.read_text(encoding="utf-8").splitlines(keepends=True)
if tailmode:
return "".join(content[-lines:])
return "".join(content[:lines])
if __name__ == "__main__":
print(app.run())
Run it
# Print the first 10 lines (default)
python quickhead.py README.md
# Print the first 5 lines
python quickhead.py README.md --lines 5
python quickhead.py README.md -n 5
# Print the last 3 lines
python quickhead.py README.md -n 3 --tailmode
python quickhead.py README.md -n 3 -t
Line-by-line explanation
Numeric conversion
Option(
"lines",
short_name="n",
converter=int,
validators=[positive_number()],
help_text="Number of lines to display.",
),
The command line always delivers raw strings. converter=int tells quickli to call
int() on the value before passing it to the handler. If the conversion fails (for
example because the user passes --lines abc), quickli raises a clear error before the
handler runs.
You can use any callable as a converter — int, float, Path, or your own function.
The positive_number validator
positive_number() runs after the converter, so it receives an int. It raises a
ValueError if the value is not positive (greater than zero). The handler can therefore
assume lines >= 1.
Default value for a converted option
def head(file: Path, lines: int = 10, tailmode: bool = False) -> str:
The default value for lines is 10. When the option is absent, the handler receives
10 directly — no conversion is needed for the default because quickli only runs the
converter for user-supplied values.
Tail mode
if tailmode:
return "".join(content[-lines:])
return "".join(content[:lines])
Python's negative slice content[-lines:] returns the last lines elements. This is a
clean way to implement both head and tail in the same handler.
What to try next
- Add a
--bytesoption that prints the first N bytes instead of lines. - Read Guide 6: Multi-Command CLI for named commands and global application options.
