use anyhow::{anyhow,bail,Error};
use directories::ProjectDirs;
use std::path::{Path,PathBuf};
use crate::Dir;
#[inline(always)]
pub(crate) fn base(project_name: &str) -> Result<ProjectDirs, Error> {
match ProjectDirs::from("", "", project_name) {
Some(p) => Ok(p),
None => Err(anyhow!("User directories could not be found")),
}
}
pub(crate) fn get_projectdir(dir: &Dir, project_name: &str) -> Result<PathBuf, Error> {
let project_dir = base(project_name)?;
use Dir::*;
Ok(match &dir {
Project => project_dir.project_path(),
Cache => project_dir.cache_dir(),
Config => project_dir.config_dir(),
Data => project_dir.data_dir(),
DataLocal => project_dir.data_local_dir(),
Preference => project_dir.preference_dir(),
}.to_path_buf())
}
#[inline(always)]
pub(crate) fn convert_error<T, E: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static>(result: Result<T, E>) -> Result<T, Error> {
match result {
Ok(t) => Ok(t),
Err(e) => Err(anyhow!(e)),
}
}
#[inline(always)]
pub(crate) fn assert_safe_path(path: &Path) -> Result<(), Error> {
if !path.is_absolute() { bail!("Aborting: dangerous PATH detected") }
Ok(())
}
#[inline(always)]
pub(crate) fn decompress<R>(reader: R) -> Result<Vec<u8>, Error>
where
R: std::io::BufRead,
{
use std::io::prelude::*;
use flate2::bufread::GzDecoder;
let mut buf = Vec::new();
GzDecoder::new(reader).read_to_end(&mut buf)?;
buf.shrink_to_fit();
Ok(buf)
}
#[inline(always)]
pub(crate) fn compress(bytes: &[u8]) -> Result<Vec<u8>, Error> {
use std::io::prelude::*;
use flate2::Compression;
use flate2::write::GzEncoder;
let mut encoder = GzEncoder::new(Vec::new(), Compression::fast());
encoder.write_all(bytes)?;
let buf = encoder.finish()?;
Ok(buf)
}
#[inline(always)]
pub(crate) fn filesize(path: &Path) -> u64 {
match std::fs::metadata(path) {
Ok(f) => f.len(),
Err(_) => 0,
}
}
macro_rules! file_bufr {
() => {
std::io::BufReader::new(
std::fs::OpenOptions::new()
.read(true)
.create(true)
.open(Self::absolute_path()?)?
)
}
}
pub(crate) use file_bufr;
macro_rules! file_bufr_gzip {
() => {
std::io::BufReader::new(
std::fs::OpenOptions::new()
.read(true)
.create(true)
.open(Self::absolute_path_gzip()?)?
)
}
}
macro_rules! file_bufw {
($path:expr) => {
std::io::BufWriter::new(
std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&$path)?
)
}
}
pub(crate) use file_bufw;
macro_rules! impl_file_bytes {
($bit:literal, $unsigned:tt) => {
#[inline]
#[cfg(target_pointer_width = $bit)]
fn file_bytes(start: usize, end: usize) -> Result<Vec<u8>, anyhow::Error> {
use std::io::Read;
use std::io::{Seek,SeekFrom};
if start > end {
bail!("file_bytes(): start > end");
}
let mut buf = {
let len = match start == end {
true => 1,
false => end - start,
};
vec![0; len]
};
let file = std::fs::File::open(Self::absolute_path()?)?;
let mut file = std::io::BufReader::new(file);
file.seek(SeekFrom::Start(start as $unsigned))?;
file.read_exact(&mut buf)?;
Ok(buf)
}
#[inline]
#[cfg(target_pointer_width = $bit)]
fn file_bytes_memmap(start: usize, end: usize) -> Result<Vec<u8>, anyhow::Error> {
if start > end {
bail!("file_bytes(): start > end");
}
let file = std::fs::File::open(Self::absolute_path()?)?;
let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
let len = mmap.len();
if mmap.len() < end {
bail!("file_bytes(): file length ({len}) less than end ({end})");
}
Ok(mmap[start..end].to_vec())
}
}
}
pub(crate) use impl_file_bytes;
macro_rules! impl_io {
($file_ext:literal) => {
#[inline]
fn into_bytes(self) -> Result<Vec<u8>, anyhow::Error> {
self.to_bytes()
}
#[inline(always)]
fn read_to_bytes() -> Result<Vec<u8>, anyhow::Error> {
use std::io::Read;
let mut bufr = crate::common::file_bufr!();
let mut vec = match bufr.get_ref().metadata() {
Ok(m) => Vec::with_capacity(m.len().try_into().unwrap_or(0)),
_ => Vec::new(),
};
bufr.read_to_end(&mut vec)?;
Ok(vec)
}
fn read_to_bytes_gzip() -> Result<Vec<u8>, anyhow::Error> {
let buf = common::decompress(crate::common::file_bufr!())?;
Ok(buf)
}
#[inline(always)]
fn exists_gzip() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::absolute_path_gzip()?;
match path.exists() {
true => Ok(crate::Metadata::new(crate::common::filesize(&path), path)),
false => Err(anyhow!("{:?} doesn't exist", path)),
}
}
#[inline(always)]
fn from_file() -> Result<Self, anyhow::Error> {
Self::__from_file()
}
#[inline(always)]
fn from_file_gzip() -> Result<Self, anyhow::Error> {
Self::from_bytes(&Self::read_to_bytes_gzip()?)
}
#[inline(always)]
unsafe fn from_file_memmap() -> Result<Self, anyhow::Error> {
let file = std::fs::File::open(Self::absolute_path()?)?;
let mmap = unsafe { memmap2::Mmap::map(&file)? };
Self::from_bytes(&*mmap)
}
#[inline(always)]
unsafe fn from_file_gzip_memmap() -> Result<Self, anyhow::Error> {
let file = std::fs::File::open(Self::absolute_path_gzip()?)?;
let mmap = unsafe { memmap2::Mmap::map(&file)? };
Self::from_bytes(&common::decompress(&*mmap)?)
}
fn save(&self) -> Result<crate::Metadata, anyhow::Error> {
use std::io::Write;
let bytes = self.to_writeable_fmt()?;
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
path.push(Self::FILE_NAME);
crate::common::file_bufw!(&path).write_all(&bytes)?;
Ok(crate::Metadata::new(bytes.len() as u64, path))
}
unsafe fn save_memmap(&self) -> Result<crate::Metadata, anyhow::Error> {
let bytes = self.to_bytes()?;
let len = bytes.len();
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
path.push(Self::FILE_NAME);
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
#[cfg(target_pointer_width = "64")]
file.set_len(len as u64)?;
#[cfg(not(target_pointer_width = "64"))]
file.set_len(len.try_into()?)?;
let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file)? };
mmap.copy_from_slice(&bytes);
mmap.flush_async()?;
Ok(crate::Metadata::new(len as u64, path))
}
fn save_gzip(&self) -> Result<crate::Metadata, anyhow::Error> {
let c = common::compress(&self.to_bytes()?)?;
let c_len = c.len();
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
path.push(Self::FILE_NAME_GZIP);
use std::io::Write;
crate::common::file_bufw!(&path).write_all(&c)?;
Ok(crate::Metadata::new(c_len as u64, path))
}
unsafe fn save_gzip_memmap(&self) -> Result<crate::Metadata, anyhow::Error> {
let c = common::compress(&self.to_bytes()?)?;
let c_len = c.len();
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
path.push(Self::FILE_NAME_GZIP);
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
#[cfg(target_pointer_width = "64")]
file.set_len(c_len as u64)?;
#[cfg(not(target_pointer_width = "64"))]
file.set_len(c_len.try_into()?)?;
let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file)? };
mmap.copy_from_slice(&c);
mmap.flush_async()?;
Ok(crate::Metadata::new(c_len as u64, path))
}
fn save_atomic(&self) -> Result<crate::Metadata, anyhow::Error> {
let bytes = self.to_writeable_fmt()?;
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
let mut tmp = path.clone();
tmp.push(Self::FILE_NAME_TMP);
path.push(Self::FILE_NAME);
use std::io::Write;
if let Err(e) = crate::common::file_bufw!(&tmp).write_all(&bytes) {
std::fs::remove_file(&tmp)?;
bail!(e);
}
if let Err(e) = std::fs::rename(&tmp, &path) {
std::fs::remove_file(&tmp)?;
bail!(e);
}
Ok(crate::Metadata::new(bytes.len() as u64, path))
}
fn save_atomic_gzip(&self) -> Result<crate::Metadata, anyhow::Error> {
let c = common::compress(&self.to_bytes()?)?;
let c_len = c.len();
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
let mut tmp = path.clone();
tmp.push(Self::FILE_NAME_GZIP_TMP);
path.push(Self::FILE_NAME_GZIP);
use std::io::Write;
if let Err(e) = crate::common::file_bufw!(&tmp).write_all(&c) {
std::fs::remove_file(&tmp)?;
bail!(e);
}
if let Err(e) = std::fs::rename(&tmp, &path) {
std::fs::remove_file(&tmp)?;
bail!(e);
}
Ok(crate::Metadata::new(c_len as u64, path))
}
unsafe fn save_atomic_memmap(&self) -> Result<crate::Metadata, anyhow::Error> {
let bytes = self.to_bytes()?;
let len = bytes.len();
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
let mut tmp = path.clone();
tmp.push(Self::FILE_NAME_TMP);
path.push(Self::FILE_NAME);
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&tmp)?;
#[cfg(target_pointer_width = "64")]
file.set_len(len as u64)?;
#[cfg(not(target_pointer_width = "64"))]
file.set_len(len.try_into()?)?;
let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file)? };
mmap.copy_from_slice(&bytes);
if let Err(e) = mmap.flush() {
std::fs::remove_file(&tmp)?;
bail!(e);
}
if let Err(e) = std::fs::rename(&tmp, &path) {
std::fs::remove_file(&tmp)?;
bail!(e);
}
Ok(crate::Metadata::new(len as u64, path))
}
unsafe fn save_atomic_gzip_memmap(&self) -> Result<crate::Metadata, anyhow::Error> {
let c = common::compress(&self.to_bytes()?)?;
let c_len = c.len();
let mut path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
let mut tmp = path.clone();
tmp.push(Self::FILE_NAME_GZIP_TMP);
path.push(Self::FILE_NAME_GZIP);
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&tmp)?;
#[cfg(target_pointer_width = "64")]
file.set_len(c_len as u64)?;
#[cfg(not(target_pointer_width = "64"))]
file.set_len(c_len.try_into()?)?;
let mut mmap = unsafe { memmap2::MmapMut::map_mut(&file)? };
mmap.copy_from_slice(&c);
if let Err(e) = mmap.flush() {
std::fs::remove_file(&tmp)?;
bail!(e);
}
if let Err(e) = std::fs::rename(&tmp, &path) {
std::fs::remove_file(&tmp)?;
bail!(e);
}
Ok(crate::Metadata::new(c_len as u64, path))
}
fn rm_atomic() -> Result<crate::Metadata, anyhow::Error> {
let mut path = Self::base_path()?;
let mut tmp = path.clone();
tmp.push(Self::FILE_NAME_TMP);
path.push(Self::FILE_NAME);
if !path.exists() { return Ok(crate::Metadata::zero(path)) }
let size = crate::common::filesize(&path);
std::fs::rename(&path, &tmp)?;
std::fs::remove_file(&tmp)?;
Ok(crate::Metadata::new(size, path))
}
fn rm_atomic_gzip() -> Result<crate::Metadata, anyhow::Error> {
let mut path = Self::base_path()?;
let mut tmp = path.clone();
tmp.push(Self::FILE_NAME_GZIP_TMP);
path.push(Self::FILE_NAME_GZIP);
if !path.exists() { return Ok(crate::Metadata::zero(path)) }
let size = crate::common::filesize(&path);
std::fs::rename(&path, &tmp)?;
std::fs::remove_file(&tmp)?;
Ok(crate::Metadata::new(size, path))
}
fn rm_tmp() -> Result<(), anyhow::Error> {
let mut tmp = Self::base_path()?;
let mut gzip = tmp.clone();
tmp.push(Self::FILE_NAME_TMP);
gzip.push(Self::FILE_NAME_GZIP_TMP);
if !tmp.exists() && !gzip.exists() { return Ok(()) }
std::fs::remove_file(tmp)?;
std::fs::remove_file(gzip)?;
Ok(())
}
#[inline(always)]
fn absolute_path_gzip() -> Result<PathBuf, anyhow::Error> {
let mut base = Self::base_path()?;
base.push(Self::FILE_NAME_GZIP);
common::assert_safe_path(&base)?;
Ok(base)
}
#[inline(always)]
fn file_size_gzip() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::absolute_path_gzip()?;
let file = std::fs::File::open(&path)?;
let size = file.metadata()?.len();
Ok(crate::Metadata::new(size, path))
}
$crate::common::impl_file_bytes!("64", u64);
$crate::common::impl_file_bytes!("32", u32);
}
}
pub(crate) use impl_io;
macro_rules! impl_common {
($file_ext:literal) => {
const OS_DIRECTORY: $crate::Dir;
const PROJECT_DIRECTORY: &'static str;
const SUB_DIRECTORIES: &'static str;
const FILE: &'static str;
const FILE_EXT: &'static str;
const FILE_NAME: &'static str;
const FILE_NAME_GZIP: &'static str;
const FILE_NAME_TMP: &'static str;
const FILE_NAME_GZIP_TMP: &'static str;
#[inline]
fn mkdir() -> Result<PathBuf, anyhow::Error> {
let path = Self::base_path()?;
std::fs::create_dir_all(&path)?;
Ok(path)
}
#[inline(always)]
fn exists() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::absolute_path()?;
match path.exists() {
true => Ok(crate::Metadata::new(crate::common::filesize(&path), path)),
false => Err(anyhow!("{:?} does not exist", path)),
}
}
#[inline(always)]
fn file_size() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::absolute_path()?;
let file = std::fs::File::open(&path)?;
let size = file.metadata()?.len();
Ok(crate::Metadata::new(size, path))
}
fn base_path() -> Result<PathBuf, anyhow::Error> {
let mut base = Self::project_dir_path()?;
if Self::SUB_DIRECTORIES.len() != 0 {
#[cfg(target_os = "windows")]
Self::SUB_DIRECTORIES.split_terminator(&['/', '\\'][..]).for_each(|dir| base.push(dir));
#[cfg(target_family = "unix")]
Self::SUB_DIRECTORIES.split_terminator('/').for_each(|dir| base.push(dir));
}
Ok(base)
}
#[inline(always)]
fn absolute_path() -> Result<PathBuf, anyhow::Error> {
let mut base = Self::base_path()?;
base.push(Self::FILE_NAME);
common::assert_safe_path(&base)?;
Ok(base)
}
fn rm() -> Result<crate::Metadata, anyhow::Error> {
let mut path = Self::base_path()?;
path.push(Self::FILE_NAME);
if !path.exists() { return Ok(crate::Metadata::zero(path)) }
let size = crate::common::filesize(&path);
std::fs::remove_file(&path)?;
Ok(crate::Metadata::new(size, path))
}
#[inline]
fn rm_sub() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::sub_dir_parent_path()?;
let size = crate::common::filesize(&path);
std::fs::remove_dir_all(&path)?;
Ok(crate::Metadata::new(size, path))
}
#[inline]
fn rm_project() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::project_dir_path()?;
let size = crate::common::filesize(&path);
std::fs::remove_dir_all(&path)?;
Ok(crate::Metadata::new(size, path))
}
#[inline(always)]
fn sub_dir_size() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::sub_dir_parent_path()?;
let dir = std::fs::File::open(&path)?;
let size = dir.metadata()?.len();
Ok(crate::Metadata::new(size, path))
}
#[inline(always)]
fn project_dir_size() -> Result<crate::Metadata, anyhow::Error> {
let path = Self::project_dir_path()?;
let file = std::fs::File::open(&path)?;
let size = file.metadata()?.len();
Ok(crate::Metadata::new(size, path))
}
fn project_dir_path() -> Result<PathBuf, anyhow::Error> {
common::get_projectdir(&Self::OS_DIRECTORY, &Self::PROJECT_DIRECTORY)
}
fn sub_dir_parent_path() -> Result<PathBuf, anyhow::Error> {
let mut base = Self::project_dir_path()?;
if Self::SUB_DIRECTORIES.len() != 0 {
#[cfg(target_os = "windows")]
if let Some(sub) = Self::SUB_DIRECTORIES.split_terminator(&['/', '\\'][..]).next() {
base.push(sub);
}
#[cfg(target_family = "unix")]
if let Some(sub) = Self::SUB_DIRECTORIES.split_terminator('/').next() {
base.push(sub);
}
}
Ok(base)
}
}
}
pub(crate) use impl_common;
macro_rules! impl_string {
($file_ext:literal) => {
#[inline(always)]
fn to_writeable_fmt(&self) -> Result<Vec<u8>, anyhow::Error> {
Ok(self.to_string()?.into_bytes())
}
#[inline(always)]
fn into_writeable_fmt(self) -> Result<Vec<u8>, anyhow::Error> {
Ok(self.to_string()?.into_bytes())
}
#[inline(always)]
fn read_to_string() -> Result<String, anyhow::Error> {
Ok(std::fs::read_to_string(Self::absolute_path()?)?)
}
common::impl_io!($file_ext);
common::impl_common!($file_ext);
};
}
pub(crate) use impl_string;
macro_rules! impl_binary {
($file_ext:literal) => {
#[inline(always)]
fn to_writeable_fmt(&self) -> Result<Vec<u8>, anyhow::Error> {
self.to_bytes()
}
#[inline(always)]
fn into_writeable_fmt(self) -> Result<Vec<u8>, anyhow::Error> {
self.to_bytes()
}
crate::common::impl_io!($file_ext);
crate::common::impl_common!($file_ext);
};
}
pub(crate) use impl_binary;
#[doc(hidden)]
#[macro_export]
macro_rules! assert_str_invalid_symbol {
($symbol:literal, $project:tt, $sub:tt, $file:tt) => {
$crate::const_assert!(!$crate::contains!($project, $symbol), "disk: 'Project Directory' must not contain '{}'", $symbol);
$crate::const_assert!(!$crate::contains!($sub, $symbol), "disk: 'Sub Directories' must not contain '{}'", $symbol);
$crate::const_assert!(!$crate::contains!($file, $symbol), "disk: 'File Name' must not contain '{}'", $symbol);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert_str_reserved {
($symbol:literal, $project:tt, $sub:tt, $file:tt) => {
$crate::const_assert!(!$crate::convert_case!(upper, $project), $symbol, "disk: 'Project Directory' must not be a reserved filename: '{}'", $symbol);
$crate::const_assert!(!$crate::convert_case!(upper, $sub), $symbol, "disk: 'Sub Directories' must not be a reserved filename: '{}'", $symbol);
$crate::const_assert!(!$crate::convert_case!(upper, $file), $symbol, "disk: 'File Name' must not be a reserved filename: '{}'", $symbol);
$crate::seq!(N in 0..10 {
const _: () = {
if !$crate::contains!($sub, '\\') && $sub.len() > 255 {
::std::panic!("disk: the single 'Sub Directory' is a reserved filename");
} else if N < $crate::split!($sub, '\\').len() {
if $crate::split!($sub, '\\')[N].len() > 255 {
::std::panic!("disk: one of the 'Sub Directories' is a reserved filename");
}
}
};
const _: () = {
if !$crate::contains!($sub, '/') && $sub.len() > 255 {
::std::panic!("disk: the single 'Sub Directory' is a reserved filename");
} else if N < $crate::split!($sub, '/').len() {
if $crate::split!($sub, '/')[N].len() > 255 {
::std::panic!("disk: one of the 'Sub Directories' is a reserved filename");
}
}
};
});
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert_str_invalid_symbol_start_end {
($symbol:literal, $project:tt, $sub:tt, $file:tt) => {
$crate::const_assert!(!$crate::starts_with!($project, $symbol), "disk: 'Project Directory' must not start with '{}'", $symbol);
$crate::const_assert!(!$crate::starts_with!($sub, $symbol), "disk: 'Sub Directories' must not start with '{}'", $symbol);
$crate::const_assert!(!$crate::starts_with!($file, $symbol), "disk: 'File Name' must not start with '{}'", $symbol);
$crate::const_assert!(!$crate::ends_with!($project, $symbol), "disk: 'Project Directory' must not end with '{}'", $symbol);
$crate::const_assert!(!$crate::ends_with!($sub, $symbol), "disk: 'Sub Directories' must not end with '{}'", $symbol);
$crate::const_assert!(!$crate::ends_with!($file, $symbol), "disk: 'File Name' must not end with '{}'", $symbol);
#[cfg(target_os = "windows")]
$crate::seq!(N in 0..10 {
const _: () = {
if N < $crate::split!($sub, '\\').len() {
if $crate::starts_with!($crate::split!($sub, '\\')[N], $symbol) {
panic!("disk: one of the 'Sub Directories' starts with an invalid symbol");
}
}
};
});
$crate::seq!(N in 0..10 {
const _: () = {
if N < $crate::split!($sub, '/').len() {
if $crate::starts_with!($crate::split!($sub, '/')[N], $symbol) {
panic!("disk: one of the 'Sub Directories' starts with an invalid symbol");
}
}
};
});
#[cfg(target_os = "windows")]
$crate::seq!(N in 0..10 {
const _: () = {
if N < $crate::split!($sub, '\\').len() {
if $crate::ends_with!($crate::split!($sub, '\\')[N], $symbol) {
panic!("disk: one of the 'Sub Directories' ends with an invalid symbol");
}
}
};
});
$crate::seq!(N in 0..10 {
const _: () = {
if N < $crate::split!($sub, '/').len() {
if $crate::ends_with!($crate::split!($sub, '/')[N], $symbol) {
panic!("disk: one of the 'Sub Directories' ends with an invalid symbol");
}
}
};
});
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert_str {
($project:tt, $sub:tt, $file:tt) => {
$crate::const_assert!($project.len() != 0, "disk: 'Project Directory' must not be an empty string");
$crate::const_assert!($file.len() != 0, "disk: 'File Name' must not be an empty string!");
$crate::const_assert!($project.len() < 255, "disk: 'Project Directory' must be less than 255 bytes long");
$crate::const_assert!($file.len() < 255, "disk: 'File Name' must be less than 255 bytes long!");
$crate::const_assert!($project.len() + $sub.len() + $file.len() < 4000, "disk: Directories combined must be less than 4000 bytes long");
$crate::const_assert!($crate::split!($sub, '/').len() < 10, "disk: 'Sub Directories' are limited to 10-depth");
#[cfg(target_os = "windows")]
$crate::seq!(N in 0..10 {
const _: () = {
if !$crate::contains!($sub, '\\') && $sub.len() > 255 {
::std::panic!("disk: the single 'Sub Directory' is longer than 255 bytes");
} else if N < $crate::split!($sub, '\\').len() {
if $crate::split!($sub, '\\')[N].len() > 255 {
::std::panic!("disk: one of the 'Sub Directories' is longer than 255 bytes");
}
}
};
});
$crate::seq!(N in 0..10 {
const _: () = {
if !$crate::contains!($sub, '/') && $sub.len() > 255 {
::std::panic!("disk: the single 'Sub Directory' is longer than 255 bytes");
} else if N < $crate::split!($sub, '/').len() {
if $crate::split!($sub, '/')[N].len() > 255 {
::std::panic!("disk: one of the 'Sub Directories' is longer than 255 bytes");
}
}
};
});
$crate::const_assert!(!$crate::contains!($project, "/"), "disk: 'Project Directory' must not contain '/'");
$crate::const_assert!(!$crate::contains!($project, "\\"), "disk: 'Project Directory' must not contain '\\'");
$crate::const_assert!(!$crate::contains!($file, "/"), "disk: 'File Name' must not contain '/'");
$crate::const_assert!(!$crate::contains!($file, "\\"), "disk: 'File Name' must not contain '\\'");
$crate::assert_str_invalid_symbol!("<", $project, $sub, $file);
$crate::assert_str_invalid_symbol!(">", $project, $sub, $file);
$crate::assert_str_invalid_symbol!(":", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("\"", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("\'", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("|", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("?", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("*", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("^", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("$", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("&", $project, $sub, $file);
$crate::assert_str_invalid_symbol!("(", $project, $sub, $file);
$crate::assert_str_invalid_symbol!(")", $project, $sub, $file);
$crate::assert_str_invalid_symbol_start_end!(" ", $project, $sub, $file);
$crate::assert_str_invalid_symbol_start_end!("/", $project, $sub, $file);
$crate::assert_str_invalid_symbol_start_end!("\\", $project, $sub, $file);
}
}
macro_rules! impl_macro_binary {
($trait:ident, $file_ext:literal) => {
use $crate::Dir;
paste::item! {
#[doc = "
Implement the [`" $trait "`] trait
File extension is `" $file_ext "` and is automatically appended.
### Input
These are the inputs you need to provide to implement [`" $trait "`].
| Variable | Description | Related Trait Constant | Type | Example |
|----------------------|-----------------------------------------|-----------------------------------|--------------------|---------------|
| `$data` | Identifier of the data to implement for | | `struct` or `enum` | `State`
| `$dir` | Which OS directory to use | [`" $trait "::OS_DIRECTORY`] | [`Dir`] | [`Dir::Data`]
| `$project_directory` | The name of the top project folder | [`" $trait "::PROJECT_DIRECTORY`] | [`&str`] | `\"MyProject\"`
| `$sub_directories` | (Optional) sub-directories before file | [`" $trait "::SUB_DIRECTORIES`] | [`&str`] | `\"some/dirs\"`
| `$file_name` | The file name to use | [`" $trait "::FILE_NAME`] | [`&str`] | `\"state\"`
| `$header` | `24` custom byte header | [`" $trait "::HEADER`] | `[u8; 24]` | `[1_u8; 24]`
| `$version` | `1` byte custom version | [`" $trait "::VERSION`] | `u8` | `5_u8`
### Example
```rust,ignore
use serde::{Serialize,Deserialize};
use disk::*;
const HEADER: [u8; 24] = [1_u8; 24];
const VERSION: u8 = 5;
" $trait:lower "!(State, Dir::Data, \"MyProject\", \"some/dirs\", \"state\", HEADER, VERSION);
#[derive(Serialize,Deserialize)]
struct State {
string: String,
number: u32,
}
```
This example would be located at `~/.local/share/myproject/some/dirs/state." $file_ext "`.
"]
#[macro_export]
macro_rules! [<$trait:lower>] {
($data:ty, $dir:expr, $project_directory:tt, $sub_directories:tt, $file_name:tt, $header:tt, $version:tt) => {
$crate::assert_str!($project_directory, $sub_directories, $file_name);
unsafe impl $crate::$trait for $data {
const OS_DIRECTORY: $crate::Dir = $dir;
const PROJECT_DIRECTORY: &'static str = $project_directory;
const SUB_DIRECTORIES: &'static str = $sub_directories;
const FILE: &'static str = $file_name;
const FILE_EXT: &'static str = $file_ext;
const FILE_NAME: &'static str = $crate::const_format!("{}.{}", $file_name, $file_ext);
const FILE_NAME_GZIP: &'static str = $crate::const_format!("{}.{}.gz", $file_name, $file_ext);
const FILE_NAME_TMP: &'static str = $crate::const_format!("{}.{}.tmp", $file_name, $file_ext);
const FILE_NAME_GZIP_TMP: &'static str = $crate::const_format!("{}.{}.gz.tmp", $file_name, $file_ext);
const HEADER: [u8; 24] = $header;
const VERSION: u8 = $version;
}
};
}
pub(crate) use [<$trait:lower>];
}
};
}
pub(crate) use impl_macro_binary;
macro_rules! impl_macro_no_ext {
($trait:ident) => {
use $crate::Dir;
paste::item! {
#[doc = "
Implement the [`" $trait "`] trait
[`" $trait "`] has no file extension.
### Input
These are the inputs you need to provide to implement [`" $trait "`].
| Variable | Description | Related Trait Constant | Type | Example |
|----------------------|-----------------------------------------|-----------------------------------|--------------------|---------------|
| `$data` | Identifier of the data to implement for | | `struct` or `enum` | `MyState`
| `$dir` | Which OS directory to use | [`" $trait "::OS_DIRECTORY`] | [`Dir`] | [`Dir::Data`]
| `$project_directory` | The name of the top project folder | [`" $trait "::PROJECT_DIRECTORY`] | [`&str`] | `\"MyProject\"`
| `$sub_directories` | (Optional) sub-directories before file | [`" $trait "::SUB_DIRECTORIES`] | [`&str`] | `\"some/dirs\"`
| `$file_name` | The file name to use | [`" $trait "::FILE_NAME`] | [`&str`] | `\"state\"`
### Example
```rust
use serde::{Serialize,Deserialize};
use disk::*;
" $trait:lower "!(State, Dir::Data, \"MyProject\", \"some/dirs\", \"state\");
#[derive(Serialize,Deserialize)]
struct State {
string: String,
number: u32,
}
```
This example would be located at `~/.local/share/myproject/some/dirs/state`.
"]
#[macro_export]
macro_rules! [<$trait:lower>] {
($data:ty, $dir:expr, $project_directory:tt, $sub_directories:tt, $file_name:tt) => {
$crate::assert_str!($project_directory, $sub_directories, $file_name);
unsafe impl $crate::$trait for $data {
const OS_DIRECTORY: $crate::Dir = $dir;
const PROJECT_DIRECTORY: &'static str = $project_directory;
const SUB_DIRECTORIES: &'static str = $sub_directories;
const FILE: &'static str = $file_name;
const FILE_EXT: &'static str = "";
const FILE_NAME: &'static str = $file_name;
const FILE_NAME_GZIP: &'static str = $crate::const_format!("{}.gz", $file_name);
const FILE_NAME_TMP: &'static str = $crate::const_format!("{}.tmp", $file_name);
const FILE_NAME_GZIP_TMP: &'static str = $crate::const_format!("{}.gz.tmp", $file_name);
}
};
}
pub(crate) use [<$trait:lower>];
}
};
}
pub(crate) use impl_macro_no_ext;
macro_rules! impl_macro {
($trait:ident, $file_ext:literal) => {
use $crate::Dir;
paste::paste! {
#[doc = "
Implement the [`" $trait "`] trait
File extension is `" $file_ext "` and is automatically appended.
### Input
These are the inputs you need to provide to implement [`" $trait "`].
| Variable | Description | Related Trait Constant | Type | Example |
|----------------------|-----------------------------------------|-----------------------------------|--------------------|---------------|
| `$data` | Identifier of the data to implement for | | `struct` or `enum` | `MyState`
| `$dir` | Which OS directory to use | [`" $trait "::OS_DIRECTORY`] | [`Dir`] | [`Dir::Data`]
| `$project_directory` | The name of the top project folder | [`" $trait "::PROJECT_DIRECTORY`] | [`&str`] | `\"MyProject\"`
| `$sub_directories` | (Optional) sub-directories before file | [`" $trait "::SUB_DIRECTORIES`] | [`&str`] | `\"some/dirs\"`
| `$file_name` | The file name to use | [`" $trait "::FILE_NAME`] | [`&str`] | `\"state\"`
### Example
```rust
use serde::{Serialize,Deserialize};
use disk::*;
" $trait:lower "!(State, Dir::Data, \"MyProject\", \"some/dirs\", \"state\");
#[derive(Serialize,Deserialize)]
struct State {
string: String,
number: u32,
}
```
This example would be located at `~/.local/share/myproject/some/dirs/state." $file_ext "`.
"]
#[macro_export]
macro_rules! [<$trait:lower>] {
($data:ty, $dir:expr, $project_directory:tt, $sub_directories:tt, $file_name:tt) => {
$crate::assert_str!($project_directory, $sub_directories, $file_name);
unsafe impl $crate::$trait for $data {
const OS_DIRECTORY: $crate::Dir = $dir;
const PROJECT_DIRECTORY: &'static str = $project_directory;
const SUB_DIRECTORIES: &'static str = $sub_directories;
const FILE: &'static str = $file_name;
const FILE_EXT: &'static str = $file_ext;
const FILE_NAME: &'static str = $crate::const_format!("{}.{}", $file_name, $file_ext);
const FILE_NAME_GZIP: &'static str = $crate::const_format!("{}.{}.gz", $file_name, $file_ext);
const FILE_NAME_TMP: &'static str = $crate::const_format!("{}.{}.tmp", $file_name, $file_ext);
const FILE_NAME_GZIP_TMP: &'static str = $crate::const_format!("{}.{}.gz.tmp", $file_name, $file_ext);
}
};
}
pub(crate) use [<$trait:lower>];
}
};
}
pub(crate) use impl_macro;