doido-generators 0.0.6

Doido code generators plus the unified CLI (server, console, db, worker, generate, new, credentials).
Documentation
use crate::generator::{GeneratedFile, Generator};
use crate::generators::{to_snake, to_table_name};
use doido_core::{anyhow::anyhow, Result};

/// `doido generate generator <name>` — scaffolds a project-local custom
/// generator under `lib/generators/<name>/`. It is a folder of template files;
/// running `doido generate <name> <Thing>` substitutes tokens into each file's
/// path and contents (see [`crate::project_generator`]).
pub struct GeneratorGenerator;

impl Generator for GeneratorGenerator {
    fn name(&self) -> &str {
        "generator"
    }

    fn generate(&self, args: &[&str]) -> Result<Vec<GeneratedFile>> {
        let name = args
            .first()
            .copied()
            .ok_or_else(|| anyhow!("generator generator requires a name argument"))?;
        let snake = to_snake(name);
        let plural = to_table_name(name);
        let base = format!("lib/generators/{snake}");

        Ok(vec![
            // A sample template whose path + contents use generator tokens, so
            // it adapts to whatever name the custom generator is later run with.
            GeneratedFile {
                path: format!("{base}/app/{plural}/{{snake}}.rs.template"),
                content: SAMPLE.to_string(),
            },
            GeneratedFile {
                path: format!("{base}/README.md"),
                content: readme(&snake),
            },
        ])
    }
}

const SAMPLE: &str = r#"// Generated by the `{snake}` project generator.
// Tokens available: {name} {snake} {singular} {pascal} {Model} {plural} {Controller}
pub struct {pascal} {
    // TODO: fields
}
"#;

fn readme(snake: &str) -> String {
    format!(
        r#"# `{snake}` generator

A project-local generator. Every file under this directory is a template; the
trailing `.template` extension (if present) is stripped from the output path.

## Usage

```
doido generate {snake} <Name>
```

## Tokens

Substituted in both file paths and file contents:

| Token | Example for `BlogPost` |
|-------|------------------------|
| `{{name}}` | `BlogPost` (as typed) |
| `{{snake}}` / `{{singular}}` | `blog_post` |
| `{{pascal}}` / `{{Model}}` | `BlogPost` |
| `{{plural}}` | `blog_posts` |
| `{{Controller}}` | `BlogPostsController` |
"#
    )
}