/ src / loader.rs
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  }