From da8b8ae55997fb0c435f91a75999881f74ebe1a5 Mon Sep 17 00:00:00 2001
From: Louis <contact@louiscap.co>
Date: Sun, 5 Jan 2025 02:23:00 +0000
Subject: [PATCH] Improve docs

---
 src/env_file.rs   | 17 +++++++++
 src/filesystem.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs        | 13 +++++++
 src/parser.rs     | 35 ++++++++++++++++--
 4 files changed, 153 insertions(+), 3 deletions(-)

diff --git a/src/env_file.rs b/src/env_file.rs
index b8d44ba..01b81e2 100644
--- a/src/env_file.rs
+++ b/src/env_file.rs
@@ -105,9 +105,26 @@ impl EnvironmentFile {
 	}
 }
 
+/// Varies the behaviour of applying an environment file.
+///
+/// ## Defaults
+///
+/// When not provided, `envish` will use the default values for this type:
+/// - `prefix` is set to `None` (Keys in env files will be used as-written)
+/// - `overwrite` is set to `false` (If a variable is already set, the value in the file will not be used)
 #[derive(Clone, Debug, Default)]
 pub struct ApplyOptions {
+	/// If present, this exact string will be prepended to the names of all environment variables
+	/// when applying.
+	///
+	/// ### Example
+	///
+	/// With the prefix "APP_", a variable with the key "DATABASE_URL" will be added to the environment as
+	/// "APP_DATABASE_URL".
 	pub prefix: Option<String>,
+	/// By default, `envish` will not update an existing environment variable, ignoring the value
+	/// in the environment file. When the `overwrite` option is set to `true`, `envish` will apply
+	/// any values from the file regardless of whether they are already set.
 	pub overwrite: bool,
 }
 
diff --git a/src/filesystem.rs b/src/filesystem.rs
index 55aded5..d446920 100644
--- a/src/filesystem.rs
+++ b/src/filesystem.rs
@@ -8,22 +8,36 @@ use std::path::Path;
 #[derive(Debug, thiserror::Error)]
 #[allow(clippy::enum_variant_names)]
 pub enum EnvFsError {
+	/// Error opening the file, or reading it into a buffer.
 	#[error(transparent)]
 	IoError(#[from] std::io::Error),
+	/// Error parsing the file from a string into an EnvironmentFile. Denotes syntax errors in the
+	/// file.
 	#[error(transparent)]
 	ParseError(#[from] crate::EnvironmentFileError),
+	/// An error thrown specifically when the given environment variable exists, but is cannot be
+	/// represented in Rust (e.g. not Unicode). This error does **not** occur when the environment
+	/// variable exists as valid Unicode.
 	#[error("The target environment variable exists, but is not Unicode")]
 	EnvironmentError,
 }
 
+/// Reads the file `.env` into memory, but does not apply it to the environment.
 pub fn env_file() -> Result<EnvironmentFile, EnvFsError> {
 	env_file_from_path(".env")
 }
 
+/// Reads the file `.env.<environment>` into memory, but does not apply it to the environment.
+///
+/// ### Example
+///
+/// Calling `env_file_suffix("production")` will attempt to read the file `.env.production` in the
+/// directory the server was launched from.
 pub fn env_file_suffix(environment: impl Display) -> Result<EnvironmentFile, EnvFsError> {
 	env_file_from_path(format!(".env.{}", environment))
 }
 
+/// Reads the file at the specified path into memory, but does not apply it to the environment.
 pub fn env_file_from_path(path: impl AsRef<Path>) -> Result<EnvironmentFile, EnvFsError> {
 	let mut file = File::open(path)?;
 	let mut buffer = String::new();
@@ -31,20 +45,97 @@ pub fn env_file_from_path(path: impl AsRef<Path>) -> Result<EnvironmentFile, Env
 	Ok(buffer.parse()?)
 }
 
+/// Look up a `.env` file in the working directory and apply its contents to the current environment.
+///
+/// ### Errors
+///
+/// This method returns an error under the following circumstances:
+///
+/// - The target file does not exist
+/// - There was an error when reading the target file
+/// - The target was not a correctly formatted `.env` file
+///
+/// ### Example
+///
+/// This example will attempt to read the file `.env` in the directory the server was launched from.
+/// ```rust
+/// use envish::dotenv;
+/// let _ = dotenv();
+/// ```
 pub fn dotenv() -> Result<(), EnvFsError> {
 	env_file()?.apply(Default::default());
 	Ok(())
 }
+
+/// Look up a `.env` file in the working directory and apply its contents to the current environment,
+/// with the provided options, allowing for prefixing and overwriting existing values.
+///
+/// ### Errors
+///
+/// This method returns an error under the following circumstances:
+///
+/// - The target file does not exist
+/// - There was an error when reading the target file
+/// - The target was not a correctly formatted `.env` file
+///
+/// ### Example
+///
+/// This example will attempt to read the file `.env`, and will prepend the given prefix to all the
+/// contained variables. For example, a variable in the file named "DATABASE_URL" will be added to
+/// the environment as "APP_DATABASE_URL".
+///
+/// ```rust
+/// use envish::{dotenv_opts, ApplyOptions};
+/// let _ = dotenv_opts(ApplyOptions::with_prefix("APP_"));
+/// ```
 pub fn dotenv_opts(options: ApplyOptions) -> Result<(), EnvFsError> {
 	env_file()?.apply(options);
 	Ok(())
 }
 
+/// Look up a `.env` file with the provided suffix in the working directory and apply its contents
+/// to the current environment.
+///
+/// ### Errors
+///
+/// This method returns an error under the following circumstances:
+///
+/// - The target file does not exist
+/// - There was an error when reading the target file
+/// - The target was not a correctly formatted `.env` file
+///
+/// ### Example
+///
+/// This example will attempt to read the file `.env.development`
+///
+/// ```rust
+/// use envish::dotenv_suffix;
+/// let _ = dotenv_suffix("development");
+/// ```
 pub fn dotenv_suffix(environment: impl Display) -> Result<(), EnvFsError> {
 	env_file_suffix(environment)?.apply(Default::default());
 	Ok(())
 }
 
+/// Look up an environment file at the given path and apply its contents to the current environment.
+///
+/// ### Errors
+///
+/// This method returns an error under the following circumstances:
+///
+/// - The target file does not exist
+/// - There was an error when reading the target file
+/// - The target was not a correctly formatted `.env` file
+///
+/// ### Example
+///
+/// This example will attempt to read the file `my_dotenv_file` at the specified path. The file must
+/// be correctly formatted as any other .env file, but does not need to have a specific name.
+///
+/// ```rust
+/// use envish::dotenv_from;
+/// let _ = dotenv_from("/some/other/path/to/my_dotenv_file");
+/// ```
 pub fn dotenv_from(path: impl AsRef<Path>) -> Result<(), EnvFsError> {
 	env_file_from_path(path)?.apply(Default::default());
 	Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index fa36368..9493597 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,17 @@
 #![allow(unused_labels)]
+//! This crate provides a simple interface for reading, parsing, and using `.env` style environment
+//! files. The goal is to provide comprehensive compatibility with the variety of .env formats
+//! and options found in popular tools across languages, both for reading and writing.
+//!
+//! ## Embedding
+//!
+//! This library is designed to be easy to use with both Rust and non-Rust projects. The core
+//! functionality covers parsing and manipulating environment files. By default, `envish` also
+//! includes filesystem support for reading and writing `.env` files, but this can be disabled by
+//! turning off default features.
+//!
+//! It is recommended to disable filesystem support when embedding `envish`, and instead
+//! using the platform-native filesystem operations in tandem with this crate's parsing module.
 
 mod env_file;
 #[cfg(feature = "fs")]
diff --git a/src/parser.rs b/src/parser.rs
index 2d41e13..64618ec 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -13,10 +13,17 @@ use nom::{
 };
 use std::fmt::Display;
 
+/// Holds one part of the value of an environment variable. Static parts will be used verbatim,
+/// while variables will be looked up at render time when an environment file is applied.
+/// Comments are always ignored by the renderer, but are preserved for serialization.
 #[derive(Clone, Debug, PartialEq)]
 pub enum ValuePart {
+	/// A static value that will be rendered verbatim.
 	Static(String),
+	/// The exact name of an environment variable that will be looked up at render time.
+	/// Case-sensitivity is dependent on the operating system.
 	Variable(String),
+	/// Arbitrary text that will not be added to the environment.
 	Comment(String),
 }
 
@@ -31,12 +38,15 @@ impl Display for ValuePart {
 }
 
 impl ValuePart {
+	/// Create a static value that will be used verbatim.
 	pub fn new(value: impl ToString) -> Self {
 		Self::Static(value.to_string())
 	}
+	/// Create a reference to another environment variable that will be interpolated into the value.
 	pub fn variable(value: impl ToString) -> Self {
 		Self::Variable(value.to_string())
 	}
+	/// Create a comment that will be ignored by the value renderer.
 	pub fn comment(value: impl ToString) -> Self {
 		Self::Comment(value.to_string())
 	}
@@ -59,10 +69,18 @@ impl ValuePart {
 	}
 }
 
+/// A whole line of an environment file. May contain comments, static strings, or interpolation
+/// variables.
 #[derive(Clone, Debug, PartialEq)]
 pub enum FileLine {
+	/// A blank lin in a file, containing only whitespace. Ignored for all purposes except 1-1
+	/// serialisation.
 	Empty,
+	/// A full line comment, starting with a '#' and containing arbitrary text for the rest of the
+	/// line.
 	Comment(String),
+	/// A key-value pair, where the key is a string and the value is a combination of static parts,
+	/// dynamic parts, and comments.
 	KeyValue { key: String, value: Vec<ValuePart> },
 }
 
@@ -79,14 +97,22 @@ impl Display for FileLine {
 }
 
 impl FileLine {
+	/// Create a new empty line.
 	pub fn empty() -> Self {
 		Self::Empty
 	}
 
+	/// Create a new full line comment.
 	pub fn comment(comment: impl ToString) -> Self {
 		Self::Comment(comment.to_string())
 	}
 
+	/// Create a new key-value line, where the value may be a combination of static strings,
+	/// variable references, and comments.
+	///
+	/// Lines constructed this way may contain comments in the middle of strings and references
+	/// without issue, but will not be able to correctly serialise if writing the non-compliant
+	/// line to a file.
 	pub fn key_value(key: impl ToString, value: impl ToOwned<Owned = Vec<ValuePart>>) -> Self {
 		Self::KeyValue {
 			key: key.to_string(),
@@ -107,8 +133,8 @@ impl FileLine {
 		Self::Comment(span.fragment().to_string())
 	}
 
-	/// Whether this line is entirely self contained. If the line is a kv pair that requires
-	/// interpolation, it is not considered complete.
+	/// Returns true if this line can be evaluated by itself. A line cannot be evaluated by itself
+	/// if it references other environment variables.
 	pub fn is_complete(&self) -> bool {
 		match self {
 			Self::KeyValue { value, .. } => value.iter().all(|part| !matches!(part, &ValuePart::Variable(..))),
@@ -116,6 +142,8 @@ impl FileLine {
 		}
 	}
 
+	/// Returns a copy of this line without comments. If the entire line is a comment, it is
+	/// converted to `Line::Empty`. Otherwise, comment parts of key-value lines are removed.
 	pub fn strip_comments(&self) -> Self {
 		match self {
 			Self::Empty => Self::Empty,
@@ -131,7 +159,8 @@ impl FileLine {
 		}
 	}
 
-	/// Convert the line into a complete value string
+	/// Convert the line into a complete value string, interpolating dynamic variables and stripping
+	/// comments. Empty lines or whole line comments will return an empty string.
 	pub fn assemble_value(&self) -> String {
 		match self {
 			Self::Empty => String::new(),
-- 
GitLab