- Remove unused parameters, dead files, and inaccurate doc.go files - Extract shared helpers, eliminate duplication - Rename cheatpath.Cheatpath to cheatpath.Path - Optimize filesystem walks (WalkDir, skip .git) - Move sheet name validation to sheet.Validate - Move integration tests to test/integration/ - Consolidate internal/mock into mocks/ - Move fuzz.sh to test/ - Inline loadSheets helper into command callers - Extract config.New into its own file - Fix stale references in HACKING.md and CLAUDE.md - Restore plan9 build target - Remove redundant and low-value tests - Clean up project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.4 KiB
Hacking Guide
This document provides a comprehensive guide for developing cheat, including setup, architecture overview, and code patterns.
Quick Start
1. Install system dependencies
The following are required and must be available on your PATH:
gitgo(>= 1.19 is recommended)make
Optional dependencies:
dockerpandoc(necessary to generate amanpage)
2. Install utility applications
Run make setup to install scc and revive, which are used by various make targets.
3. Development workflow
- Make changes to the
cheatsource code - Run
make testto run unit-tests - Fix compiler errors and failing tests as necessary
- Run
make build. Acheatexecutable will be written to thedistdirectory - Use the new executable by running
dist/cheat <command> - Run
make installto installcheatto yourPATH - Run
make build-releaseto build cross-platform binaries indist - Run
make cleanto clean thedistdirectory when desired
You may run make help to see a list of available make commands.
4. Testing
Unit Tests
Run unit tests with:
make test
Integration Tests
Integration tests that require network access are separated using build tags. Run them with:
make test-integration
To run all tests (unit and integration):
make test-all
Test Coverage
Generate a coverage report with:
make coverage # HTML report
make coverage-text # Terminal output
Architecture Overview
Package Structure
The cheat application follows a clean architecture with well-separated concerns:
cmd/cheat/: Command layer with argument parsing and command routinginternal/config: Configuration management (YAML loading, validation, paths)internal/cheatpath: Cheatsheet path management (collections, filtering)internal/sheet: Individual cheatsheet handling (parsing, search, highlighting)internal/sheets: Collection operations (loading, consolidation, filtering)internal/display: Output formatting (pager integration, colorization)internal/repo: Git repository management for community sheets
Key Design Patterns
- Filesystem-based storage: Cheatsheets are plain text files
- Override mechanism: Local sheets override community sheets with same name
- Tag system: Sheets can be categorized with tags in frontmatter
- Multiple cheatpaths: Supports personal, community, and directory-scoped sheets
Core Types and Functions
Config (internal/config)
The main configuration structure:
type Config struct {
Colorize bool `yaml:"colorize"`
Editor string `yaml:"editor"`
Cheatpaths []cp.Path `yaml:"cheatpaths"`
Style string `yaml:"style"`
Formatter string `yaml:"formatter"`
Pager string `yaml:"pager"`
Path string
}
Key functions:
New(confPath, resolve)- Load config from fileValidate()- Validate configuration valuesEditor()- Get editor from environment or defaults (package-level function)Pager()- Get pager from environment or defaults (package-level function)
Cheatpath (internal/cheatpath)
Represents a directory containing cheatsheets:
type Path struct {
Name string // Friendly name (e.g., "personal")
Path string // Filesystem path
Tags []string // Tags applied to all sheets in this path
ReadOnly bool // Whether sheets can be modified
}
Sheet (internal/sheet)
Represents an individual cheatsheet:
type Sheet struct {
Title string // Sheet name (from filename)
CheatPath string // Name of the cheatpath this sheet belongs to
Path string // Full filesystem path
Text string // Content (without frontmatter)
Tags []string // Combined tags (from frontmatter + cheatpath)
Syntax string // Syntax for highlighting
ReadOnly bool // Whether sheet can be edited
}
Key methods:
New(title, cheatpath, path, tags, readOnly)- Load from fileSearch(reg)- Search content with a compiled regexpColorize(conf)- Apply syntax highlighting (modifies sheet in place)Tagged(needle)- Check if sheet has the given tag
Common Operations
Loading and Displaying a Sheet
// Load sheet
s, err := sheet.New("tar", "personal", "/path/to/tar", []string{"personal"}, false)
if err != nil {
log.Fatal(err)
}
// Apply syntax highlighting (modifies sheet in place)
s.Colorize(conf)
// Display with pager
display.Write(s.Text, conf)
Working with Sheet Collections
// Load all sheets from cheatpaths (returns a slice of maps, one per cheatpath)
allSheets, err := sheets.Load(conf.Cheatpaths)
if err != nil {
log.Fatal(err)
}
// Consolidate to handle duplicates (later cheatpaths take precedence)
consolidated := sheets.Consolidate(allSheets)
// Filter by tag (operates on the slice of maps)
filtered := sheets.Filter(allSheets, []string{"networking"})
// Sort alphabetically (returns a sorted slice)
sorted := sheets.Sort(consolidated)
Sheet Format
Cheatsheets are plain text files that may begin with YAML frontmatter:
---
syntax: bash
tags: [networking, linux, ssh]
---
# Connect to remote server
ssh user@hostname
# Copy files over SSH
scp local_file user@hostname:/remote/path
Testing
Run tests with:
make test # Run all tests
make coverage # Generate coverage report
go test ./... # Go test directly
Test files follow Go conventions:
*_test.gofiles in same package- Table-driven tests for multiple scenarios
- Mock data in
mockspackage
Error Handling
The codebase follows consistent error handling patterns:
- Functions return explicit errors
- Errors are wrapped with context using
fmt.Errorf - User-facing errors are written to stderr
Example:
sheet, err := sheet.New(path, tags, false)
if err != nil {
return fmt.Errorf("failed to load sheet: %w", err)
}
Developing with Docker
It may be useful to test your changes within a pristine environment. An Alpine-based docker container has been provided for that purpose.
Build the docker container:
make docker-setup
Shell into the container:
make docker-sh
The cheat source code will be mounted at /app within the container.
To destroy the container:
make distclean