Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
use clap::Arg;
use std::os::unix::process::CommandExt;
use std::process;
macro_rules! die {
($fmt:expr) => ({
eprintln!($fmt);
process::exit(1);
});
($fmt:expr, $($arg:tt)*) => ({
eprintln!($fmt, $($arg)*);
process::exit(1);
});
}
fn make_command(name: &str, args: Vec<&str>) -> process::Command {
let mut command = process::Command::new(name);
for arg in args {
command.arg(arg);
}
return command;
}
fn main() {
let matches = clap::Command::new("dotenvy")
.about("Run a command using the environment in a .env file")
.override_usage("dotenvy <COMMAND> [ARGS]...")
.allow_external_subcommands(true)
.arg_required_else_help(true)
.arg(
Arg::new("FILE")
.short('f')
.long("file")
.takes_value(true)
.help("Use a specific .env file (defaults to .env)"),
)
.get_matches();
match matches.value_of("FILE") {
None => dotenvy::dotenv(),
Some(file) => dotenvy::from_filename(file),
}
.unwrap_or_else(|e| die!("error: failed to load environment: {}", e));
let mut command = match matches.subcommand() {
Some((name, matches)) => {
let args = matches
.values_of("")
.map(|v| v.collect())
.unwrap_or(Vec::new());
make_command(name, args)
}
None => die!("error: missing required argument <COMMAND>"),
};
if cfg!(target_os = "windows") {
match command.spawn().and_then(|mut child| child.wait()) {
Ok(status) => process::exit(status.code().unwrap_or(1)),
Err(error) => die!("fatal: {}", error),
};
} else {
let error = command.exec();
die!("fatal: {}", error);
};
}
+122
View File
@@ -0,0 +1,122 @@
use std::env;
use std::error;
use std::fmt;
use std::io;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
LineParse(String, usize),
Io(io::Error),
EnvVar(env::VarError),
}
impl Error {
pub fn not_found(&self) -> bool {
if let Error::Io(ref io_error) = *self {
return io_error.kind() == io::ErrorKind::NotFound;
}
false
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Io(err) => Some(err),
Error::EnvVar(err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::Io(err) => write!(fmt, "{}", err),
Error::EnvVar(err) => write!(fmt, "{}", err),
Error::LineParse(line, error_index) => write!(
fmt,
"Error parsing line: '{}', error at line index: {}",
line, error_index
),
}
}
}
#[cfg(test)]
mod test {
use std::env;
use std::error::Error as StdError;
use std::io;
use super::*;
#[test]
fn test_io_error_source() {
let err = Error::Io(io::ErrorKind::PermissionDenied.into());
let io_err = err.source().unwrap().downcast_ref::<io::Error>().unwrap();
assert_eq!(io::ErrorKind::PermissionDenied, io_err.kind());
}
#[test]
fn test_envvar_error_source() {
let err = Error::EnvVar(env::VarError::NotPresent);
let var_err = err
.source()
.unwrap()
.downcast_ref::<env::VarError>()
.unwrap();
assert_eq!(&env::VarError::NotPresent, var_err);
}
#[test]
fn test_lineparse_error_source() {
let err = Error::LineParse("test line".to_string(), 2);
assert!(err.source().is_none());
}
#[test]
fn test_error_not_found_true() {
let err = Error::Io(io::ErrorKind::NotFound.into());
assert!(err.not_found());
}
#[test]
fn test_error_not_found_false() {
let err = Error::Io(io::ErrorKind::PermissionDenied.into());
assert!(!err.not_found());
}
#[test]
fn test_io_error_display() {
let err = Error::Io(io::ErrorKind::PermissionDenied.into());
let io_err: io::Error = io::ErrorKind::PermissionDenied.into();
let err_desc = format!("{}", err);
let io_err_desc = format!("{}", io_err);
assert_eq!(io_err_desc, err_desc);
}
#[test]
fn test_envvar_error_display() {
let err = Error::EnvVar(env::VarError::NotPresent);
let var_err = env::VarError::NotPresent;
let err_desc = format!("{}", err);
let var_err_desc = format!("{}", var_err);
assert_eq!(var_err_desc, err_desc);
}
#[test]
fn test_lineparse_error_display() {
let err = Error::LineParse("test line".to_string(), 2);
let err_desc = format!("{}", err);
assert_eq!(
"Error parsing line: 'test line', error at line index: 2",
err_desc
);
}
}
+57
View File
@@ -0,0 +1,57 @@
use std::fs::File;
use std::path::{Path, PathBuf};
use std::{env, fs, io};
use crate::errors::*;
use crate::iter::Iter;
pub struct Finder<'a> {
filename: &'a Path,
}
impl<'a> Finder<'a> {
pub fn new() -> Self {
Finder {
filename: Path::new(".env"),
}
}
pub fn filename(mut self, filename: &'a Path) -> Self {
self.filename = filename;
self
}
pub fn find(self) -> Result<(PathBuf, Iter<File>)> {
let path = find(&env::current_dir().map_err(Error::Io)?, self.filename)?;
let file = File::open(&path).map_err(Error::Io)?;
let iter = Iter::new(file);
Ok((path, iter))
}
}
/// Searches for `filename` in `directory` and parent directories until found or root is reached.
pub fn find(directory: &Path, filename: &Path) -> Result<PathBuf> {
let candidate = directory.join(filename);
match fs::metadata(&candidate) {
Ok(metadata) => {
if metadata.is_file() {
return Ok(candidate);
}
}
Err(error) => {
if error.kind() != io::ErrorKind::NotFound {
return Err(Error::Io(error));
}
}
}
if let Some(parent) = directory.parent() {
find(parent, filename)
} else {
Err(Error::Io(io::Error::new(
io::ErrorKind::NotFound,
"path not found",
)))
}
}
+199
View File
@@ -0,0 +1,199 @@
use std::collections::HashMap;
use std::env;
use std::io::prelude::*;
use std::io::BufReader;
use crate::errors::*;
use crate::parse;
pub struct Iter<R> {
lines: QuotedLines<BufReader<R>>,
substitution_data: HashMap<String, Option<String>>,
}
impl<R: Read> Iter<R> {
pub fn new(reader: R) -> Iter<R> {
Iter {
lines: QuotedLines {
buf: BufReader::new(reader),
},
substitution_data: HashMap::new(),
}
}
/// Loads all variables found in the `reader` into the environment,
/// preserving any existing environment variables of the same name.
///
/// If a variable is specified multiple times within the reader's data,
/// then the first occurrence is applied.
pub fn load(mut self) -> Result<()> {
self.remove_bom()?;
for item in self {
let (key, value) = item?;
if env::var(&key).is_err() {
env::set_var(&key, value);
}
}
Ok(())
}
/// Loads all variables found in the `reader` into the environment,
/// overriding any existing environment variables of the same name.
///
/// If a variable is specified multiple times within the reader's data,
/// then the last occurrence is applied.
pub fn load_override(mut self) -> Result<()> {
self.remove_bom()?;
for item in self {
let (key, value) = item?;
env::set_var(key, value);
}
Ok(())
}
fn remove_bom(&mut self) -> Result<()> {
let buffer = self.lines.buf.fill_buf().map_err(Error::Io)?;
// https://www.compart.com/en/unicode/U+FEFF
if buffer.starts_with(&[0xEF, 0xBB, 0xBF]) {
// remove the BOM from the bufreader
self.lines.buf.consume(3);
}
Ok(())
}
}
struct QuotedLines<B> {
buf: B,
}
enum ParseState {
Complete,
Escape,
StrongOpen,
StrongOpenEscape,
WeakOpen,
WeakOpenEscape,
Comment,
WhiteSpace,
}
fn eval_end_state(prev_state: ParseState, buf: &str) -> (usize, ParseState) {
let mut cur_state = prev_state;
let mut cur_pos: usize = 0;
for (pos, c) in buf.char_indices() {
cur_pos = pos;
cur_state = match cur_state {
ParseState::WhiteSpace => match c {
'#' => return (cur_pos, ParseState::Comment),
'\\' => ParseState::Escape,
'"' => ParseState::WeakOpen,
'\'' => ParseState::StrongOpen,
_ => ParseState::Complete,
},
ParseState::Escape => ParseState::Complete,
ParseState::Complete => match c {
c if c.is_whitespace() && c != '\n' && c != '\r' => ParseState::WhiteSpace,
'\\' => ParseState::Escape,
'"' => ParseState::WeakOpen,
'\'' => ParseState::StrongOpen,
_ => ParseState::Complete,
},
ParseState::WeakOpen => match c {
'\\' => ParseState::WeakOpenEscape,
'"' => ParseState::Complete,
_ => ParseState::WeakOpen,
},
ParseState::WeakOpenEscape => ParseState::WeakOpen,
ParseState::StrongOpen => match c {
'\\' => ParseState::StrongOpenEscape,
'\'' => ParseState::Complete,
_ => ParseState::StrongOpen,
},
ParseState::StrongOpenEscape => ParseState::StrongOpen,
// Comments last the entire line.
ParseState::Comment => panic!("should have returned early"),
};
}
(cur_pos, cur_state)
}
impl<B: BufRead> Iterator for QuotedLines<B> {
type Item = Result<String>;
fn next(&mut self) -> Option<Result<String>> {
let mut buf = String::new();
let mut cur_state = ParseState::Complete;
let mut buf_pos;
let mut cur_pos;
loop {
buf_pos = buf.len();
match self.buf.read_line(&mut buf) {
Ok(0) => match cur_state {
ParseState::Complete => return None,
_ => {
let len = buf.len();
return Some(Err(Error::LineParse(buf, len)));
}
},
Ok(_n) => {
// Skip lines which start with a # before iteration
// This optimizes parsing a bit.
if buf.trim_start().starts_with('#') {
return Some(Ok(String::with_capacity(0)));
}
let result = eval_end_state(cur_state, &buf[buf_pos..]);
cur_pos = result.0;
cur_state = result.1;
match cur_state {
ParseState::Complete => {
if buf.ends_with('\n') {
buf.pop();
if buf.ends_with('\r') {
buf.pop();
}
}
return Some(Ok(buf));
}
ParseState::Escape
| ParseState::StrongOpen
| ParseState::StrongOpenEscape
| ParseState::WeakOpen
| ParseState::WeakOpenEscape
| ParseState::WhiteSpace => {}
ParseState::Comment => {
buf.truncate(buf_pos + cur_pos);
return Some(Ok(buf));
}
}
}
Err(e) => return Some(Err(Error::Io(e))),
}
}
}
}
impl<R: Read> Iterator for Iter<R> {
type Item = Result<(String, String)>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let line = match self.lines.next() {
Some(Ok(line)) => line,
Some(Err(err)) => return Some(Err(err)),
None => return None,
};
match parse::parse_line(&line, &mut self.substitution_data) {
Ok(Some(result)) => return Some(Ok(result)),
Ok(None) => {}
Err(err) => return Some(Err(err)),
}
}
}
}
+373
View File
@@ -0,0 +1,373 @@
//! [`dotenv`]: https://crates.io/crates/dotenv
//! A well-maintained fork of the [`dotenv`] crate
//!
//! This library loads environment variables from a *.env* file. This is convenient for dev environments.
mod errors;
mod find;
mod iter;
mod parse;
use std::env::{self, Vars};
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Once;
pub use crate::errors::*;
use crate::find::Finder;
pub use crate::iter::Iter;
static START: Once = Once::new();
/// Gets the value for an environment variable.
///
/// The value is `Ok(s)` if the environment variable is present and valid unicode.
///
/// Note: this function gets values from any visible environment variable key,
/// regardless of whether a *.env* file was loaded.
///
/// # Examples:
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let value = dotenvy::var("HOME")?;
/// println!("{}", value); // prints `/home/foo`
/// # Ok(())
/// # }
/// ```
pub fn var<K: AsRef<OsStr>>(key: K) -> Result<String> {
START.call_once(|| {
dotenv().ok();
});
env::var(key).map_err(Error::EnvVar)
}
/// Returns an iterator of `(key, value)` pairs for all environment variables of the current process.
/// The returned iterator contains a snapshot of the process's environment variables at the time of invocation. Modifications to environment variables afterwards will not be reflected.
///
/// # Examples:
///
/// ```no_run
/// use std::io;
///
/// let result: Vec<(String, String)> = dotenvy::vars().collect();
/// ```
pub fn vars() -> Vars {
START.call_once(|| {
dotenv().ok();
});
env::vars()
}
/// Loads environment variables from the specified path.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env* file, ignoring variables
/// already existing in the environment, then use [`from_path_override`] instead.
///
/// # Examples
///
/// ```no_run
/// use std::path::Path;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::from_path(Path::new("path/to/.env"))?;
/// # Ok(())
/// # }
/// ```
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<()> {
let iter = Iter::new(File::open(path).map_err(Error::Io)?);
iter.load()
}
/// Loads environment variables from the specified path,
/// overriding existing environment variables.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`from_path`] instead.
///
/// # Examples
///
/// ```no_run
/// use std::path::Path;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::from_path_override(Path::new("path/to/.env"))?;
/// # Ok(())
/// # }
/// ```
pub fn from_path_override<P: AsRef<Path>>(path: P) -> Result<()> {
let iter = Iter::new(File::open(path).map_err(Error::Io)?);
iter.load_override()
}
/// Returns an iterator over environment variables from the specified path.
///
/// # Examples
///
/// ```no_run
/// use std::path::Path;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// for item in dotenvy::from_path_iter(Path::new("path/to/.env"))? {
/// let (key, val) = item?;
/// println!("{}={}", key, val);
/// }
/// # Ok(())
/// # }
/// ```
pub fn from_path_iter<P: AsRef<Path>>(path: P) -> Result<Iter<File>> {
Ok(Iter::new(File::open(path).map_err(Error::Io)?))
}
/// Loads environment variables from the specified file.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env* file, ignoring variables
/// already existing in the environment, then use [`from_filename_override`] instead.
///
/// # Examples
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::from_filename("custom.env")?;
/// # Ok(())
/// # }
/// ```
///
/// It is also possible to load from a typical *.env* file like so. However, using [`dotenv`] is preferred.
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::from_filename(".env")?;
/// # Ok(())
/// # }
/// ```
pub fn from_filename<P: AsRef<Path>>(filename: P) -> Result<PathBuf> {
let (path, iter) = Finder::new().filename(filename.as_ref()).find()?;
iter.load()?;
Ok(path)
}
/// Loads environment variables from the specified file,
/// overriding existing environment variables.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`from_filename`] instead.
///
/// # Examples
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::from_filename_override("custom.env")?;
/// # Ok(())
/// # }
/// ```
///
/// It is also possible to load from a typical *.env* file like so. However, using [`dotenv_override`] is preferred.
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::from_filename_override(".env")?;
/// # Ok(())
/// # }
/// ```
pub fn from_filename_override<P: AsRef<Path>>(filename: P) -> Result<PathBuf> {
let (path, iter) = Finder::new().filename(filename.as_ref()).find()?;
iter.load_override()?;
Ok(path)
}
/// Returns an iterator over environment variables from the specified file.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// for item in dotenvy::from_filename_iter("custom.env")? {
/// let (key, val) = item?;
/// println!("{}={}", key, val);
/// }
/// # Ok(())
/// # }
/// ```
pub fn from_filename_iter<P: AsRef<Path>>(filename: P) -> Result<Iter<File>> {
let (_, iter) = Finder::new().filename(filename.as_ref()).find()?;
Ok(iter)
}
/// Loads environment variables from [`io::Read`](std::io::Read).
///
/// This is useful for loading environment variables from IPC or the network.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your `reader`,
/// the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your `reader`, ignoring variables
/// already existing in the environment, then use [`from_read_override`] instead.
///
/// For regular files, use [`from_path`] or [`from_filename`].
///
/// # Examples
///
/// ```no_run
/// # #![cfg(unix)]
/// use std::io::Read;
/// use std::os::unix::net::UnixStream;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut stream = UnixStream::connect("/some/socket")?;
/// dotenvy::from_read(stream)?;
/// # Ok(())
/// # }
/// ```
pub fn from_read<R: io::Read>(reader: R) -> Result<()> {
let iter = Iter::new(reader);
iter.load()?;
Ok(())
}
/// Loads environment variables from [`io::Read`](std::io::Read),
/// overriding existing environment variables.
///
/// This is useful for loading environment variables from IPC or the network.
///
/// Where multiple declarations for the same environment variable exist in your `reader`, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`from_read`] instead.
///
/// For regular files, use [`from_path_override`] or [`from_filename_override`].
///
/// # Examples
/// ```no_run
/// # #![cfg(unix)]
/// use std::io::Read;
/// use std::os::unix::net::UnixStream;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut stream = UnixStream::connect("/some/socket")?;
/// dotenvy::from_read_override(stream)?;
/// # Ok(())
/// # }
/// ```
pub fn from_read_override<R: io::Read>(reader: R) -> Result<()> {
let iter = Iter::new(reader);
iter.load_override()?;
Ok(())
}
/// Returns an iterator over environment variables from [`io::Read`](std::io::Read).
///
/// # Examples
///
/// ```no_run
/// # #![cfg(unix)]
/// use std::io::Read;
/// use std::os::unix::net::UnixStream;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let mut stream = UnixStream::connect("/some/socket")?;
///
/// for item in dotenvy::from_read_iter(stream) {
/// let (key, val) = item?;
/// println!("{}={}", key, val);
/// }
/// # Ok(())
/// # }
/// ```
pub fn from_read_iter<R: io::Read>(reader: R) -> Iter<R> {
Iter::new(reader)
}
/// Loads the *.env* file from the current directory or parents. This is typically what you want.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved.
///
/// Where multiple declarations for the same environment variable exist in your *.env*
/// file, the *first one* is applied.
///
/// If you wish to ensure all variables are loaded from your *.env* file, ignoring variables
/// already existing in the environment, then use [`dotenv_override`] instead.
///
/// An error will be returned if the file is not found.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::dotenv()?;
/// # Ok(())
/// # }
/// ```
pub fn dotenv() -> Result<PathBuf> {
let (path, iter) = Finder::new().find()?;
iter.load()?;
Ok(path)
}
/// Loads all variables found in the `reader` into the environment,
/// overriding any existing environment variables of the same name.
///
/// Where multiple declarations for the same environment variable exist in your *.env* file, the
/// *last one* is applied.
///
/// If you want the existing environment to take precedence,
/// or if you want to be able to override environment variables on the command line,
/// then use [`dotenv`] instead.
///
/// # Examples
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// dotenvy::dotenv_override()?;
/// # Ok(())
/// # }
/// ```
pub fn dotenv_override() -> Result<PathBuf> {
let (path, iter) = Finder::new().find()?;
iter.load_override()?;
Ok(path)
}
/// Returns an iterator over environment variables.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// for item in dotenvy::dotenv_iter()? {
/// let (key, val) = item?;
/// println!("{}={}", key, val);
/// }
/// # Ok(())
/// # }
/// ```
pub fn dotenv_iter() -> Result<iter::Iter<File>> {
let (_, iter) = Finder::new().find()?;
Ok(iter)
}
+653
View File
@@ -0,0 +1,653 @@
use std::collections::HashMap;
use std::env;
use crate::errors::*;
// for readability's sake
pub type ParsedLine = Result<Option<(String, String)>>;
pub fn parse_line(
line: &str,
substitution_data: &mut HashMap<String, Option<String>>,
) -> ParsedLine {
let mut parser = LineParser::new(line, substitution_data);
parser.parse_line()
}
struct LineParser<'a> {
original_line: &'a str,
substitution_data: &'a mut HashMap<String, Option<String>>,
line: &'a str,
pos: usize,
}
impl<'a> LineParser<'a> {
fn new(
line: &'a str,
substitution_data: &'a mut HashMap<String, Option<String>>,
) -> LineParser<'a> {
LineParser {
original_line: line,
substitution_data,
line: line.trim_end(), // we dont want trailing whitespace
pos: 0,
}
}
fn err(&self) -> Error {
Error::LineParse(self.original_line.into(), self.pos)
}
fn parse_line(&mut self) -> ParsedLine {
self.skip_whitespace();
// if its an empty line or a comment, skip it
if self.line.is_empty() || self.line.starts_with('#') {
return Ok(None);
}
let mut key = self.parse_key()?;
self.skip_whitespace();
// export can be either an optional prefix or a key itself
if key == "export" {
// here we check for an optional `=`, below we throw directly when its not found.
if self.expect_equal().is_err() {
key = self.parse_key()?;
self.skip_whitespace();
self.expect_equal()?;
}
} else {
self.expect_equal()?;
}
self.skip_whitespace();
if self.line.is_empty() || self.line.starts_with('#') {
self.substitution_data.insert(key.clone(), None);
return Ok(Some((key, String::new())));
}
let parsed_value = parse_value(self.line, self.substitution_data)?;
self.substitution_data
.insert(key.clone(), Some(parsed_value.clone()));
Ok(Some((key, parsed_value)))
}
fn parse_key(&mut self) -> Result<String> {
if !self
.line
.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
{
return Err(self.err());
}
let index = match self
.line
.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_' || c == '.'))
{
Some(index) => index,
None => self.line.len(),
};
self.pos += index;
let key = String::from(&self.line[..index]);
self.line = &self.line[index..];
Ok(key)
}
fn expect_equal(&mut self) -> Result<()> {
if !self.line.starts_with('=') {
return Err(self.err());
}
self.line = &self.line[1..];
self.pos += 1;
Ok(())
}
fn skip_whitespace(&mut self) {
if let Some(index) = self.line.find(|c: char| !c.is_whitespace()) {
self.pos += index;
self.line = &self.line[index..];
} else {
self.pos += self.line.len();
self.line = "";
}
}
}
#[derive(Eq, PartialEq)]
enum SubstitutionMode {
None,
Block,
EscapedBlock,
}
fn parse_value(
input: &str,
substitution_data: &mut HashMap<String, Option<String>>,
) -> Result<String> {
let mut strong_quote = false; // '
let mut weak_quote = false; // "
let mut escaped = false;
let mut expecting_end = false;
//FIXME can this be done without yet another allocation per line?
let mut output = String::new();
let mut substitution_mode = SubstitutionMode::None;
let mut substitution_name = String::new();
for (index, c) in input.chars().enumerate() {
//the regex _should_ already trim whitespace off the end
//expecting_end is meant to permit: k=v #comment
//without affecting: k=v#comment
//and throwing on: k=v w
if expecting_end {
if c == ' ' || c == '\t' {
continue;
} else if c == '#' {
break;
} else {
return Err(Error::LineParse(input.to_owned(), index));
}
} else if escaped {
//TODO I tried handling literal \r but various issues
//imo not worth worrying about until there's a use case
//(actually handling backslash 0x10 would be a whole other matter)
//then there's \v \f bell hex... etc
match c {
'\\' | '\'' | '"' | '$' | ' ' => output.push(c),
'n' => output.push('\n'), // handle \n case
_ => {
return Err(Error::LineParse(input.to_owned(), index));
}
}
escaped = false;
} else if strong_quote {
if c == '\'' {
strong_quote = false;
} else {
output.push(c);
}
} else if substitution_mode != SubstitutionMode::None {
if c.is_alphanumeric() {
substitution_name.push(c);
} else {
match substitution_mode {
SubstitutionMode::None => unreachable!(),
SubstitutionMode::Block => {
if c == '{' && substitution_name.is_empty() {
substitution_mode = SubstitutionMode::EscapedBlock;
} else {
apply_substitution(
substitution_data,
&substitution_name.drain(..).collect::<String>(),
&mut output,
);
if c == '$' {
substitution_mode = if !strong_quote && !escaped {
SubstitutionMode::Block
} else {
SubstitutionMode::None
}
} else {
substitution_mode = SubstitutionMode::None;
output.push(c);
}
}
}
SubstitutionMode::EscapedBlock => {
if c == '}' {
substitution_mode = SubstitutionMode::None;
apply_substitution(
substitution_data,
&substitution_name.drain(..).collect::<String>(),
&mut output,
);
} else {
substitution_name.push(c);
}
}
}
}
} else if c == '$' {
substitution_mode = if !strong_quote && !escaped {
SubstitutionMode::Block
} else {
SubstitutionMode::None
}
} else if weak_quote {
if c == '"' {
weak_quote = false;
} else if c == '\\' {
escaped = true;
} else {
output.push(c);
}
} else if c == '\'' {
strong_quote = true;
} else if c == '"' {
weak_quote = true;
} else if c == '\\' {
escaped = true;
} else if c == ' ' || c == '\t' {
expecting_end = true;
} else {
output.push(c);
}
}
//XXX also fail if escaped? or...
if substitution_mode == SubstitutionMode::EscapedBlock || strong_quote || weak_quote {
let value_length = input.len();
Err(Error::LineParse(
input.to_owned(),
if value_length == 0 {
0
} else {
value_length - 1
},
))
} else {
apply_substitution(
substitution_data,
&substitution_name.drain(..).collect::<String>(),
&mut output,
);
Ok(output)
}
}
fn apply_substitution(
substitution_data: &mut HashMap<String, Option<String>>,
substitution_name: &str,
output: &mut String,
) {
if let Ok(environment_value) = env::var(substitution_name) {
output.push_str(&environment_value);
} else {
let stored_value = substitution_data
.get(substitution_name)
.unwrap_or(&None)
.to_owned();
output.push_str(&stored_value.unwrap_or_default());
};
}
#[cfg(test)]
mod test {
use crate::iter::Iter;
use super::*;
#[test]
fn test_parse_line_env() {
// Note 5 spaces after 'KEY8=' below
let actual_iter = Iter::new(
r#"
KEY=1
KEY2="2"
KEY3='3'
KEY4='fo ur'
KEY5="fi ve"
KEY6=s\ ix
KEY7=
KEY8=
KEY9= # foo
KEY10 ="whitespace before ="
KEY11= "whitespace after ="
export="export as key"
export SHELL_LOVER=1
"#
.as_bytes(),
);
let expected_iter = vec![
("KEY", "1"),
("KEY2", "2"),
("KEY3", "3"),
("KEY4", "fo ur"),
("KEY5", "fi ve"),
("KEY6", "s ix"),
("KEY7", ""),
("KEY8", ""),
("KEY9", ""),
("KEY10", "whitespace before ="),
("KEY11", "whitespace after ="),
("export", "export as key"),
("SHELL_LOVER", "1"),
]
.into_iter()
.map(|(key, value)| (key.to_string(), value.to_string()));
let mut count = 0;
for (expected, actual) in expected_iter.zip(actual_iter) {
assert!(actual.is_ok());
assert_eq!(expected, actual.unwrap());
count += 1;
}
assert_eq!(count, 13);
}
#[test]
fn test_parse_line_comment() {
let result: Result<Vec<(String, String)>> = Iter::new(
r#"
# foo=bar
# "#
.as_bytes(),
)
.collect();
assert!(result.unwrap().is_empty());
}
#[test]
fn test_parse_line_invalid() {
// Note 4 spaces after 'invalid' below
let actual_iter = Iter::new(
r#"
invalid
very bacon = yes indeed
=value"#
.as_bytes(),
);
let mut count = 0;
for actual in actual_iter {
assert!(actual.is_err());
count += 1;
}
assert_eq!(count, 3);
}
#[test]
fn test_parse_value_escapes() {
let actual_iter = Iter::new(
r#"
KEY=my\ cool\ value
KEY2=\$sweet
KEY3="awesome stuff \"mang\""
KEY4='sweet $\fgs'\''fds'
KEY5="'\"yay\\"\ "stuff"
KEY6="lol" #well you see when I say lol wh
KEY7="line 1\nline 2"
"#
.as_bytes(),
);
let expected_iter = vec![
("KEY", r#"my cool value"#),
("KEY2", r#"$sweet"#),
("KEY3", r#"awesome stuff "mang""#),
("KEY4", r#"sweet $\fgs'fds"#),
("KEY5", r#"'"yay\ stuff"#),
("KEY6", "lol"),
("KEY7", "line 1\nline 2"),
]
.into_iter()
.map(|(key, value)| (key.to_string(), value.to_string()));
for (expected, actual) in expected_iter.zip(actual_iter) {
assert!(actual.is_ok());
assert_eq!(expected, actual.unwrap());
}
}
#[test]
fn test_parse_value_escapes_invalid() {
let actual_iter = Iter::new(
r#"
KEY=my uncool value
KEY2="why
KEY3='please stop''
KEY4=h\8u
"#
.as_bytes(),
);
for actual in actual_iter {
assert!(actual.is_err());
}
}
}
#[cfg(test)]
mod variable_substitution_tests {
use crate::iter::Iter;
use std::env;
fn assert_parsed_string(input_string: &str, expected_parse_result: Vec<(&str, &str)>) {
let actual_iter = Iter::new(input_string.as_bytes());
let expected_count = &expected_parse_result.len();
let expected_iter = expected_parse_result
.into_iter()
.map(|(key, value)| (key.to_string(), value.to_string()));
let mut count = 0;
for (expected, actual) in expected_iter.zip(actual_iter) {
assert!(actual.is_ok());
assert_eq!(expected, actual.unwrap());
count += 1;
}
assert_eq!(count, *expected_count);
}
#[test]
fn variable_in_parenthesis_surrounded_by_quotes() {
assert_parsed_string(
r#"
KEY=test
KEY1="${KEY}"
"#,
vec![("KEY", "test"), ("KEY1", "test")],
);
}
#[test]
fn substitute_undefined_variables_to_empty_string() {
assert_parsed_string(r#"KEY=">$KEY1<>${KEY2}<""#, vec![("KEY", "><><")]);
}
#[test]
fn do_not_substitute_variables_with_dollar_escaped() {
assert_parsed_string(
"KEY=>\\$KEY1<>\\${KEY2}<",
vec![("KEY", ">$KEY1<>${KEY2}<")],
);
}
#[test]
fn do_not_substitute_variables_in_weak_quotes_with_dollar_escaped() {
assert_parsed_string(
r#"KEY=">\$KEY1<>\${KEY2}<""#,
vec![("KEY", ">$KEY1<>${KEY2}<")],
);
}
#[test]
fn do_not_substitute_variables_in_strong_quotes() {
assert_parsed_string("KEY='>${KEY1}<>$KEY2<'", vec![("KEY", ">${KEY1}<>$KEY2<")]);
}
#[test]
fn same_variable_reused() {
assert_parsed_string(
r#"
KEY=VALUE
KEY1=$KEY$KEY
"#,
vec![("KEY", "VALUE"), ("KEY1", "VALUEVALUE")],
);
}
#[test]
fn with_dot() {
assert_parsed_string(
r#"
KEY.Value=VALUE
"#,
vec![("KEY.Value", "VALUE")],
);
}
#[test]
fn recursive_substitution() {
assert_parsed_string(
r#"
KEY=${KEY1}+KEY_VALUE
KEY1=${KEY}+KEY1_VALUE
"#,
vec![("KEY", "+KEY_VALUE"), ("KEY1", "+KEY_VALUE+KEY1_VALUE")],
);
}
#[test]
fn variable_without_parenthesis_is_substituted_before_separators() {
assert_parsed_string(
r#"
KEY1=test_user
KEY1_1=test_user_with_separator
KEY=">$KEY1_1<>$KEY1}<>$KEY1{<"
"#,
vec![
("KEY1", "test_user"),
("KEY1_1", "test_user_with_separator"),
("KEY", ">test_user_1<>test_user}<>test_user{<"),
],
);
}
#[test]
fn substitute_variable_from_env_variable() {
env::set_var("KEY11", "test_user_env");
assert_parsed_string(r#"KEY=">${KEY11}<""#, vec![("KEY", ">test_user_env<")]);
}
#[test]
fn substitute_variable_env_variable_overrides_dotenv_in_substitution() {
env::set_var("KEY11", "test_user_env");
assert_parsed_string(
r#"
KEY11=test_user
KEY=">${KEY11}<"
"#,
vec![("KEY11", "test_user"), ("KEY", ">test_user_env<")],
);
}
#[test]
fn consequent_substitutions() {
assert_parsed_string(
r#"
KEY1=test_user
KEY2=$KEY1_2
KEY=>${KEY1}<>${KEY2}<
"#,
vec![
("KEY1", "test_user"),
("KEY2", "test_user_2"),
("KEY", ">test_user<>test_user_2<"),
],
);
}
#[test]
fn consequent_substitutions_with_one_missing() {
assert_parsed_string(
r#"
KEY2=$KEY1_2
KEY=>${KEY1}<>${KEY2}<
"#,
vec![("KEY2", "_2"), ("KEY", "><>_2<")],
);
}
}
#[cfg(test)]
mod error_tests {
use crate::errors::Error::LineParse;
use crate::iter::Iter;
#[test]
fn should_not_parse_unfinished_substitutions() {
let wrong_value = ">${KEY{<";
let parsed_values: Vec<_> = Iter::new(
format!(
r#"
KEY=VALUE
KEY1={}
"#,
wrong_value
)
.as_bytes(),
)
.collect();
assert_eq!(parsed_values.len(), 2);
if let Ok(first_line) = &parsed_values[0] {
assert_eq!(first_line, &(String::from("KEY"), String::from("VALUE")))
} else {
panic!("Expected the first value to be parsed")
}
if let Err(LineParse(second_value, index)) = &parsed_values[1] {
assert_eq!(second_value, wrong_value);
assert_eq!(*index, wrong_value.len() - 1)
} else {
panic!("Expected the second value not to be parsed")
}
}
#[test]
fn should_not_allow_dot_as_first_character_of_key() {
let wrong_key_value = ".Key=VALUE";
let parsed_values: Vec<_> = Iter::new(wrong_key_value.as_bytes()).collect();
assert_eq!(parsed_values.len(), 1);
if let Err(LineParse(second_value, index)) = &parsed_values[0] {
assert_eq!(second_value, wrong_key_value);
assert_eq!(*index, 0)
} else {
panic!("Expected the second value not to be parsed")
}
}
#[test]
fn should_not_parse_illegal_format() {
let wrong_format = r"<><><>";
let parsed_values: Vec<_> = Iter::new(wrong_format.as_bytes()).collect();
assert_eq!(parsed_values.len(), 1);
if let Err(LineParse(wrong_value, index)) = &parsed_values[0] {
assert_eq!(wrong_value, wrong_format);
assert_eq!(*index, 0)
} else {
panic!("Expected the second value not to be parsed")
}
}
#[test]
fn should_not_parse_illegal_escape() {
let wrong_escape = r">\f<";
let parsed_values: Vec<_> =
Iter::new(format!("VALUE={}", wrong_escape).as_bytes()).collect();
assert_eq!(parsed_values.len(), 1);
if let Err(LineParse(wrong_value, index)) = &parsed_values[0] {
assert_eq!(wrong_value, wrong_escape);
assert_eq!(*index, wrong_escape.find('\\').unwrap() + 1)
} else {
panic!("Expected the second value not to be parsed")
}
}
}