use crate ::reporting :: { MarkdownUpdater, MarkdownError };
use std ::path ::Path;
type Result< T > = std ::result ::Result< T, Box< dyn std ::error ::Error > >;
#[ derive( Debug ) ]
pub enum UpdateChainError
{
Markdown( MarkdownError ),
Io( std ::io ::Error ),
ValidationFailed
{
conflicts: Vec< String >
},
EmptyChain,
}
impl std ::fmt ::Display for UpdateChainError
{
fn fmt( &self, f: &mut std ::fmt ::Formatter< '_ > ) -> std ::fmt ::Result
{
match self
{
UpdateChainError ::Markdown( err ) => write!( f, "Markdown error: {}", err ),
UpdateChainError ::Io( err ) => write!( f, "IO error: {}", err ),
UpdateChainError ::ValidationFailed { conflicts } =>
{
write!( f, "Validation failed with conflicts: {:?}", conflicts )
},
UpdateChainError ::EmptyChain => write!( f, "Update chain is empty" ),
}
}
}
impl std ::error ::Error for UpdateChainError
{
fn source( &self ) -> Option< &( dyn std ::error ::Error + 'static ) >
{
match self
{
UpdateChainError ::Markdown( err ) => Some( err ),
UpdateChainError ::Io( err ) => Some( err ),
_ => None,
}
}
}
impl From< MarkdownError > for UpdateChainError
{
fn from( err: MarkdownError ) -> Self
{
UpdateChainError ::Markdown( err )
}
}
impl From< std ::io ::Error > for UpdateChainError
{
fn from( err: std ::io ::Error ) -> Self
{
UpdateChainError ::Io( err )
}
}
#[ derive( Debug, Clone ) ]
pub struct SectionUpdate
{
pub section_name: String,
pub content: String,
}
impl SectionUpdate
{
pub fn new( section_name: impl Into< String >, content: impl Into< String > ) -> Self
{
Self
{
section_name: section_name.into(),
content: content.into(),
}
}
}
#[ derive( Debug ) ]
pub struct MarkdownUpdateChain
{
file_path: std ::path ::PathBuf,
updates: Vec< SectionUpdate >,
}
impl MarkdownUpdateChain
{
pub fn new( file_path: impl AsRef< Path > ) -> Result< Self >
{
Ok( Self
{
file_path: file_path.as_ref().to_path_buf(),
updates: Vec ::new(),
})
}
pub fn add_section( mut self, section_name: impl Into< String >, content: impl Into< String > ) -> Self
{
self.updates.push( SectionUpdate ::new( section_name, content ) );
self
}
pub fn check_all_conflicts( &self ) -> Result< Vec< String > >
{
if self.updates.is_empty()
{
return Ok( vec![] );
}
let mut all_conflicts = Vec ::new();
for update in &self.updates
{
let updater = MarkdownUpdater ::new( &self.file_path, &update.section_name )
.map_err( UpdateChainError ::from )?;
let conflicts = updater.check_conflicts()
.map_err( UpdateChainError ::from )?;
all_conflicts.extend( conflicts );
}
all_conflicts.sort();
all_conflicts.dedup();
Ok( all_conflicts )
}
pub fn execute( &self ) -> Result< () >
{
if self.updates.is_empty()
{
return Err( Box ::new( UpdateChainError ::EmptyChain ) );
}
let conflicts = self.check_all_conflicts()?;
if !conflicts.is_empty()
{
return Err( Box ::new( UpdateChainError ::ValidationFailed { conflicts } ) );
}
let backup_path = self.create_backup()?;
match self.apply_all_updates()
{
Ok( () ) =>
{
if let Some( backup ) = backup_path
{
let _ = std ::fs ::remove_file( backup );
}
Ok( () )
},
Err( e ) =>
{
if let Some( backup ) = backup_path
{
if let Err( restore_err ) = std ::fs ::copy( &backup, &self.file_path )
{
eprintln!( "⚠️ Failed to restore backup: {}", restore_err );
}
let _ = std ::fs ::remove_file( backup );
}
Err( e )
}
}
}
fn create_backup( &self ) -> Result< Option< std ::path ::PathBuf > >
{
if !self.file_path.exists()
{
return Ok( None );
}
let backup_path = self.file_path.with_extension( "bak" );
std ::fs ::copy( &self.file_path, &backup_path )
.map_err( UpdateChainError ::from )?;
Ok( Some( backup_path ) )
}
fn apply_all_updates( &self ) -> Result< () >
{
let mut current_content = if self.file_path.exists()
{
std ::fs ::read_to_string( &self.file_path )
.map_err( UpdateChainError ::from )?
}
else
{
String ::new()
};
for update in &self.updates
{
let updater = MarkdownUpdater ::new( &self.file_path, &update.section_name )
.map_err( UpdateChainError ::from )?;
current_content = updater.replace_section_content( ¤t_content, &update.content );
}
std ::fs ::write( &self.file_path, current_content )
.map_err( UpdateChainError ::from )?;
Ok( () )
}
#[ must_use ]
pub fn len( &self ) -> usize
{
self.updates.len()
}
#[ must_use ]
pub fn is_empty( &self ) -> bool
{
self.updates.is_empty()
}
#[ must_use ]
pub fn file_path( &self ) -> &Path
{
&self.file_path
}
#[ must_use ]
pub fn updates( &self ) -> &[ SectionUpdate ]
{
&self.updates
}
}