use genfile_core::
{
TemplateArchive,
ContentSource,
ContentResolver,
FileContent,
WriteMode,
Value,
HandlebarsRenderer,
MemoryFileSystem,
FileSystem,
Error,
FileRef,
UrlRef,
};
use std::path::{ Path, PathBuf };
use std::collections::HashMap;
struct HttpResolver
{
cache: HashMap< String, String >,
}
impl HttpResolver
{
fn new() -> Self
{
let mut cache = HashMap::new();
cache.insert(
"https://templates.example.com/header.hbs".to_string(),
"=== {{title}} ===".to_string(),
);
cache.insert(
"https://templates.example.com/footer.hbs".to_string(),
"Copyright {{year}} {{company}}".to_string(),
);
Self { cache }
}
}
impl ContentResolver for HttpResolver
{
fn resolve( &self, source: &ContentSource ) -> Result< FileContent, Error >
{
match source
{
ContentSource::Inline { content } =>
{
Ok( content.clone() )
}
ContentSource::File { path } =>
{
match std::fs::read_to_string( path )
{
Ok( text ) => Ok( FileContent::Text( text ) ),
Err( e ) => Err( Error::Fs( e ) ),
}
}
ContentSource::Url { url } =>
{
match self.cache.get( url )
{
Some( content ) => Ok( FileContent::Text( content.clone() ) ),
None => Err( Error::Render( format!( "URL not found: {url}" ) ) ),
}
}
}
}
}
#[ test ]
fn example_external_content_sources()
{
let mut archive = TemplateArchive::new( "website-template" );
archive.set_version( "1.0.0" );
archive.set_description( "Website template with external sources" );
archive.add_text_file(
PathBuf::from( "index.html" ),
r"<html>
<body>{{content}}</body>
</html>",
WriteMode::Rewrite
);
archive.add_file_from(
PathBuf::from( "header.txt" ),
UrlRef::new( "https://templates.example.com/header.hbs" ),
WriteMode::Rewrite
);
archive.add_file_from(
PathBuf::from( "footer.txt" ),
UrlRef::new( "https://templates.example.com/footer.hbs" ),
WriteMode::Rewrite
);
archive.set_value( "content", Value::String( "Welcome!".into() ) );
archive.set_value( "title", Value::String( "My Website".into() ) );
archive.set_value( "year", Value::Number( 2024 ) );
archive.set_value( "company", Value::String( "Acme Corp".into() ) );
let resolver = HttpResolver::new();
let renderer = HandlebarsRenderer::new();
let mut filesystem = MemoryFileSystem::new();
let report = archive.materialize_with_resolver(
Path::new( "/output" ),
&renderer,
&mut filesystem,
&resolver
).unwrap();
assert_eq!( report.files_created.len(), 3 );
let index = filesystem.read( &PathBuf::from( "/output/index.html" ) ).unwrap();
assert!( index.contains( "<body>Welcome!</body>" ) );
let header = filesystem.read( &PathBuf::from( "/output/header.txt" ) ).unwrap();
assert_eq!( header, "=== My Website ===" );
let footer = filesystem.read( &PathBuf::from( "/output/footer.txt" ) ).unwrap();
assert_eq!( footer, "Copyright 2024 Acme Corp" );
}
struct DatabaseResolver
{
templates: HashMap< String, String >,
}
impl DatabaseResolver
{
fn new() -> Self
{
let mut templates = HashMap::new();
templates.insert(
"email_welcome".to_string(),
"Hello {{username}}, welcome to {{app_name}}!".to_string(),
);
templates.insert(
"email_reset".to_string(),
"Reset your password: {{reset_link}}".to_string(),
);
Self { templates }
}
fn get_template( &self, id: &str ) -> Option< String >
{
self.templates.get( id ).cloned()
}
}
impl ContentResolver for DatabaseResolver
{
fn resolve( &self, source: &ContentSource ) -> Result< FileContent, Error >
{
match source
{
ContentSource::Inline { content } =>
{
Ok( content.clone() )
}
ContentSource::File { path } =>
{
let template_id = path.file_stem()
.and_then( | s | s.to_str() )
.unwrap_or( "" );
match self.get_template( template_id )
{
Some( content ) => Ok( FileContent::Text( content ) ),
None => Err( Error::Render( format!( "Template not found in DB: {template_id}" ) ) ),
}
}
ContentSource::Url { url } =>
{
Err( Error::Render( format!( "URLs not supported: {url}" ) ) )
}
}
}
}
#[ test ]
fn example_database_templates()
{
let mut archive = TemplateArchive::new( "email-templates" );
archive.add_file_from(
PathBuf::from( "welcome.txt" ),
FileRef::new( PathBuf::from( "email_welcome" ) ), WriteMode::Rewrite
);
archive.add_file_from(
PathBuf::from( "reset.txt" ),
FileRef::new( PathBuf::from( "email_reset" ) ),
WriteMode::Rewrite
);
archive.set_value( "username", Value::String( "Alice".into() ) );
archive.set_value( "app_name", Value::String( "MyApp".into() ) );
archive.set_value( "reset_link", Value::String( "https://example.com/reset?token=xyz".into() ) );
let resolver = DatabaseResolver::new();
let renderer = HandlebarsRenderer::new();
let mut filesystem = MemoryFileSystem::new();
archive.materialize_with_resolver(
Path::new( "/emails" ),
&renderer,
&mut filesystem,
&resolver
).unwrap();
let welcome = filesystem.read( &PathBuf::from( "/emails/welcome.txt" ) ).unwrap();
assert_eq!( welcome, "Hello Alice, welcome to MyApp!" );
let reset = filesystem.read( &PathBuf::from( "/emails/reset.txt" ) ).unwrap();
assert_eq!( reset, "Reset your password: https://example.com/reset?token=xyz" );
}
#[ test ]
fn example_serialization_with_external_sources()
{
let mut archive = TemplateArchive::new( "config-template" );
archive.add_file_from(
PathBuf::from( "settings.json" ),
UrlRef::new( "https://config.example.com/settings.json" ),
WriteMode::Rewrite
);
archive.add_file_from(
PathBuf::from( "rules.yaml" ),
FileRef::new( PathBuf::from( "/etc/templates/rules.yaml" ) ),
WriteMode::Rewrite
);
let json = archive.to_json_pretty().unwrap();
println!( "Archive JSON:\n{json}" );
assert!( json.contains( "content_source" ) );
assert!( json.contains( "https://config.example.com/settings.json" ) );
assert!( json.contains( "/etc/templates/rules.yaml" ) );
let restored = TemplateArchive::from_json( &json ).unwrap();
assert_eq!( restored.file_count(), 2 );
let settings_file = restored.get_file( Path::new( "settings.json" ) ).unwrap();
assert!( settings_file.content_source.as_ref().unwrap().is_url() );
let rules_file = restored.get_file( Path::new( "rules.yaml" ) ).unwrap();
assert!( rules_file.content_source.as_ref().unwrap().is_file() );
}