spacetimedb-cli 1.0.0-rc2

A command line interface for SpacetimeDB
Documentation
//! Various utility functions that the generate modules have in common.

use std::{
    fmt::{Display, Formatter, Result},
    ops::Deref,
};

use convert_case::{Case, Casing};
use itertools::Itertools;
use spacetimedb_lib::sats::AlgebraicTypeRef;
use spacetimedb_schema::{
    def::ModuleDef,
    identifier::Identifier,
    type_for_generate::{AlgebraicTypeUse, PrimitiveType},
};

use super::code_indenter::Indenter;

/// Turns a closure `f: Fn(&mut Formatter) -> Result` into `fmt::Display`.
pub(super) fn fmt_fn(f: impl Fn(&mut Formatter) -> Result) -> impl Display {
    struct FDisplay<F>(F);
    impl<F: Fn(&mut Formatter) -> Result> Display for FDisplay<F> {
        fn fmt(&self, f: &mut Formatter<'_>) -> Result {
            (self.0)(f)
        }
    }
    FDisplay(f)
}

pub(super) fn collect_case<'a>(case: Case, segs: impl Iterator<Item = &'a Identifier>) -> String {
    segs.map(|s| s.deref().to_case(case)).join(case.delim())
}

pub(super) fn namespace_is_empty_or_default(requested_namespace: &str) -> bool {
    // The `spacetime generate` CLI sets a default namespace of `SpacetimeDB.Types`,
    // which is intended to be consumed only by C# codegen,
    // but is passed to all codegen languages including Rust and TypeScript.
    // We want to assert that the user did not explicitly request a different namespace,
    // since we have no way to emit one.
    // So, check that the namespace either is empty or is the default.
    requested_namespace.is_empty() || requested_namespace == "SpacetimeDB.Types"
}

pub(super) fn print_lines(output: &mut Indenter, lines: &[&str]) {
    for line in lines {
        writeln!(output, "{line}");
    }
}

const AUTO_GENERATED_FILE_COMMENT: &[&str] = &[
    "// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE",
    "// WILL NOT BE SAVED. MODIFY TABLES IN RUST INSTEAD.",
    "",
];

pub(super) fn print_auto_generated_file_comment(output: &mut Indenter) {
    print_lines(output, AUTO_GENERATED_FILE_COMMENT);
}

pub(super) fn type_ref_name(module: &ModuleDef, typeref: AlgebraicTypeRef) -> String {
    let (name, _def) = module.type_def_from_ref(typeref).unwrap();
    collect_case(Case::Pascal, name.name_segments())
}

pub(super) fn is_type_filterable(ty: &AlgebraicTypeUse) -> bool {
    match ty {
        AlgebraicTypeUse::Primitive(prim) => !matches!(prim, PrimitiveType::F32 | PrimitiveType::F64),
        AlgebraicTypeUse::String | AlgebraicTypeUse::Identity | AlgebraicTypeUse::Address => true,
        _ => false,
    }
}