Files
cloudflare_ddns/src/pp.rs
Timothy Miller 3e2b8a3a40 Use rustls and regex-lite; refactor HTTP API
Switch reqwest to rustls-no-provider and add rustls crate; install
rustls provider at startup. Replace regex::Regex with regex_lite::Regex
across code. Consolidate api_get/post/put/delete into a single
api_request that takes a Method and optional body. Add .dockerignore and
UPX compression in Dockerfile. Remove unused domain/IDNA code, trim dead
helpers, tweak tokio flavor and release opt-level, and update tests to
use crate::test_client()
2026-03-25 14:49:47 -04:00

236 lines
6.1 KiB
Rust

// Verbosity levels
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Verbosity {
Quiet,
Notice,
Info,
Verbose,
}
// Emoji constants
pub const EMOJI_WARNING: &str = "\u{26A0}\u{FE0F}";
pub const EMOJI_ERROR: &str = "\u{274C}";
pub const EMOJI_LAUNCH: &str = "\u{1F680}";
pub const EMOJI_STOP: &str = "\u{1F6D1}";
pub const EMOJI_SLEEP: &str = "\u{1F634}";
pub const EMOJI_DETECT: &str = "\u{1F50D}";
pub const EMOJI_UPDATE: &str = "\u{2B06}\u{FE0F}";
pub const EMOJI_CREATE: &str = "\u{2795}";
pub const EMOJI_DELETE: &str = "\u{2796}";
pub const EMOJI_SKIP: &str = "\u{23ED}\u{FE0F}";
pub const EMOJI_NOTIFY: &str = "\u{1F514}";
pub const EMOJI_HEARTBEAT: &str = "\u{1F493}";
pub const EMOJI_CONFIG: &str = "\u{2699}\u{FE0F}";
const INDENT_PREFIX: &str = " ";
pub struct PP {
pub verbosity: Verbosity,
pub emoji: bool,
indent: usize,
}
impl PP {
pub fn new(emoji: bool, quiet: bool) -> Self {
Self {
verbosity: if quiet { Verbosity::Quiet } else { Verbosity::Verbose },
emoji,
indent: 0,
}
}
pub fn default_pp() -> Self {
Self::new(false, false)
}
pub fn is_showing(&self, level: Verbosity) -> bool {
self.verbosity >= level
}
pub fn indent(&self) -> PP {
PP {
verbosity: self.verbosity,
emoji: self.emoji,
indent: self.indent + 1,
}
}
fn output(&self, emoji: &str, msg: &str) {
let prefix = INDENT_PREFIX.repeat(self.indent);
if self.emoji && !emoji.is_empty() {
println!("{prefix}{emoji} {msg}");
} else {
println!("{prefix}{msg}");
}
}
fn output_err(&self, emoji: &str, msg: &str) {
let prefix = INDENT_PREFIX.repeat(self.indent);
if self.emoji && !emoji.is_empty() {
eprintln!("{prefix}{emoji} {msg}");
} else {
eprintln!("{prefix}{msg}");
}
}
pub fn infof(&self, emoji: &str, msg: &str) {
if self.is_showing(Verbosity::Info) {
self.output(emoji, msg);
}
}
pub fn noticef(&self, emoji: &str, msg: &str) {
if self.is_showing(Verbosity::Notice) {
self.output(emoji, msg);
}
}
pub fn warningf(&self, emoji: &str, msg: &str) {
self.output_err(emoji, msg);
}
pub fn errorf(&self, emoji: &str, msg: &str) {
self.output_err(emoji, msg);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_with_emoji_true() {
let pp = PP::new(true, false);
assert!(pp.emoji);
}
#[test]
fn new_with_emoji_false() {
let pp = PP::new(false, false);
assert!(!pp.emoji);
}
#[test]
fn new_quiet_true_sets_verbosity_quiet() {
let pp = PP::new(false, true);
assert_eq!(pp.verbosity, Verbosity::Quiet);
}
#[test]
fn new_quiet_false_sets_verbosity_verbose() {
let pp = PP::new(false, false);
assert_eq!(pp.verbosity, Verbosity::Verbose);
}
#[test]
fn quiet_shows_only_quiet_level() {
let pp = PP::new(false, true);
assert!(pp.is_showing(Verbosity::Quiet));
assert!(!pp.is_showing(Verbosity::Notice));
assert!(!pp.is_showing(Verbosity::Info));
assert!(!pp.is_showing(Verbosity::Verbose));
}
#[test]
fn verbose_shows_all_levels() {
let pp = PP::new(false, false);
assert!(pp.is_showing(Verbosity::Quiet));
assert!(pp.is_showing(Verbosity::Notice));
assert!(pp.is_showing(Verbosity::Info));
assert!(pp.is_showing(Verbosity::Verbose));
}
#[test]
fn notice_level_shows_quiet_and_notice_only() {
let mut pp = PP::new(false, false);
pp.verbosity = Verbosity::Notice;
assert!(pp.is_showing(Verbosity::Quiet));
assert!(pp.is_showing(Verbosity::Notice));
assert!(!pp.is_showing(Verbosity::Info));
assert!(!pp.is_showing(Verbosity::Verbose));
}
#[test]
fn info_level_shows_up_to_info() {
let mut pp = PP::new(false, false);
pp.verbosity = Verbosity::Info;
assert!(pp.is_showing(Verbosity::Quiet));
assert!(pp.is_showing(Verbosity::Notice));
assert!(pp.is_showing(Verbosity::Info));
assert!(!pp.is_showing(Verbosity::Verbose));
}
#[test]
fn indent_increments_indent_level() {
let pp = PP::new(true, false);
assert_eq!(pp.indent, 0);
let child = pp.indent();
assert_eq!(child.indent, 1);
let grandchild = child.indent();
assert_eq!(grandchild.indent, 2);
}
#[test]
fn indent_preserves_verbosity_and_emoji() {
let pp = PP::new(true, true);
let child = pp.indent();
assert_eq!(child.verbosity, pp.verbosity);
assert_eq!(child.emoji, pp.emoji);
}
#[test]
fn infof_does_not_panic_when_verbose() {
let pp = PP::new(false, false);
pp.infof("", "test info message");
}
#[test]
fn infof_does_not_panic_when_quiet() {
let pp = PP::new(false, true);
pp.infof("", "test info message");
}
#[test]
fn noticef_does_not_panic_when_verbose() {
let pp = PP::new(true, false);
pp.noticef(EMOJI_DETECT, "test notice message");
}
#[test]
fn noticef_does_not_panic_when_quiet() {
let pp = PP::new(false, true);
pp.noticef("", "test notice message");
}
#[test]
fn warningf_does_not_panic() {
let pp = PP::new(true, false);
pp.warningf(EMOJI_WARNING, "test warning");
}
#[test]
fn warningf_does_not_panic_when_quiet() {
let pp = PP::new(false, true);
pp.warningf("", "test warning");
}
#[test]
fn errorf_does_not_panic() {
let pp = PP::new(true, false);
pp.errorf(EMOJI_ERROR, "test error");
}
#[test]
fn errorf_does_not_panic_when_quiet() {
let pp = PP::new(false, true);
pp.errorf("", "test error");
}
#[test]
fn default_pp_is_verbose_no_emoji() {
let pp = PP::default_pp();
assert!(!pp.emoji);
assert_eq!(pp.verbosity, Verbosity::Verbose);
}
}