Building a File Search Application

The Steps for Creating a Colorized File Search Tool with Python

Oliver Bonham-Carter

What Makes a Project Successful?! πŸŽ‰

Know the Steps: When you are developing a programming project to be shared online, there are steps to follow that will help the community understand, adopt and use your project effectively. πŸ“

On For Today πŸš€

Building Real-World Python Applications

In this series, we’ll build complete, practical projects!

  • πŸ“¦ Project Setup β€” Using modern Python tools (uv)
  • 🎨 Beautiful Output β€” Rich library for colorization
  • πŸ” File Operations β€” Working with files and directories
  • 🎯 Command-Line Tools β€” argparse for user interaction
  • πŸ§ͺ Testing β€” Validating your code works correctly
  • πŸ”’ Hidden File Filtering β€” Smart directory traversal

Today’s Goal: Build a file search tool to locate information across files! πŸ”

Traversing File Systems!

Why File Searching?

Common Use Cases:

  • Finding specific files in large projects
  • Searching for code snippets or configuration settings
  • Auditing codebases for security issues or TODO comments

And Find Files and Content They Contain!

With This Solution?

Our FileSearch Tool:

  • Search by filename, type or file contents
  • Colorized, readable output
  • Filters hidden files by default

This Band Is The Only Thing Cooler Than This Project!

πŸ“‹ Anyways. Back to What We’re Building…

FileSearch β€” A Powerful File Search Utility

A command-line tool that helps you:

  • πŸ” Search by filename β€” Find files matching a pattern
  • πŸ“„ Search by content β€” Find files containing specific text
  • 🎨 Colorized output β€” Beautiful, readable results
  • πŸ”’ Smart filtering β€” Exclude hidden directories by default
  • 🌈 Syntax highlighting β€” View code files with colors
  • ⚑ Fast & flexible β€” Search anywhere, customize everything

Important

Real-World Use Cases:

  • Find all Python files that import a specific module
  • Locate configuration files across multiple projects
  • Search for TODO comments in your codebase
  • Find files by extension or naming patterns

πŸ› οΈ Part 1: Project Setup with UV

(You already know the drill, but I’ll do it again for practice!) Β―\(ツ)/Β―

What is UV?

UV is a modern, fast Python package manager created by Astral.

Why use UV instead of pip?

  • ⚑ 10-100x faster than pip
  • πŸ”’ Automatic lock files for reproducible installs
  • πŸ“¦ Built-in virtual environment management
  • 🎯 Simple commands β€” one tool for everything

Why Cover These Steps? You want to test each of these steps on your own machine to help you remember what commands to include in your README.md file when you share your project! πŸ“

Installing UV

Keep these commands handy for setting up UV on your system! It’s a game-changer for Python development. πŸš€

Installation Commands by Operating System

macOS / Linux:

# Using the official installer (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or using Homebrew (macOS only)
brew install uv

# Or using pip
pip install uv

Windows:

# Using PowerShell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

# Or using pip
pip install uv

# Or using winget
winget install --id=astral-sh.uv -e

Verify UV Installation

Tip

After installation, verify the installation was successful and that the software is available in your terminal:

uv --version # Should print the installed version of UV
uv sync # This will test the project setup and create a new uv.lock file when pyproject.toml is present

Creating Your Project

Step 1: Create the Project Structure

Commands (all operating systems):

# Create and navigate to your project directory
mkdir fileSearch
cd fileSearch

# Initialize a new UV project
uv init

# This creates:
# - pyproject.toml (project configuration)
# - README.md (documentation)
# - .python-version (Python version pin)
# - main.py (example file - we'll replace this)

Note

What just happened?

  • UV created a new Python project with all necessary configuration
  • A virtual environment is managed automatically
  • You’re ready to start coding! πŸŽ‰

Project Structure Overview

Step 2: Organize Your Code

Create the proper directory structure:

# Remove the example file
rm main.py

# Create source directory
mkdir src

# Your project structure should look like:
# fileSearch/
# β”œβ”€β”€ src/
# β”‚   └── main.py (we will create this file here!)
# β”œβ”€β”€ pyproject.toml
# β”œβ”€β”€ README.md
# └── .python-version

Best Practice:

  • Keep your source code in a src/ directory to separate it from configuration files and documentation! πŸ“
  • Mention the files of the project in the README.md to provide clear instructions for users and collaborators! πŸ“

Adding Dependencies

Step 3: Install the Rich Library

Different projects require different libraries. For this project, we’ll use Rich to provide beautiful terminal formatting and colors.

Install it:

# Add Rich as a dependency
uv add rich

# This automatically:
# - Installs Rich and its dependencies
# - Updates pyproject.toml
# - Creates/updates uv.lock
# - Activates the virtual environment

Note

Verify installation:

uv pip list

# You should see:
# Package         Version
# --------------- -------
# rich           13.x.x
# markdown-it-py  x.x.x
# pygments       x.x.x
# ...

🎯 Part 2: Understanding the Problem

What Problems Are We Solving?

Common Developer Needs:

  1. Find files by name β€” β€œWhere did I put that config file?”
  2. Search file contents β€” β€œWhich files use this function?”
  3. Readable output β€” β€œI can’t read this plain text mess!”
  4. Filter noise β€” β€œToo many .git and node_modules results!”
  5. View code nicely β€” β€œI need syntax highlighting!”

Our Solution: A Python tool that addresses all these problems with a clean, user-friendly interface! 🎨

Program Features Overview

Core Functionality

Input (Command-line arguments):

  • --filename : Search term for filenames
  • --filecontents : Search term for file contents
  • --path : Directory to search in
  • --full-search : Include hidden files/directories

Processing:

  • Recursively traverse directories
  • Filter hidden files by default
  • Search filenames and/or contents
  • Detect file types for syntax highlighting

Output:

  • Colorized tables for file lists
  • Syntax-highlighted code display
  • Panels and borders for organization
  • Status messages and count summaries

πŸ”„ Part 3: Program Flow

High-Level Algorithm

The program follows these steps:

  1. Parse command-line arguments β†’ Get user’s search criteria
  2. Determine search mode β†’ Full search or skip hidden files
  3. Execute searches β†’ Find matching files
  4. Format results β†’ Create beautiful output
  5. Display content β†’ Show file contents if requested
  6. Report completion β†’ Summary of findings

Flowchart β€” Program Logic

flowchart LR
    Start([Start]) --> Parse[Parse Args]
    Parse --> CheckMode{Full<br/>Mode?}
    
    CheckMode -->|Yes| FullMode[Include<br/>Hidden Files/Directories]
    CheckMode -->|No| NormalMode[Exclude<br/>Hidden Files/Directories]
    
    FullMode --> CheckArgs{Args?}
    NormalMode --> CheckArgs
    
    CheckArgs -->|None| ListAll[List All Files]
    CheckArgs -->|--filename| SearchName[Search<br/>Names]
    CheckArgs -->|--filecontents| SearchContent[Search<br/>Content]
    CheckArgs -->|Both| SearchBoth[Search<br/>Both]
    
    ListAll --> Display[Table<br/>Display]
    SearchName --> Display
    SearchContent --> ShowContent{Show<br/>Contents?}
    SearchBoth --> ShowContent
    
    ShowContent -->|Yes| Syntax[Syntax<br/>Highlight]
    ShowContent -->|No| Display
    Syntax --> DisplayPanel[Colored<br/>Panel]
    
    DisplayPanel --> Done([Done βœ“])
    Display --> Done
    
    classDef startEnd fill:#27AE60,stroke:#229954,stroke-width:4px,color:#fff
    classDef decision fill:#3498DB,stroke:#2874A6,stroke-width:4px,color:#fff
    classDef process fill:#9B59B6,stroke:#7D3C98,stroke-width:4px,color:#fff
    classDef search fill:#E67E22,stroke:#CA6F1E,stroke-width:4px,color:#fff
    classDef output fill:#E74C3C,stroke:#C0392B,stroke-width:4px,color:#fff
    
    class Start,Done startEnd
    class CheckMode,CheckArgs,ShowContent decision
    class Parse,FullMode,NormalMode,Syntax process
    class ListAll,SearchName,SearchContent,SearchBoth search
    class Display,DisplayPanel output

Too Complicated? Typical flowcarts look very complicated however, these β€œflows” represent the program logic in a visual way, making code inputs and outputs easier to understand. The edge cases and limitations of the code are also easier to spot. 🎨

Flowchart Explanation

Understanding the Program Flow

Decision Points:

  1. Search Mode β€” User can enable full search to include hidden files/directories
  2. Argument Selection β€” Program behavior changes based on which flags are provided
  3. Content Display β€” File contents are shown with syntax highlighting when searching by content

Key Processes:

  • File Discovery β€” Recursive directory traversal with optional filtering
  • Pattern Matching β€” Check filenames or file contents for search terms
  • Output Formatting β€” Rich library creates tables, panels, and syntax highlighting

πŸ’» Part 4: Building the Code

Code Structure Overview

We’ll build the program in these sections:

  1. Imports and Setup β€” Load required libraries
  2. Helper Functions β€” Utility functions for filtering
  3. Search Functions β€” Core search logic
  4. Display Functions β€” Output formatting
  5. Main Function β€” Command-line interface and orchestration

Step 1: Imports and Setup

Copy this code into src/main.py:

from pathlib import Path
import argparse
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.syntax import Syntax
from rich.text import Text

# Create a global console object for colorized output
console = Console()

What’s happening:

  • pathlib.Path β€” Modern, object-oriented file path handling
  • argparse β€” Parse command-line arguments
  • rich.* β€” Various Rich components for beautiful output
  • console β€” Single console instance used throughout the program

Step 2: Helper Function β€” Hidden File Detection

Add this helper function:

def is_hidden(path):
    """Check if any part of the path is hidden (starts with .)
    
    Args:
        path: A Path object representing a file or directory
        
    Returns:
        bool: True if any part of the path starts with a dot
    """
    return any(part.startswith('.') and part not in ['.', '..'] 
               for part in path.parts)
# End of is_hidden()

Note

Function Purpose:

  • Checks if a file or directory is β€œhidden” (starts with .)
  • Excludes . and .. (current and parent directory markers)
  • Returns True if ANY part of the path is hidden
  • Example: .git/config returns True because of .git

Step 3: Get Path Function

Add the first search function:

def getPath(myFileType="*", full_search=False):
    """Get all files in the current directory and subdirectories.
    
    Args:
        myFileType: File pattern to match (default: all files)
        full_search: If True, include hidden files/directories
        
    Returns:
        list: List of Path objects for matching files
    """
    # Create a Path object for the directory
    base_path = Path('./')
    
    if full_search:
        myFiles = [p for p in base_path.rglob(myFileType) 
                   if p.is_file()]
    else:
        myFiles = [p for p in base_path.rglob(myFileType) 
                   if p.is_file() and not is_hidden(p)]
    
    return myFiles
# End of getPath()

Understanding getPath()

Code Breakdown

Key Concepts:

  • .rglob("*") β€” Recursively search for all files and directories
  • p.is_file() β€” Filter to only include files (not directories)
  • Conditional filtering based on full_search parameter
  • List comprehension for efficient filtering

Example Usage:

# Get all non-hidden files
files = getPath()

# Get all files including hidden ones
all_files = getPath(full_search=True)

# Get only Python files (non-hidden)
py_files = getPath(myFileType="*.py")

Step 4: Search Filenames Function

Add filename search capability:

def searchFilenames(myPath, searchTerm, full_search=False):
    """Search for files with searchTerm in their filename.
    
    Args:
        myPath: Directory path to search in
        searchTerm: String to search for in filenames
        full_search: If True, include hidden files/directories
        
    Returns:
        list: List of Path objects for matching files
    """
    # Create a Path object for the directory
    base_path = Path(myPath)
    
    if full_search:
        myFiles = [p for p in base_path.rglob("*") 
                   if p.is_file() and searchTerm in p.name]
    else:
        myFiles = [p for p in base_path.rglob("*") 
                   if p.is_file() 
                   and searchTerm in p.name 
                   and not is_hidden(p)]
    
    return myFiles
# End of searchFilenames()

Step 5: Search File Contents Function

Add content search capability:

def searchFileContents(myPath, searchTerm, myFileType="*", 
                      full_search=False):
    """Search for files containing searchTerm in their contents.
    
    Args:
        myPath: Directory path to search in
        searchTerm: String to search for in file contents
        myFileType: File pattern to match (default: all files)
        full_search: If True, include hidden files/directories
        
    Returns:
        list: List of Path objects for matching files
    """
    # Create a Path object for the directory
    base_path = Path(myPath)
    
    myFiles = []
    for p in base_path.rglob(myFileType):
        if p.is_file():
            # Skip hidden files/directories unless full_search is enabled
            if not full_search and is_hidden(p):
                continue
            try:
                with p.open() as f:
                    if searchTerm in f.read():
                        myFiles.append(p)
            except Exception as e:
                console.print(f"[yellow]Warning: Error reading file {p}: {e}[/yellow]")
    
    return myFiles
# End of searchFileContents()

Step 6: Display File Contents Function

Add syntax-highlighted display:

def printFileContents(filePath):
    """Display file contents with syntax highlighting.
    
    Args:
        filePath: Path object or string path to the file
    """
    try:
        with open(filePath) as f:
            content = f.read()
            # Try to detect file type for syntax highlighting
            suffix = filePath.suffix.lstrip('.')
            
            # List of supported languages for syntax highlighting
            if suffix in ['py', 'js', 'java', 'cpp', 'c', 'rs', 'go', 
                         'rb', 'php', 'html', 'css', 'json', 'xml', 
                         'yaml', 'yml', 'toml', 'md', 'sh', 'bash']:
                syntax = Syntax(content, suffix, theme="monokai", 
                              line_numbers=True)
                console.print(Panel(syntax, title=f"[cyan]{filePath}[/cyan]", 
                                  border_style="blue"))
            else:
                console.print(Panel(content, title=f"[cyan]{filePath}[/cyan]", 
                                  border_style="blue"))
    except Exception as e:
        console.print(f"[red]Error reading file {filePath}: {e}[/red]")
# End of printFileContents()

Understanding Syntax Highlighting

How Rich Syntax Works

Features:

  • File Extension Detection β€” .suffix gets the file extension
  • Conditional Highlighting β€” Only apply syntax to known code file types
  • Monokai Theme β€” Professional dark theme for code
  • Line Numbers β€” Easy reference for code discussion
  • Panel Display β€” Beautiful border and title

Supported Languages:

Python, JavaScript, Java, C/C++, Rust, Go, Ruby, PHP, HTML, CSS, JSON, XML, YAML, TOML, Markdown, Shell scripts

Fallback: Plain text display for unknown file types

Sample Output

Note

We outline the contents of a file with syntax highlighting and line numbers, making it easy to read and understand the structure of the code or text.

Step 7: Main Function β€” Part 1 (Argument Parser)

Set up command-line interface:

def main():
    # Set up command-line argument parser
    parser = argparse.ArgumentParser(
        description='Search for files by filename or file contents',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
Examples:
  python main.py --filename test
  python main.py --filecontents "import sys"
  python main.py --filename test --filecontents python
        '''
    )
    
    parser.add_argument(
        '--filename',
        type=str,
        help='Search term to find in filenames'
    )
    
    parser.add_argument(
        '--filecontents',
        type=str,
        help='Search term to find in file contents'
    )

Maybe this function is too long? Typically, the functions of a program should be concise and focused on a single task. Consider breaking this function into smaller helper functions for readability and maintainability. In this case, the function is to create a prototype of the main functionality. Later, you can refactor it into smaller, more manageable functions. πŸ“

Step 7: Main Function β€” Part 2 (More Arguments)

Continue adding arguments:

    parser.add_argument(
        '--path',
        type=str,
        default='./',
        help='Base path to search (default: current directory)'
    )
    
    parser.add_argument(
        '--full-search',
        action='store_true',
        help='Include hidden files and directories (those starting with .)'
    )
    
    args = parser.parse_args()
    
    # Display search mode
    if args.full_search:
        console.print("[bold yellow]Search mode: FULL (including hidden files/directories)[/bold yellow]\n")
    else:
        console.print("[bold green]Search mode: NORMAL (excluding hidden files/directories)[/bold green]")
        console.print("[dim]Tip: Use --full-search to include hidden files and directories[/dim]\n")

Step 7: Main Function β€” Part 3 (List All Files)

Handle no arguments (list all files):

    # If no arguments provided, show all files
    if not args.filename and not args.filecontents:
        myFiles = getPath(full_search=args.full_search)
        console.print(Panel("[bold cyan]All files in the current directory and subdirectories[/bold cyan]", 
                          border_style="cyan"))
        
        if myFiles:
            table = Table(show_header=True, header_style="bold magenta")
            table.add_column("#", style="dim", width=6)
            table.add_column("File Path", style="cyan")
            
            for idx, file_path in enumerate(myFiles, 1):
                table.add_row(str(idx), str(file_path))
            
            console.print(table)
            console.print(f"\n[bold green]Total: {len(myFiles)} files found[/bold green]")
        else:
            console.print("[yellow]No files found.[/yellow]")
        
        console.print("\n[dim]Tip: Use --filename or --filecontents to search for specific files[/dim]")
        console.print("[dim]Run with --help for more information[/dim]")

Step 7: Main Function β€” Part 4 (Search by Filename)

Handle filename search:

    # Search by filename if provided
    if args.filename:
        myFiles = searchFilenames(args.path, args.filename, 
                                 full_search=args.full_search)
        console.print(Panel(f"[bold cyan]Files containing '{args.filename}' in their name[/bold cyan]", 
                          border_style="cyan"))
        
        if myFiles:
            table = Table(show_header=True, header_style="bold magenta")
            table.add_column("#", style="dim", width=6)
            table.add_column("File Path", style="green")
            
            for idx, file_path in enumerate(myFiles, 1):
                table.add_row(str(idx), str(file_path))
            
            console.print(table)
            console.print(f"\n[bold green]Total: {len(myFiles)} files found[/bold green]")
        else:
            console.print("[yellow]No files found.[/yellow]")

Step 7: Main Function β€” Part 5 (Search by Content)

Handle content search:

    # Search by file contents if provided
    if args.filecontents:
        myFiles = searchFileContents(args.path, args.filecontents, 
                                    full_search=args.full_search)
        console.print(Panel(f"[bold cyan]Files containing '{args.filecontents}' in their contents[/bold cyan]", 
                          border_style="cyan"))
        
        if myFiles:
            console.print(f"[bold green]Found {len(myFiles)} file(s)[/bold green]\n")
            for idx, file_path in enumerate(myFiles, 1):
                console.print(f"[bold yellow]{idx}. {file_path}[/bold yellow]")
                printFileContents(file_path)
                console.print()  # Add spacing between files
        else:
            console.print("[yellow]No files found.[/yellow]")
    
    console.print("[bold green]βœ“ Done.[/bold green]")

# End of main()

Step 7: Main Function β€” Part 6 (Entry Point)

Add the program entry point at the very end:

if __name__ == "__main__":
    main()

Note

Why if __name__ == "__main__":?

  • This block only runs when the file is executed directly
  • If someone imports your module, it won’t automatically run
  • Standard Python best practice for executable scripts
  • Allows the code to be both a script and an importable module

πŸŽ‰ Code Complete!

Congratulations! You’ve Built the Entire Program!

Your src/main.py now contains:

βœ… Import statements and setup
βœ… Helper function for hidden file detection
βœ… Three search functions (all files, by name, by content)
βœ… Display function with syntax highlighting
βœ… Main function with full command-line interface
βœ… Entry point for program execution

Total Lines: ~190 lines of well-documented, professional Python code!

πŸ§ͺ Part 5: Testing Your Program

Manual Testing β€” Basic Commands

Test 1: List all files

python src/main.py

Expected: Table showing all non-hidden files in current directory

Test 2: Search by filename

python src/main.py --filename main

Expected: Finds main.py and displays in table

Test 3: Search by content

python src/main.py --filecontents "import"

Expected: Finds files with β€œimport” and shows syntax-highlighted contents

More Test Cases

Advanced Testing

Test 4: Full search mode

python src/main.py --filename pyproject --full-search

Expected: Finds pyproject.toml (even if in hidden directory)

Test 5: Combined search

python src/main.py --filename py --filecontents "def"

Expected: Finds Python files containing function definitions

Test 6: Custom path

python src/main.py --path ./src --filename py

Expected: Only searches in src/ directory

Test 7: Help message

python src/main.py --help

Expected: Shows all available options

Creating Test Files

Set Up a Test Environment

Create test files to verify functionality:

# Create a test directory
mkdir test_data
cd test_data

# Create some test files
echo "def hello(): print('Hello')" > test1.py
echo "import os" > test2.py
echo "# This is a comment" > test3.py
echo "Configuration data" > config.txt
mkdir .hidden
echo "Secret file" > .hidden/secret.txt

# Go back to project root
cd ..

Add Files Another Way: Just edit text files using your editor and store them in the test_data/ directory. 🎨

Using the Test Environment

Verify your program works correctly with these test files!

# Should find 3 Python files
python src/main.py --path ./test_data --filename py

# Should find files with 'import'
python src/main.py --path ./test_data --filecontents "import"

# Should NOT find .hidden files (normal mode)
python src/main.py --path ./test_data --filename secret

# SHOULD find .hidden files (full search mode)
python src/main.py --path ./test_data --filename secret --full-search

Automated Testing with pytest (Part 1)

Optional: Unit Tests

Install pytest:

uv add --dev pytest

Create tests/test_main.py:

import pytest
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from main import is_hidden, getPath, searchFilenames

def test_is_hidden():
    """Test hidden file detection"""
    assert is_hidden(Path(".git/config")) == True
    assert is_hidden(Path("src/main.py")) == False
    assert is_hidden(Path(".gitignore")) == True
    assert is_hidden(Path("README.md")) == False
# End of test_is_hidden()

Automated Testing with pytest (Part 2)

Optional: Unit Tests, Continued

def test_getPath():
    """Test file listing"""
    files = getPath(full_search=False)
    assert isinstance(files, list)
    # Should not include hidden files
    assert not any(is_hidden(f) for f in files)
# End of test_getPath()

Automated Testing with pytest (Part 3)

Important

def test_searchFilenames():
    """Test filename search"""
    # Search for Python files
    py_files = searchFilenames("./src", "py", full_search=False)
    assert all(".py" in str(f) or "py" in f.name for f in py_files)
# End of test_searchFilenames()

Running Automated Tests With Pytest

Execute Your Test Suite

Run all tests:

pytest tests/

Run with verbose output:

pytest -v tests/

Run with coverage report:

uv add --dev pytest-cov
pytest --cov=src tests/

Expected output:

======================== test session starts =========================
collected 3 items

tests/test_main.py::test_is_hidden PASSED                     [ 33%]
tests/test_main.py::test_getPath PASSED                       [ 66%]
tests/test_main.py::test_searchFilenames PASSED               [100%]

========================= 3 passed in 0.12s ==========================

Verification: Testing Checklist (Part 1)

What kind of tests should you run to verify your program works correctly? Use this checklist to make sure you’ve covered all the important cases!

βœ… Basic Functionality:

βœ… Edge Cases:

Verification: Testing Checklist (Part 2)

βœ… Output Quality:

πŸš€ Part 6: Using Your Tool

Real-World Usage Examples

Find all Python files with a specific import:

python src/main.py --filecontents "from pathlib import Path"

Locate all configuration files:

python src/main.py --filename config

Search for TODO comments across your project:

python src/main.py --filecontents "TODO" --full-search

Find all markdown documentation:

python src/main.py --filename .md

Audit for security issues:

python src/main.py --filecontents "password" --full-search

Extending the Project (Part 1)

Ideas for Enhancement

Beginner:

  • Add case-insensitive search option (--ignore-case)
  • Count total matches and display statistics
  • Save results to a file (--output results.txt)
  • Add file size information to the table

Intermediate:

  • Regular expression support for advanced patterns
  • Exclude certain file types (--exclude "*.log")
  • Search within specific date ranges
  • Export results as JSON or CSV

Extending the Project (Part 2)

More Ideas for Enhancement

Advanced:

  • Parallel processing for faster searches
  • Interactive mode with menu selection
  • Fuzzy matching for typo tolerance
  • Integration with git to search only tracked files

πŸ“š What We Covered

Skills Acquired in This Project

Python Concepts:

βœ… Modern project structure with UV
βœ… Command-line argument parsing with argparse
βœ… File system operations with pathlib
βœ… List comprehensions and generators
βœ… Exception handling best practices
βœ… Function design and documentation
βœ… Conditional logic and control flow

Tools and Libraries:

βœ… UV package manager
βœ… Rich library for terminal UI
βœ… pytest for unit testing
βœ… Syntax highlighting and themes

Software Engineering:

βœ… Code organization and modularity
βœ… User experience design
βœ… Error handling strategies
βœ… Testing methodologies

🎯 Challenge Problems

Practice Exercises

Challenge 1: Add a Counter

Modify the program to count how many times the search term appears in each file, not just which files contain it.

Challenge 2: File Size Filter

Add options --min-size and --max-size to filter files by size (e.g., --min-size 1KB --max-size 1MB).

Challenge 3: Date Filter

Add options to search only files modified within a certain date range.

Challenge 4: Export Results

Add a --export option that saves results to a JSON file with file paths, sizes, and match counts.

Challenge 5: Interactive Mode

Create an interactive mode where users can repeatedly search without restarting the program.

Challenge 1: Solution β€” Add a Counter

Counting Occurrences

Modify searchFileContents() to return count information:

def searchFileContents(myPath, searchTerm, myFileType="*", full_search=False):
    """Search for files containing searchTerm in their contents."""
    base_path = Path(myPath)
    
    results = []  # Store (path, count) tuples
    for p in base_path.rglob(myFileType):
        if p.is_file():
            if not full_search and is_hidden(p):
                continue
            try:
                with p.open() as f:
                    content = f.read()
                    count = content.count(searchTerm)
                    if count > 0:
                        results.append((p, count))
            except Exception as e:
                console.print(f"[yellow]Warning: Error reading file {p}: {e}[/yellow]")
    
    return results

# Update display code to show counts:
if myFiles:
    for idx, (file_path, count) in enumerate(myFiles, 1):
        console.print(f"[bold yellow]{idx}. {file_path} (Found {count} times)[/bold yellow]")
        printFileContents(file_path)

Challenge 2: Solution β€” File Size Filter

Adding Size Parameters

Add to argument parser:

parser.add_argument(
    '--min-size',
    type=str,
    help='Minimum file size (e.g., 1KB, 1MB)'
)

parser.add_argument(
    '--max-size',
    type=str,
    help='Maximum file size (e.g., 10MB)'
)

# Helper function to parse size strings
def parse_size(size_str):
    """Convert size string like '1KB' to bytes."""
    units = {'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3}
    size_str = size_str.upper()
    for unit, multiplier in units.items():
        if size_str.endswith(unit):
            return float(size_str[:-len(unit)]) * multiplier
    return float(size_str)

# Add size filtering to search functions:
if args.min_size or args.max_size:
    min_bytes = parse_size(args.min_size) if args.min_size else 0
    max_bytes = parse_size(args.max_size) if args.max_size else float('inf')
    myFiles = [f for f in myFiles 
               if min_bytes <= f.stat().st_size <= max_bytes]

Challenge 3: Solution β€” Date Filter

Getting Started: Here is some code to study that will help you write your date filter functionality. 🎨

Important

from pathlib import Path
from datetime import datetime

# Define the file path
file_path = Path('your_report.csv')

# Get file status information
stats = file_path.stat()

# Extract and convert timestamps
modified_date = datetime.fromtimestamp(stats.st_mtime)
created_date = datetime.fromtimestamp(stats.st_ctime)

print(f"Last Modified: {modified_date.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Created: {created_date.strftime('%Y-%m-%d %H:%M:%S')}")

πŸ“– Resources and References

Keep On Learning: If you want to learn more about the concepts and tools used in this project, here are some helpful resources! I used these to make these slides!! 🎨

Learn More

Documentation:

Related Topics:

  • File I/O operations in Python
  • Regular expressions for pattern matching
  • Command-line tool design principles
  • Software testing best practices