Python Project: Build a Word Count Command-Line App (Summary)

Nice job! After tackling ten separate tasks, you’ve successfully completed this coding challenge. You now have a working clone of the Unix wc command, which you implemented in Python. How did it go for you? Share your solution in the comments sections!

Sample Solution

You can expand the collapsible section below to reveal the complete source code of a sample solution for this coding challenge:

Python src/wordcount.py
import sys
from argparse import ArgumentParser, Namespace
from dataclasses import dataclass
from enum import IntFlag, auto
from functools import cached_property
from pathlib import Path
from typing import NamedTuple

class SelectedCounts(IntFlag):
    NONE = 0
    LINES = auto()
    WORDS = auto()
    CHARS = auto()
    BYTES = auto()
    DEFAULT = LINES | WORDS | BYTES

class Arguments(Namespace):
    @cached_property
    def selected_counts(self):
        selected = self.lines | self.words | self.chars | self.bytes
        return selected or SelectedCounts.DEFAULT

class Counts(NamedTuple):
    lines: int = 0
    words: int = 0
    chars: int = 0
    bytes: int = 0

    def max_digits(self, selected_counts):
        return len(str(max(self.numbers(selected_counts))))

    def __add__(self, other):
        return Counts(
            lines=self.lines + other.lines,
            words=self.words + other.words,
            chars=self.chars + other.chars,
            bytes=self.bytes + other.bytes,
        )

    def as_string(self, max_digits, selected_counts):
        return " ".join(
            f"{number:>{max_digits}}"
            for number in self.numbers(selected_counts)
        )

    def numbers(self, selected_counts):
        return [
            getattr(self, flag.name.lower())
            for flag in SelectedCounts
            if flag & selected_counts
        ]

@dataclass(frozen=True)
class FileInfo:
    path: Path
    counts: Counts

    @classmethod
    def from_path(cls, path):
        if path.name == "-":
            raw_text = sys.stdin.buffer.read()
        elif path.is_file():
            raw_text = path.read_bytes()
        else:
            return cls(path, Counts())
        text = raw_text.decode("utf-8")
        return cls(
            path,
            Counts(
                lines=text.count("\n"),
                words=len(text.split()),
                chars=len(text),
                bytes=len(raw_text),
            ),
        )

def main():
    args = parse_args()
    if len(args.paths) > 0:
        file_infos = [FileInfo.from_path(path) for path in args.paths]
    else:
        file_infos = [FileInfo.from_path(Path("-"))]
    display(file_infos, args.selected_counts)

def parse_args():
    parser = ArgumentParser()
    parser.add_argument("paths", nargs="*", type=Path)
    for flag in SelectedCounts:
        parser.add_argument(
            f"--{flag.name.lower()}",
            action="store_const",
            const=flag,
            default=SelectedCounts.NONE,
        )
    return parser.parse_args(namespace=Arguments())

def display(file_infos, selected_counts):
    total_counts = sum((info.counts for info in file_infos), Counts())
    max_digits = total_counts.max_digits(selected_counts)
    for info in file_infos:
        line = info.counts.as_string(max_digits, selected_counts)
        if info.path == Path("-"):
            print(line)
        elif not info.path.exists():
            print(line, info.path, "(no such file or directory)")
        elif info.path.is_dir():
            print(line, f"{info.path}/ (is a directory)")
        else:
            print(line, info.path)
    if len(file_infos) > 1:
        print(total_counts.as_string(max_digits, selected_counts), "total")

Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Already a member? Sign-In

Locked learning resources

The full lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Already a member? Sign-In

Become a Member to join the conversation.