Skip to content

Instantly share code, notes, and snippets.

@yeiichi
Last active April 30, 2025 08:56
Show Gist options
  • Select an option

  • Save yeiichi/18d9cb1adb634d5f24b469241acd3b01 to your computer and use it in GitHub Desktop.

Select an option

Save yeiichi/18d9cb1adb634d5f24b469241acd3b01 to your computer and use it in GitHub Desktop.
Quickstart script for generic project directories with Sphinx documentation.
#!/usr/bin/env python3
"""
Quickstart script for a Generic project.
This Python script automates the creation of a generic project directory structure
with Sphinx documentation integration.
"""
import argparse
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Dict
@dataclass(frozen=True)
class ProjectConfig:
"""Configuration constants for project setup"""
PROJECT_ROOT: Path = Path(__file__).resolve().parent
TEMPLATE_DIR: Path = PROJECT_ROOT / 'templates'
PJ_REPO_TEMPLATE: Path = TEMPLATE_DIR / 'pj_repo_tmpl'
DOCS_SOURCE_DIR: str = 'docs/source'
AUTHOR: str = 'Your Name'
# ANSI color codes
YELLOW: str = '\033[93m'
RED: str = '\033[31m'
RESET: str = '\033[0m'
# Project directory structure
DIRECTORIES = {
'specifications': 'specifications',
'contracts': 'contracts',
'schedule': 'schedule',
'meetings': 'meetings',
'deliverables': 'deliverables',
'references': 'references',
'miscellaneous': 'miscellaneous'
}
class ProjectCreator:
def __init__(self, project_name: str):
self.project_name = project_name
self.cwd = Path.cwd()
self.project_dir = self.cwd / project_name
def create(self) -> None:
"""Create and set up the entire project structure"""
self._ensure_project_directory_available()
self._create_project_directory()
self._create_symlinks()
self._setup_sphinx()
self._update_sphinx_toctree()
self._show_completion_message()
def _ensure_project_directory_available(self) -> None:
"""Ensure the project directory doesn't exist or get a new name"""
while self.project_dir.exists():
print(f'Project name is already taken. Please choose a different name.')
self.project_name = input(
f'{ProjectConfig.YELLOW}Enter a new project name: {ProjectConfig.RESET}')
self.project_dir = self.cwd / self.project_name
def _create_project_directory(self) -> None:
"""Create the initial project directory structure"""
try:
shutil.copytree(ProjectConfig.PJ_REPO_TEMPLATE, self.project_dir, dirs_exist_ok=False)
print(f'{ProjectConfig.YELLOW}Project created at {self.project_dir}{ProjectConfig.RESET}')
except OSError as e:
print(f'{ProjectConfig.RED}Error creating project directory: {e}{ProjectConfig.RESET}')
raise
def _create_symlinks(self) -> None:
"""Create symlinks for all project directories"""
alias_map = self._get_directory_aliases()
for source, alias in alias_map.items():
try:
alias.parent.mkdir(parents=True, exist_ok=True)
alias.symlink_to(source, target_is_directory=True)
print(f'Symlink created at {alias}.')
except OSError as e:
print(f'{ProjectConfig.RED}Error creating symlink: {e}{ProjectConfig.RESET}')
def _get_directory_aliases(self) -> Dict[Path, Path]:
"""Generate mapping of source directories to their aliases"""
aliases = {
self.project_dir / dir_name:
self.project_dir / ProjectConfig.DOCS_SOURCE_DIR / dir_name
for dir_name in ProjectConfig.DIRECTORIES.values()
}
return {source.resolve(): alias.resolve() for source, alias in aliases.items()}
def _setup_sphinx(self) -> None:
"""Initialize Sphinx documentation"""
print(f'{ProjectConfig.YELLOW}\nCreating Sphinx directories.{ProjectConfig.RESET}')
subprocess.run(
['sphinx-quickstart', 'docs', '-q', '--sep',
'-p', self.project_name, '-a', ProjectConfig.AUTHOR],
cwd=self.project_dir
)
def _update_sphinx_toctree(self) -> None:
"""Update the Sphinx toctree with project directories"""
index_file = self.project_dir / ProjectConfig.DOCS_SOURCE_DIR / 'index.rst'
toctree_entries = ' ' + '\n '.join(
f'{dir_name}/index' for dir_name in ProjectConfig.DIRECTORIES.values())
try:
lines = index_file.read_text().splitlines()
lines.insert(12, toctree_entries)
index_file.write_text('\n'.join(lines) + '\n')
except OSError as e:
print(f'{ProjectConfig.RED}Error updating toctree: {e}{ProjectConfig.RESET}')
def _show_completion_message(self) -> None:
"""Display final instructions to the user"""
print(f"""\
To move to the new project directory, run the following command:
{ProjectConfig.YELLOW}cd {self.project_dir}{ProjectConfig.RESET}
""")
def main() -> None:
parser = argparse.ArgumentParser(
prog='pj_quickstart',
description='Quickstart Generic project')
parser.add_argument('project_name', help='Name of the project')
args = parser.parse_args()
creator = ProjectCreator(args.project_name)
creator.create()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment