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}