use {
docuglot::Language,
fehler::throws,
log::trace,
market::{queue::Procurer, ConsumeFailure, ConsumeFault, Consumer, Failure, Producer},
parse_display::Display as ParseDisplay,
std::{
env, fs,
io::{self, ErrorKind},
},
thiserror::Error as ThisError,
url::{ParseError, Url},
};
#[derive(Debug, ThisError)]
pub enum RootDirError {
#[error("current working directory is invalid: {0}")]
GetWorkingDir(#[from] io::Error),
#[error("unable to create URL of root directory `{0}`")]
Create(String),
}
#[throws(RootDirError)]
fn root_dir() -> Url {
let dir = env::current_dir()?;
#[allow(clippy::map_err_ignore)] Url::from_directory_path(&dir)
.map_err(|_| RootDirError::Create(format!("{}", dir.display())))?
}
#[throws(RootDirError)]
pub(crate) fn create_file_system() -> (FileCommandProducer, FileConsumer) {
let (url_producer, url_consumer) = market::queue::create_supply_chain();
(
FileCommandProducer {
root_dir: root_dir()?,
url_producer,
},
FileConsumer { url_consumer },
)
}
pub(crate) struct FileConsumer {
url_consumer: Procurer<Url>,
}
impl Consumer for FileConsumer {
type Good = File;
type Failure = ConsumeFailure<ConsumeFileError>;
#[throws(Self::Failure)]
fn consume(&self) -> Self::Good {
#[allow(clippy::map_err_ignore)]
let path_url = self
.url_consumer
.consume()
.map_err(|_| ConsumeFailure::EmptyStock)?;
File::read(path_url).map_err(ConsumeFileError::from)?
}
}
pub(crate) struct FileCommandProducer {
root_dir: Url,
url_producer: market::queue::Supplier<Url>,
}
impl FileCommandProducer {
pub(crate) const fn root_dir(&self) -> &Url {
&self.root_dir
}
}
impl Producer for FileCommandProducer {
type Good = FileCommand;
type Failure = FileError;
#[allow(clippy::unwrap_in_result)] #[throws(Self::Failure)]
fn produce(&self, good: Self::Good) {
match good {
#[allow(clippy::unwrap_used)] Self::Good::Read { path } => self
.url_producer
.produce(self.root_dir.join(&path)?)
.unwrap(),
}
}
}
#[derive(Debug, ThisError)]
pub enum FileError {
#[error("")]
Io(#[from] io::Error),
#[error("")]
Create(#[from] ParseError),
}
impl Failure for FileError {
type Fault = Self;
}
#[derive(Debug, ParseDisplay)]
pub(crate) enum FileCommand {
#[display("Read `{path}`")]
Read {
path: String,
},
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct File {
url: Url,
text: String,
}
impl File {
#[throws(ReadFileError)]
fn read(url: Url) -> Self {
trace!("read {}", url.path());
#[allow(clippy::map_err_ignore)]
Self {
text: fs::read_to_string(url.to_file_path().map_err(|_| ReadFileError {
file: url.to_string(),
error: ErrorKind::NotFound,
})?)
.map_err(|error| ReadFileError {
file: url.to_string(),
error: error.kind(),
})?,
url,
}
}
pub(crate) const fn text(&self) -> &String {
&self.text
}
pub(crate) const fn url(&self) -> &Url {
&self.url
}
pub(crate) fn language(&self) -> Language {
if self.url.path().ends_with(".rs") {
Language::Rust
} else {
Language::Plaintext
}
}
}
#[derive(Debug, ConsumeFault, ThisError)]
pub enum ConsumeFileError {
#[error("")]
Read(#[from] ReadFileError),
}
#[derive(Debug, ThisError)]
#[error("failed to read `{file}`: {error:?}")]
pub struct ReadFileError {
error: ErrorKind,
file: String,
}