willbe 0.34.0

Utility to publish multi-crate and multi-workspace environments and maintain their consistency.
Documentation
//! Workspace template generation using `genfile_core`.
//!
//! # Architecture Decision: `genfile_core` Integration
//!
//! **Migration Date:** 2025-10-19
//!
//! This module was migrated from a custom 472-line template implementation to use
//! the `genfile_core` library for template processing. The migration provides:
//!
//! - **Code Reuse:** Eliminated duplicated template logic across wTools ecosystem
//! - **Testing:** Inherited 215 comprehensive tests from `genfile_core`
//! - **Security:** Path traversal validation with 27 dedicated tests
//! - **Features:** Binary file support, JSON/YAML serialization, external content
//!
//! # Key Implementation Patterns
//!
//! ## Parameter Definition
//!
//! Parameters are defined using direct struct construction instead of builders:
//! ```rust,ignore
//! archive.add_parameter(ParameterDescriptor {
//!   parameter: "project_name".into(),
//!   is_mandatory: true,
//!   default_value: None,
//!   description: None,
//! });
//! ```
//!
//! ## Template File Addition
//!
//! All template files are added using `add_text_file()` with embedded content:
//! ```rust,ignore
//! archive.add_text_file(
//!   PathBuf::from("./Cargo.toml"),
//!   include_str!("../../template/workspace/Cargo.hbs"),
//!   WriteMode::Rewrite
//! );
//! ```
//!
//! ## Value Setting and Materialization
//!
//! Values are set before materialization:
//! ```rust,ignore
//! archive.set_value("project_name", Value::String(name));
//! archive.materialize(output_path)?;
//! ```
//!
//! # Template Syntax Gotcha
//!
//! **IMPORTANT:** Handlebars interprets `{{foo bar}}` (with space) as a helper call
//! `foo(bar)`, not variable substitution. Always use `{{foo}}` for variables.
//!
//! Bug fixed 2025-10-19: `{{repository url}}` → `{{url}}`

#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{

  use crate :: *;
  use std ::
  {
  fs,
  path :: { Path, PathBuf },
 };
  use error ::untyped ::bail;
  // qqq: group dependencies
  use iter_tools ::iter ::Itertools;
  use genfile_core ::
  {
  TemplateArchive,
  WriteMode,
  Value,
  ParameterDescriptor,
 };

  /// Template for creating workspace files.
  ///
  /// Uses `genfile_core`'s `TemplateArchive` for template processing.
  /// See module documentation for migration details and patterns.
  #[ derive( Debug ) ]
  pub struct WorkspaceTemplate
  {
  archive: TemplateArchive,
 }

  impl WorkspaceTemplate
  {
  /// Returns list of parameter names
  #[ must_use ]
  pub fn get_parameters( &self ) -> Vec< String >
  {
   self.archive.parameters.descriptors.iter().map( | d | d.parameter.clone() ).collect()
 }
 }

  impl Default for WorkspaceTemplate
  {
  #[ allow( clippy ::too_many_lines ) ]
  fn default() -> Self
  {
   let mut archive = TemplateArchive ::new( "workspace" );

   // Define parameters
   archive.add_parameter
   (
  ParameterDescriptor
  {
   parameter: "project_name".into(),
   is_mandatory: true,
   default_value: None,
   description: None,
 }
 );
   archive.add_parameter
   (
  ParameterDescriptor
  {
   parameter: "url".into(),
   is_mandatory: true,
   default_value: None,
   description: None,
 }
 );
   archive.add_parameter
   (
  ParameterDescriptor
  {
   parameter: "branches".into(),
   is_mandatory: true,
   default_value: None,
   description: None,
 }
 );

   // Add template files
   archive.add_text_file
   (
  PathBuf ::from( "./.gitattributes" ),
  include_str!( "../../template/workspace/.gitattributes" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./.gitignore" ),
  include_str!( "../../template/workspace/.gitignore1" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./.gitpod.yml" ),
  include_str!( "../../template/workspace/.gitpod.yml" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./Cargo.toml" ),
  include_str!( "../../template/workspace/Cargo.hbs" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./Makefile" ),
  include_str!( "../../template/workspace/Makefile" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./readme.md" ),
  include_str!( "../../template/workspace/readme.md" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./.cargo/config.toml" ),
  include_str!( "../../template/workspace/.cargo/config.toml" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./module/Cargo.toml" ),
  include_str!( "../../template/workspace/module/module1/Cargo.toml.x" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./module/module1/readme.md" ),
  include_str!( "../../template/workspace/module/module1/readme.md" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./module/module1/examples/module1_example.rs" ),
  include_str!( "../../template/workspace/module/module1/examples/module1_example.rs" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./module/module1/src/lib.rs" ),
  include_str!( "../../template/workspace/module/module1/src/lib.rs" ),
  WriteMode ::Rewrite
 );
   archive.add_text_file
   (
  PathBuf ::from( "./module/module1/tests/hello_test.rs" ),
  include_str!( "../../template/workspace/module/module1/tests/hello_test.rs" ),
  WriteMode ::Rewrite
 );

   Self { archive }
 }
 }

  // zzz
  // qqq: for Petro: should return report
  // qqq: for Petro: should have typed error
  /// Creates workspace template
  /// # Errors
  /// qqq: doc
  /// # Panics
  /// qqq: doc
  pub fn action
  (
  path: &Path,
  mut template: WorkspaceTemplate,
  repository_url: String,
  branches: Vec< String >
 )
  -> error ::untyped ::Result< () > // qqq: use typed error
  {
  if fs ::read_dir( path )?.count() != 0
  {
   bail!( "Directory should be empty" )
 }

  // Set parameter values
  let project_name = path.file_name().unwrap().to_string_lossy().to_string();
  template.archive.set_value( "project_name", Value ::String( project_name ) );
  template.archive.set_value( "url", Value ::String( repository_url ) );

  let branches_str = branches.into_iter().map( | b | format!( r#""{b}""# ) ).join( ", " );
  template.archive.set_value( "branches", Value ::String( branches_str ) );

  // Materialize the template
  template.archive.materialize( path ).map_err( | e | error ::untyped ::format_err!( "{e}" ) )?;

  Ok( () )
 }
}

crate ::mod_interface!
{
  own use action;
  orphan use WorkspaceTemplate;
}