1
0
mirror of https://github.com/jht5945/rust_util.git synced 2026-01-14 00:00:04 +08:00

Compare commits

..

22 Commits

Author SHA1 Message Date
254585bb90 feat: v0.6.51 2026-01-04 23:16:31 +08:00
e13b2a3db4 feat: v0.6.50 2025-08-24 22:57:45 +08:00
e2b258ca09 feat: add env_var 2025-08-24 16:11:03 +08:00
4b596db8de feat: update cname 2025-07-26 10:45:54 +08:00
af4b91d4e9 feat: v0.5.48 2025-07-26 08:52:31 +08:00
c1ef4c4b53 feat: v0.6.47 2024-01-20 14:51:02 +08:00
ca597cf0d6 feat: v0.6.46 2023-10-28 16:30:32 +08:00
c56b7deaaf feat: fix util_term 2023-10-25 01:15:28 +08:00
16e859c785 feat: fix util_term 2023-10-25 01:13:36 +08:00
29519b3a43 feat: v0.6.44 2023-10-25 01:08:58 +08:00
32a2b529fd feat: v0.6.43, add trait UnixEpochTime 2023-10-15 10:35:33 +08:00
5cdad86ea6 feat: v0.6.42 2023-09-09 17:33:56 +08:00
7e5bec260f Update CNAME 2023-04-16 08:40:13 +08:00
30e9384505 feat: add page config 2021-07-05 00:05:40 +08:00
80ff02e6b8 feat: add logger to std err 2021-07-05 00:04:26 +08:00
7aa8f4c43c Create CNAME 2021-07-04 08:02:54 +08:00
efbbf4261a Set theme jekyll-theme-cayman 2021-07-04 08:02:33 +08:00
568a2173d5 feat: add opt_value_result! 2021-06-13 20:31:43 +08:00
c69cb1c35b fix: failure and exit 2021-06-01 00:02:04 +08:00
019a410750 fix: typo 2021-05-31 23:57:11 +08:00
8d4b8aa1e0 fix: clippy 2021-05-30 01:28:16 +08:00
c8a0f22425 feat: add runtime 2021-05-30 01:01:56 +08:00
20 changed files with 457 additions and 197 deletions

1
CNAME Normal file
View File

@@ -0,0 +1 @@
rust-util.ruststack.org

View File

@@ -1,6 +1,6 @@
[package]
name = "rust_util"
version = "0.6.35"
version = "0.6.51"
authors = ["Hatter Jiang <jht5945@gmail.com>"]
edition = "2018"
description = "Hatter's Rust Util"

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -13,6 +13,12 @@ impl Command for TestCommand {
fn run(&self, _arg_matches: &ArgMatches, _: &ArgMatches) -> CommandError {
println!("hello test!");
let a: Option<String> = None;
let b = move || -> rust_util::XResult<String> {
Ok(rust_util::opt_value_result!(a, "test: {}", 1))
};
let c = b();
println!("{:?}", c);
Ok(None)
}
}

View File

@@ -1,9 +1,22 @@
#[macro_use] extern crate rust_util;
use std::sync::mpsc::channel;
use std::thread;
use rust_util::{XResult, SimpleError};
use rust_util::util_msg::{set_logger_sender, set_logger_std_out};
// cargo run --example log
fn main() -> XResult<()> {
let (sender, receiver) = channel::<String>();
set_logger_sender(sender);
let _t = thread::spawn(move || {
loop {
let msg = receiver.recv();
println!("[RECV]: {:?}", msg)
}
});
std::env::set_var("LOGGER_LEVEL", "*");
println!(r##"env LOGGER_LEVEL set to:
debug or *
@@ -20,6 +33,11 @@ error or ^"##);
println!("{:?}", test_opt_result());
set_logger_std_out(false);
information!("Std err!");
warning!("Std err!");
set_logger_std_out(true);
simple_error!("helloworld {}", 1)
}

View File

@@ -20,6 +20,7 @@ pub mod util_git;
#[cfg(feature = "use_clap")]
pub mod util_clap;
pub mod util_tlv;
pub mod util_runtime;
/// iff!(condition, result_when_true, result_when_false)
#[macro_export] macro_rules! iff {
@@ -39,6 +40,12 @@ pub mod util_tlv;
#[macro_export] macro_rules! failure {
($($arg:tt)+) => ( rust_util::util_msg::print_error(&format!($($arg)+)); )
}
#[macro_export] macro_rules! println_ex {
($($arg:tt)+) => ( rust_util::util_msg::print_ex(&format!($($arg)+), true); )
}
#[macro_export] macro_rules! print_ex {
($($arg:tt)+) => ( rust_util::util_msg::print_ex(&format!($($arg)+), false); )
}
#[macro_export] macro_rules! debugging {
($($arg:tt)+) => ( rust_util::util_msg::when(rust_util::util_msg::MessageType::DEBUG, || {
rust_util::util_msg::print_debug(&format!($($arg)+))
@@ -47,6 +54,7 @@ pub mod util_tlv;
#[macro_export] macro_rules! failure_and_exit {
($($arg:tt)+) => ( {
rust_util::util_msg::print_error(&format!($($arg)+));
rust_util::util_runtime::invoke_callbacks();
std::process::exit(-1);
} )
}
@@ -64,6 +72,17 @@ pub fn new_box_ioerror(m: &str) -> Box<dyn Error> {
Box::new(IoError::new(ErrorKind::Other, m))
}
#[macro_export] macro_rules! opt_value_result {
($ex: expr, $($arg:tt)+) => (
match $ex {
Some(o) => o,
None => return Err(rust_util::SimpleError::new(
format!("{}, file: {}, line: {}", format!($($arg)+), file!(), line!())
).into()),
}
)
}
#[macro_export] macro_rules! opt_result {
($ex: expr, $($arg:tt)+) => (
match $ex {

View File

@@ -16,6 +16,7 @@ pub trait DefaultCommand {
}
pub struct DefaultCommandImpl;
impl DefaultCommand for DefaultCommandImpl {
fn process_command<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a> {
app.arg(Arg::with_name("verbose").long("verbose").short("v").multiple(true).help("Show verbose info"))
@@ -40,7 +41,7 @@ impl CommandExecutor {
}
pub fn new(default_cmd: Option<Box<dyn DefaultCommand>>) -> Self {
CommandExecutor{
CommandExecutor {
default_cmd,
commands: Vec::new(),
}
@@ -60,8 +61,8 @@ impl CommandExecutor {
pub fn run(&self) -> XResult<()> {
let app = App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"));
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"));
self.run_with(app)
}
@@ -77,18 +78,25 @@ impl CommandExecutor {
if let Some(sub_cmd_matches) = matches.subcommand_matches(command.name()) {
match command.run(&matches, sub_cmd_matches)? {
None => return Ok(()),
Some(code) => process::exit(code),
Some(code) => {
crate::util_runtime::invoke_callbacks();
process::exit(code);
}
}
}
}
match &self.default_cmd {
None => {
util_msg::print_error("No default command, please try help (--help)");
crate::util_runtime::invoke_callbacks();
process::exit(1);
},
}
Some(default_cmd) => match default_cmd.run(&matches)? {
None => return Ok(()),
Some(code) => process::exit(code),
Some(code) => {
crate::util_runtime::invoke_callbacks();
process::exit(code);
}
},
}
}

View File

@@ -1,6 +1,6 @@
use std::io::{self, Error, ErrorKind};
use std::process::{Command, ExitStatus, Output};
use crate::util_msg::{print_debug, print_error, MessageType};
use crate::util_msg::{print_debug, print_error, MessageType, print_message};
pub fn run_command_or_exit(cmd: &str, args: &[&str]) -> Output {
let mut c = Command::new(cmd);
@@ -12,23 +12,30 @@ pub fn run_command_or_exit(cmd: &str, args: &[&str]) -> Output {
match output {
Err(e) => {
print_error(&format!("Run command: {:?}, failed: {}", c, e));
crate::util_runtime::invoke_callbacks();
std::process::exit(-1);
},
}
Ok(output) => {
if !output.status.success() {
print_error(&format!(r##"Run command failed, code: {:?}
print_output(MessageType::ERROR, &output);
}
output
}
}
}
pub fn print_output(message_type: MessageType, output: &Output) {
crate::util_msg::when(message_type, || {
print_message(message_type, &format!(r##"Run command failed, code: {:?}
-----std out---------------------------------------------------------------
{}
-----std err---------------------------------------------------------------
{}
---------------------------------------------------------------------------"##,
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)));
}
output
}
}
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)));
});
}
pub fn run_command_and_wait(cmd: &mut Command) -> io::Result<ExitStatus> {

View File

@@ -1,10 +1,31 @@
use std::env;
pub fn is_env_on(var: &str) -> bool {
env::var(var).ok().map(|val| is_on(&val)).unwrap_or(false)
env_var(var).map(|val| is_on(&val)).unwrap_or(false)
}
pub fn is_env_off(var: &str) -> bool {
env_var(var).map(|val| is_off(&val)).unwrap_or(false)
}
pub fn is_on(val: &str) -> bool {
let lower_val = val.to_lowercase();
vec!["true", "yes", "1"].iter().any(|x| *x == lower_val)
["true", "yes", "1"].iter().any(|x| *x == lower_val)
}
pub fn is_off(val: &str) -> bool {
let lower_val = val.to_lowercase();
["false", "no", "0"].iter().any(|x| *x == lower_val)
}
pub fn env_var(var: &str) -> Option<String> {
let var_from_env = env::var(var).ok();
if var_from_env.is_some() {
return var_from_env;
}
let var_content = crate::util_file::read_file_content(&format!("~/.config/envs/{}", var));
if let Ok(var_content) = var_content {
return Some(var_content.trim().to_string());
}
None
}

View File

@@ -1,8 +1,7 @@
use std::env;
use std::fs::{self, File};
use std::io::{Lines, BufReader};
use std::path::{Path, PathBuf};
use crate::{iff, util_os, util_io, util_msg, new_box_ioerror, XResult};
use crate::{util_os, util_io, util_msg, new_box_ioerror, XResult};
pub struct JoinFilesReader {
files: Vec<String>,
@@ -11,14 +10,13 @@ pub struct JoinFilesReader {
}
fn open_file_as_lines(f: &str) -> XResult<Lines<BufReader<File>>> {
let f = File::open(&f)?;
let f = File::open(f)?;
let br = BufReader::new(f);
use std::io::BufRead;
Ok(br.lines())
}
impl JoinFilesReader {
pub fn new(fns: &[&str]) -> XResult<Self> {
let mut files: Vec<String> = vec![];
for f in fns {
@@ -49,10 +47,11 @@ impl Iterator for JoinFilesReader {
} else {
// if open file failed, will not continue read files
self.file_lines = Some(Box::new(match open_file_as_lines(&self.files[self.file_ptr]) {
Ok(ln) => ln, Err(e) => return Some(Err(e)),
Ok(ln) => ln,
Err(e) => return Some(Err(e)),
}));
}
},
}
},
None => return None,
}
@@ -129,13 +128,15 @@ pub fn locate_file(files: &[String]) -> Option<PathBuf> {
None
}
#[deprecated]
pub fn get_home_str() -> Option<String> {
iff!(util_os::is_macos_or_linux(), env::var("HOME").ok(), None)
util_os::get_user_home()
}
pub fn resolve_file_path(path: &str) -> String {
let home_path = match get_home_str() {
Some(p) => p, None => return path.to_owned(),
let home_path = match util_os::get_user_home() {
Some(p) => p,
None => return path.to_owned(),
};
match path {
"~" => home_path,
@@ -145,13 +146,13 @@ pub fn resolve_file_path(path: &str) -> String {
}
pub fn get_home_path() -> Option<PathBuf> {
Some(PathBuf::from(get_home_str()?))
Some(PathBuf::from(util_os::get_user_home()?))
}
pub fn get_absolute_path(path: &str) -> Option<PathBuf> {
match path {
"~" => Some(PathBuf::from(get_home_str()?)),
path if path.starts_with("~/") => Some(PathBuf::from(&format!("{}/{}", get_home_str()?, &path[2..]))),
"~" => Some(PathBuf::from(util_os::get_user_home()?)),
path if path.starts_with("~/") => Some(PathBuf::from(&format!("{}/{}", util_os::get_user_home()?, &path[2..]))),
path => fs::canonicalize(path).ok(),
}
}
@@ -168,47 +169,49 @@ pub fn is_symlink(path: &Path) -> bool {
}
pub fn walk_dir<FError, FProcess, FFilter>(dir: &Path,
func_walk_error: &FError,
func_process_file: &FProcess,
func_filter_dir: &FFilter) -> XResult<()>
where FError: Fn(&Path, Box<dyn std::error::Error>),
FProcess: Fn(&Path),
FFilter: Fn(&Path) -> bool {
func_walk_error: &FError,
func_process_file: &FProcess,
func_filter_dir: &FFilter) -> XResult<()>
where FError: Fn(&Path, Box<dyn std::error::Error>),
FProcess: Fn(&Path),
FFilter: Fn(&Path) -> bool {
walk_dir_with_depth_check(&mut 0u32, dir, func_walk_error, func_process_file, func_filter_dir)
}
fn walk_dir_with_depth_check<FError, FProcess, FFilter>(depth: &mut u32, dir: &Path,
func_walk_error: &FError,
func_process_file: &FProcess,
func_filter_dir: &FFilter) -> XResult<()>
where FError: Fn(&Path, Box<dyn std::error::Error>),
FProcess: Fn(&Path),
FFilter: Fn(&Path) -> bool {
func_walk_error: &FError,
func_process_file: &FProcess,
func_filter_dir: &FFilter) -> XResult<()>
where FError: Fn(&Path, Box<dyn std::error::Error>),
FProcess: Fn(&Path),
FFilter: Fn(&Path) -> bool {
if *depth > 100u32 {
return Err(new_box_ioerror(&format!("Depth exceed, depth: {}, path: {:?}", *depth, dir)));
}
let read_dir = match dir.read_dir() {
Ok(rd) => rd, Err(err) => {
func_walk_error(&dir, Box::new(err));
Ok(rd) => rd,
Err(err) => {
func_walk_error(dir, Box::new(err));
return Ok(());
},
}
};
for dir_entry_item in read_dir {
let dir_entry = match dir_entry_item {
Ok(item) => item, Err(err) => {
func_walk_error(&dir, Box::new(err));
Ok(item) => item,
Err(err) => {
func_walk_error(dir, Box::new(err));
continue; // Ok?
},
}
};
let path_buf = dir_entry.path();
let sub_dir = path_buf.as_path();
if sub_dir.is_file() {
func_process_file(&sub_dir);
} else if sub_dir.is_dir() && func_filter_dir(&sub_dir) {
func_process_file(sub_dir);
} else if sub_dir.is_dir() && func_filter_dir(sub_dir) {
*depth += 1;
if let Err(err) = walk_dir_with_depth_check(depth, &sub_dir, func_walk_error, func_process_file, func_filter_dir) {
func_walk_error(&sub_dir, err);
if let Err(err) = walk_dir_with_depth_check(depth, sub_dir, func_walk_error, func_process_file, func_filter_dir) {
func_walk_error(sub_dir, err);
}
*depth -= 1;
} // should process else ? not file, dir

View File

@@ -1,5 +1,6 @@
use std::process::Command;
use crate::{XResult, util_msg, util_cmd};
use crate::{util_cmd, util_msg, XResult};
const LANG: &str = "LANG";
const EN_US: &str = "en_US";
@@ -16,7 +17,7 @@ pub struct GitStatusChange {
impl GitStatusChange {
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.modified.is_empty()
&& self.deleted.is_empty() && self.untracked.is_empty()
&& self.deleted.is_empty() && self.untracked.is_empty()
}
}
@@ -58,7 +59,7 @@ pub fn git_branch(working_dir: Option<&str>) -> XResult<Option<String>> {
util_msg::print_info(&format!("Exec: {:?}", cmd));
let output = cmd.output()?;
let git_branch = String::from_utf8(output.stdout)?;
let current_branch = git_branch.lines().find(|ln| ln.trim().starts_with("*"));
let current_branch = git_branch.lines().find(|ln| ln.trim().starts_with('*'));
Ok(current_branch.map(|ln| ln.trim()[1..].trim().into()))
}
@@ -75,7 +76,7 @@ pub fn git_add(working_dir: Option<&str>, files: &[String]) {
let mut cmd = new_git_command(working_dir);
cmd.arg("add");
for f in files {
cmd.arg(&f);
cmd.arg(f);
}
util_msg::print_info(&format!("Exec: {:?}", cmd));
if let Err(e) = util_cmd::run_command_and_wait(&mut cmd) {
@@ -89,7 +90,7 @@ pub fn git_commit(working_dir: Option<&str>, message: &str, files: &[String]) {
cmd.arg("-m");
cmd.arg(message);
for f in files {
cmd.arg(&f);
cmd.arg(f);
}
util_msg::print_info(&format!("Exec: {:?}", cmd));
if let Err(e) = util_cmd::run_command_and_wait(&mut cmd) {
@@ -100,19 +101,19 @@ pub fn git_commit(working_dir: Option<&str>, message: &str, files: &[String]) {
fn parse_git_status_change(git_status: &str) -> XResult<GitStatusChange> {
let mut git_status_change: GitStatusChange = Default::default();
for ln in git_status.lines() {
if ln.starts_with("\t") {
if ln.starts_with('\t') {
let ln = ln.trim();
if ln.starts_with("new file:") {
let f = ln["new file:".len()..].trim();
if let Some(new_file) = ln.strip_prefix("new file:") {
let f = new_file.trim();
git_status_change.added.push(f.to_owned());
} else if ln.starts_with("deleted:") {
let f = ln["deleted:".len()..].trim();
} else if let Some(deleted) = ln.strip_prefix("deleted:") {
let f = deleted.trim();
git_status_change.deleted.push(f.to_owned());
} else if ln.starts_with("modified:") {
let f = ln["modified:".len()..].trim();
} else if let Some(modified) = ln.strip_prefix("modified:") {
let f = modified.trim();
git_status_change.modified.push(f.to_owned());
} else if ln.starts_with("renamed:") {
let f = ln["renamed:".len()..].trim();
} else if let Some(renamed) = ln.strip_prefix("renamed:") {
let f = renamed.trim();
let mut fs = f.split("->");
let fa = fs.next();
let fb = fs.next();

View File

@@ -1,11 +1,11 @@
use std::io::{self, Write, ErrorKind, prelude::*};
use std::fs::File;
use std::time::{SystemTime, Duration};
use std::io::{self, ErrorKind, prelude::*, Write};
use std::time::{Duration, SystemTime};
use crate::{SimpleError, XResult};
use crate::util_size;
use crate::util_msg;
use crate::util_file;
use crate::util_msg;
use crate::util_size;
pub const DEFAULT_BUF_SIZE: usize = 8 * 1024;
@@ -13,15 +13,13 @@ pub fn stdout_or_file_write(file: Option<&str>, overwrite: bool) -> XResult<Box<
match file {
None => Ok(Box::new(io::stdout())),
Some(output) => {
if let Ok(_) = File::open(output) {
if !overwrite {
return Err(SimpleError::new(format!("File exists: {}", output)).into());
}
if File::open(output).is_ok() && !overwrite {
return Err(SimpleError::new(format!("File exists: {}", output)).into());
}
Ok(Box::new(File::create(output).map_err(|e| {
SimpleError::new(format!("Create file: {}, failed: {}", output, e))
})?))
},
}
}
}
@@ -59,10 +57,7 @@ impl PrintStatusContext {
if written > self.total_written_bytes && (written - self.total_written_bytes > self.print_interval_bytes) {
return true;
}
match last_print_cost.as_millis() {
m if m > self.print_interval_time.as_millis() => true,
_ => false,
}
last_print_cost.as_millis() > self.print_interval_time.as_millis()
};
if should_update_status_line() {
self.last_print_time = now;
@@ -84,7 +79,7 @@ pub fn get_read_stdin_or_file(file: &str) -> XResult<Box<dyn Read>> {
if file.is_empty() {
Ok(Box::new(io::stdin()))
} else {
match File::open(&util_file::resolve_file_path(file)) {
match File::open(util_file::resolve_file_path(file)) {
Ok(f) => Ok(Box::new(f)),
Err(err) => Err(SimpleError::new(format!("Open file {}, erorr: {}", file, err)).into()),
}
@@ -104,18 +99,18 @@ pub fn read_to_bytes(read: &mut dyn Read) -> XResult<Vec<u8>> {
}
pub fn copy_io_default<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, total: i64) -> io::Result<u64>
where R: io::Read, W: io::Write {
where R: io::Read, W: io::Write {
copy_io_with_head(reader, writer, total, "Downloading", &mut PrintStatusContext::default())
}
pub fn copy_io<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, total: i64, print_status_context: &mut PrintStatusContext)
-> io::Result<u64>
where R: io::Read, W: io::Write {
-> io::Result<u64>
where R: io::Read, W: io::Write {
copy_io_with_head(reader, writer, total, "Downloading", print_status_context)
}
pub fn copy_io_with_head<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, total: i64, head: &str, print_status_context: &mut PrintStatusContext) -> io::Result<u64>
where R: io::Read, W: io::Write {
where R: io::Read, W: io::Write {
let written = copy_io_callback(reader, writer, total, print_status_context, &mut |total, written, _len, print_status_context| {
print_status_last_line(head, total, written as i64, print_status_context);
});
@@ -124,9 +119,9 @@ pub fn copy_io_with_head<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, t
}
pub fn copy_io_callback<R: ?Sized, W: ?Sized, FCallback>(reader: &mut R, writer: &mut W, total: i64, print_status_context: &mut PrintStatusContext, callback: &mut FCallback) -> io::Result<u64>
where R: io::Read,
W: io::Write,
FCallback: Fn(i64, u64, usize, &mut PrintStatusContext) {
where R: io::Read,
W: io::Write,
FCallback: Fn(i64, u64, usize, &mut PrintStatusContext) {
let mut written = 0u64;
let mut buf: [u8; DEFAULT_BUF_SIZE] = [0u8; DEFAULT_BUF_SIZE];
loop {
@@ -150,18 +145,18 @@ pub fn print_status_last_line(head: &str, total: i64, written: i64, print_status
}
let cost_as_secs = cost.as_secs();
if cost_as_secs > 0 {
download_speed = format!("{}/s", util_size::get_display_size((written / (cost_as_secs as i64)) as i64));
download_speed = format!("{}/s", util_size::get_display_size(written / (cost_as_secs as i64)));
}
if total > 0 {
util_msg::print_lastline(&format!("{}, Total: {}, Finished: {}, Speed: {}",
head,
util_size::get_display_size(total),
util_size::get_display_size(written),
download_speed));
head,
util_size::get_display_size(total),
util_size::get_display_size(written),
download_speed));
} else {
util_msg::print_lastline(&format!("{}, Finished: {}, Speed: {}",
head,
util_size::get_display_size(written),
download_speed));
head,
util_size::get_display_size(written),
download_speed));
}
}

View File

@@ -1,13 +1,31 @@
use std::env;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, RwLock};
use std::sync::mpsc::Sender;
lazy_static! {
pub static ref IS_ATTY: bool = is_atty();
static ref LOGGER_LEVEL: MessageType = get_logger_level();
static ref LOGGER_SENDER: Arc<RwLock<Option<Sender<String>>>> = Arc::new(RwLock::new(None));
static ref LOGGER_TO_STDOUT: Arc<RwLock<bool>> = Arc::new(RwLock::new(true));
static ref PRINT_MESSAGE_LOCK: Arc<Mutex<()>> = Arc::new(Mutex::new(()));
}
pub fn set_logger_sender(sender: Sender<String>) {
let mut logger_sender_opt = LOGGER_SENDER.write().unwrap();
logger_sender_opt.replace(sender);
}
pub fn set_logger_std_out(is_std_out: bool) {
let mut std_out = LOGGER_TO_STDOUT.write().unwrap();
*std_out = is_std_out;
}
pub fn get_logger_std_out() -> bool {
*LOGGER_TO_STDOUT.read().unwrap()
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Copy)]
pub enum MessageType { DEBUG, INFO, OK, WARN, ERROR }
@@ -15,9 +33,9 @@ impl MessageType {
pub fn get_u8_value(&self) -> u8 {
match self {
MessageType::DEBUG => 0,
MessageType::INFO => 1,
MessageType::OK => 2,
MessageType::WARN => 3,
MessageType::INFO => 1,
MessageType::OK => 2,
MessageType::WARN => 3,
MessageType::ERROR => 4,
}
}
@@ -25,18 +43,18 @@ impl MessageType {
pub fn get_logger_level() -> MessageType {
if let Some(logger_level) = env::var("LOGGER_LEVEL").ok()
.or_else(|| env::var("LOGGER").ok())
.or_else(|| env::var("LEVEL").ok()) {
.or_else(|| env::var("LOGGER").ok())
.or_else(|| env::var("LEVEL").ok()) {
match logger_level.trim().to_lowercase().as_str() {
"debug" | "*" => MessageType::DEBUG,
"info" | "?" => MessageType::INFO,
"ok" | "#" => MessageType::OK,
"warn" | "!" => MessageType::WARN,
"info" | "?" => MessageType::INFO,
"ok" | "#" => MessageType::OK,
"warn" | "!" => MessageType::WARN,
"error" | "^" => MessageType::ERROR,
_ => {
print_message_ex(Some(term::color::YELLOW), "[WARN ]", &format!("Unknown logger level: {}, set to default INFO", logger_level));
MessageType::INFO
},
}
}
} else {
MessageType::INFO
@@ -44,46 +62,99 @@ pub fn get_logger_level() -> MessageType {
}
pub fn is_atty() -> bool {
let stdout_fileno = unsafe { libc::isatty(libc::STDOUT_FILENO as i32) };
let stdout_fileno = unsafe { libc::isatty(libc::STDOUT_FILENO) };
stdout_fileno != 0
}
pub fn print_color(color: Option<term::color::Color>, is_bold: bool, m: &str) {
match term::stdout() {
Some(mut t) => {
if *IS_ATTY {
if let Some(c) = color {
t.fg(c).ok();
pub fn print_color(is_std_out: bool, color: Option<term::color::Color>, is_bold: bool, m: &str) {
if is_std_out {
match term::stdout() {
Some(mut t) => {
if *IS_ATTY {
if let Some(c) = color {
t.fg(c).ok();
}
if is_bold {
t.attr(term::Attr::Bold).ok();
}
write!(t, "{}", m).ok();
t.reset().ok();
} else {
write!(t, "{}", m).ok();
}
if is_bold {
t.attr(term::Attr::Bold).ok();
}
write!(t, "{}", m).ok();
t.reset().ok();
} else {
write!(t, "{}", m).ok();
}
},
None => print!("{}", m),
None => print!("{}", m),
}
} else {
match term::stderr() {
Some(mut t) => {
if *IS_ATTY {
if let Some(c) = color {
t.fg(c).ok();
}
if is_bold {
t.attr(term::Attr::Bold).ok();
}
write!(t, "{}", m).ok();
t.reset().ok();
} else {
write!(t, "{}", m).ok();
}
}
None => eprint!("{}", m),
}
}
}
pub fn print_color_and_flush(color: Option<term::color::Color>, is_bold: bool, m: &str) {
print_color(color, is_bold, m);
print_color(true, color, is_bold, m);
flush_stdout();
}
pub fn print_message_ex(color: Option<term::color::Color>, h: &str, message: &str) {
{
let logger_sender_opt = LOGGER_SENDER.read().unwrap();
if let Some(logger_sender) = &*logger_sender_opt {
logger_sender.send(format!("{} {}", h, message)).ok();
return;
}
}
let is_std_out = get_logger_std_out();
let mut lock = PRINT_MESSAGE_LOCK.lock().unwrap();
print_color(color, true, h);
println!(" {}", message);
print_color(is_std_out, color, true, h);
if is_std_out {
println!(" {}", message);
} else {
eprintln!(" {}", message)
}
*lock = ();
}
pub fn print_ok (message: &str) { print_message(MessageType::OK, message); }
pub fn print_warn (message: &str) { print_message(MessageType::WARN, message); }
pub fn print_ex(message: &str, new_line: bool) {
if get_logger_std_out() {
if new_line {
println!("{}", message)
} else {
print!("{}", message)
}
} else {
#[allow(clippy::collapsible_else_if)]
if new_line {
eprintln!("{}", message)
} else {
eprint!("{}", message)
}
}
}
pub fn print_ok(message: &str) { print_message(MessageType::OK, message); }
pub fn print_warn(message: &str) { print_message(MessageType::WARN, message); }
pub fn print_error(message: &str) { print_message(MessageType::ERROR, message); }
pub fn print_info (message: &str) { print_message(MessageType::INFO, message); }
pub fn print_info(message: &str) { print_message(MessageType::INFO, message); }
pub fn print_debug(message: &str) { print_message(MessageType::DEBUG, message); }
#[inline]
@@ -92,7 +163,11 @@ pub fn is_logger_level_enabled(mt: MessageType) -> bool {
mt.get_u8_value() >= logger_level.get_u8_value()
}
pub fn when<F>(mt: MessageType, f: F) where F: Fn() -> () {
pub fn when_debug<F>(f: F) where F: Fn() {
when(MessageType::DEBUG, f)
}
pub fn when<F>(mt: MessageType, f: F) where F: Fn() {
if is_logger_level_enabled(mt) {
f();
}
@@ -101,10 +176,10 @@ pub fn when<F>(mt: MessageType, f: F) where F: Fn() -> () {
pub fn print_message(mt: MessageType, message: &str) {
if is_logger_level_enabled(mt) {
match mt {
MessageType::OK => print_message_ex(Some(term::color::GREEN), "[OK ]", message),
MessageType::WARN => print_message_ex(Some(term::color::YELLOW), "[WARN ]", message),
MessageType::ERROR => print_message_ex(Some(term::color::RED), "[ERROR]", message),
MessageType::INFO => print_message_ex(None, "[INFO ]", message),
MessageType::OK => print_message_ex(Some(term::color::GREEN), "[OK ]", message),
MessageType::WARN => print_message_ex(Some(term::color::YELLOW), "[WARN ]", message),
MessageType::ERROR => print_message_ex(Some(term::color::RED), "[ERROR]", message),
MessageType::INFO => print_message_ex(None, "[INFO ]", message),
MessageType::DEBUG => print_message_ex(Some(term::color::MAGENTA), "[DEBUG]", message),
}
}
@@ -147,13 +222,13 @@ pub fn get_term_width_message(message: &str, left: usize) -> String {
Some((w, _h)) => {
let len = message.len();
if w > len {
return message.to_string();
return message.to_string();
}
let mut s = String::new();
s.push_str(&message[0..find_char_boundary(&message, w - 10 - 5 - left)]);
s.push_str(&message[0..find_char_boundary(message, w - 10 - 5 - left)]);
s.push_str("[...]");
s.push_str(&message[find_char_boundary(&message, len - 10)..]);
s.push_str(&message[find_char_boundary(message, len - 10)..]);
s
},
}
}
}

View File

@@ -11,6 +11,12 @@ pub struct IpAndIpMaskMatcher {
ip_and_ip_mask_set: HashSet<u64>,
}
impl Default for IpAndIpMaskMatcher {
fn default() -> Self {
Self::new()
}
}
impl IpAndIpMaskMatcher {
pub fn new() -> Self {
IpAndIpMaskMatcher {
@@ -79,7 +85,7 @@ impl IpAddress {
pub fn to_u32(&self) -> u32 {
match self {
IpAddress::Ipv4(ipv4) => {
u32::from_be_bytes(ipv4.clone())
u32::from_be_bytes(*ipv4)
}
}
}
@@ -133,7 +139,7 @@ impl IpAddressMask {
let socket_addr_v4_octets = socket_addr_v4.ip().octets();
match self {
IpAddressMask::Ipv4(self_ipv4_octets, mask) => {
let self_ipv4_u32 = ipv4_to_u32(&self_ipv4_octets);
let self_ipv4_u32 = ipv4_to_u32(self_ipv4_octets);
let addr_ipv4_u32 = ipv4_to_u32(&socket_addr_v4_octets);
let mask_u32 = ipv4_mask(*mask);
self_ipv4_u32 & mask_u32 == addr_ipv4_u32 & mask_u32

View File

@@ -1,3 +1,7 @@
use std::env;
use std::path::PathBuf;
use crate::iff;
pub fn is_macos() -> bool {
cfg!(target_os = "macos")
@@ -10,3 +14,13 @@ pub fn is_linux() -> bool {
pub fn is_macos_or_linux() -> bool {
is_macos() || is_linux()
}
pub fn get_user_home() -> Option<String> {
iff!(is_macos_or_linux(), env::var("HOME").ok(), None)
}
pub fn get_full_work_dir() -> Option<String> {
PathBuf::from(".").canonicalize().ok().and_then(|p| {
p.to_str().map(ToString::to_string)
})
}

24
src/util_runtime.rs Normal file
View File

@@ -0,0 +1,24 @@
use std::sync::Mutex;
use crate::util_msg::MessageType;
lazy_static! {
static ref EXIT_CALLBACK: Mutex<Vec<Box<dyn Fn() + Send + 'static>>> = Mutex::new(vec![]);
}
pub fn register_callback<F>(f: F) where F: Fn() + Send + 'static {
let mut exit_callbacks = EXIT_CALLBACK.lock().unwrap();
exit_callbacks.push(Box::new(f));
}
pub fn invoke_callbacks() {
let mut exit_callbacks = EXIT_CALLBACK.lock().unwrap();
let total = exit_callbacks.len();
let mut index = 0;
while exit_callbacks.len() > 0 {
crate::util_msg::when(MessageType::DEBUG, || {
crate::util_msg::print_debug(&format!("Running exit callbacks: {} of {}", index, total));
});
exit_callbacks.remove(0)();
index += 1;
}
}

View File

@@ -13,7 +13,7 @@ pub fn parse_size(size: &str) -> XResult<i64> {
lower_size.ends_with('b'), &lower_size[0..lower_size.len()-1], &lower_size
);
let parse_and_process_size = |mul: i64| -> XResult<i64> {
Ok((mul as f64 * no_last_b_size[0..no_last_b_size.len()-1].parse::<f64>()?) as i64)
Ok((mul as f64 * no_last_b_size[0..no_last_b_size.len() - 1].parse::<f64>()?) as i64)
};
match no_last_b_size.chars().last() {
Some('k') => parse_and_process_size(SIZE_KB),
@@ -27,17 +27,17 @@ pub fn parse_size(size: &str) -> XResult<i64> {
pub fn get_display_size(size: i64) -> String {
if size < SIZE_KB {
size.to_string()
format!("{} {}", size, crate::iff!(size == 1, "byte", "bytes" ))
} else if size < SIZE_MB {
format!("{:.*}KB", 2, (size as f64) / SIZE_KB as f64)
format!("{:.*}KiB", 2, (size as f64) / SIZE_KB as f64)
} else if size < SIZE_GB {
format!("{:.*}MB", 2, (size as f64) / SIZE_MB as f64)
format!("{:.*}MiB", 2, (size as f64) / SIZE_MB as f64)
} else if size < SIZE_TB {
format!("{:.*}GB", 2, (size as f64) / SIZE_GB as f64)
format!("{:.*}GiB", 2, (size as f64) / SIZE_GB as f64)
} else if size < SIZE_PB {
format!("{:.*}TB", 2, (size as f64) / SIZE_TB as f64)
format!("{:.*}TiB", 2, (size as f64) / SIZE_TB as f64)
} else {
format!("{:.*}PB", 2, (size as f64) / SIZE_PB as f64)
format!("{:.*}PiB", 2, (size as f64) / SIZE_PB as f64)
}
}
@@ -54,11 +54,11 @@ fn test_parse_size() {
#[test]
fn test_get_display_size() {
assert_eq!(get_display_size(0), "0");
assert_eq!(get_display_size(111), "111");
assert_eq!(get_display_size(1024), "1.00KB");
assert_eq!(get_display_size(1024 * 1024), "1.00MB");
assert_eq!(get_display_size(1024 * 1024 * 1024), "1.00GB");
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024), "1.00TB");
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024 * 1024), "1.00PB");
assert_eq!(get_display_size(0), "0 bytes");
assert_eq!(get_display_size(111), "111 bytes");
assert_eq!(get_display_size(1024), "1.00KiB");
assert_eq!(get_display_size(1024 * 1024), "1.00MiB");
assert_eq!(get_display_size(1024 * 1024 * 1024), "1.00GiB");
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024), "1.00TiB");
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024 * 1024), "1.00PiB");
}

View File

@@ -1,29 +1,6 @@
/// Split string to lines, splited by '\r', '\n' or "\r\n"
pub fn read_str_to_lines(s: &str) -> Vec<String> {
s.lines().map(|ln| ln.to_owned()).collect()
// let mut r = vec![];
// let mut line = String::new();
// let mut cs = s.chars().peekable();
// while let Some(c) = cs.next() {
// if c == '\n' || c == '\r' {
// r.push(line.clone());
// line.clear();
// if c == '\r' {
// if let Some(nc) = cs.peek() {
// if *nc == '\n' {
// cs.next();
// }
// }
// }
// } else {
// line.push(c);
// }
// }
// if !line.is_empty() {
// r.push(line);
// }
// r
}
pub fn split_kv(s: &str, split: char) -> (String, String) {
@@ -61,25 +38,30 @@ fn test_read_str_to_lines() {
let s = "";
let lines = read_str_to_lines(s);
assert_eq!(lines.len(), 0);
} {
}
{
let s = "\n";
let lines = read_str_to_lines(s);
assert_eq!(lines.len(), 1);
} {
}
{
let s = "\r";
let lines = read_str_to_lines(s);
assert_eq!(lines.len(), 1);
} {
}
{
let s = "\r\n";
let lines = read_str_to_lines(s);
assert_eq!(lines.len(), 1);
} {
}
{
let s = "aa\r\nbb";
let lines = read_str_to_lines(s);
assert_eq!(lines.len(), 2);
assert_eq!(lines[0], "aa");
assert_eq!(lines[1], "bb");
} {
}
{
let s = "aa\r\nbb\ncc";
let lines = read_str_to_lines(s);
assert_eq!(lines.len(), 3);

View File

@@ -1,23 +1,58 @@
use std::io::{self, Write};
pub const RED: &str = "\x1B[91m";
pub const GREEN: &str = "\x1B[92m";
use crate::util_msg;
pub const STD_BLACK: &str = "\x1B[30m";
pub const STD_RED: &str = "\x1B[31m";
pub const STD_GREEN: &str = "\x1B[32m";
pub const STD_YELLOW: &str = "\x1B[33m";
pub const STD_BLUE: &str = "\x1B[34m";
pub const STD_MAGENTA: &str = "\x1B[35m"; // 品红色/洋红
pub const STD_CYAN: &str = "\x1B[36m"; // 青色
pub const STD_WHITE: &str = "\x1B[37m";
pub const BLACK: &str = "\x1B[90m";
pub const RED: &str = "\x1B[91m";
pub const GREEN: &str = "\x1B[92m";
pub const YELLOW: &str = "\x1B[93m";
pub const BOLD: &str = "\x1B[1m";
pub const UNDER: &str = "\x1B[4m";
pub const END: &str = "\x1B[0m";
pub const BLUE: &str = "\x1B[94m";
pub const MAGENTA: &str = "\x1B[95m";
pub const CYAN: &str = "\x1B[96m";
pub const WHITE: &str = "\x1B[97m";
pub const BG_STD_BLACK: &str = "\x1B[40m";
pub const BG_STD_RED: &str = "\x1B[41m";
pub const BG_STD_GREEN: &str = "\x1B[42m";
pub const BG_STD_YELLOW: &str = "\x1B[43m";
pub const BG_STD_BLUE: &str = "\x1B[44m";
pub const BG_STD_MAGENTA: &str = "\x1B[45m";
pub const BG_STD_CYAN: &str = "\x1B[46m";
pub const BG_STD_WHITE: &str = "\x1B[47m";
pub const BG_BLACK: &str = "\x1B[100m";
pub const BG_RED: &str = "\x1B[101m";
pub const BG_GREEN: &str = "\x1B[102m";
pub const BG_YELLOW: &str = "\x1B[103m";
pub const BG_BLUE: &str = "\x1B[104m";
pub const BG_MAGENTA: &str = "\x1B[105m";
pub const BG_CYAN: &str = "\x1B[106m";
pub const BG_WHITE: &str = "\x1B[107m";
pub const BOLD: &str = "\x1B[1m";
pub const UNDER: &str = "\x1B[4m";
pub const END: &str = "\x1B[0m";
pub fn read_yes_no(hint: &str) -> bool {
loop {
print!("{} (Yes/No): ", hint);
util_msg::print_ex(&format!("{} (Yes/No): ", hint), false);
io::stdout().flush().ok();
let mut buff = String::new();
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
let buff = buff.trim().to_lowercase();
if vec!["y", "yes"].contains(&buff.as_str()) {
if ["y", "yes"].contains(&buff.as_str()) {
return true;
}
if vec!["n", "no"].contains(&buff.as_str()) {
if ["n", "no"].contains(&buff.as_str()) {
return false;
}
}

View File

@@ -1,11 +1,44 @@
use std::time::{SystemTime, Duration};
use std::time::{Duration, SystemTime};
pub trait UnixEpochTime {
fn to_secs(&self) -> Option<u64>;
fn to_millis(&self) -> Option<u64>;
fn from_secs(secs: u64) -> Self;
fn from_millis(millis: u64) -> Self;
}
impl UnixEpochTime for SystemTime {
fn to_secs(&self) -> Option<u64> {
self.duration_since(SystemTime::UNIX_EPOCH).ok().map(|d| d.as_secs())
}
fn to_millis(&self) -> Option<u64> {
self.duration_since(SystemTime::UNIX_EPOCH).ok().map(|d| d.as_millis() as u64)
}
fn from_secs(secs: u64) -> Self {
SystemTime::UNIX_EPOCH + Duration::from_secs(secs)
}
fn from_millis(millis: u64) -> Self {
SystemTime::UNIX_EPOCH + Duration::from_millis(millis)
}
}
pub fn get_current_secs() -> u64 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0 /* SHOULD NOT HAPPEN */ )
get_secs(&SystemTime::now())
}
pub fn get_current_millis() -> u128 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map(|d| d.as_millis()).unwrap_or(0 /* SHOULD NOT HAPPEN */ )
get_millis(&SystemTime::now())
}
pub fn get_secs(system_time: &SystemTime) -> u64 {
system_time.duration_since(SystemTime::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0 /* SHOULD NOT HAPPEN */)
}
pub fn get_millis(system_time: &SystemTime) -> u128 {
system_time.duration_since(SystemTime::UNIX_EPOCH).map(|d| d.as_millis()).unwrap_or(0 /* SHOULD NOT HAPPEN */)
}
pub fn parse_duration(t: &str) -> Option<Duration> {
@@ -20,18 +53,18 @@ pub fn parse_duration(t: &str) -> Option<Duration> {
Some('m') => parse_and_process_time(60 * 1000),
Some('h') => parse_and_process_time(60 * 60 * 1000),
Some('d') => parse_and_process_time(24 * 60 * 60 * 1000),
_ => t.parse::<u64>().map(|t| Duration::from_millis(t)).ok(),
_ => t.parse::<u64>().map(Duration::from_millis).ok(),
}
}
#[test]
fn test_get_current_secs() {
assert!(get_current_secs() != 0);
assert_ne!(get_current_secs(), 0);
}
#[test]
fn test_get_current_millis() {
assert!(get_current_millis() != 0);
assert_ne!(get_current_millis(), 0);
}
#[test]
@@ -47,5 +80,16 @@ fn test_parse_duration() {
assert_eq!(Duration::from_millis(60000), parse_duration("1m").unwrap());
assert_eq!(Duration::from_millis(3600000), parse_duration("1h").unwrap());
assert_eq!(Duration::from_millis(1800000), parse_duration("0.5h").unwrap());
assert_eq!(Duration::from_millis(24*3600000), parse_duration("1d").unwrap());
}
assert_eq!(Duration::from_millis(24 * 3600000), parse_duration("1d").unwrap());
}
#[test]
fn test_unix_epoch() {
let t = SystemTime::now();
let s = t.to_secs().unwrap();
let m = t.to_millis().unwrap();
let t2 = SystemTime::from_secs(s);
let t3 = SystemTime::from_millis(m);
assert_eq!(get_secs(&t), get_secs(&t2));
assert_eq!(get_millis(&t), get_millis(&t3));
}