456 lines
13 KiB
Bash
Executable File
456 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# new-project - Create a new project in Rob's development ecosystem
|
|
#
|
|
# This script automates:
|
|
# - Creating local project directory
|
|
# - Creating Gitea repository via API
|
|
# - Setting up documentation symlinks
|
|
# - Generating project files from templates
|
|
# - Updating build-public-docs.sh
|
|
#
|
|
# Usage:
|
|
# new-project myproject --title "My Project" --tagline "Short description"
|
|
# new-project myproject # Interactive mode
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
HUB_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
PROJECTS_ROOT="$HOME/PycharmProjects"
|
|
PROJECT_DOCS_ROOT="$PROJECTS_ROOT/project-docs"
|
|
TEMPLATES_DIR="$HUB_ROOT/templates"
|
|
CONFIG_DIR="$HOME/.config/development-hub"
|
|
TOKEN_FILE="$CONFIG_DIR/gitea-token"
|
|
|
|
GITEA_URL="https://gitea.brrd.tech"
|
|
GITEA_SSH="ssh://git@gitea.brrd.tech:222"
|
|
GITEA_OWNER="rob"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
|
|
|
|
# Parse arguments
|
|
PROJECT_NAME=""
|
|
PROJECT_TITLE=""
|
|
PROJECT_TAGLINE=""
|
|
DRY_RUN=false
|
|
SKIP_GITEA=false
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
Usage: $(basename "$0") <project-name> [options]
|
|
|
|
Create a new project in the development ecosystem.
|
|
|
|
Arguments:
|
|
project-name Name for the project (lowercase, alphanumeric, hyphens)
|
|
|
|
Options:
|
|
--title "Title" Display title (default: derived from name)
|
|
--tagline "..." Short description (prompted if not provided)
|
|
--dry-run Show what would happen without doing it
|
|
--skip-gitea Skip Gitea repo creation (for offline use)
|
|
--help, -h Show this help message
|
|
|
|
Examples:
|
|
$(basename "$0") my-tool --title "My Tool" --tagline "A useful tool"
|
|
$(basename "$0") my-tool # Interactive mode
|
|
|
|
After creation, your project will be at:
|
|
~/PycharmProjects/<project-name>/
|
|
|
|
With documentation at:
|
|
~/PycharmProjects/project-docs/docs/projects/<project-name>/
|
|
EOF
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--title)
|
|
PROJECT_TITLE="$2"
|
|
shift 2
|
|
;;
|
|
--tagline)
|
|
PROJECT_TAGLINE="$2"
|
|
shift 2
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
--skip-gitea)
|
|
SKIP_GITEA=true
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-*)
|
|
log_error "Unknown option: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [[ -z "$PROJECT_NAME" ]]; then
|
|
PROJECT_NAME="$1"
|
|
else
|
|
log_error "Unexpected argument: $1"
|
|
exit 1
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
validate_name() {
|
|
local name="$1"
|
|
|
|
if [[ -z "$name" ]]; then
|
|
log_error "Project name is required"
|
|
show_help
|
|
exit 1
|
|
fi
|
|
|
|
# Check format: lowercase, alphanumeric, hyphens
|
|
if [[ ! "$name" =~ ^[a-z][a-z0-9-]*$ ]]; then
|
|
log_error "Project name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if project already exists
|
|
if [[ -d "$PROJECTS_ROOT/$name" ]]; then
|
|
log_error "Project directory already exists: $PROJECTS_ROOT/$name"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if docs already exist
|
|
if [[ -d "$PROJECT_DOCS_ROOT/docs/projects/$name" ]]; then
|
|
log_error "Documentation directory already exists: $PROJECT_DOCS_ROOT/docs/projects/$name"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Derive title from name if not provided
|
|
derive_title() {
|
|
local name="$1"
|
|
# Convert hyphens to spaces and capitalize each word
|
|
echo "$name" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1'
|
|
}
|
|
|
|
prompt_for_missing() {
|
|
if [[ -z "$PROJECT_TITLE" ]]; then
|
|
local default_title
|
|
default_title=$(derive_title "$PROJECT_NAME")
|
|
read -rp "Project title [$default_title]: " PROJECT_TITLE
|
|
PROJECT_TITLE="${PROJECT_TITLE:-$default_title}"
|
|
fi
|
|
|
|
if [[ -z "$PROJECT_TAGLINE" ]]; then
|
|
read -rp "Project tagline (short description): " PROJECT_TAGLINE
|
|
if [[ -z "$PROJECT_TAGLINE" ]]; then
|
|
log_error "Tagline is required"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
load_gitea_token() {
|
|
# Check environment variable first
|
|
if [[ -n "$GITEA_TOKEN" ]]; then
|
|
log_info "Using GITEA_TOKEN from environment"
|
|
return 0
|
|
fi
|
|
|
|
# Check token file
|
|
if [[ -f "$TOKEN_FILE" ]]; then
|
|
GITEA_TOKEN=$(cat "$TOKEN_FILE")
|
|
log_info "Loaded Gitea token from $TOKEN_FILE"
|
|
return 0
|
|
fi
|
|
|
|
# Prompt user to create token
|
|
log_warn "No Gitea API token found"
|
|
echo ""
|
|
echo "To create a token:"
|
|
echo " 1. Go to $GITEA_URL/user/settings/applications"
|
|
echo " 2. Generate a new token with 'repo' scope"
|
|
echo " 3. Copy the token"
|
|
echo ""
|
|
read -rsp "Paste your Gitea API token: " GITEA_TOKEN
|
|
echo ""
|
|
|
|
if [[ -z "$GITEA_TOKEN" ]]; then
|
|
log_error "Token is required for Gitea API access"
|
|
log_info "Use --skip-gitea to skip repository creation"
|
|
exit 1
|
|
fi
|
|
|
|
# Save token for future use
|
|
mkdir -p "$CONFIG_DIR"
|
|
echo "$GITEA_TOKEN" > "$TOKEN_FILE"
|
|
chmod 600 "$TOKEN_FILE"
|
|
log_success "Token saved to $TOKEN_FILE"
|
|
}
|
|
|
|
create_gitea_repo() {
|
|
local name="$1"
|
|
local description="$2"
|
|
|
|
log_step "Creating Gitea repository: $name"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "[DRY RUN] Would create repo: $GITEA_URL/$GITEA_OWNER/$name"
|
|
return 0
|
|
fi
|
|
|
|
local response
|
|
response=$(curl -s -w "\n%{http_code}" -X POST "$GITEA_URL/api/v1/user/repos" \
|
|
-H "Authorization: token $GITEA_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"name\": \"$name\",
|
|
\"description\": \"$description\",
|
|
\"private\": false,
|
|
\"auto_init\": false
|
|
}")
|
|
|
|
local http_code
|
|
http_code=$(echo "$response" | tail -n1)
|
|
local body
|
|
body=$(echo "$response" | sed '$d')
|
|
|
|
if [[ "$http_code" == "201" ]]; then
|
|
log_success "Created repository: $GITEA_URL/$GITEA_OWNER/$name"
|
|
elif [[ "$http_code" == "409" ]]; then
|
|
log_warn "Repository already exists on Gitea"
|
|
else
|
|
log_error "Failed to create repository (HTTP $http_code)"
|
|
echo "$body" | head -5
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
create_local_project() {
|
|
local name="$1"
|
|
local project_dir="$PROJECTS_ROOT/$name"
|
|
|
|
log_step "Creating local project: $project_dir"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "[DRY RUN] Would create: $project_dir"
|
|
return 0
|
|
fi
|
|
|
|
mkdir -p "$project_dir"
|
|
cd "$project_dir"
|
|
|
|
git init --quiet
|
|
git remote add origin "$GITEA_SSH/$GITEA_OWNER/$name.git"
|
|
|
|
log_success "Created local project with git"
|
|
}
|
|
|
|
apply_template() {
|
|
local template="$1"
|
|
local output="$2"
|
|
local name="$3"
|
|
local title="$4"
|
|
local tagline="$5"
|
|
|
|
local year
|
|
year=$(date +%Y)
|
|
local date
|
|
date=$(date +%Y-%m-%d)
|
|
|
|
sed -e "s|{{PROJECT_NAME}}|$name|g" \
|
|
-e "s|{{PROJECT_TITLE}}|$title|g" \
|
|
-e "s|{{PROJECT_TAGLINE}}|$tagline|g" \
|
|
-e "s|{{YEAR}}|$year|g" \
|
|
-e "s|{{DATE}}|$date|g" \
|
|
-e "s|{{GITEA_URL}}|$GITEA_URL|g" \
|
|
-e "s|{{GITEA_OWNER}}|$GITEA_OWNER|g" \
|
|
"$template" > "$output"
|
|
}
|
|
|
|
generate_project_files() {
|
|
local name="$1"
|
|
local title="$2"
|
|
local tagline="$3"
|
|
local project_dir="$PROJECTS_ROOT/$name"
|
|
|
|
log_step "Generating project files from templates"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "[DRY RUN] Would generate files in $project_dir"
|
|
return 0
|
|
fi
|
|
|
|
# Generate each file from template
|
|
apply_template "$TEMPLATES_DIR/gitignore.template" "$project_dir/.gitignore" "$name" "$title" "$tagline"
|
|
apply_template "$TEMPLATES_DIR/CLAUDE.md.template" "$project_dir/CLAUDE.md" "$name" "$title" "$tagline"
|
|
apply_template "$TEMPLATES_DIR/README.md.template" "$project_dir/README.md" "$name" "$title" "$tagline"
|
|
apply_template "$TEMPLATES_DIR/pyproject.toml.template" "$project_dir/pyproject.toml" "$name" "$title" "$tagline"
|
|
|
|
log_success "Generated project files"
|
|
}
|
|
|
|
setup_documentation() {
|
|
local name="$1"
|
|
local title="$2"
|
|
local tagline="$3"
|
|
local docs_dir="$PROJECT_DOCS_ROOT/docs/projects/$name"
|
|
local project_dir="$PROJECTS_ROOT/$name"
|
|
|
|
log_step "Setting up documentation"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "[DRY RUN] Would create: $docs_dir"
|
|
log_info "[DRY RUN] Would create symlink: $project_dir/docs"
|
|
return 0
|
|
fi
|
|
|
|
# Create docs directory
|
|
mkdir -p "$docs_dir"
|
|
|
|
# Generate documentation files
|
|
apply_template "$TEMPLATES_DIR/overview.md.template" "$docs_dir/overview.md" "$name" "$title" "$tagline"
|
|
apply_template "$TEMPLATES_DIR/updating-documentation.md.template" "$docs_dir/updating-documentation.md" "$name" "$title" "$tagline"
|
|
|
|
# Create symlink
|
|
ln -s "../project-docs/docs/projects/$name" "$project_dir/docs"
|
|
|
|
log_success "Created documentation with symlink"
|
|
}
|
|
|
|
update_build_script() {
|
|
local name="$1"
|
|
local title="$2"
|
|
local tagline="$3"
|
|
local build_script="$PROJECT_DOCS_ROOT/scripts/build-public-docs.sh"
|
|
|
|
log_step "Updating build-public-docs.sh"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "[DRY RUN] Would add to PROJECT_CONFIG:"
|
|
log_info " PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GITEA_OWNER|$name|$name\""
|
|
return 0
|
|
fi
|
|
|
|
# Find the last PROJECT_CONFIG line and add after it
|
|
local config_line="PROJECT_CONFIG[\"$name\"]=\"$title|$tagline|$GITEA_OWNER|$name|$name\""
|
|
|
|
# Check if already exists
|
|
if grep -q "PROJECT_CONFIG\[\"$name\"\]" "$build_script"; then
|
|
log_warn "Project already in build script"
|
|
return 0
|
|
fi
|
|
|
|
# Add after the last PROJECT_CONFIG line
|
|
sed -i "/^PROJECT_CONFIG\[\"ramble\"\]/a $config_line" "$build_script"
|
|
|
|
log_success "Added project to build script"
|
|
}
|
|
|
|
initial_commit() {
|
|
local name="$1"
|
|
local project_dir="$PROJECTS_ROOT/$name"
|
|
|
|
log_step "Creating initial commit"
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_info "[DRY RUN] Would commit and push"
|
|
return 0
|
|
fi
|
|
|
|
cd "$project_dir"
|
|
git add .
|
|
git commit -m "Initial project setup
|
|
|
|
Created by development-hub/new-project script"
|
|
|
|
if [[ "$SKIP_GITEA" != true ]]; then
|
|
git push -u origin main 2>/dev/null || git push -u origin master 2>/dev/null || {
|
|
log_warn "Could not push to remote. You may need to push manually."
|
|
}
|
|
fi
|
|
|
|
log_success "Created initial commit"
|
|
}
|
|
|
|
print_summary() {
|
|
local name="$1"
|
|
local title="$2"
|
|
|
|
echo ""
|
|
echo -e "${GREEN}========================================${NC}"
|
|
echo -e "${GREEN} Project '$title' created successfully!${NC}"
|
|
echo -e "${GREEN}========================================${NC}"
|
|
echo ""
|
|
echo "Locations:"
|
|
echo " Local: $PROJECTS_ROOT/$name/"
|
|
echo " Gitea: $GITEA_URL/$GITEA_OWNER/$name"
|
|
echo " Docs: $PROJECT_DOCS_ROOT/docs/projects/$name/"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. cd $PROJECTS_ROOT/$name"
|
|
echo " 2. Start developing!"
|
|
echo ""
|
|
echo "To publish documentation:"
|
|
echo " $PROJECT_DOCS_ROOT/scripts/build-public-docs.sh $name --deploy"
|
|
echo ""
|
|
echo "Public docs will be available at:"
|
|
echo " https://pages.brrd.tech/$GITEA_OWNER/$name/"
|
|
echo ""
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
validate_name "$PROJECT_NAME"
|
|
prompt_for_missing
|
|
|
|
echo ""
|
|
log_info "Creating project: $PROJECT_NAME"
|
|
log_info "Title: $PROJECT_TITLE"
|
|
log_info "Tagline: $PROJECT_TAGLINE"
|
|
echo ""
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
log_warn "DRY RUN - No changes will be made"
|
|
echo ""
|
|
fi
|
|
|
|
# Load Gitea token (unless skipping)
|
|
if [[ "$SKIP_GITEA" != true ]]; then
|
|
load_gitea_token
|
|
create_gitea_repo "$PROJECT_NAME" "$PROJECT_TAGLINE"
|
|
fi
|
|
|
|
create_local_project "$PROJECT_NAME"
|
|
generate_project_files "$PROJECT_NAME" "$PROJECT_TITLE" "$PROJECT_TAGLINE"
|
|
setup_documentation "$PROJECT_NAME" "$PROJECT_TITLE" "$PROJECT_TAGLINE"
|
|
update_build_script "$PROJECT_NAME" "$PROJECT_TITLE" "$PROJECT_TAGLINE"
|
|
initial_commit "$PROJECT_NAME"
|
|
|
|
if [[ "$DRY_RUN" != true ]]; then
|
|
print_summary "$PROJECT_NAME" "$PROJECT_TITLE"
|
|
fi
|
|
}
|
|
|
|
main "$@"
|