feat: add type hints, new CLI features, and improve project structure
chore: add uv.lock to gitignore Major changes: - Add type hints and docstrings to all functions (PEP 484) - Add __main__.py for module execution (python -m genpass) - Expose public API in __init__.py - Add config file support (~/.genpass/config.json) - Add entropy calculation (--entropy) - Add clipboard support (--clipboard/-c) - Add ambiguous characters exclusion (--no-ambiguous) - Add output formats: plain, json, delimited (--format) - Add sensible defaults (all character types enabled by default) - Add input validation for length and count - Update shell completions for all new options - Add pre-commit config with ruff, mypy, black - Update pyproject.toml with dev dependencies and tool configs - Add requirements.txt and requirements-dev.txt - Update README.md with comprehensive examples BREAKING CHANGE: Default behavior now includes all character types
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -109,6 +109,9 @@ ipython_config.py
|
|||||||
# https://pdm.fming.dev/#use-with-ide
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
.pdm.toml
|
.pdm.toml
|
||||||
|
|
||||||
|
# uv
|
||||||
|
uv.lock
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
|
|||||||
19
.pre-commit-config.yaml
Normal file
19
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.1.6
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
- id: ruff-format
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.7.1
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
additional_dependencies: []
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
|
rev: 23.11.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
language_version: python3
|
||||||
310
README.md
310
README.md
@@ -1,31 +1,38 @@
|
|||||||
# GenPass
|
# GenPass
|
||||||
|
|
||||||
**Secure password generator CLI** written in Python.
|
**Secure password generator CLI** written in Python.
|
||||||
Generate strong, random passwords from the command line with customizable character sets.
|
Generate strong, random passwords from the command line with customizable character sets.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Specify password length and quantity
|
- **Default behavior**: All character types enabled by default (lowercase, uppercase, digits, symbols)
|
||||||
- Include/exclude:
|
- Specify password length (`-l`) and quantity (`-n`)
|
||||||
- Lowercase letters (`--lower`)
|
- Include/exclude character types:
|
||||||
- Uppercase letters (`--upper`)
|
- `--lower` - Lowercase letters
|
||||||
- Digits (`--digits`)
|
- `--upper` - Uppercase letters
|
||||||
- Symbols (`--symbols`)
|
- `--digits` - Digits
|
||||||
- Optional custom symbol set (`--symbol-set`)
|
- `--symbols` - Symbols
|
||||||
- Ensure at least one character from each selected type (can be disabled with `--no-ensure`)
|
- `--no-ambiguous` - Exclude confusing characters (l, 1, I, O, 0)
|
||||||
|
- `--symbol-set` - Custom symbol set
|
||||||
|
- `--no-ensure` - Disable "at least one from each type" rule
|
||||||
|
- `--entropy` - Calculate and display password entropy
|
||||||
|
- `--clipboard` / `-c` - Copy password to clipboard
|
||||||
|
- `--format` - Output format: `plain`, `json`, `delimited`
|
||||||
|
- `--save-config` - Save current options as defaults
|
||||||
|
- `--config` - Show current configuration
|
||||||
- Shell completions for **bash**, **zsh**, and **fish**
|
- Shell completions for **bash**, **zsh**, and **fish**
|
||||||
- Easy installation via **pipx** or local script
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### 1. Via pipx (recommended)
|
### 1. Via pipx (recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipx install git+https://github.com/kilyabin/GenPass
|
pipx install git+https://github.com/kilyabin/GenPass
|
||||||
````
|
```
|
||||||
|
|
||||||
### 2. Local installation
|
### 2. Local installation
|
||||||
|
|
||||||
@@ -33,26 +40,62 @@ Clone the repo and run the install script:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/kilyabin/GenPass.git
|
git clone https://github.com/kilyabin/GenPass.git
|
||||||
cd genpass
|
cd GenPass
|
||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
This will also set up shell completions for bash, zsh, and fish.
|
### 3. Development installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install --editable .
|
||||||
|
# or
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Generate a single password (default 16 characters):
|
### Basic Usage
|
||||||
|
|
||||||
|
Generate a single password (default 16 characters, all character types):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a password with specific character types:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
genpass --lower --upper --digits --symbols
|
genpass --lower --upper --digits --symbols
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Length and Quantity
|
||||||
|
|
||||||
Generate 5 passwords of length 20:
|
Generate 5 passwords of length 20:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
genpass -l 20 -n 5 --lower --upper --digits --symbols
|
genpass -l 20 -n 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a 32-character password:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass -l 32
|
||||||
|
```
|
||||||
|
|
||||||
|
### Character Sets
|
||||||
|
|
||||||
|
Password without symbols (only letters and digits):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass --lower --upper --digits
|
||||||
|
```
|
||||||
|
|
||||||
|
Only lowercase and digits:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass --lower --digits
|
||||||
```
|
```
|
||||||
|
|
||||||
Use a custom symbol set:
|
Use a custom symbol set:
|
||||||
@@ -61,10 +104,134 @@ Use a custom symbol set:
|
|||||||
genpass --symbols --symbol-set "!@#%&"
|
genpass --symbols --symbol-set "!@#%&"
|
||||||
```
|
```
|
||||||
|
|
||||||
Disable "ensure each type" rule:
|
### Excluding Ambiguous Characters
|
||||||
|
|
||||||
|
Exclude confusing characters like `l`, `1`, `I`, `O`, `0`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
genpass --lower --upper --digits --no-ensure
|
genpass --no-ambiguous
|
||||||
|
```
|
||||||
|
|
||||||
|
Combine with other options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass -l 20 --no-ambiguous --lower --upper --digits
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Formats
|
||||||
|
|
||||||
|
Plain text (default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass -n 3
|
||||||
|
```
|
||||||
|
|
||||||
|
JSON format:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass -n 3 --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"passwords": [
|
||||||
|
"abc123...",
|
||||||
|
"def456...",
|
||||||
|
"ghi789..."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Delimited format (one per line):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass -n 3 --format delimited
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entropy Calculation
|
||||||
|
|
||||||
|
Display password entropy (in bits):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass --entropy
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Kx9#mP2$vL5@nQ8w
|
||||||
|
# Entropy: 94.56 bits
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clipboard Support
|
||||||
|
|
||||||
|
Copy the generated password to clipboard:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass --clipboard
|
||||||
|
# or
|
||||||
|
genpass -c
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
✓ Copied to clipboard
|
||||||
|
Kx9#mP2$vL5@nQ8w
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: Clipboard support requires `pyperclip`. Install with `pip install pyperclip`.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Save default settings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass -l 24 --no-ambiguous --save-config
|
||||||
|
```
|
||||||
|
|
||||||
|
Show current configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
genpass --config
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration is stored in `~/.genpass/config.json`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Real-world Usage
|
||||||
|
|
||||||
|
**Generate a password for a website:**
|
||||||
|
```bash
|
||||||
|
genpass -l 16 --clipboard
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generate multiple passwords and save to file:**
|
||||||
|
```bash
|
||||||
|
genpass -n 10 -l 20 --format json > passwords.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generate a memorable password (no ambiguous chars):**
|
||||||
|
```bash
|
||||||
|
genpass -l 12 --no-ambiguous
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generate a high-entropy password:**
|
||||||
|
```bash
|
||||||
|
genpass -l 32 --entropy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Script-friendly JSON output:**
|
||||||
|
```bash
|
||||||
|
genpass --format json | jq -r '.passwords[0]'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set default password length for all future sessions:**
|
||||||
|
```bash
|
||||||
|
genpass -l 20 --save-config
|
||||||
|
genpass # Now generates 20-char passwords by default
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -75,19 +242,16 @@ After installation, completions are automatically copied to your shell folders.
|
|||||||
Restart your shell or source the completion files manually:
|
Restart your shell or source the completion files manually:
|
||||||
|
|
||||||
**Bash**
|
**Bash**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source ~/.local/share/bash-completion/completions/genpass
|
source ~/.local/share/bash-completion/completions/genpass
|
||||||
```
|
```
|
||||||
|
|
||||||
**Zsh**
|
**Zsh**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
source ~/.local/share/zsh/site-functions/_genpass
|
source ~/.local/share/zsh/site-functions/_genpass
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fish**
|
**Fish**
|
||||||
|
|
||||||
```fish
|
```fish
|
||||||
source ~/.local/share/fish/vendor_completions.d/genpass.fish
|
source ~/.local/share/fish/vendor_completions.d/genpass.fish
|
||||||
```
|
```
|
||||||
@@ -96,20 +260,111 @@ source ~/.local/share/fish/vendor_completions.d/genpass.fish
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
1. Install in editable mode for development:
|
1. Install in editable mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipx install --editable .
|
pipx install --editable .
|
||||||
|
# or
|
||||||
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Make changes in `genpass/cli.py` and test immediately.
|
2. Install dev dependencies:
|
||||||
|
```bash
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run linting:
|
||||||
|
```bash
|
||||||
|
ruff check genpass/
|
||||||
|
mypy genpass/
|
||||||
|
black --check genpass/
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run tests:
|
||||||
|
```bash
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Usage
|
||||||
|
|
||||||
|
Use GenPass as a Python library:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from genpass import generate_password, get_character_pools, calculate_entropy
|
||||||
|
|
||||||
|
# Get character pools
|
||||||
|
pools = get_character_pools(
|
||||||
|
use_lower=True,
|
||||||
|
use_upper=True,
|
||||||
|
use_digits=True,
|
||||||
|
use_symbols=True,
|
||||||
|
symbol_set="!@#$%",
|
||||||
|
exclude_ambiguous=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate password
|
||||||
|
password = generate_password(length=16, pools=pools)
|
||||||
|
print(password)
|
||||||
|
|
||||||
|
# Calculate entropy
|
||||||
|
entropy = calculate_entropy(password, sum(len(p) for p in pools))
|
||||||
|
print(f"Entropy: {entropy:.2f} bits")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command-Line Options
|
||||||
|
|
||||||
|
```
|
||||||
|
positional arguments:
|
||||||
|
(none)
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help Show help message
|
||||||
|
-l, --length LENGTH Password length (default: 16)
|
||||||
|
-n, --count COUNT Number of passwords (default: 1)
|
||||||
|
--lower Include lowercase letters
|
||||||
|
--upper Include uppercase letters
|
||||||
|
--digits Include digits
|
||||||
|
--symbols Include symbols
|
||||||
|
--symbol-set SET Custom symbol set
|
||||||
|
--no-ensure Disable ensure-each-type rule
|
||||||
|
--no-ambiguous Exclude ambiguous characters
|
||||||
|
--entropy Show password entropy
|
||||||
|
-c, --clipboard Copy to clipboard
|
||||||
|
--format FORMAT Output format: plain, json, delimited
|
||||||
|
--config Show current configuration
|
||||||
|
--save-config Save options as defaults
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please fork the repo and submit pull requests.
|
Contributions are welcome! Please follow these guidelines:
|
||||||
Ensure code follows PEP8 style and add shell completion tests if applicable.
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Make your changes
|
||||||
|
4. Run tests and linting (`pytest`, `ruff check`, `mypy`)
|
||||||
|
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
This project uses:
|
||||||
|
- **Black** for code formatting
|
||||||
|
- **Ruff** for linting
|
||||||
|
- **MyPy** for type checking
|
||||||
|
|
||||||
|
Install dev dependencies and set up pre-commit hooks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -117,5 +372,8 @@ Ensure code follows PEP8 style and add shell completion tests if applicable.
|
|||||||
|
|
||||||
This project is licensed under the **MIT License** – see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the **MIT License** – see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
```
|
---
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
_genpass() {
|
_genpass() {
|
||||||
local cur opts
|
local cur opts
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
opts="--help -l --length -n --count --lower --upper --digits --symbols --symbol-set --no-ensure"
|
opts="-h --help -l --length -n --count --lower --upper --digits --symbols --symbol-set --no-ensure --no-ambiguous --entropy -c --clipboard --format --config --save-config"
|
||||||
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
||||||
}
|
}
|
||||||
complete -F _genpass genpass
|
complete -F _genpass genpass
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
complete -c genpass -l length -s l -d "Password length"
|
complete -c genpass -s h -l help -d "Show help message"
|
||||||
complete -c genpass -l count -s n -d "Number of passwords"
|
complete -c genpass -s l -l length -d "Password length" -r
|
||||||
complete -c genpass -l lower -d "Lowercase letters"
|
complete -c genpass -s n -l count -d "Number of passwords" -r
|
||||||
complete -c genpass -l upper -d "Uppercase letters"
|
complete -c genpass -l lower -d "Include lowercase letters"
|
||||||
complete -c genpass -l digits -d "Digits"
|
complete -c genpass -l upper -d "Include uppercase letters"
|
||||||
complete -c genpass -l symbols -d "Symbols"
|
complete -c genpass -l digits -d "Include digits"
|
||||||
complete -c genpass -l symbol-set -d "Custom symbol set"
|
complete -c genpass -l symbols -d "Include symbols"
|
||||||
complete -c genpass -l no-ensure -d "Disable character guarantees"
|
complete -c genpass -l symbol-set -d "Custom symbol set" -r
|
||||||
|
complete -c genpass -l no-ensure -d "Disable ensure-each-type rule"
|
||||||
|
complete -c genpass -l no-ambiguous -d "Exclude ambiguous characters"
|
||||||
|
complete -c genpass -l entropy -d "Show password entropy"
|
||||||
|
complete -c genpass -s c -l clipboard -d "Copy to clipboard"
|
||||||
|
complete -c genpass -l format -d "Output format" -r -f -a "plain json delimited"
|
||||||
|
complete -c genpass -l config -d "Show current configuration"
|
||||||
|
complete -c genpass -l save-config -d "Save options as defaults"
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
#compdef genpass
|
#compdef genpass
|
||||||
_arguments \
|
_arguments \
|
||||||
'--length[-l]:password length:' \
|
'-h[show help]' \
|
||||||
'--count[-n]:number of passwords:' \
|
'--help[show help]' \
|
||||||
'--lower[use lowercase letters]' \
|
'-l[password length]:length:' \
|
||||||
'--upper[use uppercase letters]' \
|
'--length[password length]:length:' \
|
||||||
'--digits[use digits]' \
|
'-n[number of passwords]:count:' \
|
||||||
'--symbols[use symbols]' \
|
'--count[number of passwords]:count:' \
|
||||||
'--symbol-set[custom symbol set]' \
|
'--lower[include lowercase letters]' \
|
||||||
'--no-ensure[do not enforce each type]'
|
'--upper[include uppercase letters]' \
|
||||||
|
'--digits[include digits]' \
|
||||||
|
'--symbols[include symbols]' \
|
||||||
|
'--symbol-set[custom symbol set]:symbols:' \
|
||||||
|
'--no-ensure[disable ensure-each-type rule]' \
|
||||||
|
'--no-ambiguous[exclude ambiguous characters (l, 1, I, O, 0)]' \
|
||||||
|
'--entropy[show password entropy]' \
|
||||||
|
'-c[copy to clipboard]' \
|
||||||
|
'--clipboard[copy to clipboard]' \
|
||||||
|
'--format[output format]:format:(plain json delimited)' \
|
||||||
|
'--config[show current configuration]' \
|
||||||
|
'--save-config[save options as defaults]'
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
GenPass - Secure password generator.
|
||||||
|
|
||||||
|
This package provides functionality to generate strong, random passwords
|
||||||
|
with customizable character sets and various security features.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
>>> from genpass import generate_password, get_character_pools
|
||||||
|
>>> pools = get_character_pools(True, True, True, True, "!@#$%", False)
|
||||||
|
>>> password = generate_password(16, pools)
|
||||||
|
>>> print(password)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from genpass.cli import (
|
||||||
|
AMBIGUOUS_CHARS,
|
||||||
|
DEFAULT_SYMBOLS,
|
||||||
|
calculate_entropy,
|
||||||
|
generate_password,
|
||||||
|
get_character_pools,
|
||||||
|
)
|
||||||
|
|
||||||
|
__version__ = "2.0.0"
|
||||||
|
__all__ = [
|
||||||
|
"generate_password",
|
||||||
|
"get_character_pools",
|
||||||
|
"calculate_entropy",
|
||||||
|
"DEFAULT_SYMBOLS",
|
||||||
|
"AMBIGUOUS_CHARS",
|
||||||
|
"__version__",
|
||||||
|
]
|
||||||
|
|||||||
8
genpass/__main__.py
Normal file
8
genpass/__main__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
Allow running genpass as a module: python -m genpass
|
||||||
|
"""
|
||||||
|
|
||||||
|
from genpass.cli import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
512
genpass/cli.py
512
genpass/cli.py
@@ -1,47 +1,515 @@
|
|||||||
|
"""
|
||||||
|
GenPass - Secure password generator CLI.
|
||||||
|
|
||||||
|
This module provides functionality to generate strong, random passwords
|
||||||
|
with customizable character sets, entropy calculation, and various output formats.
|
||||||
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
def generate_password(length, pools, ensure_each=True):
|
try:
|
||||||
|
import pyperclip # type: ignore
|
||||||
|
|
||||||
|
HAS_PYPERCLIP = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_PYPERCLIP = False
|
||||||
|
|
||||||
|
# Default character sets
|
||||||
|
DEFAULT_SYMBOLS = "!@#$%^&*()-_=+[]{};:,.<>?"
|
||||||
|
AMBIGUOUS_CHARS = "l1IO0"
|
||||||
|
|
||||||
|
# Config file path
|
||||||
|
CONFIG_DIR = Path.home() / ".genpass"
|
||||||
|
CONFIG_FILE = CONFIG_DIR / "config.json"
|
||||||
|
|
||||||
|
|
||||||
|
def load_config() -> dict:
|
||||||
|
"""
|
||||||
|
Load user configuration from ~/.genpass/config.json.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing user configuration settings.
|
||||||
|
Returns empty dict if config file doesn't exist or is invalid.
|
||||||
|
"""
|
||||||
|
if CONFIG_FILE.exists():
|
||||||
|
try:
|
||||||
|
with open(CONFIG_FILE, encoding="utf-8") as f:
|
||||||
|
config = json.load(f)
|
||||||
|
return config if isinstance(config, dict) else {}
|
||||||
|
except (OSError, json.JSONDecodeError):
|
||||||
|
return {}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def save_config(config: dict) -> None:
|
||||||
|
"""
|
||||||
|
Save user configuration to ~/.genpass/config.json.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Dictionary containing configuration settings to save.
|
||||||
|
"""
|
||||||
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_entropy(password: str, pool_size: int) -> float:
|
||||||
|
"""
|
||||||
|
Calculate the entropy (in bits) of a generated password.
|
||||||
|
|
||||||
|
Entropy is calculated as: length * log2(pool_size)
|
||||||
|
Higher entropy indicates a stronger password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
password: The generated password.
|
||||||
|
pool_size: Total number of possible characters in the pool.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Entropy value in bits.
|
||||||
|
"""
|
||||||
|
if pool_size <= 1:
|
||||||
|
return 0.0
|
||||||
|
return len(password) * math.log2(pool_size)
|
||||||
|
|
||||||
|
|
||||||
|
def get_character_pools(
|
||||||
|
use_lower: bool,
|
||||||
|
use_upper: bool,
|
||||||
|
use_digits: bool,
|
||||||
|
use_symbols: bool,
|
||||||
|
symbol_set: str,
|
||||||
|
exclude_ambiguous: bool,
|
||||||
|
) -> List[str]:
|
||||||
|
"""
|
||||||
|
Build character pools based on user preferences.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
use_lower: Include lowercase letters.
|
||||||
|
use_upper: Include uppercase letters.
|
||||||
|
use_digits: Include digits.
|
||||||
|
use_symbols: Include symbols.
|
||||||
|
symbol_set: Custom set of symbols to use.
|
||||||
|
exclude_ambiguous: Exclude ambiguous characters (l, 1, I, O, 0).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of character pool strings.
|
||||||
|
"""
|
||||||
|
pools = []
|
||||||
|
|
||||||
|
if use_lower:
|
||||||
|
chars = string.ascii_lowercase
|
||||||
|
if exclude_ambiguous:
|
||||||
|
chars = chars.replace("l", "")
|
||||||
|
pools.append(chars)
|
||||||
|
|
||||||
|
if use_upper:
|
||||||
|
chars = string.ascii_uppercase
|
||||||
|
if exclude_ambiguous:
|
||||||
|
chars = chars.replace("IO", "")
|
||||||
|
pools.append(chars)
|
||||||
|
|
||||||
|
if use_digits:
|
||||||
|
chars = string.digits
|
||||||
|
if exclude_ambiguous:
|
||||||
|
chars = chars.replace("10", "")
|
||||||
|
pools.append(chars)
|
||||||
|
|
||||||
|
if use_symbols:
|
||||||
|
chars = symbol_set
|
||||||
|
if exclude_ambiguous:
|
||||||
|
for char in AMBIGUOUS_CHARS:
|
||||||
|
chars = chars.replace(char, "")
|
||||||
|
if chars:
|
||||||
|
pools.append(chars)
|
||||||
|
|
||||||
|
return pools
|
||||||
|
|
||||||
|
|
||||||
|
def generate_password(
|
||||||
|
length: int,
|
||||||
|
pools: List[str],
|
||||||
|
ensure_each: bool = True,
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Generate a secure random password.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
length: Desired password length.
|
||||||
|
pools: List of character pools to choose from.
|
||||||
|
ensure_each: Ensure at least one character from each pool.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Generated password string.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If no character pools provided or length too small.
|
||||||
|
"""
|
||||||
if not pools:
|
if not pools:
|
||||||
raise ValueError("No character sets selected")
|
raise ValueError("No character sets selected")
|
||||||
password = []
|
|
||||||
|
password: List[str] = []
|
||||||
|
|
||||||
|
# Ensure at least one character from each pool
|
||||||
if ensure_each:
|
if ensure_each:
|
||||||
if length < len(pools):
|
if length < len(pools):
|
||||||
raise ValueError("Password length too small")
|
raise ValueError("Password length too small for ensure_each option")
|
||||||
for pool in pools:
|
for pool in pools:
|
||||||
password.append(secrets.choice(pool))
|
password.append(secrets.choice(pool))
|
||||||
|
|
||||||
|
# Fill remaining length with random characters from all pools
|
||||||
all_chars = "".join(pools)
|
all_chars = "".join(pools)
|
||||||
while len(password) < length:
|
while len(password) < length:
|
||||||
password.append(secrets.choice(all_chars))
|
password.append(secrets.choice(all_chars))
|
||||||
|
|
||||||
|
# Shuffle to randomize positions
|
||||||
secrets.SystemRandom().shuffle(password)
|
secrets.SystemRandom().shuffle(password)
|
||||||
|
|
||||||
return "".join(password)
|
return "".join(password)
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(prog="genpass", description="Secure password generator")
|
def format_output(
|
||||||
parser.add_argument("-l", "--length", type=int, default=16)
|
passwords: List[str],
|
||||||
parser.add_argument("-n", "--count", type=int, default=1)
|
output_format: str,
|
||||||
parser.add_argument("--lower", action="store_true")
|
show_entropy: bool = False,
|
||||||
parser.add_argument("--upper", action="store_true")
|
entropy_value: Optional[float] = None,
|
||||||
parser.add_argument("--digits", action="store_true")
|
) -> str:
|
||||||
parser.add_argument("--symbols", action="store_true")
|
"""
|
||||||
parser.add_argument("--symbol-set", default="!@#$%^&*()-_=+[]{};:,.<>?")
|
Format passwords for output.
|
||||||
parser.add_argument("--no-ensure", action="store_true")
|
|
||||||
|
Args:
|
||||||
|
passwords: List of generated passwords.
|
||||||
|
output_format: Output format ('plain', 'json', 'delimited').
|
||||||
|
show_entropy: Whether to include entropy information.
|
||||||
|
entropy_value: Entropy value to include in output.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted output string.
|
||||||
|
"""
|
||||||
|
if output_format == "json":
|
||||||
|
output_data: dict = {"passwords": passwords}
|
||||||
|
if show_entropy and entropy_value is not None:
|
||||||
|
output_data["entropy_bits"] = round(entropy_value, 2)
|
||||||
|
return json.dumps(output_data, indent=2)
|
||||||
|
|
||||||
|
if output_format == "delimited":
|
||||||
|
result = "\n".join(passwords)
|
||||||
|
if show_entropy and entropy_value is not None:
|
||||||
|
result += f"\n# Entropy: {entropy_value:.2f} bits"
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Plain format (default)
|
||||||
|
result = "\n".join(passwords)
|
||||||
|
if show_entropy and entropy_value is not None:
|
||||||
|
result += f"\n# Entropy: {entropy_value:.2f} bits"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def copy_to_clipboard(text: str) -> bool:
|
||||||
|
"""
|
||||||
|
Copy text to system clipboard.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Text to copy to clipboard.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False otherwise.
|
||||||
|
"""
|
||||||
|
if not HAS_PYPERCLIP:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
pyperclip.copy(text)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_args(args: argparse.Namespace) -> None:
|
||||||
|
"""
|
||||||
|
Validate command-line arguments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: Parsed command-line arguments.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If arguments are invalid.
|
||||||
|
"""
|
||||||
|
if args.length <= 0:
|
||||||
|
raise ValueError("Password length must be positive")
|
||||||
|
|
||||||
|
if args.length > 1000:
|
||||||
|
raise ValueError("Password length cannot exceed 1000")
|
||||||
|
|
||||||
|
if args.count <= 0:
|
||||||
|
raise ValueError("Password count must be positive")
|
||||||
|
|
||||||
|
if args.count > 100:
|
||||||
|
raise ValueError("Password count cannot exceed 100")
|
||||||
|
|
||||||
|
if args.symbol_set and len(args.symbol_set) < 1:
|
||||||
|
raise ValueError("Symbol set cannot be empty")
|
||||||
|
|
||||||
|
|
||||||
|
def create_parser() -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
Create and configure the argument parser.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Configured ArgumentParser instance.
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="genpass",
|
||||||
|
description="Secure password generator CLI - Generate strong, random passwords",
|
||||||
|
epilog="Examples:\n"
|
||||||
|
" genpass Generate one 16-char password (all char types)\n"
|
||||||
|
" genpass -l 20 -n 5 Generate 5 passwords of length 20\n"
|
||||||
|
" genpass --lower --upper --digits Generate password without symbols\n"
|
||||||
|
" genpass --no-ambiguous Exclude confusing characters\n"
|
||||||
|
" genpass --entropy Show password entropy\n"
|
||||||
|
" genpass --format json Output as JSON\n"
|
||||||
|
" genpass --clipboard Copy to clipboard\n",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Password options
|
||||||
|
parser.add_argument(
|
||||||
|
"-l",
|
||||||
|
"--length",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Password length (default: 16)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-n",
|
||||||
|
"--count",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="Number of passwords to generate (default: 1)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Character sets
|
||||||
|
parser.add_argument(
|
||||||
|
"--lower",
|
||||||
|
action="store_true",
|
||||||
|
default=None,
|
||||||
|
help="Include lowercase letters (default: yes)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--upper",
|
||||||
|
action="store_true",
|
||||||
|
default=None,
|
||||||
|
help="Include uppercase letters (default: yes)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--digits",
|
||||||
|
action="store_true",
|
||||||
|
default=None,
|
||||||
|
help="Include digits (default: yes)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--symbols",
|
||||||
|
action="store_true",
|
||||||
|
default=None,
|
||||||
|
help="Include symbols (default: yes)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--symbol-set",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Custom symbol set (default: " + DEFAULT_SYMBOLS.replace("%", "%%") + ")",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Options
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-ensure",
|
||||||
|
action="store_true",
|
||||||
|
help="Disable ensuring at least one char from each selected type",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-ambiguous",
|
||||||
|
action="store_true",
|
||||||
|
help="Exclude ambiguous characters (l, 1, I, O, 0)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--entropy",
|
||||||
|
action="store_true",
|
||||||
|
help="Calculate and display password entropy",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--clipboard",
|
||||||
|
"-c",
|
||||||
|
action="store_true",
|
||||||
|
help="Copy the first password to clipboard",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Output options
|
||||||
|
parser.add_argument(
|
||||||
|
"--format",
|
||||||
|
choices=["plain", "json", "delimited"],
|
||||||
|
default="plain",
|
||||||
|
help="Output format (default: plain)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
action="store_true",
|
||||||
|
help="Show current configuration and exit",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--save-config",
|
||||||
|
action="store_true",
|
||||||
|
help="Save current options as defaults",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def apply_defaults(args: argparse.Namespace, config: dict) -> argparse.Namespace:
|
||||||
|
"""
|
||||||
|
Apply default values from config to arguments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: Parsed command-line arguments.
|
||||||
|
config: Loaded configuration dictionary.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated arguments with defaults applied.
|
||||||
|
"""
|
||||||
|
if args.length is None:
|
||||||
|
args.length = config.get("length", 16)
|
||||||
|
|
||||||
|
if args.count is None:
|
||||||
|
args.count = config.get("count", 1)
|
||||||
|
|
||||||
|
# For boolean flags, use config if not explicitly set
|
||||||
|
if args.lower is None:
|
||||||
|
args.lower = config.get("lower", True)
|
||||||
|
|
||||||
|
if args.upper is None:
|
||||||
|
args.upper = config.get("upper", True)
|
||||||
|
|
||||||
|
if args.digits is None:
|
||||||
|
args.digits = config.get("digits", True)
|
||||||
|
|
||||||
|
if args.symbols is None:
|
||||||
|
args.symbols = config.get("symbols", True)
|
||||||
|
|
||||||
|
if args.symbol_set is None:
|
||||||
|
args.symbol_set = config.get("symbol_set", DEFAULT_SYMBOLS)
|
||||||
|
|
||||||
|
if not getattr(args, "no_ambiguous", False):
|
||||||
|
args.no_ambiguous = config.get("no_ambiguous", False)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main entry point for the genpass CLI."""
|
||||||
|
parser = create_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
pools = []
|
# Load config
|
||||||
if args.lower: pools.append(string.ascii_lowercase)
|
config = load_config()
|
||||||
if args.upper: pools.append(string.ascii_uppercase)
|
|
||||||
if args.digits: pools.append(string.digits)
|
|
||||||
if args.symbols: pools.append(args.symbol_set)
|
|
||||||
|
|
||||||
if not pools:
|
# Show config if requested
|
||||||
print("Select at least one character set", file=sys.stderr)
|
if args.config:
|
||||||
|
if config:
|
||||||
|
print(json.dumps(config, indent=2))
|
||||||
|
else:
|
||||||
|
print("No configuration file found. Using defaults.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply defaults from config
|
||||||
|
args = apply_defaults(args, config)
|
||||||
|
|
||||||
|
# Validate arguments
|
||||||
|
try:
|
||||||
|
validate_args(args)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for _ in range(args.count):
|
# Build character pools
|
||||||
print(generate_password(args.length, pools, ensure_each=not args.no_ensure))
|
try:
|
||||||
|
pools = get_character_pools(
|
||||||
|
use_lower=args.lower,
|
||||||
|
use_upper=args.upper,
|
||||||
|
use_digits=args.digits,
|
||||||
|
use_symbols=args.symbols,
|
||||||
|
symbol_set=args.symbol_set,
|
||||||
|
exclude_ambiguous=args.no_ambiguous,
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not pools:
|
||||||
|
print("Error: Select at least one character set", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Generate passwords
|
||||||
|
passwords: List[str] = []
|
||||||
|
entropy_value: Optional[float] = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
for _ in range(args.count):
|
||||||
|
pwd = generate_password(
|
||||||
|
length=args.length,
|
||||||
|
pools=pools,
|
||||||
|
ensure_each=not args.no_ensure,
|
||||||
|
)
|
||||||
|
passwords.append(pwd)
|
||||||
|
|
||||||
|
# Calculate entropy for the first password
|
||||||
|
if args.entropy:
|
||||||
|
pool_size = sum(len(pool) for pool in pools)
|
||||||
|
entropy_value = calculate_entropy(passwords[0], pool_size)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Format output
|
||||||
|
output = format_output(
|
||||||
|
passwords=passwords,
|
||||||
|
output_format=args.format,
|
||||||
|
show_entropy=args.entropy,
|
||||||
|
entropy_value=entropy_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy to clipboard if requested
|
||||||
|
if args.clipboard and passwords:
|
||||||
|
if copy_to_clipboard(passwords[0]):
|
||||||
|
print("✓ Copied to clipboard", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Warning: Clipboard functionality unavailable. Install 'pyperclip'.",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Print output
|
||||||
|
print(output)
|
||||||
|
|
||||||
|
# Save config if requested
|
||||||
|
if args.save_config:
|
||||||
|
new_config = {
|
||||||
|
"length": args.length,
|
||||||
|
"count": args.count,
|
||||||
|
"lower": args.lower,
|
||||||
|
"upper": args.upper,
|
||||||
|
"digits": args.digits,
|
||||||
|
"symbols": args.symbols,
|
||||||
|
"symbol_set": args.symbol_set,
|
||||||
|
"no_ambiguous": args.no_ambiguous,
|
||||||
|
}
|
||||||
|
save_config(new_config)
|
||||||
|
print(f"✓ Configuration saved to {CONFIG_FILE}", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ cp completions/genpass.zsh "$PREFIX/zsh/site-functions/_genpass"
|
|||||||
cp completions/genpass.fish "$PREFIX/fish/vendor_completions.d/genpass.fish"
|
cp completions/genpass.fish "$PREFIX/fish/vendor_completions.d/genpass.fish"
|
||||||
|
|
||||||
echo "[✓] Installation complete. Restart your shell."
|
echo "[✓] Installation complete. Restart your shell."
|
||||||
|
echo "[✓] Clipboard support: pipx inject genpass pyperclip"
|
||||||
|
|||||||
@@ -1,14 +1,103 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "genpass"
|
name = "genpass"
|
||||||
version = "1.0.0"
|
version = "2.0.0"
|
||||||
description = "Secure password generator CLI"
|
description = "Secure password generator CLI"
|
||||||
authors = [{ name = "kilyabin" }]
|
authors = [{ name = "kilyabin" }]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = { text = "MIT" }
|
||||||
requires-python = ">= 3.8"
|
requires-python = ">= 3.8"
|
||||||
|
keywords = ["password", "generator", "security", "cli", "random"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Topic :: Security",
|
||||||
|
"Topic :: Utilities",
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"pyperclip>=1.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"pytest-cov>=4.0.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
"mypy>=1.0.0",
|
||||||
|
"black>=23.0.0",
|
||||||
|
"pre-commit>=3.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
genpass = "genpass.cli:main"
|
genpass = "genpass.cli:main"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/kilyabin/GenPass"
|
||||||
|
Repository = "https://github.com/kilyabin/GenPass.git"
|
||||||
|
Issues = "https://github.com/kilyabin/GenPass/issues"
|
||||||
|
Changelog = "https://github.com/kilyabin/GenPass/blob/main/CHANGELOG.md"
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
packages = ["genpass"]
|
packages = ["genpass"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = "-v --cov=genpass --cov-report=term-missing"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py38"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
ignore = ["E501"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.isort]
|
||||||
|
known-first-party = ["genpass"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.9"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 100
|
||||||
|
target-version = ["py38", "py39", "py310", "py311", "py312"]
|
||||||
|
include = '\.pyi?$'
|
||||||
|
exclude = '''
|
||||||
|
/(
|
||||||
|
\.git
|
||||||
|
| \.hg
|
||||||
|
| \.mypy_cache
|
||||||
|
| \.tox
|
||||||
|
| \.venv
|
||||||
|
| _build
|
||||||
|
| buck-out
|
||||||
|
| build
|
||||||
|
| dist
|
||||||
|
)/
|
||||||
|
'''
|
||||||
|
|||||||
7
requirements-dev.txt
Normal file
7
requirements-dev.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyperclip>=1.8.0
|
||||||
|
pytest>=7.0.0
|
||||||
|
pytest-cov>=4.0.0
|
||||||
|
ruff>=0.1.0
|
||||||
|
mypy>=1.0.0
|
||||||
|
black>=23.0.0
|
||||||
|
pre-commit>=3.0.0
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pyperclip>=1.8.0
|
||||||
Reference in New Issue
Block a user