Skip to main content

CPITD: Copy Paste Is The Devil

A static code analysis tool that rakes you over the coals for using copy/paste. Because copy/paste is the devil. Language agnostic, and blazingly fast.


Installation

pip install cpitd

Requires Python 3.10+.

Development

For development (linting, tests, docs):

git clone https://github.com/scythia-marrow/cpitd.git
cd cpitd
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pre-commit install

Quick Start

# Scan current directory
cpitd

# Scan specific paths
cpitd src/ lib/

# JSON output for CI pipelines
cpitd --format json src/ | jq '.[]'

Configuration

Settings can live in pyproject.toml so you don't repeat yourself on every invocation:

[tool.cpitd]
format = "human"
ignore = ["tests/fixtures/*", "vendor/*"]
suppress = ["*@abstractmethod*"]

CLI flags always override file config. For list options (ignore, suppress, languages), CLI values are appended to file values rather than replacing them.


Suppressing False Positives

Some clones are intentional—boilerplate required by a language or framework. Use --suppress to silence them.

--suppress PATTERN accepts fnmatch glob patterns matched against raw source lines (including one line of context above each clone chunk, to catch decorators). If any line in either side of a clone pair matches, the group is suppressed.

You can also annotate specific sites inline—the filter reads raw source, so comments are visible even though the tokenizer strips them. Add a suppression comment to any line inside or immediately above a clone:

Language Inline annotation
Python # cpitd: suppress
C/C++ // cpitd: suppress
Rust // cpitd: suppress

Then pass --suppress "*cpitd: suppress*" (or set it in pyproject.toml).

Python

Abstract base class implementations — ABCs force you to repeat method signatures across subclasses. Suppress them with:

cpitd src/ --suppress "*@abstractmethod*"

Or in pyproject.toml:

[tool.cpitd]
suppress = ["*@abstractmethod*", "*@override*"]

Protocol / interface boilerplate — if you use a decorator to mark protocol implementations (e.g. @protocol_impl), pass that pattern:

cpitd src/ --suppress "*@protocol_impl*"

C / C++

Header guards — every .h file has them. Suppress both styles:

cpitd src/ \
  --suppress "*#ifndef *_H*" \
  --suppress "*#pragma once*"

Or in pyproject.toml:

[tool.cpitd]
suppress = ["*#ifndef *_H*", "*#pragma once*"]
ignore = ["**/*.h"]   # alternatively, just skip headers entirely

Rust

Trait implementations — implementing the same trait for multiple types produces near-identical impl blocks. Suppress by matching the impl ... for ... line:

cpitd src/ --suppress "*impl * for *"

Derive macros — #[derive(Debug, Clone, PartialEq)] lines repeat everywhere but are rarely meaningful clones. Suppress them:

cpitd src/ --suppress "*#[derive(*"

In pyproject.toml:

[tool.cpitd]
suppress = [
    "*impl*Display*for*",
    "*impl*From*for*",
    "*#[derive(*",
]

Pre-commit Hook

Add cpitd to .pre-commit-config.yaml as a local hook (cpitd must be installed in the environment where hooks run):

repos:
  - repo: local
    hooks:
      - id: cpitd-clone-detection
        name: cpitd (clone detection)
        entry: cpitd src/ --ignore "tests/fixtures/*"
        language: system
        pass_filenames: false
        always_run: true

Then install the hook:

pre-commit install

The hook runs cpitd on every commit. Tune entry with --suppress or any other flag -- or lean on [tool.cpitd] in pyproject.toml so the hook entry stays short.

To run the hook manually without committing:

pre-commit run cpitd-clone-detection

CLI Options Reference

Flag Default Description
--format human|json human Output format
--ignore PATTERN — Glob patterns to exclude (repeatable)
--languages LANG — Restrict to specific languages (repeatable)
--suppress PATTERN — Suppress clones whose source lines match (repeatable)

Metadata

Release files for cpitd 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cpitd 0.3.0
File Size Uploaded
cpitd-0.3.0.tar.gz 34.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cpitd 0.3.0
File Interpreter ABI Platform
cpitd-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 57.1 kB

Release files / cpitd-0.3.0.tar.gz

Download URL cpitd-0.3.0.tar.gz
Size 34.7 kB
Tags Source
SHA-256 checksum
How to use checksums
0dfc884f445d3bbaa3d00518c02b3632b4752cbc0073060e4e9eb9c70923a6f8
BLAKE2b-256 checksum
How to use checksums
2c7657d12c4f40c7d5d330a263f1601e0c9609f95cf5dda733aa8d7dd60b1ac4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release files / cpitd-0.3.0-py3-none-any.whl

Download URL cpitd-0.3.0-py3-none-any.whl
Size 22.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a4030303050b0d8910cd13d37c2a05163d4f780863243538e636125df32ce121
BLAKE2b-256 checksum
How to use checksums
8afc27331b3e5389545da13ee9f2d39bb3b6dede5fc5859e85910da7cc1368ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page