rletxtconv/
lib.rs

1// lib.rs
2use std::{
3    fs::{self, OpenOptions},
4    io,
5    path::Path,
6};
7
8use thiserror::Error;
9
10pub mod formats;
11pub use formats::Format;
12use universe::Universe;
13pub mod universe;
14
15#[derive(Error, Debug)]
16pub enum ConwayError {
17    #[error("IO error: {0}")]
18    Io(#[from] io::Error),
19
20    #[error("Format detection error: {0}")]
21    FormatDetection(String),
22
23    #[error("Parsing error: {0}")]
24    Parsing(String),
25
26    #[error("Writing error: {0}")]
27    Writing(String),
28}
29
30pub type Result<T> = std::result::Result<T, ConwayError>;
31
32/// Detect the format of a Conway's Game of Life file
33pub fn detect_format(content: &str) -> Result<Format> {
34    // Check for RLE header signature
35    for line in content.lines() {
36        let trimmed = line.trim_start();
37        if trimmed.starts_with('#') || trimmed.is_empty() {
38            continue;
39        }
40
41        if formats::rle::is_valid_header(trimmed) {
42            return Ok(Format::Rle);
43        }
44
45        return Ok(Format::Plaintext);
46    }
47
48    Err(ConwayError::FormatDetection(
49        "Could not detect file format".into(),
50    ))
51}
52
53/// Parses a file and returns its format and the grid of cells (Universe)
54pub fn parse_file(input_path: &Path) -> Result<(Format, Universe)> {
55    let content = fs::read_to_string(input_path)?;
56    parse_text(content.as_str())
57}
58
59/// Parses a string and returns its format and the grid of cells (Universe)
60pub fn parse_text(input: &str) -> Result<(Format, Universe)> {
61    let input_format = detect_format(input)?;
62    Ok((input_format, formats::parse(input, input_format)?))
63}
64
65/// Convert a file from one format to another
66pub fn convert_file(input_path: &Path, output_path: &Path, force: bool) -> Result<()> {
67    let mut open_options = OpenOptions::new();
68    let open_options = open_options
69        .write(true)
70        .create(force)
71        .truncate(force)
72        .create_new(!force);
73
74    let mut output_file = open_options.open(output_path)?;
75
76    let (input_format, universe) = parse_file(input_path)?;
77
78    let target_format = match input_format {
79        Format::Rle => Format::Plaintext,
80        Format::Plaintext => Format::Rle,
81    };
82
83    formats::write(&universe, &mut output_file, target_format)?;
84
85    Ok(())
86}
87
88/// Removes comment lines from the pattern starting with the specified character
89pub(crate) fn filter_comment_lines(content: &str, starting_char: char) -> Vec<&str> {
90    content
91        .lines()
92        .map(str::trim)
93        .filter(|line| !line.starts_with(starting_char) && !line.is_empty())
94        .collect()
95}