dktcli 1.0.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.
Files changed (48) hide show
  1. dktcli-1.0.0/LICENSE +21 -0
  2. dktcli-1.0.0/PKG-INFO +148 -0
  3. dktcli-1.0.0/README.md +129 -0
  4. dktcli-1.0.0/pyproject.toml +33 -0
  5. dktcli-1.0.0/setup.cfg +4 -0
  6. dktcli-1.0.0/src/devkittools/__init__.py +3 -0
  7. dktcli-1.0.0/src/devkittools/__main__.py +6 -0
  8. dktcli-1.0.0/src/devkittools/cli.py +179 -0
  9. dktcli-1.0.0/src/devkittools/tools/__init__.py +1 -0
  10. dktcli-1.0.0/src/devkittools/tools/bench.py +73 -0
  11. dktcli-1.0.0/src/devkittools/tools/changeloggen.py +181 -0
  12. dktcli-1.0.0/src/devkittools/tools/clean.py +110 -0
  13. dktcli-1.0.0/src/devkittools/tools/colortool.py +126 -0
  14. dktcli-1.0.0/src/devkittools/tools/commitwriter.py +199 -0
  15. dktcli-1.0.0/src/devkittools/tools/contextpacker.py +226 -0
  16. dktcli-1.0.0/src/devkittools/tools/cron.py +152 -0
  17. dktcli-1.0.0/src/devkittools/tools/doctor.py +74 -0
  18. dktcli-1.0.0/src/devkittools/tools/du.py +72 -0
  19. dktcli-1.0.0/src/devkittools/tools/enc.py +131 -0
  20. dktcli-1.0.0/src/devkittools/tools/envtool.py +141 -0
  21. dktcli-1.0.0/src/devkittools/tools/findtool.py +119 -0
  22. dktcli-1.0.0/src/devkittools/tools/gen.py +182 -0
  23. dktcli-1.0.0/src/devkittools/tools/gx.py +184 -0
  24. dktcli-1.0.0/src/devkittools/tools/hashtool.py +93 -0
  25. dktcli-1.0.0/src/devkittools/tools/httptool.py +131 -0
  26. dktcli-1.0.0/src/devkittools/tools/ignore.py +213 -0
  27. dktcli-1.0.0/src/devkittools/tools/initproj.py +166 -0
  28. dktcli-1.0.0/src/devkittools/tools/jsontool.py +200 -0
  29. dktcli-1.0.0/src/devkittools/tools/net.py +114 -0
  30. dktcli-1.0.0/src/devkittools/tools/portkiller.py +246 -0
  31. dktcli-1.0.0/src/devkittools/tools/regextool.py +84 -0
  32. dktcli-1.0.0/src/devkittools/tools/repostats.py +197 -0
  33. dktcli-1.0.0/src/devkittools/tools/secrets_scan.py +108 -0
  34. dktcli-1.0.0/src/devkittools/tools/serve.py +118 -0
  35. dktcli-1.0.0/src/devkittools/tools/snip.py +94 -0
  36. dktcli-1.0.0/src/devkittools/tools/system.py +157 -0
  37. dktcli-1.0.0/src/devkittools/tools/timetool.py +130 -0
  38. dktcli-1.0.0/src/devkittools/tools/todo.py +69 -0
  39. dktcli-1.0.0/src/devkittools/tools/tree.py +86 -0
  40. dktcli-1.0.0/src/devkittools/tools/watch.py +82 -0
  41. dktcli-1.0.0/src/devkittools/utils.py +561 -0
  42. dktcli-1.0.0/src/dktcli.egg-info/PKG-INFO +148 -0
  43. dktcli-1.0.0/src/dktcli.egg-info/SOURCES.txt +46 -0
  44. dktcli-1.0.0/src/dktcli.egg-info/dependency_links.txt +1 -0
  45. dktcli-1.0.0/src/dktcli.egg-info/entry_points.txt +3 -0
  46. dktcli-1.0.0/src/dktcli.egg-info/top_level.txt +1 -0
  47. dktcli-1.0.0/tests/test_dkt.py +64 -0
  48. dktcli-1.0.0/tests/test_tools.py +193 -0
dktcli-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 <your name>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
dktcli-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: dktcli
3
+ Version: 1.0.0
4
+ Summary: devkittools: the developer's swiss army knife - 35+ zero-dependency CLI tools (ports, serve, json, git helpers, secrets scan, cron, and more) under one command: dkt
5
+ License: MIT
6
+ Keywords: cli,developer-tools,devtools,git,changelog,ollama,productivity,utilities,toolbox
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Build Tools
14
+ Classifier: Topic :: Utilities
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # dkt - the developer's swiss army knife
21
+
22
+ **35+ small, sharp CLI tools under one command: `dkt`.**
23
+ Pure Python, **zero dependencies**, Windows / macOS / Linux. Colored output, spinners, aligned tables,
24
+ typo suggestions ("did you mean..."), tab-completion, and an interactive menu.
25
+
26
+ ```text
27
+ ____ _ _______
28
+ | _ \| |/ /_ _| devkittools
29
+ | | | | ' / | | the developer's swiss army knife
30
+ | |_| | . \ | |
31
+ |____/|_|\_\ |_|
32
+ ```
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install dktcli
38
+ dkt # see every tool
39
+ ```
40
+
41
+ No PyPI? From this folder: `pip install .` - or on Windows just double-click **`install.bat`**
42
+ (it also fixes your PATH). If `dkt` isn't found, `python -m devkittools` always works.
43
+
44
+ **Update:** `dkt update` (PyPI installs) or double-click **`update.bat`** (does `git pull` if this is a clone).
45
+ **Uninstall:** `uninstall.bat` or `pip uninstall dktcli`.
46
+
47
+ ## The tools
48
+
49
+ Run `dkt` for the list, `dkt <tool> -h` for examples of any tool, or `dkt menu` for an interactive launcher.
50
+
51
+ ### Servers & Network
52
+ | Command | Alias | What it does |
53
+ |---|---|---|
54
+ | `dkt portkiller` | `pk` | what's using port 3000? kill it |
55
+ | `dkt serve` | `sv` | static server with CORS, SPA mode, LAN URL, `--open` |
56
+ | `dkt http` | `req` | HTTP requests with pretty JSON, timing, headers, downloads |
57
+ | `dkt net` | | `ip` (local/public), `dns`, `check host:port` |
58
+
59
+ ### Project & Code
60
+ | Command | Alias | What it does |
61
+ |---|---|---|
62
+ | `dkt repostats` | `rs` | lines per language, biggest files, git activity, health check |
63
+ | `dkt tree` | `tr` | pretty tree, respects .gitignore, `--sizes`, `--copy` |
64
+ | `dkt find` | `fd` | project-wide search (regex, context lines) + find files + safe find & replace |
65
+ | `dkt todo` | `td` | every TODO / FIXME / HACK with file:line |
66
+ | `dkt secrets` | `sec` | scan for leaked API keys / tokens / private keys (CI-friendly exit code) |
67
+ | `dkt clean` | `cl` | delete node_modules, `__pycache__`, caches with sizes + confirmation |
68
+ | `dkt du` | | what's eating my disk, with bars |
69
+ | `dkt watch` | `w` | re-run any command when files change |
70
+ | `dkt bench` | `bn` | benchmark / compare commands |
71
+ | `dkt env` | | env vars (secrets masked), PATH doctor, `.env` vs `.env.example` |
72
+ | `dkt ignore` | `gi` | .gitignore generator (python, node, unity, minecraft, godot...) |
73
+ | `dkt license` | `lic` | LICENSE file (MIT, ISC, BSD, Unlicense) |
74
+ | `dkt init` | `new` | scaffold python / node / web projects with git + license |
75
+
76
+ ### Git
77
+ | Command | What it does |
78
+ |---|---|
79
+ | `dkt gx log` | compact colored graph |
80
+ | `dkt gx branches [--prune]` | branch overview, delete merged branches |
81
+ | `dkt gx undo` / `wip` / `amend` / `sync` / `who` | everyday shortcuts |
82
+ | `dkt changeloggen` (`clg`) | CHANGELOG.md from conventional commits |
83
+
84
+ ### AI helpers
85
+ | Command | What it does |
86
+ |---|---|
87
+ | `dkt projectcontextpacker` (`pcp`) | pack a project into one AI-prompt file, with token counts |
88
+ | `dkt commitwriter` (`cw`) | commit messages from your diff using local Ollama |
89
+
90
+ ### Generate & Convert
91
+ | Command | Alias | What it does |
92
+ |---|---|---|
93
+ | `dkt gen` | `g` | `uuid` (v4/v7), `ulid`, `password`, `token`, `id`, `lorem`, `slug` |
94
+ | `dkt enc` | `e` | base64 / url / hex / html encode & decode |
95
+ | `dkt jwt` | | decode a JWT, shows expiry |
96
+ | `dkt hash` | `h` | md5/sha*/blake2, files, `--verify`, `--hmac` |
97
+ | `dkt time` | `tm` | epoch <-> date, date math, differences |
98
+ | `dkt color` | `col` | hex/rgb/hsl, live swatches, contrast ratio, palettes |
99
+
100
+ ### Data & Text
101
+ | Command | Alias | What it does |
102
+ |---|---|---|
103
+ | `dkt json` | `js` | `fmt`, `min`, `get a.b[0]`, `keys`, `flat`, `csv`, `validate` (forgives comments / trailing commas) |
104
+ | `dkt regex` | `rx` | highlighted matches, named groups, `--replace` |
105
+ | `dkt cron` | | explain a cron expression + next runs |
106
+ | `dkt snip` | | personal snippet notebook (`add` / `get --copy` / `ls` / `rm`) |
107
+
108
+ ### System & dkt itself
109
+ `dkt doctor` (system info + which dev tools you have), `dkt update`, `dkt config`, `dkt completion`, `dkt menu`, `dkt about`.
110
+
111
+ ## Handy patterns
112
+ ```bash
113
+ dkt js fmt --paste --copy # clean up JSON from your clipboard
114
+ cat resp.json | dkt js get data.items[0].id
115
+ dkt sv --open --spa # serve the folder, SPA fallback, open browser
116
+ dkt w -c "pytest -q" --clear # tests on every save
117
+ dkt sec --json || echo "secrets found" # CI gate
118
+ dkt tr --copy # paste a project tree into docs / AI chats
119
+ dkt new python mytool # new project, ready to go
120
+ ```
121
+
122
+ ## Tab completion
123
+ ```powershell
124
+ dkt completion powershell | Out-String | Add-Content $PROFILE # PowerShell
125
+ ```
126
+ ```bash
127
+ eval "$(dkt completion bash)" # add to ~/.bashrc (zsh: dkt completion zsh)
128
+ ```
129
+
130
+ ## Settings
131
+ `dkt config set author "Your Name"` (used by `license` / `init`), `dkt config set model qwen2.5-coder:7b` (commit writer).
132
+ Env vars `DKT_<KEY>` override the file. `NO_COLOR=1` disables colors, `DKT_ASCII=1` disables unicode symbols.
133
+
134
+ ## Publishing to PyPI (maintainers)
135
+ 1. The PyPI package is called **`dktcli`** (set in `pyproject.toml`); the command you type is still `dkt`.
136
+ 2. Run **`publish.bat`** (builds, checks, uploads with your API token) - or use the included GitHub Action
137
+ (`.github/workflows/publish.yml`, trusted publishing) and just create a Release.
138
+ 3. Bump `__version__` in `src/devkittools/__init__.py` for every release.
139
+
140
+ ## Development
141
+ ```bash
142
+ pip install -e .
143
+ python -m unittest discover -s tests
144
+ ```
145
+ Adding a tool = one file in `src/devkittools/tools/` with a `META` tuple and a `register()` function, then add it to `MODULES` in `cli.py`.
146
+
147
+ ## License
148
+ MIT
dktcli-1.0.0/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # dkt - the developer's swiss army knife
2
+
3
+ **35+ small, sharp CLI tools under one command: `dkt`.**
4
+ Pure Python, **zero dependencies**, Windows / macOS / Linux. Colored output, spinners, aligned tables,
5
+ typo suggestions ("did you mean..."), tab-completion, and an interactive menu.
6
+
7
+ ```text
8
+ ____ _ _______
9
+ | _ \| |/ /_ _| devkittools
10
+ | | | | ' / | | the developer's swiss army knife
11
+ | |_| | . \ | |
12
+ |____/|_|\_\ |_|
13
+ ```
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install dktcli
19
+ dkt # see every tool
20
+ ```
21
+
22
+ No PyPI? From this folder: `pip install .` - or on Windows just double-click **`install.bat`**
23
+ (it also fixes your PATH). If `dkt` isn't found, `python -m devkittools` always works.
24
+
25
+ **Update:** `dkt update` (PyPI installs) or double-click **`update.bat`** (does `git pull` if this is a clone).
26
+ **Uninstall:** `uninstall.bat` or `pip uninstall dktcli`.
27
+
28
+ ## The tools
29
+
30
+ Run `dkt` for the list, `dkt <tool> -h` for examples of any tool, or `dkt menu` for an interactive launcher.
31
+
32
+ ### Servers & Network
33
+ | Command | Alias | What it does |
34
+ |---|---|---|
35
+ | `dkt portkiller` | `pk` | what's using port 3000? kill it |
36
+ | `dkt serve` | `sv` | static server with CORS, SPA mode, LAN URL, `--open` |
37
+ | `dkt http` | `req` | HTTP requests with pretty JSON, timing, headers, downloads |
38
+ | `dkt net` | | `ip` (local/public), `dns`, `check host:port` |
39
+
40
+ ### Project & Code
41
+ | Command | Alias | What it does |
42
+ |---|---|---|
43
+ | `dkt repostats` | `rs` | lines per language, biggest files, git activity, health check |
44
+ | `dkt tree` | `tr` | pretty tree, respects .gitignore, `--sizes`, `--copy` |
45
+ | `dkt find` | `fd` | project-wide search (regex, context lines) + find files + safe find & replace |
46
+ | `dkt todo` | `td` | every TODO / FIXME / HACK with file:line |
47
+ | `dkt secrets` | `sec` | scan for leaked API keys / tokens / private keys (CI-friendly exit code) |
48
+ | `dkt clean` | `cl` | delete node_modules, `__pycache__`, caches with sizes + confirmation |
49
+ | `dkt du` | | what's eating my disk, with bars |
50
+ | `dkt watch` | `w` | re-run any command when files change |
51
+ | `dkt bench` | `bn` | benchmark / compare commands |
52
+ | `dkt env` | | env vars (secrets masked), PATH doctor, `.env` vs `.env.example` |
53
+ | `dkt ignore` | `gi` | .gitignore generator (python, node, unity, minecraft, godot...) |
54
+ | `dkt license` | `lic` | LICENSE file (MIT, ISC, BSD, Unlicense) |
55
+ | `dkt init` | `new` | scaffold python / node / web projects with git + license |
56
+
57
+ ### Git
58
+ | Command | What it does |
59
+ |---|---|
60
+ | `dkt gx log` | compact colored graph |
61
+ | `dkt gx branches [--prune]` | branch overview, delete merged branches |
62
+ | `dkt gx undo` / `wip` / `amend` / `sync` / `who` | everyday shortcuts |
63
+ | `dkt changeloggen` (`clg`) | CHANGELOG.md from conventional commits |
64
+
65
+ ### AI helpers
66
+ | Command | What it does |
67
+ |---|---|
68
+ | `dkt projectcontextpacker` (`pcp`) | pack a project into one AI-prompt file, with token counts |
69
+ | `dkt commitwriter` (`cw`) | commit messages from your diff using local Ollama |
70
+
71
+ ### Generate & Convert
72
+ | Command | Alias | What it does |
73
+ |---|---|---|
74
+ | `dkt gen` | `g` | `uuid` (v4/v7), `ulid`, `password`, `token`, `id`, `lorem`, `slug` |
75
+ | `dkt enc` | `e` | base64 / url / hex / html encode & decode |
76
+ | `dkt jwt` | | decode a JWT, shows expiry |
77
+ | `dkt hash` | `h` | md5/sha*/blake2, files, `--verify`, `--hmac` |
78
+ | `dkt time` | `tm` | epoch <-> date, date math, differences |
79
+ | `dkt color` | `col` | hex/rgb/hsl, live swatches, contrast ratio, palettes |
80
+
81
+ ### Data & Text
82
+ | Command | Alias | What it does |
83
+ |---|---|---|
84
+ | `dkt json` | `js` | `fmt`, `min`, `get a.b[0]`, `keys`, `flat`, `csv`, `validate` (forgives comments / trailing commas) |
85
+ | `dkt regex` | `rx` | highlighted matches, named groups, `--replace` |
86
+ | `dkt cron` | | explain a cron expression + next runs |
87
+ | `dkt snip` | | personal snippet notebook (`add` / `get --copy` / `ls` / `rm`) |
88
+
89
+ ### System & dkt itself
90
+ `dkt doctor` (system info + which dev tools you have), `dkt update`, `dkt config`, `dkt completion`, `dkt menu`, `dkt about`.
91
+
92
+ ## Handy patterns
93
+ ```bash
94
+ dkt js fmt --paste --copy # clean up JSON from your clipboard
95
+ cat resp.json | dkt js get data.items[0].id
96
+ dkt sv --open --spa # serve the folder, SPA fallback, open browser
97
+ dkt w -c "pytest -q" --clear # tests on every save
98
+ dkt sec --json || echo "secrets found" # CI gate
99
+ dkt tr --copy # paste a project tree into docs / AI chats
100
+ dkt new python mytool # new project, ready to go
101
+ ```
102
+
103
+ ## Tab completion
104
+ ```powershell
105
+ dkt completion powershell | Out-String | Add-Content $PROFILE # PowerShell
106
+ ```
107
+ ```bash
108
+ eval "$(dkt completion bash)" # add to ~/.bashrc (zsh: dkt completion zsh)
109
+ ```
110
+
111
+ ## Settings
112
+ `dkt config set author "Your Name"` (used by `license` / `init`), `dkt config set model qwen2.5-coder:7b` (commit writer).
113
+ Env vars `DKT_<KEY>` override the file. `NO_COLOR=1` disables colors, `DKT_ASCII=1` disables unicode symbols.
114
+
115
+ ## Publishing to PyPI (maintainers)
116
+ 1. The PyPI package is called **`dktcli`** (set in `pyproject.toml`); the command you type is still `dkt`.
117
+ 2. Run **`publish.bat`** (builds, checks, uploads with your API token) - or use the included GitHub Action
118
+ (`.github/workflows/publish.yml`, trusted publishing) and just create a Release.
119
+ 3. Bump `__version__` in `src/devkittools/__init__.py` for every release.
120
+
121
+ ## Development
122
+ ```bash
123
+ pip install -e .
124
+ python -m unittest discover -s tests
125
+ ```
126
+ Adding a tool = one file in `src/devkittools/tools/` with a `META` tuple and a `register()` function, then add it to `MODULES` in `cli.py`.
127
+
128
+ ## License
129
+ MIT
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dktcli"
7
+ dynamic = ["version"]
8
+ description = "devkittools: the developer's swiss army knife - 35+ zero-dependency CLI tools (ports, serve, json, git helpers, secrets scan, cron, and more) under one command: dkt"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ keywords = ["cli", "developer-tools", "devtools", "git", "changelog", "ollama", "productivity", "utilities", "toolbox"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Topic :: Software Development :: Build Tools",
21
+ "Topic :: Utilities",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.scripts]
26
+ dkt = "devkittools.cli:main"
27
+ devkittools = "devkittools.cli:main"
28
+
29
+ [tool.setuptools.dynamic]
30
+ version = { attr = "devkittools.__version__" }
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
dktcli-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """devkittools - a suite of small developer CLI tools (command: dkt)."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,179 @@
1
+ """Entry point for the `dkt` command."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import difflib
6
+ import re
7
+ import shlex
8
+ import sys
9
+ from typing import List, Optional
10
+
11
+ from . import __version__
12
+ from .tools import (bench, changeloggen, clean, colortool, commitwriter, contextpacker, cron, doctor, du, enc,
13
+ envtool, findtool, gen, gx, hashtool, httptool, ignore, initproj, jsontool, net, portkiller,
14
+ regextool, repostats, secrets_scan, serve, snip, system, timetool, todo, tree, watch)
15
+ from .utils import DktError, banner, c, is_interactive, ask, setup_console, sym, term_width
16
+
17
+ VERSION_STR = "devkittools %s" % __version__
18
+
19
+ # Order here = order in the help screen.
20
+ MODULES = [
21
+ portkiller, serve, httptool, net,
22
+ repostats, tree, findtool, todo, secrets_scan, clean, du, watch, bench, envtool, ignore, initproj,
23
+ gx, changeloggen,
24
+ contextpacker, commitwriter,
25
+ gen, enc, hashtool, timetool, colortool,
26
+ jsontool, regextool, cron, snip,
27
+ doctor,
28
+ ]
29
+ CATEGORY_ORDER = ["Servers & Network", "Project & Code", "Git", "AI Helpers", "Generate & Convert",
30
+ "Data & Text", "System"]
31
+ EXTRA = [ # (category, name, alias, summary) for commands without a META of their own
32
+ ("Generate & Convert", "jwt", "", "decode JSON Web Tokens"),
33
+ ("Project & Code", "license", "lic", "write a LICENSE file"),
34
+ ("dkt itself", "update", "", "upgrade dkt to the newest version"),
35
+ ("dkt itself", "config", "", "view / change settings"),
36
+ ("dkt itself", "completion", "", "shell tab-completion script"),
37
+ ("dkt itself", "menu", "", "interactive launcher"),
38
+ ("dkt itself", "about", "", "logo, version and tips"),
39
+ ]
40
+ CATEGORY_ORDER.append("dkt itself")
41
+
42
+
43
+ def all_tools():
44
+ rows = [m.META for m in MODULES] + EXTRA
45
+ return rows
46
+
47
+
48
+ def grouped_help() -> str:
49
+ lines = []
50
+ cats = {}
51
+ for cat, name, alias, summary in all_tools():
52
+ cats.setdefault(cat, []).append((name, alias, summary))
53
+ for cat in CATEGORY_ORDER:
54
+ items = cats.get(cat)
55
+ if not items:
56
+ continue
57
+ lines.append("")
58
+ lines.append(" " + c(cat, "bold bmagenta"))
59
+ for name, alias, summary in items:
60
+ if len(name) > 12 and alias:
61
+ name, alias = alias, name
62
+ short = ("(%s)" % alias) if alias else ""
63
+ lines.append(" %s %s %s" % (c(name.ljust(10), "bold bcyan"), c(short.ljust(6), "gray"), summary))
64
+ return "\n".join(lines)
65
+
66
+
67
+ def print_overview() -> None:
68
+ banner()
69
+ print(grouped_help())
70
+ print()
71
+ print(" " + c("usage:", "bold") + " dkt <tool> [options] " + c("dkt <tool> -h for examples", "gray"))
72
+ print(" " + c("tip:", "bold") + " dkt menu for an interactive launcher, dkt completion for tab-complete")
73
+ print()
74
+
75
+
76
+ class OverviewAction(argparse.Action):
77
+ def __init__(self, option_strings, dest, **kw):
78
+ super().__init__(option_strings, dest, nargs=0, **kw)
79
+
80
+ def __call__(self, parser, namespace, values, option_string=None):
81
+ print_overview()
82
+ parser.exit(0)
83
+
84
+
85
+ class DktParser(argparse.ArgumentParser):
86
+ tool_names: List[str] = []
87
+
88
+ def error(self, message):
89
+ m = re.search(r"invalid choice: '([^']*)'", message)
90
+ if m and self.prog == "dkt":
91
+ bad = m.group(1)
92
+ guess = difflib.get_close_matches(bad, self.tool_names, n=3, cutoff=0.5)
93
+ sys.stderr.write("%s unknown tool '%s'\n" % (c("error:", "red"), bad))
94
+ if guess:
95
+ sys.stderr.write(" did you mean: %s ?\n" % ", ".join(c(g, "bold bcyan") for g in guess))
96
+ sys.stderr.write(" run 'dkt' to see all tools\n")
97
+ self.exit(2)
98
+ super().error(message)
99
+
100
+
101
+ def build_parser():
102
+ parser = DktParser(prog="dkt", description="devkittools - the developer's swiss army knife.",
103
+ add_help=False, formatter_class=argparse.RawDescriptionHelpFormatter)
104
+ parser.add_argument("-h", "--help", action=OverviewAction, help="show all tools")
105
+ parser.add_argument("-v", "-V", "--version", action="version", version=VERSION_STR, help="show version")
106
+ common = argparse.ArgumentParser(add_help=False)
107
+ common.add_argument("-v", "-V", "--version", action="version", version=VERSION_STR, help="show version and exit")
108
+ sub = parser.add_subparsers(dest="command", metavar="<tool>", title="tools")
109
+ for m in MODULES:
110
+ m.register(sub, common)
111
+ system.register(sub, common)
112
+ menu = sub.add_parser("menu", aliases=["m"], parents=[common], help="interactive launcher")
113
+ menu.set_defaults(func=lambda a: run_menu())
114
+ DktParser.tool_names = sorted(sub.choices)
115
+ return parser
116
+
117
+
118
+ def run_menu() -> int:
119
+ if not is_interactive():
120
+ print_overview()
121
+ return 0
122
+ banner()
123
+ tools = []
124
+ cats = {}
125
+ for cat, name, alias, summary in all_tools():
126
+ if name in ("menu",):
127
+ continue
128
+ cats.setdefault(cat, []).append((name, alias, summary))
129
+ n = 0
130
+ for cat in CATEGORY_ORDER:
131
+ if cat not in cats:
132
+ continue
133
+ print(" " + c(cat, "bold bmagenta"))
134
+ for name, alias, summary in cats[cat]:
135
+ n += 1
136
+ tools.append(name)
137
+ print(" %s %s %s" % (c(str(n).rjust(2), "gray"), c(name.ljust(10), "bold bcyan"), summary))
138
+ print()
139
+ while True:
140
+ pick = ask("pick a tool by number or name (q to quit)")
141
+ if pick.lower() in ("", "q", "quit", "exit"):
142
+ return 0
143
+ name = tools[int(pick) - 1] if pick.isdigit() and 0 < int(pick) <= len(tools) else pick
144
+ print()
145
+ main([name, "--help"] if True else [])
146
+ line = ask("arguments (blank = run with none, 'b' = back)")
147
+ if line.lower() == "b":
148
+ continue
149
+ try:
150
+ argv = [name] + shlex.split(line)
151
+ except ValueError as e:
152
+ print(c("bad quoting: %s" % e, "red"))
153
+ continue
154
+ print()
155
+ main(argv)
156
+ print()
157
+
158
+
159
+ def main(argv: Optional[List[str]] = None) -> int:
160
+ setup_console()
161
+ parser = build_parser()
162
+ args = parser.parse_args(argv)
163
+ if not getattr(args, "func", None):
164
+ print_overview()
165
+ return 0
166
+ try:
167
+ return int(args.func(args) or 0)
168
+ except DktError as e:
169
+ print(c("error:", "red"), e, file=sys.stderr)
170
+ return 1
171
+ except KeyboardInterrupt:
172
+ print(file=sys.stderr)
173
+ return 130
174
+ except BrokenPipeError:
175
+ return 0
176
+
177
+
178
+ if __name__ == "__main__":
179
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """Individual dkt tools. Each module exposes register(subparsers, common) and run(args)."""
@@ -0,0 +1,73 @@
1
+ """dkt bench (bn) - time a command over several runs."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import statistics
6
+ import subprocess
7
+ import time
8
+
9
+ from ..utils import DktError, Spinner, bar, c, kv
10
+
11
+ META = ("Project & Code", "bench", "bn", "benchmark any command: min/mean/max over N runs")
12
+
13
+ EPILOG = """\
14
+ examples:
15
+ dkt bn "python script.py"
16
+ dkt bn "node a.js" "node b.js" -n 20 compare two commands
17
+ dkt bn "make build" --warmup 2 --runs 5
18
+ """
19
+
20
+
21
+ def fmt(s: float) -> str:
22
+ return "%.0f ms" % (s * 1000) if s < 1 else "%.2f s" % s
23
+
24
+
25
+ def time_cmd(cmd: str, runs: int, warmup: int):
26
+ times = []
27
+ fails = 0
28
+ for i in range(warmup + runs):
29
+ t0 = time.perf_counter()
30
+ rc = subprocess.call(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
31
+ dt = time.perf_counter() - t0
32
+ if i >= warmup:
33
+ times.append(dt)
34
+ fails += rc != 0
35
+ return times, fails
36
+
37
+
38
+ def run(args) -> int:
39
+ if not args.commands:
40
+ raise DktError('Give at least one command, e.g. dkt bn "python script.py"')
41
+ results = []
42
+ for cmd in args.commands:
43
+ with Spinner("benchmarking: %s" % cmd):
44
+ times, fails = time_cmd(cmd, args.runs, args.warmup)
45
+ results.append((cmd, times, fails))
46
+ best = min(statistics.mean(t) for _, t, _ in results)
47
+ for cmd, times, fails in results:
48
+ mean = statistics.mean(times)
49
+ print("\n " + c(cmd, "bold bcyan"))
50
+ rows = [("mean", c(fmt(mean), "bold") + (" " + c("± " + fmt(statistics.stdev(times)), "gray") if len(times) > 1 else "")),
51
+ ("range", "%s … %s" % (fmt(min(times)), fmt(max(times)))),
52
+ ("runs", "%d%s" % (len(times), c(" (%d failed)" % fails, "red") if fails else ""))]
53
+ if len(results) > 1:
54
+ rows.append(("vs best", "%.2fx" % (mean / best) if mean > best else c("fastest", "bgreen")))
55
+ kv(rows)
56
+ if len(results) > 1:
57
+ print()
58
+ mx = max(statistics.mean(t) for _, t, _ in results)
59
+ for cmd, times, _ in results:
60
+ m = statistics.mean(times)
61
+ print(" %s %s %s" % (c(bar(m, mx, 24), "bmagenta"), fmt(m).rjust(8), cmd))
62
+ print()
63
+ return 0
64
+
65
+
66
+ def register(sub, common) -> None:
67
+ p = sub.add_parser("bench", aliases=["bn"], parents=[common], help=META[3],
68
+ description="Benchmark shell commands.", epilog=EPILOG,
69
+ formatter_class=argparse.RawDescriptionHelpFormatter)
70
+ p.add_argument("commands", nargs="*", metavar="CMD")
71
+ p.add_argument("-n", "--runs", type=int, default=10)
72
+ p.add_argument("--warmup", type=int, default=1)
73
+ p.set_defaults(func=run)