loader.rs
1 use std::path::PathBuf; 2 use std::fs; 3 use std::io::Read; 4 use crate::{Environment, ParseOptions, Error}; 5 6 pub struct Korni; 7 8 impl Korni { 9 #[allow(clippy::should_implement_trait)] 10 pub fn from_str(input: &str) -> KorniBuilder { 11 KorniBuilder::new(input) 12 } 13 14 pub fn from_bytes(input: &[u8]) -> KorniBuilder { 15 KorniBuilder::from_bytes(input) 16 } 17 18 pub fn from_file(path: impl Into<PathBuf>) -> OwnedKorniBuilder { 19 OwnedKorniBuilder::from_file(path) 20 } 21 22 pub fn from_reader(reader: impl Read + 'static) -> OwnedKorniBuilder { 23 OwnedKorniBuilder::from_reader(reader) 24 } 25 26 pub fn find_file(filename: &str) -> Result<OwnedKorniBuilder, Error> { 27 let current = std::env::current_dir() 28 .map_err(|e| Error::Io(format!("Failed to get current directory: {}", e)))?; 29 30 let mut dir = current.as_path(); 31 loop { 32 let file_path = dir.join(filename); 33 if file_path.exists() { 34 return Ok(OwnedKorniBuilder::from_file(file_path)); 35 } 36 if let Some(parent) = dir.parent() { 37 dir = parent; 38 } else { 39 break; 40 } 41 } 42 Err(Error::Io(format!("File '{}' not found in current directory or ancestors", filename))) 43 } 44 } 45 46 #[derive(Debug)] 47 enum Source<'a> { 48 Str(&'a str), 49 Bytes(&'a [u8]), 50 } 51 52 pub struct KorniBuilder<'a> { 53 source: Source<'a>, 54 options: ParseOptions, 55 } 56 57 impl<'a> KorniBuilder<'a> { 58 pub fn new(source_str: &'a str) -> Self { 59 Self { 60 source: Source::Str(source_str), 61 options: ParseOptions::default(), 62 } 63 } 64 65 pub fn from_bytes(bytes: &'a [u8]) -> Self { 66 Self { 67 source: Source::Bytes(bytes), 68 options: ParseOptions::default(), 69 } 70 } 71 72 pub fn preserve_comments(mut self) -> Self { 73 self.options.include_comments = true; 74 self 75 } 76 77 pub fn track_positions(mut self) -> Self { 78 self.options.track_positions = true; 79 self 80 } 81 82 pub fn parse(self) -> Result<Environment<'a>, Error> { 83 let input = match self.source { 84 Source::Str(s) => s, 85 Source::Bytes(b) => std::str::from_utf8(b).map_err(|_| Error::InvalidUtf8 { 86 offset: 0, 87 reason: "Invalid UTF-8 sequence".into(), 88 })?, 89 }; 90 91 let entries = crate::parse_with_options(input, self.options); 92 Ok(Environment::from_entries(entries)) 93 } 94 } 95 96 pub struct OwnedKorniBuilder { 97 path: Option<PathBuf>, 98 reader: Option<Box<dyn Read>>, 99 options: ParseOptions, 100 } 101 102 impl OwnedKorniBuilder { 103 pub fn from_file(path: impl Into<PathBuf>) -> Self { 104 Self { 105 path: Some(path.into()), 106 reader: None, 107 options: ParseOptions::default(), 108 } 109 } 110 111 pub fn from_reader(reader: impl Read + 'static) -> Self { 112 Self { 113 path: None, 114 reader: Some(Box::new(reader)), 115 options: ParseOptions::default(), 116 } 117 } 118 119 pub fn preserve_comments(mut self) -> Self { 120 self.options.include_comments = true; 121 self 122 } 123 124 pub fn track_positions(mut self) -> Self { 125 self.options.track_positions = true; 126 self 127 } 128 129 pub fn parse(self) -> Result<Environment<'static>, Error> { 130 let content = if let Some(path) = self.path { 131 fs::read_to_string(&path) 132 .map_err(|e| Error::Io(format!("Failed to read file {}: {}", path.display(), e)))? 133 } else if let Some(mut reader) = self.reader { 134 let mut s = String::new(); 135 reader.read_to_string(&mut s) 136 .map_err(|e| Error::Io(format!("Failed to read from reader: {}", e)))?; 137 s 138 } else { 139 return Err(Error::Generic { offset: 0, message: "No source provided".into() }); 140 }; 141 142 let entries = crate::parse_with_options(&content, self.options); 143 let env_local = Environment::from_entries(entries); 144 Ok(env_local.into_owned()) 145 } 146 }