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}