codestamp 0.1.9__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codestamp-0.2.0/PKG-INFO +238 -0
- codestamp-0.2.0/README.md +210 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/pyproject.toml +1 -1
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp/cli.py +94 -25
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp/core.py +11 -13
- codestamp-0.2.0/src/codestamp.egg-info/PKG-INFO +238 -0
- codestamp-0.1.9/PKG-INFO +0 -145
- codestamp-0.1.9/README.md +0 -117
- codestamp-0.1.9/src/codestamp.egg-info/PKG-INFO +0 -145
- {codestamp-0.1.9 → codestamp-0.2.0}/setup.cfg +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp/__init__.py +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp/filetypes.py +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp/git.py +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp.egg-info/SOURCES.txt +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp.egg-info/dependency_links.txt +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp.egg-info/entry_points.txt +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp.egg-info/requires.txt +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/src/codestamp.egg-info/top_level.txt +0 -0
- {codestamp-0.1.9 → codestamp-0.2.0}/tests/test_core.py +0 -0
codestamp-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codestamp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI tool to manage copyright headers in source-code files
|
|
5
|
+
Author: Shardul Kulkarni
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Medhavee-Inc/codestamp
|
|
8
|
+
Project-URL: Issues, https://github.com/Medhavee-Inc/codestamp/issues
|
|
9
|
+
Keywords: copyright,license,header,cli,source-code
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Version Control
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: click>=8.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# codestamp
|
|
30
|
+
|
|
31
|
+
> Stamp and manage copyright headers across your source-code files.
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/codestamp/)
|
|
34
|
+
[](https://pypi.org/project/codestamp/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
Built by **Medhavee Inc** · Author **Shardul Kulkarni**
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install codestamp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or for local development:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/yourusername/codestamp
|
|
51
|
+
cd codestamp
|
|
52
|
+
pip install -r requirements-dev.txt
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
1. Create a `license.txt` in your project root whose **first line** is your copyright statement:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Copyright (c) 2026 Medhavee Inc. All rights reserved. Proprietary and confidential.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
2. Run from the project root:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Add headers to files changed in the last git commit (CI-friendly default)
|
|
70
|
+
codestamp
|
|
71
|
+
|
|
72
|
+
# Add headers to ALL source files in the tree
|
|
73
|
+
codestamp --bulk
|
|
74
|
+
|
|
75
|
+
# Add headers to staged files only
|
|
76
|
+
codestamp --staged
|
|
77
|
+
|
|
78
|
+
# Skip specific files or directories using an exclusion file
|
|
79
|
+
codestamp --bulk --exclude .csignore
|
|
80
|
+
|
|
81
|
+
# Preview without writing anything
|
|
82
|
+
codestamp --bulk --dry-run
|
|
83
|
+
|
|
84
|
+
# Update every file's copyright to a new statement
|
|
85
|
+
codestamp update "Copyright (c) 2026 Medhavee Foundation. All rights reserved."
|
|
86
|
+
|
|
87
|
+
# See all supported file types
|
|
88
|
+
codestamp list-types
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Commands
|
|
94
|
+
|
|
95
|
+
| Command | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `codestamp` | Process files added/modified in the last git commit (default) |
|
|
98
|
+
| `codestamp --bulk` | Process all source files in the project tree |
|
|
99
|
+
| `codestamp --staged` | Process only currently git-staged files |
|
|
100
|
+
| `codestamp --dry-run` | Preview all actions without writing any files |
|
|
101
|
+
| `codestamp update "<text>"` | Replace old copyright header with new text everywhere |
|
|
102
|
+
| `codestamp list-types` | Show all supported file extensions and comment styles |
|
|
103
|
+
|
|
104
|
+
### Options
|
|
105
|
+
|
|
106
|
+
| Flag | Default | Description |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `--license <path>` | `license.txt` | Custom path to your license/copyright file |
|
|
109
|
+
| `--exclude <path>` | _(none)_ | Path to an exclusion file (see below) |
|
|
110
|
+
| `--dry-run` | off | Preview mode — no files are written |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Excluding files and directories
|
|
115
|
+
|
|
116
|
+
Pass `--exclude <file>` to point codestamp at a plain-text exclusion list. Each line can be a file path, a directory, a glob pattern, or a comment:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# .csignore — paths codestamp should never touch
|
|
120
|
+
|
|
121
|
+
# Third-party files
|
|
122
|
+
vendor/google_utils.py
|
|
123
|
+
lib/react_helpers.js
|
|
124
|
+
|
|
125
|
+
# Entire directories
|
|
126
|
+
vendor/
|
|
127
|
+
third_party/
|
|
128
|
+
node_modules/
|
|
129
|
+
|
|
130
|
+
# Glob patterns
|
|
131
|
+
*.min.js
|
|
132
|
+
*.generated.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then run:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
codestamp --bulk --exclude .csignore
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Rules supported per line:
|
|
142
|
+
|
|
143
|
+
| Pattern | Matches |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `vendor/lib.py` | Exact file path |
|
|
146
|
+
| `vendor/` or `vendor` | Any file inside that directory |
|
|
147
|
+
| `*.min.js` | Glob matched against the full file path |
|
|
148
|
+
| `# comment` | Ignored |
|
|
149
|
+
| _(blank line)_ | Ignored |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Output & status codes
|
|
154
|
+
|
|
155
|
+
Every file processed is reported with a status icon:
|
|
156
|
+
|
|
157
|
+
| Icon | Status | Meaning |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| ✅ | `ADDED` | Copyright header successfully stamped |
|
|
160
|
+
| 🔄 | `UPDATED` | Existing header replaced with new text |
|
|
161
|
+
| ⏭ | `SKIPPED` | File already has your copyright — left untouched |
|
|
162
|
+
| ➖ | `UNSUPPORTED` | File extension not recognised |
|
|
163
|
+
| ❌ | `ERROR` | Could not read or write the file |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Supported file types
|
|
168
|
+
|
|
169
|
+
| Style | Extensions |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `# comment` | `.py` `.sh` `.bash` `.zsh` `.rb` `.pl` `.r` `.yaml` `.yml` `.toml` `.conf` `.tf` `.tfvars` |
|
|
172
|
+
| `// comment` | `.js` `.ts` `.jsx` `.tsx` `.java` `.go` `.swift` `.kt` `.kts` `.rs` `.cs` `.cpp` `.cc` `.c` `.h` `.hpp` `.dart` `.scala` `.groovy` `.php` |
|
|
173
|
+
| `/* comment */` | `.css` `.scss` `.less` |
|
|
174
|
+
| `<!-- comment -->` | `.html` `.svelte` `.vue` `.xml` `.svg` |
|
|
175
|
+
|
|
176
|
+
Run `codestamp list-types` to see the full up-to-date list.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## How headers are inserted
|
|
181
|
+
|
|
182
|
+
- **Shebang lines** (`#!/usr/bin/env python3`) are always respected — the header is inserted on line 2, never before the shebang.
|
|
183
|
+
- Files that already contain your copyright text are silently **skipped**.
|
|
184
|
+
- The `update` command rewrites `license.txt` with the new header text after updating all files.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Git integration
|
|
189
|
+
|
|
190
|
+
The default mode (no flags) processes files from the **last commit**, making it a perfect fit for CI pipelines:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
# .github/workflows/copyright.yml
|
|
194
|
+
name: Copyright Headers
|
|
195
|
+
|
|
196
|
+
on: [push]
|
|
197
|
+
|
|
198
|
+
jobs:
|
|
199
|
+
stamp:
|
|
200
|
+
runs-on: ubuntu-latest
|
|
201
|
+
steps:
|
|
202
|
+
- uses: actions/checkout@v4
|
|
203
|
+
with:
|
|
204
|
+
fetch-depth: 2 # needed so HEAD~1 diff works
|
|
205
|
+
|
|
206
|
+
- name: Install codestamp
|
|
207
|
+
run: pip install codestamp
|
|
208
|
+
|
|
209
|
+
- name: Stamp new files
|
|
210
|
+
run: |
|
|
211
|
+
codestamp --exclude .csignore
|
|
212
|
+
git diff --quiet || (
|
|
213
|
+
git config user.email "bot@ci"
|
|
214
|
+
git config user.name "CI Bot"
|
|
215
|
+
git commit -am "chore: add copyright headers"
|
|
216
|
+
git push
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Contributing
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
git clone https://github.com/yourusername/codestamp
|
|
226
|
+
cd codestamp
|
|
227
|
+
pip install -r requirements-dev.txt
|
|
228
|
+
pip install -e .
|
|
229
|
+
pytest
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Pull requests are welcome! Please open an issue first for larger changes.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT © Shardul Kulkarni / Medhavee Inc
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# codestamp
|
|
2
|
+
|
|
3
|
+
> Stamp and manage copyright headers across your source-code files.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/codestamp/)
|
|
6
|
+
[](https://pypi.org/project/codestamp/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Built by **Medhavee Inc** · Author **Shardul Kulkarni**
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install codestamp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or for local development:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git clone https://github.com/yourusername/codestamp
|
|
23
|
+
cd codestamp
|
|
24
|
+
pip install -r requirements-dev.txt
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
1. Create a `license.txt` in your project root whose **first line** is your copyright statement:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Copyright (c) 2026 Medhavee Inc. All rights reserved. Proprietary and confidential.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Run from the project root:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Add headers to files changed in the last git commit (CI-friendly default)
|
|
42
|
+
codestamp
|
|
43
|
+
|
|
44
|
+
# Add headers to ALL source files in the tree
|
|
45
|
+
codestamp --bulk
|
|
46
|
+
|
|
47
|
+
# Add headers to staged files only
|
|
48
|
+
codestamp --staged
|
|
49
|
+
|
|
50
|
+
# Skip specific files or directories using an exclusion file
|
|
51
|
+
codestamp --bulk --exclude .csignore
|
|
52
|
+
|
|
53
|
+
# Preview without writing anything
|
|
54
|
+
codestamp --bulk --dry-run
|
|
55
|
+
|
|
56
|
+
# Update every file's copyright to a new statement
|
|
57
|
+
codestamp update "Copyright (c) 2026 Medhavee Foundation. All rights reserved."
|
|
58
|
+
|
|
59
|
+
# See all supported file types
|
|
60
|
+
codestamp list-types
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
| Command | Description |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `codestamp` | Process files added/modified in the last git commit (default) |
|
|
70
|
+
| `codestamp --bulk` | Process all source files in the project tree |
|
|
71
|
+
| `codestamp --staged` | Process only currently git-staged files |
|
|
72
|
+
| `codestamp --dry-run` | Preview all actions without writing any files |
|
|
73
|
+
| `codestamp update "<text>"` | Replace old copyright header with new text everywhere |
|
|
74
|
+
| `codestamp list-types` | Show all supported file extensions and comment styles |
|
|
75
|
+
|
|
76
|
+
### Options
|
|
77
|
+
|
|
78
|
+
| Flag | Default | Description |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `--license <path>` | `license.txt` | Custom path to your license/copyright file |
|
|
81
|
+
| `--exclude <path>` | _(none)_ | Path to an exclusion file (see below) |
|
|
82
|
+
| `--dry-run` | off | Preview mode — no files are written |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Excluding files and directories
|
|
87
|
+
|
|
88
|
+
Pass `--exclude <file>` to point codestamp at a plain-text exclusion list. Each line can be a file path, a directory, a glob pattern, or a comment:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# .csignore — paths codestamp should never touch
|
|
92
|
+
|
|
93
|
+
# Third-party files
|
|
94
|
+
vendor/google_utils.py
|
|
95
|
+
lib/react_helpers.js
|
|
96
|
+
|
|
97
|
+
# Entire directories
|
|
98
|
+
vendor/
|
|
99
|
+
third_party/
|
|
100
|
+
node_modules/
|
|
101
|
+
|
|
102
|
+
# Glob patterns
|
|
103
|
+
*.min.js
|
|
104
|
+
*.generated.py
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Then run:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
codestamp --bulk --exclude .csignore
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Rules supported per line:
|
|
114
|
+
|
|
115
|
+
| Pattern | Matches |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `vendor/lib.py` | Exact file path |
|
|
118
|
+
| `vendor/` or `vendor` | Any file inside that directory |
|
|
119
|
+
| `*.min.js` | Glob matched against the full file path |
|
|
120
|
+
| `# comment` | Ignored |
|
|
121
|
+
| _(blank line)_ | Ignored |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Output & status codes
|
|
126
|
+
|
|
127
|
+
Every file processed is reported with a status icon:
|
|
128
|
+
|
|
129
|
+
| Icon | Status | Meaning |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| ✅ | `ADDED` | Copyright header successfully stamped |
|
|
132
|
+
| 🔄 | `UPDATED` | Existing header replaced with new text |
|
|
133
|
+
| ⏭ | `SKIPPED` | File already has your copyright — left untouched |
|
|
134
|
+
| ➖ | `UNSUPPORTED` | File extension not recognised |
|
|
135
|
+
| ❌ | `ERROR` | Could not read or write the file |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Supported file types
|
|
140
|
+
|
|
141
|
+
| Style | Extensions |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `# comment` | `.py` `.sh` `.bash` `.zsh` `.rb` `.pl` `.r` `.yaml` `.yml` `.toml` `.conf` `.tf` `.tfvars` |
|
|
144
|
+
| `// comment` | `.js` `.ts` `.jsx` `.tsx` `.java` `.go` `.swift` `.kt` `.kts` `.rs` `.cs` `.cpp` `.cc` `.c` `.h` `.hpp` `.dart` `.scala` `.groovy` `.php` |
|
|
145
|
+
| `/* comment */` | `.css` `.scss` `.less` |
|
|
146
|
+
| `<!-- comment -->` | `.html` `.svelte` `.vue` `.xml` `.svg` |
|
|
147
|
+
|
|
148
|
+
Run `codestamp list-types` to see the full up-to-date list.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## How headers are inserted
|
|
153
|
+
|
|
154
|
+
- **Shebang lines** (`#!/usr/bin/env python3`) are always respected — the header is inserted on line 2, never before the shebang.
|
|
155
|
+
- Files that already contain your copyright text are silently **skipped**.
|
|
156
|
+
- The `update` command rewrites `license.txt` with the new header text after updating all files.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Git integration
|
|
161
|
+
|
|
162
|
+
The default mode (no flags) processes files from the **last commit**, making it a perfect fit for CI pipelines:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
# .github/workflows/copyright.yml
|
|
166
|
+
name: Copyright Headers
|
|
167
|
+
|
|
168
|
+
on: [push]
|
|
169
|
+
|
|
170
|
+
jobs:
|
|
171
|
+
stamp:
|
|
172
|
+
runs-on: ubuntu-latest
|
|
173
|
+
steps:
|
|
174
|
+
- uses: actions/checkout@v4
|
|
175
|
+
with:
|
|
176
|
+
fetch-depth: 2 # needed so HEAD~1 diff works
|
|
177
|
+
|
|
178
|
+
- name: Install codestamp
|
|
179
|
+
run: pip install codestamp
|
|
180
|
+
|
|
181
|
+
- name: Stamp new files
|
|
182
|
+
run: |
|
|
183
|
+
codestamp --exclude .csignore
|
|
184
|
+
git diff --quiet || (
|
|
185
|
+
git config user.email "bot@ci"
|
|
186
|
+
git config user.name "CI Bot"
|
|
187
|
+
git commit -am "chore: add copyright headers"
|
|
188
|
+
git push
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git clone https://github.com/yourusername/codestamp
|
|
198
|
+
cd codestamp
|
|
199
|
+
pip install -r requirements-dev.txt
|
|
200
|
+
pip install -e .
|
|
201
|
+
pytest
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Pull requests are welcome! Please open an issue first for larger changes.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT © Shardul Kulkarni / Medhavee Inc
|
|
@@ -5,11 +5,12 @@ Manage copyright headers in source-code files.
|
|
|
5
5
|
|
|
6
6
|
Usage examples
|
|
7
7
|
--------------
|
|
8
|
-
codestamp
|
|
9
|
-
codestamp --bulk
|
|
10
|
-
codestamp --staged
|
|
11
|
-
codestamp --dry-run
|
|
12
|
-
codestamp
|
|
8
|
+
codestamp # git-diff mode (last commit)
|
|
9
|
+
codestamp --bulk # all source files in tree
|
|
10
|
+
codestamp --staged # only staged files
|
|
11
|
+
codestamp --dry-run # preview, no writes
|
|
12
|
+
codestamp --exclude .csignore # skip paths listed in a file
|
|
13
|
+
codestamp update "Copyright 2026…" # update existing headers
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
from __future__ import annotations
|
|
@@ -41,14 +42,13 @@ _LOGO_LINES = [
|
|
|
41
42
|
" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ",
|
|
42
43
|
]
|
|
43
44
|
|
|
44
|
-
# Gradient: magenta → blue → cyan → green (top to bottom)
|
|
45
45
|
_LOGO_COLORS = [
|
|
46
|
-
("magenta",
|
|
47
|
-
("bright_magenta",True),
|
|
48
|
-
("bright_blue",
|
|
49
|
-
("bright_cyan",
|
|
50
|
-
("cyan",
|
|
51
|
-
("bright_green",
|
|
46
|
+
("magenta", True),
|
|
47
|
+
("bright_magenta", True),
|
|
48
|
+
("bright_blue", True),
|
|
49
|
+
("bright_cyan", True),
|
|
50
|
+
("cyan", True),
|
|
51
|
+
("bright_green", True),
|
|
52
52
|
]
|
|
53
53
|
|
|
54
54
|
_DIVIDER = " " + "─" * 76
|
|
@@ -58,23 +58,73 @@ def _print_banner() -> None:
|
|
|
58
58
|
click.echo()
|
|
59
59
|
for line, (color, bold) in zip(_LOGO_LINES, _LOGO_COLORS):
|
|
60
60
|
click.echo(click.style(line, fg=color, bold=bold))
|
|
61
|
-
|
|
62
61
|
click.echo()
|
|
63
|
-
|
|
64
|
-
# Tagline — each segment in its own accent color
|
|
65
62
|
by_label = click.style(" ✦ ", fg="bright_yellow", bold=True)
|
|
66
63
|
org = click.style("Medhavee Inc", fg="bright_yellow", bold=True)
|
|
67
64
|
pipe1 = click.style(" │ ", fg="bright_black")
|
|
68
65
|
auth_label = click.style("author ", fg="bright_black")
|
|
69
66
|
author = click.style("Shardul Kulkarni", fg="bright_white", bold=True)
|
|
70
67
|
pipe2 = click.style(" │ ", fg="bright_black")
|
|
71
|
-
version = click.style("v0.
|
|
72
|
-
|
|
68
|
+
version = click.style("v0.2.0", fg="bright_green")
|
|
73
69
|
click.echo(by_label + org + pipe1 + auth_label + author + pipe2 + version)
|
|
74
70
|
click.echo(click.style(_DIVIDER, fg="bright_black"))
|
|
75
71
|
click.echo()
|
|
76
72
|
|
|
77
73
|
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Exclusion list loader
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def _load_exclusions(exclude_file: str | None) -> set[str]:
|
|
79
|
+
"""
|
|
80
|
+
Parse an exclusion file and return a set of normalised path strings.
|
|
81
|
+
|
|
82
|
+
Each line can be:
|
|
83
|
+
- a file path e.g. vendor/lib.py
|
|
84
|
+
- a directory e.g. third_party/
|
|
85
|
+
- a glob-style e.g. *.min.js (matched against the full path)
|
|
86
|
+
- a comment e.g. # this is ignored
|
|
87
|
+
- blank line (ignored)
|
|
88
|
+
"""
|
|
89
|
+
if not exclude_file:
|
|
90
|
+
return set()
|
|
91
|
+
|
|
92
|
+
p = Path(exclude_file)
|
|
93
|
+
if not p.exists():
|
|
94
|
+
click.echo(click.style(f" ✖ Exclusion file '{exclude_file}' not found.", fg="bright_red"), err=True)
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
exclusions: set[str] = set()
|
|
98
|
+
for raw in p.read_text(encoding="utf-8").splitlines():
|
|
99
|
+
line = raw.strip()
|
|
100
|
+
if not line or line.startswith("#"):
|
|
101
|
+
continue
|
|
102
|
+
exclusions.add(line.rstrip("/")) # normalise trailing slash on dirs
|
|
103
|
+
return exclusions
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _is_excluded(filepath: str, exclusions: set[str]) -> bool:
|
|
107
|
+
"""Return True if *filepath* matches any entry in *exclusions*."""
|
|
108
|
+
if not exclusions:
|
|
109
|
+
return False
|
|
110
|
+
p = Path(filepath)
|
|
111
|
+
for entry in exclusions:
|
|
112
|
+
entry_path = Path(entry)
|
|
113
|
+
# Exact file match
|
|
114
|
+
if p == entry_path:
|
|
115
|
+
return True
|
|
116
|
+
# Directory prefix match (e.g. "vendor" excludes "vendor/foo/bar.py")
|
|
117
|
+
try:
|
|
118
|
+
p.relative_to(entry_path)
|
|
119
|
+
return True
|
|
120
|
+
except ValueError:
|
|
121
|
+
pass
|
|
122
|
+
# Glob match against full path string
|
|
123
|
+
if p.match(entry):
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
78
128
|
# ---------------------------------------------------------------------------
|
|
79
129
|
# Helpers
|
|
80
130
|
# ---------------------------------------------------------------------------
|
|
@@ -101,12 +151,12 @@ def _collect_all_files(root: str = ".") -> list[str]:
|
|
|
101
151
|
def _read_license(license_path: str) -> str:
|
|
102
152
|
p = Path(license_path)
|
|
103
153
|
if not p.exists():
|
|
104
|
-
click.echo(click.style(f" ✖ '{license_path}' not found.", fg="
|
|
154
|
+
click.echo(click.style(f" ✖ '{license_path}' not found.", fg="bright_red", bold=True), err=True)
|
|
105
155
|
click.echo(click.style(" Tip: run from the project root, or pass --license <path>", fg="bright_black"), err=True)
|
|
106
156
|
sys.exit(1)
|
|
107
157
|
first = p.read_text(encoding="utf-8").splitlines()[0].strip()
|
|
108
158
|
if not first:
|
|
109
|
-
click.echo(click.style(f" ✖ '{license_path}' is empty.", fg="
|
|
159
|
+
click.echo(click.style(f" ✖ '{license_path}' is empty.", fg="bright_red", bold=True), err=True)
|
|
110
160
|
sys.exit(1)
|
|
111
161
|
return first
|
|
112
162
|
|
|
@@ -136,13 +186,13 @@ def _print_results(results: list[Result]) -> None:
|
|
|
136
186
|
errors = sum(1 for r in results if r.status == Status.ERROR)
|
|
137
187
|
|
|
138
188
|
click.echo()
|
|
139
|
-
click.echo(click.style(" " + "─" *
|
|
189
|
+
click.echo(click.style(" " + "─" * 55, fg="bright_black"))
|
|
140
190
|
click.echo(
|
|
141
191
|
" "
|
|
142
192
|
+ click.style(f"✅ {added} added ", fg="bright_green", bold=True)
|
|
143
193
|
+ click.style(f"🔄 {updated} updated ", fg="bright_cyan", bold=True)
|
|
144
194
|
+ click.style(f"⏭ {skipped} skipped ", fg="yellow", bold=True)
|
|
145
|
-
+ click.style(f"❌ {errors} errors",
|
|
195
|
+
+ click.style(f"❌ {errors} errors", fg="bright_red" if errors else "bright_black", bold=bool(errors))
|
|
146
196
|
)
|
|
147
197
|
click.echo()
|
|
148
198
|
|
|
@@ -153,12 +203,15 @@ def _print_results(results: list[Result]) -> None:
|
|
|
153
203
|
|
|
154
204
|
@click.group(invoke_without_command=True, context_settings={"help_option_names": ["-h", "--help"]})
|
|
155
205
|
@click.pass_context
|
|
156
|
-
@click.option("--license",
|
|
206
|
+
@click.option("--license", "license_path", default="license.txt", show_default=True,
|
|
157
207
|
help="Path to license.txt (first line = copyright text).")
|
|
208
|
+
@click.option("--exclude", "exclude_file", default=None,
|
|
209
|
+
help="Path to a file listing paths/dirs to exclude (one per line).")
|
|
158
210
|
@click.option("--bulk", is_flag=True, help="Process ALL source files in the current tree.")
|
|
159
211
|
@click.option("--staged", is_flag=True, help="Process only currently staged git files.")
|
|
160
212
|
@click.option("--dry-run", is_flag=True, help="Preview actions without writing any files.")
|
|
161
|
-
def cli(ctx: click.Context, license_path: str,
|
|
213
|
+
def cli(ctx: click.Context, license_path: str, exclude_file: str | None,
|
|
214
|
+
bulk: bool, staged: bool, dry_run: bool) -> None:
|
|
162
215
|
"""Stamp copyright headers onto your source-code files.\n
|
|
163
216
|
Default mode: processes files added/modified in the last git commit.
|
|
164
217
|
"""
|
|
@@ -168,7 +221,11 @@ def cli(ctx: click.Context, license_path: str, bulk: bool, staged: bool, dry_run
|
|
|
168
221
|
return
|
|
169
222
|
|
|
170
223
|
copyright_text = _read_license(license_path)
|
|
224
|
+
exclusions = _load_exclusions(exclude_file)
|
|
225
|
+
|
|
171
226
|
_kv("Copyright :", copyright_text, "bright_white")
|
|
227
|
+
if exclusions:
|
|
228
|
+
_kv("Excluding :", f"{len(exclusions)} rule(s) from {exclude_file}", "yellow")
|
|
172
229
|
|
|
173
230
|
if dry_run:
|
|
174
231
|
click.echo(click.style("\n ⚠ Dry-run mode — no files will be changed.", fg="bright_yellow"))
|
|
@@ -191,6 +248,10 @@ def cli(ctx: click.Context, license_path: str, bulk: bool, staged: bool, dry_run
|
|
|
191
248
|
mode = "GIT DIFF — files in last commit"
|
|
192
249
|
files = get_last_commit_files()
|
|
193
250
|
|
|
251
|
+
# Apply exclusions
|
|
252
|
+
if exclusions:
|
|
253
|
+
files = [f for f in files if not _is_excluded(f, exclusions)]
|
|
254
|
+
|
|
194
255
|
_kv("Mode :", mode, "bright_cyan")
|
|
195
256
|
_kv("Files :", str(len(files)), "bright_white")
|
|
196
257
|
|
|
@@ -210,26 +271,34 @@ def cli(ctx: click.Context, license_path: str, bulk: bool, staged: bool, dry_run
|
|
|
210
271
|
@click.argument("new_header")
|
|
211
272
|
@click.option("--license", "license_path", default="license.txt", show_default=True,
|
|
212
273
|
help="Path to license.txt (first line is the OLD copyright text).")
|
|
274
|
+
@click.option("--exclude", "exclude_file", default=None,
|
|
275
|
+
help="Path to a file listing paths/dirs to exclude (one per line).")
|
|
213
276
|
@click.option("--dry-run", is_flag=True, help="Preview without writing.")
|
|
214
|
-
def update(new_header: str, license_path: str, dry_run: bool) -> None:
|
|
277
|
+
def update(new_header: str, license_path: str, exclude_file: str | None, dry_run: bool) -> None:
|
|
215
278
|
"""Replace existing copyright headers with NEW_HEADER across all source files.
|
|
216
279
|
|
|
217
280
|
Also rewrites license.txt with the new header (unless --dry-run).
|
|
218
281
|
|
|
219
282
|
\b
|
|
220
283
|
Example:
|
|
221
|
-
codestamp update "Copyright (c) 2026
|
|
284
|
+
codestamp update "Copyright (c) 2026 Medhavee Foundation. All rights reserved."
|
|
222
285
|
"""
|
|
223
286
|
old_copyright = _read_license(license_path)
|
|
287
|
+
exclusions = _load_exclusions(exclude_file)
|
|
224
288
|
|
|
225
289
|
_kv("Old :", old_copyright, "bright_black")
|
|
226
290
|
_kv("New :", new_header, "bright_white")
|
|
291
|
+
if exclusions:
|
|
292
|
+
_kv("Excluding :", f"{len(exclusions)} rule(s) from {exclude_file}", "yellow")
|
|
227
293
|
|
|
228
294
|
if dry_run:
|
|
229
295
|
click.echo(click.style("\n ⚠ Dry-run mode — no files will be changed.", fg="bright_yellow"))
|
|
230
296
|
click.echo()
|
|
231
297
|
|
|
232
298
|
files = _collect_all_files()
|
|
299
|
+
if exclusions:
|
|
300
|
+
files = [f for f in files if not _is_excluded(f, exclusions)]
|
|
301
|
+
|
|
233
302
|
if not files:
|
|
234
303
|
click.echo(click.style(" No files found.", fg="yellow"))
|
|
235
304
|
return
|