1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
use anyhow::{bail, Context as _, Result};
use std::fmt;
use std::fs::File;
use std::path::Path;
use crate::{Position, Rule};
mod plaintext;
pub use plaintext::{Plaintext, PlaintextBuilder};
mod rle;
pub use rle::{Rle, RleBuilder};
/// Provides several methods for Conway's Game of Life pattern file formats.
pub trait Format: fmt::Display {
/// Returns the rule.
///
/// # Examples
///
/// ```
/// use life_backend::{Format, Rule};
/// use life_backend::format::Rle;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let pattern = "\
/// #N T-tetromino\n\
/// x = 3, y = 2, rule = B3/S23\n\
/// 3o$bo!\n\
/// ";
/// let handler: Box<dyn Format> = Box::new(pattern.parse::<Rle>()?);
/// assert_eq!(handler.rule(), Rule::conways_life());
/// # Ok(())
/// # }
/// ```
///
fn rule(&self) -> Rule;
/// Creates an owning iterator over the series of live cell positions in ascending order.
///
/// # Examples
///
/// ```
/// use life_backend::{Format, Position, Rule};
/// use life_backend::format::Rle;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let pattern = "\
/// #N T-tetromino\n\
/// x = 3, y = 2, rule = B3/S23\n\
/// 3o$bo!\n\
/// ";
/// let handler: Box<dyn Format> = Box::new(pattern.parse::<Rle>()?);
/// assert!(handler.live_cells().eq([Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)]));
/// # Ok(())
/// # }
/// ```
///
fn live_cells(&self) -> Box<dyn Iterator<Item = Position<usize>> + '_>;
}
/// Attempts to open a file with the file format handler specified by the file extension.
///
/// # Examples
///
/// ```
/// use life_backend::format;
/// use life_backend::Rule;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let path = "patterns/rpentomino.cells";
/// let handler = format::open(path)?;
/// assert_eq!(handler.rule(), Rule::conways_life());
/// assert_eq!(handler.live_cells().count(), 5);
/// # Ok(())
/// # }
/// ```
///
/// ```
/// use life_backend::format;
/// use life_backend::Rule;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let path = "patterns/bheptomino.rle";
/// let handler = format::open(path)?;
/// assert_eq!(handler.rule(), Rule::conways_life());
/// assert_eq!(handler.live_cells().count(), 7);
/// # Ok(())
/// # }
/// ```
///
pub fn open<P>(path: P) -> Result<Box<dyn Format>>
where
P: AsRef<Path>,
{
let path_for_display = path.as_ref().to_owned();
let ext = path
.as_ref()
.extension()
.with_context(|| format!("\"{}\" has no extension", path_for_display.display()))?
.to_owned();
let file = File::open(path).with_context(|| format!("Failed to open \"{}\"", path_for_display.display()))?;
let result: Box<dyn Format> = if ext.as_os_str() == "cells" {
Box::new(Plaintext::new(file)?)
} else if ext.as_os_str() == "rle" {
Box::new(Rle::new(file)?)
} else {
bail!("\"{}\" has unknown extension", path_for_display.display());
};
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_no_extension() {
let path = "patterns/rpentomino";
let result = open(path);
assert!(result.is_err());
}
#[test]
fn open_unknown_extension() {
let path = "patterns/rpentomino.unknown";
let result = open(path);
assert!(result.is_err());
}
}