pub(crate) mod cli;
pub(crate) mod pdf;
pub(crate) const AUTHOR_JGABAUT: &str = "jgabaut";
pub(crate) const AUTHOR_GIONINJO: &str = "gioninjo";
pub(crate) const AUTHOR_GIONINJO_LINK: &str = "https://github.com/gioninjo";
pub(crate) const AUTHOR_JGABAUT_LINK: &str = "https://github.com/jgabaut";
pub(crate) const COPYRIGHT_INFO: &str = "Copyright (C) 2024-2026 jgabaut, gioninjo
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.";
pub(crate) const PROJECT_NAME: &str = env!("CARGO_PKG_NAME");
pub(crate) const PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub(crate) const PROJECT_VERSION_FULL: &str = env!("VERSION_STRING");
pub(crate) const SHORT_PROJECT_VERSION: &str = env!("SHORT_VERSION_STRING");
pub(crate) const PROJECT_BUILD_TYPE: &str = env!("BUILD_TYPE");
pub(crate) const PROJECT_BRANCH: &str = env!("BRANCH_NAME");
pub(crate) const _COMMIT_HASH: &str = env!("COMMIT_HASH");
pub(crate) const COMMIT_HASH_PLUS: &str = env!("COMMIT_HASH_PLUS");
pub(crate) const BUILD_DATE: &str = env!("BUILD_DATE");
pub(crate) const RFD_BACKEND: &str = env!("RFD_BACKEND");
pub(crate) const ESOX_LESSCLONE_BACKEND: &str = env!("ESOX_LESSCLONE_BACKEND");
use chrono::{Datelike, Local};
use raylib::math::Rectangle;
use raylib::misc::AsF32;
use std::path::Path;
#[inline]
pub(crate) fn rrect<T1: AsF32, T2: AsF32, T3: AsF32, T4: AsF32>(
x: T1,
y: T2,
width: T3,
height: T4,
) -> Rectangle {
Rectangle::new(x.as_f32(), y.as_f32(), width.as_f32(), height.as_f32())
}
pub(crate) trait CommaFormat {
fn comma(self) -> String;
}
impl CommaFormat for f32 {
fn comma(self) -> String {
let mut s = self.to_string();
if let Some(pos) = s.find('.') {
s.replace_range(pos..=pos, ",");
}
s
}
}
pub(crate) fn sanitize_filename(input: &str) -> String {
if input.is_empty() {
return "_".to_string();
}
let mut out = String::with_capacity(input.len());
for c in input.chars() {
let valid = match c {
'\0' => false,
c if c.is_whitespace() => false,
'/' => false,
'\\' if cfg!(windows) => false,
'<' | '>' | ':' | '"' | '|' | '?' | '*' if cfg!(windows) => false,
c if c.is_control() => false,
_ => true,
};
out.push(if valid { c } else { '_' });
}
#[cfg(windows)]
{
while out.ends_with('.') || out.ends_with(' ') {
out.pop();
}
}
if out.is_empty() {
out.push('_');
}
if out == "." || out == ".." {
out.insert(0, '_');
}
#[cfg(windows)]
{
let upper = out.to_ascii_uppercase();
let base = upper.split('.').next().unwrap();
if matches!(
base,
"CON"
| "PRN"
| "AUX"
| "NUL"
| "COM1"
| "COM2"
| "COM3"
| "COM4"
| "COM5"
| "COM6"
| "COM7"
| "COM8"
| "COM9"
| "LPT1"
| "LPT2"
| "LPT3"
| "LPT4"
| "LPT5"
| "LPT6"
| "LPT7"
| "LPT8"
| "LPT9"
) {
out.insert(0, '_');
}
}
out
}
pub(crate) fn gen_logfile_name(
ref_samp_filename: &Path,
station_code: &str,
is_main_log: bool,
) -> String {
let date = Local::now().format("%d%m%Y").to_string();
let ref_samp_filename_noext = ref_samp_filename
.file_stem()
.unwrap()
.to_string_lossy()
.into_owned();
let tail = if is_main_log { "log" } else { "intermediates" };
let unsafe_name = format!(
"{}_{}_{}_{}.csv",
date, ref_samp_filename_noext, station_code, tail
);
sanitize_filename(&unsafe_name)
}
pub(crate) fn is_holiday() -> bool {
let today = Local::now();
let m = today.month();
let d = today.day();
(m == 11 && d == 21) || (m == 12 && d == 17) || (m == 4 && d == 1)
}