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
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@

**Modern Python web framework project generator with production-ready architecture.**

Scaffoldr is a CLI tool designed to help developers quickly set up and manage project structures, configurations, and
boilerplate code for modern Python web frameworks. It automates the initial setup process, allowing developers to focus
on writing code rather than spending time on repetitive tasks.
Scaffoldr is a command-line toolkit that rapidly scaffolds full-featured Python web projects using modern best
practices. Pick a template, and Scaffoldr creates a consistent, opinionated project layout with sensible defaults for
packaging, configuration, testing, OpenAPI docs, and containerized deployments — all designed to be easy to extend and
maintain.

It accelerates day-one productivity by removing repetitive setup work: boilerplate routes, dependency management,
linting, unit-test scaffolds, CI pipeline stubs, and a ready-to-run Docker configuration are included out of the box.
Templates are pluggable, letting teams standardize on their preferred frameworks and conventions while keeping the setup
fast and repeatable.

[![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Expand Down Expand Up @@ -60,8 +66,24 @@ scaffoldr generate my-project --framework fastapi --database postgres --auth jwt
# Get help
scaffoldr --help
scaffoldr generate --help

# Disable the animated banner
scaffoldr --no-banner generate my-project
```

### ✨ Animated Banner

Scaffoldr features a beautiful animated ASCII banner with rainbow wave effects that displays when you run the CLI! The
banner showcases the "SCAFFOLDR" logo with:

- 🌈 **Rainbow color cycling** - Dynamic HSL color transitions
- 🌊 **Wave animations** - Smooth sine wave effects across the text
- ⚡ **High frame rate** - Smooth 15 FPS animations for 3 seconds
- 🎨 **Gradient finale** - Beautiful blue-to-purple gradient after animation
- 🚫 **Optional disable** - Use `--no-banner` to skip the animation

The banner is inspired by GitHub Copilot CLI and adds a delightful touch to your scaffolding experience!

## 🏗️ Supported Frameworks

| Framework | Status | Description |
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "scaffoldr"
version = "0.2.1"
description = "Flask/FastAPI Architecture Application Generator"
description = "Generate production-ready Python web projects in seconds."
authors = [
{ name = "Vetrichelvan", email = "pythonhubdev@gmail.com" }
]
Expand All @@ -21,6 +21,7 @@ dependencies = [
"python-semantic-release>=10.4.1",
"typer>=0.19.2",
"uv-build>=0.8.22",
"rich>=13.7.0",
]

[dependency-groups]
Expand All @@ -36,8 +37,8 @@ dev = [
"syrupy>=4.9.1",
]

[tool.scripts]
scaffoldr = "scaffoldr.app:typer_app"
[project.scripts]
scaffoldr = "scaffoldr.app:app"


[tool.commitizen]
Expand Down
24 changes: 24 additions & 0 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"include": [
"src",
"tests"
],
"exclude": [
".venv",
"**/__pycache__",
"dist",
"build",
".git"
],
"defineConstant": {
"DEBUG": true
},
"stubPath": "",
"reportMissingImports": "error",
"reportMissingTypeStubs": false,
"pythonVersion": "3.13",
"pythonPlatform": "All",
"useLibraryCodeForTypes": true,
"autoImportCompletions": true,
"reportUnusedCallResult": false
}
1 change: 1 addition & 0 deletions src/scaffoldr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.2.1"
3 changes: 3 additions & 0 deletions src/scaffoldr/animation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from scaffoldr.animation.banner import BannerAnimation

__all__ = ["BannerAnimation"]
86 changes: 86 additions & 0 deletions src/scaffoldr/animation/banner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import time
import sys

from scaffoldr.core.constants import ascii_art

# ANSI reset code
reset = "\033[0m"

# Cyan color
cyan = "\033[96m"

# Shine style: bold white
shine_style = "\033[1;97m"


class BannerAnimation:
def __init__(self):
# Dimensions
self.HEIGHT: int = len(ascii_art)
self.WIDTH: int = len(ascii_art[0])

# Initialize self.STATE with spaces
self.STATE: list[list[str]] = [[" "] * self.WIDTH for _ in range(self.HEIGHT)]
self.is_true: bool = True

def run(self):
# Reveal animation: add columns from right to left, inserting at left and pushing right
for col in range(self.WIDTH - 1, -1, -1):
for row in range(self.HEIGHT):
new_char = ascii_art[row][col]
self.STATE[row].insert(0, new_char)
self.STATE[row].pop()

# Move cursor up if not first frame (to overwrite in place)
if not self.is_true:
sys.stdout.write(f"\033[{self.HEIGHT}A")
else:
self.is_true = False

# Redraw the current self.STATE
for row in range(self.HEIGHT):
for char in self.STATE[row]:
sys.stdout.write(cyan + char + reset)
sys.stdout.write("\n")
sys.stdout.flush()
time.sleep(0.02) # Adjust speed here (slower for more visible flow)

# Pause after full reveal
time.sleep(1)

# Move cursor up to prepare for shine animation (overwrites the static reveal)
sys.stdout.write(f"\033[{self.HEIGHT}A")

# Shine animation: sweep a highlight from left to right
shine_width = 5 # Width of the shine highlight
shine_speed = 0.02 # Speed of shine movement

# For shine, use the final ascii_art (or self.STATE, which is now full)
is_first = True
for offset in range(self.WIDTH + shine_width):
if not is_first:
sys.stdout.write(f"\033[{self.HEIGHT}A")
else:
is_first = False

# Redraw each line with shine applied
for row in range(self.HEIGHT):
for j in range(self.WIDTH):
char = self.STATE[row][j]
if offset - shine_width <= j < offset:
# Apply shine style
sys.stdout.write(shine_style + char + reset)
else:
# Cyan color
sys.stdout.write(cyan + char + reset)
sys.stdout.write("\n")
sys.stdout.flush()
time.sleep(shine_speed)

# Move cursor up and redraw without shine for final static
sys.stdout.write(f"\033[{self.HEIGHT}A")
for row in range(self.HEIGHT):
for char in self.STATE[row]:
sys.stdout.write(cyan + char + reset)
sys.stdout.write("\n")
sys.stdout.flush()
58 changes: 58 additions & 0 deletions src/scaffoldr/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from scaffoldr.animation.banner import BannerAnimation
import typer
from typing import Annotated
from scaffoldr import __version__
from scaffoldr.core.constants import console

app = typer.Typer(
name="scaffoldr",
help="",
add_completion=False,
)


@app.callback(invoke_without_command=True)
def main(
ctx: typer.Context,
no_banner: Annotated[
bool,
typer.Option("--no-banner", help="Disable animated banner"),
] = False,
) -> None:
"""Scaffoldr - Building better Python projects, faster."""
# Show animated banner unless disabled
if not no_banner:
BannerAnimation().run()

# If no subcommand was called, show help
if ctx.invoked_subcommand is None:
typer.echo(ctx.get_help())


@app.command(
name="version",
help="Get the current version of Scaffoldr CLI",
)
def version() -> None:
"""Display the current version of Scaffoldr CLI."""
console.print(f"[bold green]Scaffoldr CLI version: {__version__}[/bold green]")


@app.command()
def generate(
project_name: Annotated[
str,
typer.Argument(help="Name of the project to generate"),
],
framework: Annotated[
str,
typer.Option("--framework", "-f", help="Framework to use"),
] = "fastapi",
) -> None:
"""Generate a new project with the specified framework."""
typer.echo(f"Generating {project_name} with {framework} framework...")
typer.echo("🚧 Coming soon! This feature is under development.")


if __name__ == "__main__":
app()
Empty file added src/scaffoldr/core/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions src/scaffoldr/core/constants/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .art import ascii_art
from .const import console

__all__ = ["ascii_art", "console"]
8 changes: 8 additions & 0 deletions src/scaffoldr/core/constants/art.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ascii_art = [
list("███████╗ ██████╗ █████╗ ███████╗███████╗ ██████╗ ██╗ ██████╗ ██████╗ "),
list("██╔════╝██╔════╝██╔══██╗██╔════╝██╔════╝██╔═══██╗██║ ██╔══██╗██╔══██╗"),
list("███████╗██║ ███████║█████╗ █████╗ ██║ ██║██║ ██║ ██║██████╔╝"),
list("╚════██║██║ ██╔══██║██╔══╝ ██╔══╝ ██║ ██║██║ ██║ ██║██╔══██╗"),
list("███████║╚██████╗██║ ██║██║ ██║ ╚██████╔╝███████╗██████╔╝██║ ██║"),
list("╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝"),
]
3 changes: 3 additions & 0 deletions src/scaffoldr/core/constants/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import rich

console = rich.console.Console()
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for scaffoldr."""
Loading