Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,23 @@ Click this button to create a new repository for your project, then clone the ne
### Rename the project
After cloning the repository, rename the project by running:
```bash
mise run project --name "" --description "" --author "" --email "" --github ""
mise run project
```
Pass the following parameters:
The script will interactively prompt you for the following details, with smart defaults based on your environment:

Parameter | Description
--- | ---
`name` | Project new name
`name` | Project new name (defaults to current directory name)
`description` | Project short description
`author` | Author name
`email`| Author email
`author` | Author name (defaults to `git config user.name`)
`email`| Author email (defaults to `git config user.email`)
`github`| GitHub username (for GitHub funding)

You can also pass these as flags:
```bash
mise run project --name "my-project" --description "A cool project" --author "Alice" --email "alice@example.com" --github "alice"
```


## Prerequisites
### Dev container
Expand Down Expand Up @@ -104,11 +109,14 @@ Try running `mise run app -- -h` or `mise run app -- --help` to get the help mes
```bash
Usage: app [OPTIONS]

Say hello
Say hello to a user.

Options:
-n, --name TEXT Name [default: World]
-h, --help Show this message and exit.
-n, --name <name> The name of the person to greet. [default: World]
-V, --version Show the version and exit.
-h, --help Show this message and exit.

Example: app --name Alice
```

### Docker
Expand Down
3 changes: 2 additions & 1 deletion project/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


@command(
name="app",
context_settings={"help_option_names": ["-h", "--help"]},
help="Say hello to a user.",
epilog="Example: app --name Alice",
Expand All @@ -14,7 +15,7 @@
show_default=True,
metavar="<name>",
)
@version_option()
@version_option(None, "-V", "--version")
def main(name: str = "World"):
"""
Say hello to the given name.
Expand Down
93 changes: 80 additions & 13 deletions scripts/rename.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,72 @@
import os
import re
import shutil
import subprocess
from pathlib import Path

from click import ClickException, UsageError, command, echo, option


@command()
@option("--name", required=True, help="Project new name")
@option("--description", required=True, help="Project short description")
@option("--author", required=True, help="Author name")
@option("--email", required=True, help="Author email")
@option("--github", required=True, help="GitHub username")
from click import ClickException, UsageError, command, confirm, echo, option, secho


def _get_git_config(key: str) -> str:
try:
return subprocess.check_output(["/usr/bin/git", "config", key], text=True).strip() # noqa: S603
except (subprocess.CalledProcessError, FileNotFoundError):
return ""


def _get_default_github() -> str:
# Try git config first
username = _get_git_config("github.user") or _get_git_config("user.name")
if username and re.match(r"^[a-zA-Z0-9-]+$", username):
return username

# Try to extract from remote URL
try:
url = subprocess.check_output( # noqa: S603
["/usr/bin/git", "remote", "get-url", "origin"], text=True
).strip()
if "github.com" in url:
if url.startswith("https"):
return url.split("/")[-2]
if url.startswith("git@"):
return url.split(":")[-1].split("/")[0]
except (subprocess.CalledProcessError, FileNotFoundError):
pass

return ""


@command(context_settings={"help_option_names": ["-h", "--help"]})
@option(
"--name",
prompt="Project name",
default=lambda: Path.cwd().name,
help="Project new name",
)
@option(
"--description",
prompt="Project description",
default="A Python project",
help="Project short description",
)
@option(
"--author",
prompt="Author name",
default=lambda: _get_git_config("user.name"),
help="Author name",
)
@option(
"--email",
prompt="Author email",
default=lambda: _get_git_config("user.email"),
help="Author email",
)
@option(
"--github",
prompt="GitHub username",
default=_get_default_github,
help="GitHub username",
)
def main(name: str, description: str, author: str, email: str, github: str):
# Validate inputs to prevent configuration injection
for label, value in [
Expand Down Expand Up @@ -49,11 +104,23 @@ def toml_escape(s: str) -> str:

source = name.replace("-", "_").lower()

echo(f"Initializing project '{name}' (source: '{source}')...")
echo(f"\nProject Configuration:")
echo(f" Name: {name}")
echo(f" Source: {source}")
echo(f" Description: {description}")
echo(f" Author: {author} <{email}>")
echo(f" GitHub: {github}\n")

if not confirm("Do you want to proceed with these settings?", default=True):
secho("Aborted! ❌", fg="red")
return

secho(f"\nInitializing project '{name}'... 🚀", fg="green", bold=True)

# 1. Rename project directory
if os.path.isdir("project"):
shutil.move("project", source)
secho(f"Renamed 'project' directory to '{source}'", fg="blue")
elif not os.path.isdir(source):
raise ClickException(f"Error: Neither 'project' nor '{source}' directory found.")

Expand All @@ -75,16 +142,16 @@ def toml_escape(s: str) -> str:
for filepath, pattern, replacement in replacements:
path = Path(filepath)
if not path.exists():
echo(f"Warning: File {filepath} not found, skipping.")
secho(f" Warning: File {filepath} not found, skipping. ⚠️", fg="yellow")
continue

content = path.read_text()
# Use a lambda for replacement to avoid regex backreference injection
new_content = re.sub(pattern, lambda _: replacement, content, flags=re.MULTILINE)
path.write_text(new_content)
echo(f"Updated {filepath}")
secho(f" Updated {filepath} ✅", fg="blue")

echo("Project initialization complete.")
secho("\nProject initialization complete! ✨", fg="green", bold=True)


if __name__ == "__main__":
Expand Down
8 changes: 8 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ def test_help():
result = runner.invoke(main, ["--help"])
assert result.exit_code == 0
assert "--name <name>" in result.output
assert "-V, --version" in result.output
assert "Example: app --name Alice" in result.output


def test_version():
runner = CliRunner()
result = runner.invoke(main, ["-V"])
assert result.exit_code == 0
assert "app, version 0.1.0" in result.output


def test_greet():
runner = CliRunner()
result = runner.invoke(main, ["--name", "Jules"])
Expand Down