use std ::path ::Path;
use crate ::
{
Error,
FileSystem,
HandlebarsRenderer,
RealFileSystem,
TemplateRenderer,
Values,
WriteMode,
validate_path,
};
use super ::
{
FileContent,
MaterializationReport,
TemplateArchive,
};
impl TemplateArchive
{
#[cfg(feature = "json")]
pub fn to_json( &self ) -> Result< String, Error >
{
serde_json::to_string( self )
.map_err( | e | Error::Render( format!( "JSON serialization failed: {e}" ) ) )
}
#[cfg(feature = "json")]
pub fn to_json_pretty( &self ) -> Result< String, Error >
{
serde_json::to_string_pretty( self )
.map_err( | e | Error::Render( format!( "JSON serialization failed: {e}" ) ) )
}
#[cfg(feature = "json")]
pub fn from_json( json: &str ) -> Result< Self, Error >
{
serde_json::from_str( json )
.map_err( | e | Error::Render( format!( "JSON deserialization failed: {e}" ) ) )
}
#[cfg(feature = "yaml")]
pub fn to_yaml( &self ) -> Result< String, Error >
{
serde_yaml::to_string( self )
.map_err( | e | Error::Render( format!( "YAML serialization failed: {e}" ) ) )
}
#[cfg(feature = "yaml")]
pub fn from_yaml( yaml: &str ) -> Result< Self, Error >
{
serde_yaml::from_str( yaml )
.map_err( | e | Error::Render( format!( "YAML deserialization failed: {e}" ) ) )
}
pub fn materialize( &self, base_path: &Path ) -> Result< MaterializationReport, Error >
{
let renderer = HandlebarsRenderer::new();
let mut filesystem = RealFileSystem::new();
self.materialize_with_components( base_path, &renderer, &mut filesystem )
}
pub fn materialize_with_components<R, FS>(
&self,
base_path: &Path,
renderer: &R,
filesystem: &mut FS
) -> Result< MaterializationReport, Error >
where
R: TemplateRenderer,
FS: FileSystem,
{
let mut report = MaterializationReport::default();
let values = self.values.as_ref().map( Values::to_serializable ).unwrap_or_default();
for dir in self.list_directories()
{
report.directories_created.push( dir );
}
for file in &self.files
{
validate_path( &file.path )?;
let full_path = base_path.join( &file.path );
let final_content = match &file.content
{
FileContent::Text( template ) =>
{
renderer.render( template, &values )?
}
FileContent::Binary( bytes ) =>
{
String::from_utf8_lossy( bytes ).to_string()
}
};
let existed = filesystem.exists( &full_path );
filesystem.write( &full_path, &final_content )?;
report.total_bytes_written += final_content.len();
if existed
{
report.files_updated.push( file.path.clone() );
}
else
{
report.files_created.push( file.path.clone() );
}
}
Ok( report )
}
#[cfg(feature = "external_content")]
pub fn materialize_with_resolver<R, FS, CR>(
&self,
base_path: &Path,
renderer: &R,
filesystem: &mut FS,
resolver: &CR
) -> Result< MaterializationReport, Error >
where
R: TemplateRenderer,
FS: FileSystem,
CR: crate::ContentResolver,
{
let mut report = MaterializationReport::default();
let values = self.values.as_ref().map( Values::to_serializable ).unwrap_or_default();
for dir in self.list_directories()
{
report.directories_created.push( dir );
}
for file in &self.files
{
validate_path( &file.path )?;
let full_path = base_path.join( &file.path );
let content = if let Some( source ) = &file.content_source
{
resolver.resolve( source )?
}
else
{
file.content.clone()
};
let final_content = match &content
{
FileContent::Text( template ) =>
{
renderer.render( template, &values )?
}
FileContent::Binary( bytes ) =>
{
String::from_utf8_lossy( bytes ).to_string()
}
};
let existed = filesystem.exists( &full_path );
filesystem.write( &full_path, &final_content )?;
report.total_bytes_written += final_content.len();
if existed
{
report.files_updated.push( file.path.clone() );
}
else
{
report.files_created.push( file.path.clone() );
}
}
Ok( report )
}
#[cfg(feature = "external_content")]
pub fn materialize_with_storage<R, CS, CR>(
&self,
base_path: &Path,
renderer: &R,
storage: &mut CS,
resolver: &CR
) -> Result< MaterializationReport, Error >
where
R: TemplateRenderer,
CS: crate::ContentStorage,
CR: crate::ContentResolver,
{
let mut report = MaterializationReport::default();
let values = self.values.as_ref().map( Values::to_serializable ).unwrap_or_default();
for dir in self.list_directories()
{
report.directories_created.push( dir );
}
for file in &self.files
{
validate_path( &file.path )?;
let full_path = base_path.join( &file.path );
let content = if let Some( source ) = &file.content_source
{
resolver.resolve( source )?
}
else
{
file.content.clone()
};
let rendered_content = match &content
{
FileContent::Text( template ) =>
{
FileContent::Text( renderer.render( template, &values )? )
}
FileContent::Binary( bytes ) =>
{
FileContent::Binary( bytes.clone() )
}
};
storage.store( &full_path, &rendered_content )?;
report.total_bytes_written += match &rendered_content
{
FileContent::Text( s ) => s.len(),
FileContent::Binary( b ) => b.len(),
};
report.files_created.push( file.path.clone() );
}
Ok( report )
}
pub fn pack_from_dir( name: impl Into< String >, base_path: &Path ) -> Result< Self, Error >
{
let mut archive = Self::new( name );
fn visit_dir( archive: &mut TemplateArchive, base: &Path, current: &Path ) -> Result< (), Error >
{
for entry in std::fs::read_dir( current )?
{
let entry = entry?;
let path = entry.path();
if path.is_dir()
{
visit_dir( archive, base, &path )?;
}
else if path.is_file()
{
let rel_path = path.strip_prefix( base )
.map_err( | e | Error::Render( format!( "Path error: {e}" ) ) )?
.to_path_buf();
let data = std::fs::read( &path )?;
let content = match String::from_utf8( data.clone() )
{
Ok( text ) => FileContent::Text( text ),
Err( _ ) => FileContent::Binary( data ),
};
archive.add_file( rel_path, content, WriteMode::Rewrite );
}
}
Ok( () )
}
visit_dir( &mut archive, base_path, base_path )?;
Ok( archive )
}
#[cfg(feature = "external_content")]
pub fn internalize< CR >( &mut self, resolver: &CR ) -> Result< (), Error >
where
CR: crate::ContentResolver,
{
for file in &mut self.files
{
if let Some( source ) = &file.content_source
{
let content = resolver.resolve( source )?;
file.content = content;
file.content_source = None;
}
}
Ok( () )
}
#[cfg(feature = "external_content")]
pub fn externalize( &mut self, base_path: &Path ) -> Result< (), Error >
{
std::fs::create_dir_all( base_path )?;
for file in &mut self.files
{
if file.content_source.is_none()
{
let content_filename = format!( "{}.content", file.path.display() ).replace( '/', "_" );
let content_path = base_path.join( &content_filename );
match &file.content
{
FileContent::Text( text ) =>
{
std::fs::write( &content_path, text )?;
}
FileContent::Binary( bytes ) =>
{
std::fs::write( &content_path, bytes )?;
}
}
file.content_source = Some( crate::ContentSource::File { path: content_path } );
file.content = FileContent::Text( String::new() );
}
}
Ok( () )
}
#[cfg(feature = "json")]
pub fn save_to_file( &self, path: &Path ) -> Result< (), Error >
{
let json = self.to_json_pretty()?;
std::fs::write( path, json )?;
Ok( () )
}
#[cfg(feature = "json")]
pub fn load_from_file( path: &Path ) -> Result< Self, Error >
{
let json = std::fs::read_to_string( path )?;
Self::from_json( &json )
}
}