3 Formatting, and a corpus
Read the tutorial. Build it yourself is the other way to read this part.
just setuptally works, and it has tests. Two things help the work ahead. Code in one style is easier to read and to change,
so we install ruff, a formatter and linter. And a count of three words shows little, so just setup makes a
corpus, a text of 2000 words to count.
The code before this part. The code after it.
3.1 ruff, a tool for development
Each person writes code in a style of their own, with other quotes, blank lines and lengths of line. A formatter
writes every file in one style. A linter reads the code, and reports mistakes that the tests do not find. ruff is
both. It goes in the group dev of the project. The group dev is for the people who work on tally, so a user
who installs tally does not get ruff.
One command does it all. uv add writes ruff into pyproject.toml and uv.lock, and installs it in .venv.
This change came from running uv add --dev ruff.
In pyproject.toml, a new table [dependency-groups] has the group dev, with the line "ruff>=0.17.0",. In
uv.lock, the entry name = "ruff" gives the exact version, and the files of ruff for each system.
uv run ruff format --checkruff reads every Python file of the project, and prints 5 files already formatted.
What changed
pyproject.toml: ruff, in a group of its own. The version is the newest on the day that ruff was added. 5 lines added. It comes fromuv add --dev ruff. The file.- look at
"ruff>=0.17.0",
- look at
uv.lock: the exact version of ruff, which every learner gets withjust setup. 35 lines added. It comes fromuv add --dev ruff. The file.- look at
name = "ruff"
- look at
The diff of pyproject.toml
@@ -12,3 +12,8 @@ tally = "tally:main"
[build-system]
requires = ["uv_build>=0.12.13,<0.13.0"]
build-backend = "uv_build"
+
+[dependency-groups]
+dev = [
+ "ruff>=0.17.0",
+]3.2 A line length for ruff
ruff and each editor have their own idea of how long a line can be. A setting in pyproject.toml makes them agree.
It is a table [tool.ruff], with a line length of 100.
A patch to pyproject.toml.
At the end of pyproject.toml, the table [tool.ruff] sets line-length = 100. The linter and the formatter
both read it.
uv run ruff check --show-settings src | grep line_length
uv run ruff format --checkThe first command prints linter.line_length = 100, so ruff reads the setting. The code still fits, and every file
is already formatted.
What changed
pyproject.toml: one setting, for the linter and the formatter both. 3 lines added. The file.- look at
line-length = 100
- look at
The diff of pyproject.toml
@@ -17,3 +17,6 @@ build-backend = "uv_build"
dev = [
"ruff>=0.17.0",
]
+
+[tool.ruff]
+line-length = 1003.3 A corpus, which just setup makes
A count of a b a shows little. tally needs a long text to count, the same for every learner. A script,
scripts/corpus.py, writes data/corpus.txt, 2000 words from a short list, in the same order each time. The
script comes whole, as a patch.
A patch to scripts/corpus.py.
The corpus is an artifact, a file that the project makes and git does not hold. So .gitignore names the folder
data/. git leaves an ignored file alone, so the corpus stays when the code changes.
A patch to .gitignore.
just setup makes the environment, and now it runs the script too. So every learner has the corpus. The script
does not touch a corpus that is already there, so a second just setup writes nothing.
A patch to justfile.
The new file scripts/corpus.py picks each word from the list WORDS, with a number that it works out from the
number before it. The first number is always 7, so the words are the same each time. .gitignore now ends with
data/, and the recipe setup of the justfile runs the script after uv sync.
just setup
uv run tally data/corpus.txt | sort -rn | head -3just setup prints wrote data/corpus.txt: 2000 words. sort -rn puts the lines of tally in order of count, highest
first, and head -3 keeps the first three. They are 127 line, 126 class and 125 word.
What changed
.gitignore: git does not hold the corpus. 3 lines added. The file.- look at
data/
- look at
justfile:just setupnow makes the corpus too. 1 line added. The file.- look at
uv run python scripts/corpus.py data/corpus.txt
- look at
scripts/corpus.py: the script writes the same 2000 words each time, and does not touch a corpus that is already there. New, 25 lines. The file.- look at
if target.exists():
- look at
The diff of .gitignore
@@ -8,3 +8,6 @@ wheels/
# Virtual environments
.venv
+
+# Artifacts that just setup makes: they are not in git
+data/The diff of justfile
@@ -5,6 +5,7 @@ default:
# Make the environment of this step: the first command of every step
setup:
uv sync
+ uv run python scripts/corpus.py data/corpus.txt
# Count the words of a file
run file:The diff of scripts/corpus.py
@@ -0,0 +1,25 @@
+"""Write data/corpus.txt, a text of 2000 words to count, the same every time. Leave it alone if it is there."""
+
+import sys
+from pathlib import Path
+
+WORDS = "the a word count text tally test step move commit tag note slide class shell file line code".split()
+
+
+def main() -> None:
+ target = Path(sys.argv[1] if len(sys.argv) > 1 else "data/corpus.txt")
+ if target.exists():
+ print(f"{target} is there")
+ return
+ seed, words = 7, []
+ for _ in range(2000):
+ seed = (seed * 1103515245 + 12345) % 2**31
+ words.append(WORDS[seed % len(WORDS)])
+ target.parent.mkdir(parents=True, exist_ok=True)
+ lines = (" ".join(words[i : i + 12]) for i in range(0, len(words), 12))
+ target.write_text("\n".join(lines) + "\n")
+ print(f"wrote {target}: {len(words)} words")
+
+
+if __name__ == "__main__":
+ main()