Skip to main content

i_ching/core/
data.rs

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        // Try to load from embedded data first, then fall back to files
61        Self::load_embedded().or_else(|_| Self::load_from_files())
62    }
63
64    /// Load data embedded in the binary at compile time
65    fn load_embedded() -> Result<Self, Box<dyn std::error::Error>> {
66        // Embed the JSON files at compile time
67        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    /// Load data from external files (fallback for development)
80    fn load_from_files() -> Result<Self, Box<dyn std::error::Error>> {
81        let data_dir = Self::find_data_directory()?;
82
83        // Load trigrams
84        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        // Load hexagrams
95        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        // Try multiple locations in order of preference
113        let candidates = vec![
114            // 1. Current working directory
115            PathBuf::from("data"),
116            // 2. Relative to the executable
117            env::current_exe()?.parent().unwrap().join("data"),
118            // 3. Relative to the executable's parent (for development)
119            env::current_exe()?
120                .parent()
121                .unwrap()
122                .parent()
123                .unwrap()
124                .join("data"),
125            // 4. In the same directory as the executable
126            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}