rustango 0.7.0

A Django-inspired ORM + admin + multi-tenancy for Rust. One crate, opt in via features.
Documentation
//! Tenancy variant of `startapp` — same Django-shape file layout as
//! `rustango::migrate::manage startapp`, with one twist: when
//! `--with-manage-bin` is passed, the generated `src/bin/manage.rs`
//! wires `crate::tenancy::manage::run` instead of the single-
//! tenant dispatcher. Useful for bootstrapping multi-tenant projects.
//!
//! Falls through to [`rustango::migrate::scaffold::startapp`] for the
//! actual file writes — this module just owns the tenancy template
//! and the argv parser.

use std::io::Write;

use crate::tenancy::error::TenancyError;
use crate::tenancy::manage::args::next_value;

/// Tenancy-aware `manage.rs` template. Wires
/// `crate::tenancy::manage::run` so the resulting binary recognizes
/// `create-tenant` / `migrate-tenants` / `run-server` / etc. on top of
/// the standard `migrate` / `makemigrations` set.
pub const TENANCY_MANAGE_BIN: &str =
    "//! Generated by `manage startapp --with-manage-bin` (tenancy-aware).
//!
//! UX: `cargo run --bin manage -- create-tenant <slug>`,
//! `cargo run --bin manage -- run-server`, plus everything the
//! single-tenant dispatcher offers (`migrate`, `makemigrations`, …).

use crate::sql::sqlx::PgPool;
use crate::tenancy::TenantPools;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Pull your models into this binary so `inventory` registers
    // them. For the `manage startapp <name>` shape:
    //   #[allow(unused_imports)]
    //   use super::<name>::models::*;
    // Tenancy registry models (Org / Operator / User) come along
    // automatically via the `crate::tenancy` crate import.

    let registry_url = std::env::var(\"DATABASE_URL\")?;
    let pool = PgPool::connect(&registry_url).await?;
    let pools = TenantPools::new(pool);
    let dir: &std::path::Path = \"./migrations\".as_ref();
    crate::tenancy::manage::run(&pools, &registry_url, dir, std::env::args().skip(1)).await?;
    Ok(())
}
";

pub(super) fn startapp_cmd<W: Write>(
    args: &[String],
    w: &mut W,
) -> Result<(), TenancyError> {
    let mut iter = args.iter();
    let app_name = iter
        .next()
        .cloned()
        .ok_or_else(|| TenancyError::Validation(usage()))?;
    let mut with_manage_bin = false;
    while let Some(flag) = iter.next() {
        match flag.as_str() {
            "--with-manage-bin" => with_manage_bin = true,
            "--help" | "-h" => {
                writeln!(w, "{}", usage())?;
                return Ok(());
            }
            "--app-name" => {
                // Belt-and-braces: accept --app-name <X> as an alias
                // even though the positional form is canonical.
                let _ = next_value(&mut iter, "--app-name")?;
            }
            other => {
                return Err(TenancyError::Validation(format!(
                    "startapp: unknown argument `{other}` (run --help for usage)"
                )));
            }
        }
    }
    let opts = rustango::migrate::scaffold::StartAppOptions {
        app_name: app_name.clone(),
        manage_bin: with_manage_bin.then_some(TENANCY_MANAGE_BIN),
    };
    let cwd = std::env::current_dir().map_err(TenancyError::Io)?;
    let report = rustango::migrate::scaffold::startapp(&cwd, &opts)
        .map_err(TenancyError::Migrate)?;
    write_report(w, &app_name, &report)
}

fn write_report<W: Write>(
    w: &mut W,
    app_name: &str,
    report: &rustango::migrate::scaffold::StartAppReport,
) -> Result<(), TenancyError> {
    if report.written.is_empty() && report.skipped.is_empty() {
        writeln!(w, "startapp: nothing to do")?;
        return Ok(());
    }
    writeln!(w, "startapp `{app_name}` (tenancy-aware)")?;
    for path in &report.written {
        writeln!(w, "  + wrote {path}")?;
    }
    for path in &report.skipped {
        writeln!(w, "  · {path} already exists — left untouched")?;
    }
    if !report.written.is_empty() {
        writeln!(w, "next:")?;
        writeln!(
            w,
            "  add `mod {app_name};` to src/main.rs (or src/lib.rs) so"
        )?;
        writeln!(
            w,
            "  the derive macros' `inventory` registrations are pulled in."
        )?;
        writeln!(w)?;
        writeln!(
            w,
            "  for tenancy: `manage init-tenancy && manage migrate` to"
        )?;
        writeln!(
            w,
            "  materialize the registry + tenant bootstrap migrations."
        )?;
    }
    Ok(())
}

fn usage() -> String {
    "startapp <name> [--with-manage-bin]\n  \
     Scaffold a Django-shape app module under src/<name>/ (mod.rs +\n  \
     models.rs + views.rs + urls.rs). Idempotent: existing files\n  \
     are left untouched. <name> must be a valid Rust identifier.\n\n  \
     --with-manage-bin\n  \
     Also write src/bin/manage.rs with the **tenancy-aware**\n  \
     dispatcher boilerplate (crate::tenancy::manage::run). Skipped\n  \
     if the file already exists."
        .to_owned()
}