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:
kilyabin
2026-03-10 00:42:16 +04:00
parent c11345d576
commit eeca690c0a
13 changed files with 969 additions and 67 deletions

3
.gitignore vendored
View File

@@ -109,6 +109,9 @@ ipython_config.py
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# uv
uv.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

19
.pre-commit-config.yaml Normal file
View 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

308
README.md
View File

@@ -7,25 +7,32 @@ Generate strong, random passwords from the command line with customizable charac
## Features
- Specify password length and quantity
- Include/exclude:
- Lowercase letters (`--lower`)
- Uppercase letters (`--upper`)
- Digits (`--digits`)
- Symbols (`--symbols`)
- Optional custom symbol set (`--symbol-set`)
- Ensure at least one character from each selected type (can be disabled with `--no-ensure`)
- **Default behavior**: All character types enabled by default (lowercase, uppercase, digits, symbols)
- Specify password length (`-l`) and quantity (`-n`)
- Include/exclude character types:
- `--lower` - Lowercase letters
- `--upper` - Uppercase letters
- `--digits` - Digits
- `--symbols` - Symbols
- `--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**
- Easy installation via **pipx** or local script
---
## Installation
### 1. Via pipx (recommended)
```bash
pipx install git+https://github.com/kilyabin/GenPass
````
```
### 2. Local installation
@@ -33,26 +40,62 @@ Clone the repo and run the install script:
```bash
git clone https://github.com/kilyabin/GenPass.git
cd genpass
cd GenPass
./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
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
genpass --lower --upper --digits --symbols
```
### Length and Quantity
Generate 5 passwords of length 20:
```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:
@@ -61,10 +104,134 @@ Use a custom symbol set:
genpass --symbols --symbol-set "!@#%&"
```
Disable "ensure each type" rule:
### Excluding Ambiguous Characters
Exclude confusing characters like `l`, `1`, `I`, `O`, `0`:
```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:
**Bash**
```bash
source ~/.local/share/bash-completion/completions/genpass
```
**Zsh**
```bash
source ~/.local/share/zsh/site-functions/_genpass
```
**Fish**
```fish
source ~/.local/share/fish/vendor_completions.d/genpass.fish
```
@@ -96,20 +260,111 @@ source ~/.local/share/fish/vendor_completions.d/genpass.fish
## Development
1. Install in editable mode for development:
1. Install in editable mode:
```bash
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
Contributions are welcome! Please fork the repo and submit pull requests.
Ensure code follows PEP8 style and add shell completion tests if applicable.
Contributions are welcome! Please follow these guidelines:
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.
```
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history.

View File

@@ -1,7 +1,7 @@
_genpass() {
local cur opts
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") )
}
complete -F _genpass genpass

View File

@@ -1,8 +1,15 @@
complete -c genpass -l length -s l -d "Password length"
complete -c genpass -l count -s n -d "Number of passwords"
complete -c genpass -l lower -d "Lowercase letters"
complete -c genpass -l upper -d "Uppercase letters"
complete -c genpass -l digits -d "Digits"
complete -c genpass -l symbols -d "Symbols"
complete -c genpass -l symbol-set -d "Custom symbol set"
complete -c genpass -l no-ensure -d "Disable character guarantees"
complete -c genpass -s h -l help -d "Show help message"
complete -c genpass -s l -l length -d "Password length" -r
complete -c genpass -s n -l count -d "Number of passwords" -r
complete -c genpass -l lower -d "Include lowercase letters"
complete -c genpass -l upper -d "Include uppercase letters"
complete -c genpass -l digits -d "Include digits"
complete -c genpass -l symbols -d "Include symbols"
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"

View File

@@ -1,10 +1,21 @@
#compdef genpass
_arguments \
'--length[-l]:password length:' \
'--count[-n]:number of passwords:' \
'--lower[use lowercase letters]' \
'--upper[use uppercase letters]' \
'--digits[use digits]' \
'--symbols[use symbols]' \
'--symbol-set[custom symbol set]' \
'--no-ensure[do not enforce each type]'
'-h[show help]' \
'--help[show help]' \
'-l[password length]:length:' \
'--length[password length]:length:' \
'-n[number of passwords]:count:' \
'--count[number of passwords]:count:' \
'--lower[include lowercase letters]' \
'--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]'

View File

@@ -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
View File

@@ -0,0 +1,8 @@
"""
Allow running genpass as a module: python -m genpass
"""
from genpass.cli import main
if __name__ == "__main__":
main()

View File

@@ -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 json
import math
import secrets
import string
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:
raise ValueError("No character sets selected")
password = []
password: List[str] = []
# Ensure at least one character from each pool
if ensure_each:
if length < len(pools):
raise ValueError("Password length too small")
raise ValueError("Password length too small for ensure_each option")
for pool in pools:
password.append(secrets.choice(pool))
# Fill remaining length with random characters from all pools
all_chars = "".join(pools)
while len(password) < length:
password.append(secrets.choice(all_chars))
# Shuffle to randomize positions
secrets.SystemRandom().shuffle(password)
return "".join(password)
def main():
parser = argparse.ArgumentParser(prog="genpass", description="Secure password generator")
parser.add_argument("-l", "--length", type=int, default=16)
parser.add_argument("-n", "--count", type=int, default=1)
parser.add_argument("--lower", action="store_true")
parser.add_argument("--upper", action="store_true")
parser.add_argument("--digits", action="store_true")
parser.add_argument("--symbols", action="store_true")
parser.add_argument("--symbol-set", default="!@#$%^&*()-_=+[]{};:,.<>?")
parser.add_argument("--no-ensure", action="store_true")
def format_output(
passwords: List[str],
output_format: str,
show_entropy: bool = False,
entropy_value: Optional[float] = None,
) -> str:
"""
Format passwords for output.
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()
pools = []
if args.lower: pools.append(string.ascii_lowercase)
if args.upper: pools.append(string.ascii_uppercase)
if args.digits: pools.append(string.digits)
if args.symbols: pools.append(args.symbol_set)
# Load config
config = load_config()
if not pools:
print("Select at least one character set", file=sys.stderr)
# Show config if requested
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)
# Build character pools
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):
print(generate_password(args.length, pools, ensure_each=not args.no_ensure))
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__":
main()

View File

@@ -21,3 +21,4 @@ cp completions/genpass.zsh "$PREFIX/zsh/site-functions/_genpass"
cp completions/genpass.fish "$PREFIX/fish/vendor_completions.d/genpass.fish"
echo "[✓] Installation complete. Restart your shell."
echo "[✓] Clipboard support: pipx inject genpass pyperclip"

View File

@@ -1,14 +1,103 @@
[project]
name = "genpass"
version = "1.0.0"
version = "2.0.0"
description = "Secure password generator CLI"
authors = [{ name = "kilyabin" }]
readme = "README.md"
license = "MIT"
license = { text = "MIT" }
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]
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]
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
View 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
View File

@@ -0,0 +1 @@
pyperclip>=1.8.0