forbidden_bands/
lib.rs

1//! A crate for working with old 8-bit string formats
2#![warn(missing_docs)]
3#![warn(unsafe_code)]
4
5use std::{fs::File, io::BufReader, path::Path, sync::RwLock};
6
7// See the notes about optional JSON support in the Cargo.toml file
8// #[cfg(feature = "json")]
9use serde::{Deserialize, Serialize};
10// #[cfg(feature = "json")]
11// use serde_json::{Map, Value};
12
13pub mod config_data;
14pub mod error;
15pub mod petscii;
16
17/// An individual system config
18/// Contains character set mappings
19// #[cfg(feature = "json")]
20#[derive(Clone, Serialize, Deserialize)]
21pub struct SystemConfig {
22    /// Version of this system
23    pub version: String,
24
25    /// character_set_map contains the actual mapping from 8-bit characters
26    /// to Unicode characters and vice-versa
27    ///
28    /// Some "legacy computing" forbidden band crates like the
29    /// Commodore (CBM) PETSCII crate also have intermediate maps and
30    /// tables.  In the case of CBM there is a set of "screen code"
31    /// tables that hold information about the in-memory values of
32    /// characters on the screen.
33    ///
34    /// TODO: I want to get dynamic loading and unloading of modules
35    /// working.  It will require some refactoring with dyn traits and
36    /// serialization / deserialization to make sure everything works.
37    pub character_set_map: petscii::PetsciiConfig,
38}
39
40/// Configuration format
41// #[cfg(feature = "json")]
42#[derive(Serialize, Deserialize)]
43// TODO: system should be dynamic
44pub struct Config {
45    /// Version of the configuration root
46    pub version: String,
47    /// A mapping for PETSCII systems
48    /// TODO: Remove this, individual modules should create their own
49    /// keys, in an approved namespace like good little modules.
50    pub petscii: SystemConfig,
51}
52
53/// The global configuration settings
54/// This is used by default if a custom configuration isn't used
55/// when creating a string.
56// Each string with configuration is a "reader" on the config data
57// structure.  There may be hundreds or thousands floating around.
58// Use a reader-writer lock type to keep track of them.  When the lock
59// count reaches zero, we can modify the config.
60pub static CONFIG: RwLock<Option<Config>> = RwLock::new(None);
61
62/// Trait that defines a set of methods that allow loading and
63/// unloading configuration data
64pub trait Configuration {
65    /// Load the configuration data from the default configuration
66    /// string
67    fn load() -> std::result::Result<Config, error::Error>;
68
69    /// Load configuration from a file
70    fn load_from_file(filename: &str) -> std::result::Result<Config, error::Error>;
71}
72
73impl Configuration for Config {
74    fn load() -> std::result::Result<Config, error::Error> {
75        let json_str = config_data::CONFIG_DATA;
76
77        let config: Config = serde_json::from_str(json_str)?;
78
79        Ok(config)
80    }
81
82    fn load_from_file(filename: &str) -> std::result::Result<Config, error::Error> {
83        // read_to_string is inefficient see [``std::io::BufReader``]
84        let path = Path::new(filename);
85        let file = File::open(path)?;
86        let reader = BufReader::new(file);
87
88        let config: Config = serde_json::from_reader(reader)?;
89
90        Ok(config)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::{Config, Configuration};
97
98    #[test]
99    fn config_works() {
100        let config = Config::load().expect("Error loading config");
101
102        // Test config
103        let key: String = 167.to_string();
104        let res: Option<&serde_json::Value> = config
105            .petscii
106            .character_set_map
107            .c64_petscii_unshifted_codes_to_screen_codes
108            .get(&key);
109        match res.unwrap() {
110            serde_json::Value::Array(v) => {
111                assert_eq!(v.first().unwrap().as_u64().unwrap(), 1);
112                assert_eq!(v.get(1).unwrap().as_u64().unwrap(), 103);
113            }
114            _ => {
115                assert!(false);
116            }
117        }
118
119        let key: String = 103.to_string();
120        let res = config
121            .petscii
122            .character_set_map
123            .c64_screen_codes_set_1_to_unicode_codes
124            .get(&key);
125        assert!(res.is_none());
126
127        // let key: String = 92.to_string();
128        // let res = config.petscii.character_set_map.get(&key);
129        // assert_eq!(res.unwrap(), 163);
130    }
131
132    #[test]
133    fn config_from_file_works() {
134        let config_fn = String::from("data/config.json");
135        let config = Config::load_from_file(&config_fn).expect("error loading config file");
136
137        // Test config
138        let version: String = config.version;
139        assert_eq!(version, "0.2.0");
140
141        // let key: String = 84.to_string();
142        // let res = config.petscii.character_set_map.get(&key);
143        // assert!(res.is_none());
144
145        // let key: String = 92.to_string();
146        // let res = config.petscii.character_set_map.get(&key);
147        // assert_eq!(res.unwrap(), 163);
148    }
149}