1 A table, and a tool for the docs

Read the tutorial. Build it yourself is the other way to read this part.

just setup

A list of counts is hard to read at a glance. A table lines up the words and the counts. A small package, tabulate, draws tables, so tally depends on it. The docs come later, and their tool comes now, in a group of its own.

The code before this part. The code after it.

1.1 tabulate, in a range of versions

tally needs tabulate to draw a table. A new version 1.0 of tabulate could change how it draws one, so tally takes any 0.9 version, and no later one. uv add with a range of versions puts the range in pyproject.toml, and the exact version in uv.lock:

This change came from running uv add "tabulate>=0.9,<0.10".

In pyproject.toml, the list dependencies now holds tabulate>=0.9,<0.10. In uv.lock, tabulate has the version 0.9.0, the one version in that range.

uv run python -c "import tabulate; print(tabulate.__version__)"

It prints 0.9.0.

What changed

  • pyproject.toml: tabulate, with the range of its versions. 3 lines added, 1 removed. It comes from uv add "tabulate>=0.9,<0.10". The file.
    • look at "tabulate>=0.9,<0.10",
  • uv.lock: the exact version of tabulate. 13 lines added. It comes from uv add "tabulate>=0.9,<0.10". The file.
The diff of pyproject.toml
@@ -4,7 +4,9 @@ version = "0.1.0"
 description = "Count the words in a text"
 readme = "README.md"
 requires-python = ">=3.12"
-dependencies = []
+dependencies = [
+    "tabulate>=0.9,<0.10",
+]
 
 [project.scripts]
 tally = "tally:main"

This change on GitHub. The code after it.

1.2 tally --table

tally prints one line for each word, with the count first. A new option, --table, prints the same counts as a table, with a header. main takes the option out of the arguments, and passes the rows to tabulate.

A patch to src/tally/__init__.py.

In main of src/tally/__init__.py, the flag as_table is true when the arguments hold --table. The rows then go to tabulate, with the header word and times.

uv run tally --top 3 --table data/corpus.txt

It prints a header, word and times, a line of dashes, and the three words with their counts in a column on the right.

What changed

  • src/tally/__init__.py: the option --table, which prints the rows with tabulate. 11 lines added, 3 removed. The file.
    • look at print(tabulate(rows, headers=["word", "times"]))
The diff of src/tally/__init__.py
@@ -2,6 +2,8 @@
 
 import sys
 
+from tabulate import tabulate
+
 
 def count(text: str) -> dict[str, int]:
     "How many times each word of `text` appears, ignoring case."
@@ -17,11 +19,17 @@ def top(counts: dict[str, int], n: int) -> list[tuple[str, int]]:
 
 
 def main() -> None:
-    "Count the words of a file, or of the standard input. With --top N, only the N most common."
-    args, n = sys.argv[1:], None
+    "Count the words of a file, or of the standard input. Options: --top N, and --table."
+    args = sys.argv[1:]
+    as_table = "--table" in args
+    args, n = [arg for arg in args if arg != "--table"], None
     if args[:1] == ["--top"]:
         n, args = int(args[1]), args[2:]
     text = open(args[0]).read() if args else sys.stdin.read()
     counts = count(text)
-    for word, times in top(counts, n) if n else sorted(counts.items()):
+    rows = top(counts, n) if n else sorted(counts.items())
+    if as_table:
+        print(tabulate(rows, headers=["word", "times"]))
+        return
+    for word, times in rows:
         print(times, word)

This change on GitHub. The code after it.

1.3 mdformat, in the group docs

The docs will be Markdown files. mdformat formats Markdown, and its option --check says whether a file is formatted. Only the people who write the docs need it, so it goes in a group of its own, docs:

This change came from running uv add --group docs mdformat.

uv sync installs the group dev and no other group, unless the project names more. One more line in the table [tool.uv] of pyproject.toml names both groups, so that just setup installs mdformat too.

A patch to pyproject.toml.

pyproject.toml has a group docs with mdformat, and at its end a table [tool.uv] whose default-groups names dev and docs.

uv sync -q && uv run --no-sync mdformat --version

uv sync installs mdformat. Then uv run --no-sync runs it without another sync, and it prints mdformat 1.0.0, or a newer version.

What changed

  • pyproject.toml: the group docs, and the groups that uv sync installs. 6 lines added. It comes from uv add --group docs mdformat. The file.
    • look at default-groups = ["dev", "docs"]
  • uv.lock: the exact versions of mdformat and of the packages it needs. 37 lines added. It comes from uv add --group docs mdformat. The file.
The diff of pyproject.toml
@@ -19,6 +19,12 @@ build-backend = "uv_build"
 dev = [
     "ruff>=0.17.0",
 ]
+docs = [
+    "mdformat>=1.0.0",
+]
 
 [tool.ruff]
 line-length = 100
+
+[tool.uv]
+default-groups = ["dev", "docs"]

This change on GitHub. The code after it.