life_backend/
format.rs

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