#![ allow( clippy::needless_pass_by_value ) ]
use unilang::semantic::VerifiedCommand;
use unilang::data::{ OutputData, ErrorData };
use unilang::interpreter::ExecutionContext;
use core::fmt::Write as _;
#[ allow( clippy::too_many_lines ) ]
pub fn materialize_handler(
cmd : VerifiedCommand,
_ctx : ExecutionContext
) -> Result< OutputData, ErrorData >
{
let destination = cmd.get_path( "destination" )
.ok_or_else( || crate::error::usage_error( "Missing required parameter: destination" ) )?;
let verbosity = cmd.get_integer( "verbosity" ).unwrap_or( 1 );
let dry = cmd.get_boolean( "dry" ).unwrap_or( false );
let archive = crate::handlers::shared_state::get_current_archive()
.ok_or_else( || crate::error::state_error( "No archive loaded. Use .archive.load first." ) )?;
let mandatory_params = archive.parameters.list_mandatory();
if !mandatory_params.is_empty()
{
let missing : Vec< &str > = mandatory_params
.iter()
.filter( | p |
{
archive
.values
.as_ref()
.is_none_or( | v | !v.has_value( p ) )
})
.copied()
.collect();
if !missing.is_empty()
{
return Err( crate::error::validation_error( format!(
"Missing mandatory parameter values: {}. Use .value.set to provide values.",
missing.join( ", " )
) ) );
}
}
if dry
{
let file_count = archive.files.len();
let param_count = archive.values.as_ref().map_or( 0, genfile_core::Values::len );
let output_content = match verbosity
{
0 => String::new(),
1 => format!( "Dry run: Would materialize {} files to {}", file_count, destination.display() ),
_ =>
{
let files_preview = archive
.files
.iter()
.take( 5 )
.map( | f | format!( " - {}", f.path.display() ) )
.collect::< Vec< _ > >()
.join( "\n" );
let more = if file_count > 5 { format!( "\n ... and {} more files", file_count - 5 ) } else { String::new() };
format!(
"Dry run: Would materialize templates\n\
Destination: {}\n\
Archive: {}\n\
Files: {}\n\
Parameters: {}\n\
Files to create:\n\
{}{}",
destination.display(),
archive.name,
file_count,
param_count,
files_preview,
more
)
}
};
return Ok( OutputData
{
content : output_content,
format : "text".to_string(),
execution_time_ms : None,
} );
}
let report = archive.materialize( destination )
.map_err( | e | crate::error::format_error( &e, "MATERIALIZE" ) )?;
let total_files = report.files_created.len() + report.files_updated.len() + report.files_skipped.len();
let output_content = match verbosity
{
0 => String::new(),
1 => format!( "Materialized {} files to {}", total_files, destination.display() ),
_ =>
{
let mut details = format!(
"Materialized templates\n\
Archive: {}\n\
Destination: {}\n\
Created: {}\n\
Updated: {}\n\
Skipped: {}",
archive.name,
destination.display(),
report.files_created.len(),
report.files_updated.len(),
report.files_skipped.len()
);
if verbosity >= 3 && !report.files_created.is_empty()
{
details.push_str( "\n\nCreated files:\n" );
for file in &report.files_created
{
let _ = writeln!( &mut details, " - {}", file.display() );
}
}
if verbosity >= 3 && !report.files_updated.is_empty()
{
details.push_str( "\nUpdated files:\n" );
for file in &report.files_updated
{
let _ = writeln!( &mut details, " - {}", file.display() );
}
}
details
}
};
Ok( OutputData
{
content : output_content,
format : "text".to_string(),
execution_time_ms : None,
} )
}
#[ allow( clippy::too_many_lines ) ]
pub fn unpack_handler(
cmd : VerifiedCommand,
_ctx : ExecutionContext
) -> Result< OutputData, ErrorData >
{
use std::fs;
let destination = cmd.get_path( "destination" )
.ok_or_else( || crate::error::usage_error( "Missing required parameter: destination" ) )?;
let verbosity = cmd.get_integer( "verbosity" ).unwrap_or( 1 );
let dry = cmd.get_boolean( "dry" ).unwrap_or( false );
let archive = crate::handlers::shared_state::get_current_archive()
.ok_or_else( || crate::error::state_error( "No archive loaded. Use .archive.load first." ) )?;
let file_count = archive.files.len();
if dry
{
let output_content = match verbosity
{
0 => String::new(),
1 => format!( "Dry run: Would unpack {} files to {}", file_count, destination.display() ),
_ =>
{
let files_preview = archive
.files
.iter()
.take( 5 )
.map( | f | format!( " - {}", f.path.display() ) )
.collect::< Vec< _ > >()
.join( "\n" );
let more = if file_count > 5 { format!( "\n ... and {} more files", file_count - 5 ) } else { String::new() };
format!(
"Dry run: Would unpack templates\n\
Destination: {}\n\
Archive: {}\n\
Files: {}\n\
Mode: raw (no rendering)\n\
Files to create:\n\
{}{}",
destination.display(),
archive.name,
file_count,
files_preview,
more
)
}
};
return Ok( OutputData
{
content : output_content,
format : "text".to_string(),
execution_time_ms : None,
} );
}
fs::create_dir_all( destination )
.map_err( | e | crate::error::file_error( format!( "Failed to create destination directory: {e}" ) ) )?;
let mut files_created = Vec::new();
for file in &archive.files
{
let file_path = destination.join( &file.path );
if let Some( parent ) = file_path.parent()
{
fs::create_dir_all( parent )
.map_err( | e | crate::error::file_error( format!( "Failed to create directory {}: {}", parent.display(), e ) ) )?;
}
match &file.content
{
genfile_core::FileContent::Text( text ) =>
{
fs::write( &file_path, text )
.map_err( | e | crate::error::file_error( format!( "Failed to write file {}: {}", file_path.display(), e ) ) )?;
}
genfile_core::FileContent::Binary( bytes ) =>
{
fs::write( &file_path, bytes )
.map_err( | e | crate::error::file_error( format!( "Failed to write file {}: {}", file_path.display(), e ) ) )?;
}
}
files_created.push( file_path );
}
let output_content = match verbosity
{
0 => String::new(),
1 => format!( "Unpacked {} files to {}", files_created.len(), destination.display() ),
_ =>
{
let mut details = format!(
"Unpacked templates\n\
Archive: {}\n\
Destination: {}\n\
Files: {}\n\
Mode: raw (no rendering)",
archive.name,
destination.display(),
files_created.len()
);
if verbosity >= 3 && !files_created.is_empty()
{
details.push_str( "\n\nCreated files:\n" );
for file in &files_created
{
let _ = writeln!( &mut details, " - {}", file.display() );
}
}
details
}
};
Ok( OutputData
{
content : output_content,
format : "text".to_string(),
execution_time_ms : None,
} )
}