Building a Custom wc Tool with Python and Typer

Building a Custom wc Tool with Python and Typer

Introduction

Welcome to my first blog post! In this project, I tackled an exciting coding challenge from John Cricket. The challenge was to create a custom wc command-line tool named ccwc using Python and the Typer library. This tool mimics the functionality of the wc command in Linux, providing counts of bytes, lines, words, and characters in a file.

Learnings and Key Takeaways

Before diving into the implementation, here are the key learnings and takeaways from this project:

  • CLI Tool Development: Building command-line tools with Typer is efficient and straightforward.

  • File Handling in Python: Understanding how to read and process files and standard input in Python.

  • Project Configuration: Using pyproject.toml to manage project metadata and dependencies.

  • Error Handling: Importance of providing user-friendly error messages.

Tech Stack

  • Language: Python

  • Library: Typer

  • Why Python? It's simple, readable, and powerful for scripting and automation.

  • Why Typer? It makes building command-line interfaces straightforward and efficient.

Project Structure

Here's the project structure for ccwc:

ccwc/
  ├── ccwc/
  │   ├── __init__.py
  │   ├── main.py
  ├── tests/
  │   ├── test_ccwc.py
  ├── test.txt
  ├── pyproject.toml
  ├── README.md

Setting Up Typer and Basic Structure

First, let's set up Typer and create the basic structure of our command-line tool:

import typer
import sys
from typing import List

app = typer.Typer()

Counting Bytes

The following function counts the bytes in a file:

def count_bytes(file_path: str = None):
    if file_path:
        with open(file_path, 'rb') as file:
            content = file.read()
    else:
        content = sys.stdin.buffer.read()
    byte_count = len(content)
    return byte_count

Counting Lines

To count lines in a file, we use:

def count_lines(file_path: str = None):
    if file_path:
        with open(file_path, 'r') as file:
            lines = file.readlines()
    else:
        lines = sys.stdin.read().splitlines()
    line_count = len(lines)
    return line_count

Counting Words

The function to count words is:

def count_words(file_path: str = None):
    if file_path:
        with open(file_path, 'r') as file:
            content = file.read()
    else:
        content = sys.stdin.read()
    words = content.split()
    word_count = len(words)
    return word_count

Counting Characters

For counting characters, we use:

def count_chars(file_path: str = None):
    if file_path:
        with open(file_path, 'r', encoding='utf-8') as file:
            content = file.read()
    else:
        content = sys.stdin.read()
    char_count = len(content)
    return char_count

Main Command and Options

Setting up the main command and handling multiple options with Typer:

@app.command()
def main(
    files: List[str] = typer.Argument(None, help="The files to process"),
    c: bool = typer.Option(False, "--bytes", "-c", help="Print the byte counts"),
    l: bool = typer.Option(False, "--lines", "-l", help="Print the newline counts"),
    w: bool = typer.Option(False, "--words", "-w", help="Print the word counts"),
    m: bool = typer.Option(False, "--chars", "-m", help="Print the character counts"),
    version: bool = typer.Option(False, "--version", help="Show the version and exit")
):
    if version:
        typer.echo("ccwc version 0.1.0")
        raise typer.Exit()

    if not files:
        files = [None]

    for file in files:
        if c:
            byte_count = count_bytes(file)
            print(f"{byte_count:8} {'-' if file is None else file}")
        elif l:
            line_count = count_lines(file)
            print(f"{line_count:8} {'-' if file is None else file}")
        elif w:
            word_count = count_words(file)
            print(f"{word_count:8} {'-' if file is None else file}")
        elif m:
            char_count = count_chars(file)
            print(f"{char_count:8} {'-' if file is None else file}")
        else:
            line_count = count_lines(file)
            word_count = count_words(file)
            byte_count = count_bytes(file)
            print(f"{line_count:8} {word_count:8} {byte_count:8} {'-' if file is None else file}")

if __name__ == "__main__":
    app()

Understandingpyproject.toml

The pyproject.toml file is crucial for configuring your project. Here's a brief overview:

[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "ccwc"
version = "0.1.0"
description = "A wc-like tool for counting lines, words, bytes, and characters in files"
readme = "README.md"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "your.email@example.com"}
]
dependencies = [
    "typer[all]>=0.4.0"
]

[project.scripts]
ccwc = "ccwc.main:app"
  • [build-system]: Specifies the tools required to build the project.

  • [project]: Contains metadata about the project like name, version, description, authors, and dependencies.

  • [project.scripts]: Defines command-line scripts for the project.

Installing and Testing

To install and test your tool:

  1. Create a Virtual Environment:

     python -m venv venv
     source venv/bin/activate  # On macOS and Linux
     # venv\Scripts\activate  # On Windows
    
  2. Install the Package:

     pip install --upgrade pip setuptools wheel
     pip install .
    
  3. Verify Installation:

     codeccwc --help
     ccwc --version
     ccwc -c test.txt
     ccwc -l test.txt
     ccwc -w test.txt
     ccwc -m test.txt
     ccwc test.txt
     cat test.txt | ccwc -l
    

Future Enhancements

Future Enhancements:

  • Add more options and functionalities, such as excluding specific file types or including subdirectories.

  • Improve error handling to provide more user-friendly messages.

  • Optimize performance for handling large files more efficiently.

  • Implement additional features like parallel processing for multiple files.

Conclusion

This project was a fantastic learning experience, and I'm excited to share it with you all. Whether you're a beginner looking to enhance your Python skills or an experienced developer seeking new insights, I hope you find this post helpful.

Thank you for reading! If you have any questions or suggestions, please leave a comment below.

Watch the YouTube Video:Creating a Custom wc Command-Line Tool with Python and Typer

GitHub Repository:https://github.com/Nik-Jain/cc_ccwc

Don't forget to like, subscribe, and share your thoughts!