holochain_scaffolding_cli 0.4000.4

CLI to easily generate and modify holochain apps
Documentation
use std::{path::PathBuf, str::FromStr};

use convert_case::{Case, Casing};
use dialoguer::{theme::ColorfulTheme, Select};
use serde::Serialize;

use crate::{
    error::{ScaffoldError, ScaffoldResult},
    file_tree::FileTree,
    reserved_words::check_for_reserved_keywords,
    templates::{collection::scaffold_collection_templates, ScaffoldedTemplate},
};

use self::coordinator::add_collection_to_coordinators;

use super::{
    app::AppFileTree,
    entry_type::{
        definitions::{EntryTypeReference, Referenceable},
        integrity::get_all_entry_types,
        utils::choose_entry_type_reference,
    },
    link_type::integrity::add_link_type_to_integrity_zome,
    zome::ZomeFileTree,
};

pub mod coordinator;

#[derive(Serialize, Debug, Clone, Copy)]
#[serde(tag = "type")]
pub enum CollectionType {
    Global,
    ByAuthor,
}

impl FromStr for CollectionType {
    type Err = ScaffoldError;

    fn from_str(s: &str) -> ScaffoldResult<Self> {
        match s {
            "global" => Ok(CollectionType::Global),
            "by-author" => Ok(CollectionType::ByAuthor),
            _ => Err(ScaffoldError::InvalidCollectionType(
                s.to_string(),
                "global, by-author".to_string(),
            )),
        }
    }
}

pub fn choose_collection_type() -> ScaffoldResult<CollectionType> {
    let selection = Select::with_theme(&ColorfulTheme::default())
        .with_prompt("Which type of collection should be scaffolded?")
        .default(0)
        .item("Global (get all entries of the selected entry types)")
        .item("By author (get entries of the selected entry types that a given author has created)")
        .interact()?;
    match selection {
        0 => Ok(CollectionType::Global),
        1 => Ok(CollectionType::ByAuthor),
        _ => Err(ScaffoldError::InvalidCollectionType(
            selection.to_string(),
            "".into(),
        )),
    }
}

pub fn scaffold_collection(
    integrity_zome_file_tree: ZomeFileTree,
    template_file_tree: &FileTree,
    collection_name: &str,
    maybe_collection_type: Option<CollectionType>,
    maybe_entry_type: Option<EntryTypeReference>,
    no_ui: bool,
    no_spec: bool,
) -> ScaffoldResult<ScaffoldedTemplate> {
    check_for_reserved_keywords(collection_name)?;

    let all_entries = get_all_entry_types(&integrity_zome_file_tree)?.ok_or(
        ScaffoldError::NoEntryTypesDefFoundForIntegrityZome(
            integrity_zome_file_tree.dna_file_tree.dna_manifest.name(),
            integrity_zome_file_tree.zome_manifest.name.0.to_string(),
        ),
    )?;

    let collection_type = match maybe_collection_type {
        Some(t) => Ok(t),
        None => choose_collection_type(),
    }?;

    let all_entries_names: Vec<String> = all_entries
        .clone()
        .into_iter()
        .map(|e| e.entry_type)
        .collect();
    let entry_type = match maybe_entry_type {
        Some(et) => {
            if all_entries_names.contains(&et.entry_type.to_case(Case::Pascal)) {
                Ok(et.clone())
            } else {
                Err(ScaffoldError::EntryTypeNotFound(
                    et.entry_type.clone(),
                    integrity_zome_file_tree.dna_file_tree.dna_manifest.name(),
                    integrity_zome_file_tree.zome_manifest.name.0.to_string(),
                ))
            }
        }
        None => choose_entry_type_reference(&all_entries, "Which entry type should be collected?"),
    }?;

    let link_type_name = collection_name.to_case(Case::Pascal);

    let zome_file_tree = add_link_type_to_integrity_zome(
        integrity_zome_file_tree,
        &link_type_name,
        &None,
        &Some(Referenceable::EntryType(entry_type.clone())),
        true,
        &PathBuf::from(format!("{}.rs", entry_type.entry_type.to_case(Case::Snake))),
    )?;

    let (dna_file_tree, coordinator_zome, deletable) = add_collection_to_coordinators(
        zome_file_tree,
        collection_name,
        &link_type_name,
        &collection_type,
        &entry_type,
    )?;

    let dna_name = dna_file_tree.dna_manifest.name();

    let app_file_tree = AppFileTree::get_or_choose(dna_file_tree.file_tree(), None)?;

    let app_name = app_file_tree.app_manifest.app_name().to_string();

    scaffold_collection_templates(
        app_file_tree.file_tree(),
        template_file_tree,
        &app_name,
        &dna_name,
        &coordinator_zome,
        &collection_type,
        collection_name,
        &entry_type,
        deletable,
        no_ui,
        no_spec,
    )
}