1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::env;
4use std::fs;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Trigram {
9 pub name: String,
10 pub chinese: String,
11 pub unicode: String,
12 pub symbolic: String,
13 pub element: String,
14 pub attribute: String,
15 pub lines: String,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct HexagramJudgment {
20 pub text: String,
21 pub commentary: String,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct HexagramImage {
26 pub text: String,
27 pub commentary: String,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct LineInterpretation {
32 pub text: String,
33 pub comments: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct Hexagram {
38 pub number: u8,
39 pub name: String,
40 pub chinese: String,
41 pub pinyin: String,
42 pub unicode: String,
43 pub binary: String,
44 pub opposite: String,
45 pub upper_trigram: String,
46 pub lower_trigram: String,
47 pub description: String,
48 pub judgment: HexagramJudgment,
49 pub image: HexagramImage,
50 pub lines: HashMap<String, LineInterpretation>,
51}
52
53pub struct IChingData {
54 pub trigrams: HashMap<String, Trigram>,
55 pub hexagrams: HashMap<String, Hexagram>,
56}
57
58impl IChingData {
59 pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
60 Self::load_embedded().or_else(|_| Self::load_from_files())
62 }
63
64 fn load_embedded() -> Result<Self, Box<dyn std::error::Error>> {
66 let trigrams_content = include_str!("../../data/trigrams.json");
68 let hexagrams_content = include_str!("../../data/hexagrams.json");
69
70 let trigrams: HashMap<String, Trigram> = serde_json::from_str(trigrams_content)?;
71 let hexagrams: HashMap<String, Hexagram> = serde_json::from_str(hexagrams_content)?;
72
73 Ok(IChingData {
74 trigrams,
75 hexagrams,
76 })
77 }
78
79 fn load_from_files() -> Result<Self, Box<dyn std::error::Error>> {
81 let data_dir = Self::find_data_directory()?;
82
83 let trigrams_path = data_dir.join("trigrams.json");
85 let trigrams_content = fs::read_to_string(&trigrams_path).map_err(|e| {
86 format!(
87 "Failed to read trigrams.json from {}: {}",
88 trigrams_path.display(),
89 e
90 )
91 })?;
92 let trigrams: HashMap<String, Trigram> = serde_json::from_str(&trigrams_content)?;
93
94 let hexagrams_path = data_dir.join("hexagrams.json");
96 let hexagrams_content = fs::read_to_string(&hexagrams_path).map_err(|e| {
97 format!(
98 "Failed to read hexagrams.json from {}: {}",
99 hexagrams_path.display(),
100 e
101 )
102 })?;
103 let hexagrams: HashMap<String, Hexagram> = serde_json::from_str(&hexagrams_content)?;
104
105 Ok(IChingData {
106 trigrams,
107 hexagrams,
108 })
109 }
110
111 fn find_data_directory() -> Result<PathBuf, Box<dyn std::error::Error>> {
112 let candidates = vec![
114 PathBuf::from("data"),
116 env::current_exe()?.parent().unwrap().join("data"),
118 env::current_exe()?
120 .parent()
121 .unwrap()
122 .parent()
123 .unwrap()
124 .join("data"),
125 env::current_exe()?.parent().unwrap().to_path_buf(),
127 ];
128
129 for candidate in candidates {
130 if candidate.join("trigrams.json").exists() && candidate.join("hexagrams.json").exists()
131 {
132 return Ok(candidate);
133 }
134 }
135
136 Err("Could not find data directory with trigrams.json and hexagrams.json. Please ensure the data files are in one of these locations: ./data/, next to the executable, or in the parent directory.".into())
137 }
138
139 pub fn get_hexagram(&self, number: u8) -> Option<&Hexagram> {
140 self.hexagrams.get(&number.to_string())
141 }
142
143 pub fn get_trigram(&self, name: &str) -> Option<&Trigram> {
144 self.trigrams.get(name)
145 }
146
147 pub fn get_line_interpretation(
148 &self,
149 hexagram_number: u8,
150 line_position: u8,
151 ) -> Option<&LineInterpretation> {
152 self.get_hexagram(hexagram_number)?
153 .lines
154 .get(&line_position.to_string())
155 }
156}