from __future__ import annotations
import argparse
import json
import sys
from collections.abc import Sequence
from pathlib import Path
from .angular import (
AngularCommandError,
execute_invocations,
format_invocations,
resolve_angular_command,
)
from .build import create_build_plan, write_build_plan
from .config import ConfigError, load_project_config
from .validation import validate_openapi_file, validate_project_config, validate_ui_file
[docs]
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="django-angular3")
subparsers = parser.add_subparsers(dest="command", required=True)
validate_openapi = subparsers.add_parser(
"validate-openapi", help="Validate an OpenAPI source document."
)
validate_openapi.add_argument("path", help="Path to the OpenAPI document.")
validate_ui = subparsers.add_parser(
"validate-ui", help="Validate a UI definition document."
)
validate_ui.add_argument("path", help="Path to the UI definition document.")
validate_project = subparsers.add_parser(
"validate-project", help="Validate a django-angular3 project configuration."
)
validate_project.add_argument(
"path",
nargs="?",
default="django-angular3.json",
help="Path to the project config.",
)
build = subparsers.add_parser(
"build", help="Validate a project and emit a deterministic build plan."
)
build.add_argument(
"path", nargs="?", default="django-angular3.json", help="Path to the config."
)
build.add_argument(
"--output",
default="build",
help="Directory where the build plan should be written.",
)
build.add_argument(
"--dry-run",
action="store_true",
help="Print the build plan instead of writing it to disk.",
)
ng_new = subparsers.add_parser("ng_new", help="Create an empty Angular workspace.")
ng_new.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_new.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
ng_workspace = subparsers.add_parser(
"ng_workspace",
help="Bootstrap the configured Angular workspace with angular-django2.",
)
ng_workspace.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_workspace.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
ng_config = subparsers.add_parser(
"ng_config", help="Configure Angular workspace defaults."
)
ng_config.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_config.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
ng_build = subparsers.add_parser(
"ng_build", help="Build the configured Angular application."
)
ng_build.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_build.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
ng_gen_app = subparsers.add_parser(
"ng_gen_app",
help="Generate an Angular application in the configured workspace.",
)
ng_gen_app.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_gen_app.add_argument(
"--app-name", default=None, help="Optional Angular application name."
)
ng_gen_app.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
ng_openapi_gen = subparsers.add_parser(
"ng_openapi_gen", help="Run ng-openapi-gen for the configured OpenAPI source."
)
ng_openapi_gen.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_openapi_gen.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
ng_add = subparsers.add_parser("ng_add", help="Run ng add for an Angular package.")
ng_add.add_argument(
"path", nargs="?", default=None, help="Path to the project config."
)
ng_add.add_argument(
"--package",
default=None,
help="Package to add (defaults to setting: ng_add_package).",
)
ng_add.add_argument(
"--dry-run",
action="store_true",
help=(
"Print the resolved Angular subprocess call list instead of "
"invoking Angular tooling."
),
)
install_tutorial = subparsers.add_parser(
"install-tutorial",
help="Install the simple_crm tutorial project to a local directory.",
)
install_tutorial.add_argument(
"dest",
nargs="?",
default="simple_crm",
help="Destination directory (default: simple_crm).",
)
return parser
[docs]
def main(argv: Sequence[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.command == "validate-openapi":
return _run_validation(
validate_openapi_file(args.path), f"OpenAPI document {args.path}"
)
if args.command == "validate-ui":
return _run_validation(validate_ui_file(args.path), f"UI document {args.path}")
if args.command == "validate-project":
try:
config = load_project_config(args.path)
except ConfigError as exc:
print(f"Configuration error: {exc}", file=sys.stderr)
return 1
return _run_validation(
validate_project_config(config), f"Project configuration {Path(args.path)}"
)
if args.command == "build":
try:
config = load_project_config(args.path)
except ConfigError as exc:
print(f"Configuration error: {exc}", file=sys.stderr)
return 1
errors = validate_project_config(config)
if errors:
return _run_validation(errors, f"Project configuration {Path(args.path)}")
plan = create_build_plan(config)
if args.dry_run:
print(json.dumps(plan.to_dict(), indent=2))
return 0
plan_path = write_build_plan(plan, args.output)
print(f"Wrote build plan to {plan_path}")
return 0
if args.command == "install-tutorial":
return _run_install_tutorial(args.dest)
if args.command in {
"ng_new",
"ng_workspace",
"ng_config",
"ng_build",
"ng_gen_app",
"ng_openapi_gen",
"ng_add",
}:
plan_options = {}
if args.command == "ng_gen_app":
plan_options["app_name"] = args.app_name
if args.command == "ng_add":
plan_options["package"] = args.package
return _run_angular_command(
args.command, args.path, dry_run=args.dry_run, **plan_options
)
parser.error("Unknown command")
return 2
def _run_validation(errors: list[str], label: str) -> int:
if errors:
print(f"{label} is invalid.", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
return 1
print(f"{label} is valid.")
return 0
def _run_angular_command(
command_name: str, path: str | Path | None, *, dry_run: bool, **options: str | None
) -> int:
try:
invocations = resolve_angular_command(command_name, path, **options)
except (AngularCommandError, ConfigError, TypeError, ValueError) as exc:
print(exc, file=sys.stderr)
return 1
if dry_run:
print(format_invocations(invocations))
return 0
try:
execute_invocations(invocations)
except AngularCommandError as exc:
print(exc, file=sys.stderr)
return 1
print(f"Executed {len(invocations)} command(s).")
return 0
def _run_install_tutorial(dest: str) -> int:
import shutil
src = Path(__file__).parent / "examples" / "01_simple_crm"
dest_path = Path(dest)
if dest_path.exists():
print(f"Error: destination '{dest_path}' already exists.", file=sys.stderr)
return 1
shutil.copytree(
src,
dest_path,
ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"),
)
print(f"Tutorial project installed to '{dest_path}'.")
print()
print("Next steps:")
print(f" cd {dest_path}")
print(" python manage.py migrate")
print(" python manage.py createsuperuser")
print(" python manage.py runserver")
return 0
if __name__ == "__main__":
raise SystemExit(main())