1
0
mirror of https://github.com/jht5945/rust_util.git synced 2026-01-12 15:20:05 +08:00

Compare commits

...

100 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
53720edc00 feat: v0.6.34 2021-05-28 01:35:27 +08:00
e89d6be38d feat: cmd 2021-05-28 01:00:43 +08:00
7e752d95b5 feat: cmd 2021-05-28 00:59:07 +08:00
b4c789aebc feat: add failure and exit 2021-05-22 13:32:15 +08:00
b91e6e060a feat: add opt_result 2021-04-30 23:01:35 +08:00
6ed6a9d26f feat: v0.6.31 2021-04-24 19:08:07 +08:00
4d6b7b3180 feat: ip address support ord, eq 2021-02-28 10:49:53 +08:00
4b39ac48fd feat: add git_rev_parse_head 2021-01-24 22:38:19 +08:00
cb1b8187fa feat: update dependency version, add clap run_with 2021-01-24 22:31:11 +08:00
50159a0ca8 chore: uses 2021-01-03 13:12:55 +08:00
aa8b4ed447 style: style 2021-01-03 13:06:39 +08:00
a805d7cb1c feat: add stdout_or_file_write 2021-01-03 13:05:35 +08:00
a8ae90ab4d style: naming 2021-01-03 12:24:45 +08:00
da71d5c0c2 feat: add test 2021-01-03 11:05:22 +08:00
797d75bf71 feat: opt use_clap 2021-01-03 11:02:41 +08:00
5df135fda9 feat: use XResult<()> 2021-01-03 01:30:10 +08:00
249d7ee36e fix: use clap 2021-01-03 01:23:00 +08:00
19d967ef0e feat: add use clap 2021-01-03 01:19:34 +08:00
9d9423cfec feat: add use clap 2021-01-03 01:18:32 +08:00
f0bd9a719a feat: add simple_error 2021-01-01 18:58:04 +08:00
1a3c8cbeb4 feat: add join_path 2020-12-30 00:19:24 +08:00
17c06e304d feat: add git branch 2020-12-28 23:59:45 +08:00
59705779d1 chore: run_command_and_wait add return status code 2020-12-26 22:03:21 +08:00
0001f67f0d feat: add justfile 2020-11-29 23:24:46 +08:00
c61c8d37e4 feat: add read_yes_no 2020-11-29 23:20:36 +08:00
0c38427bcb chore: example and readme 2020-11-29 12:05:13 +08:00
6881cebf72 feat: add git rename 2020-11-29 11:55:43 +08:00
a55ad86546 feat: add util_git 2020-11-29 10:24:25 +08:00
56f84533aa feat: add util_term 2020-11-28 10:31:35 +08:00
807bf490fb style: opt_value style 2020-11-15 22:15:00 +08:00
6b4f7bf3ef feat: add opt_value! 2020-11-15 22:13:15 +08:00
6e821a4cd9 chore: coding style 2020-11-08 23:10:33 +08:00
755dc5619d feat: add parse_duration 2020-11-08 22:58:31 +08:00
90ffe7c7d9 feat: add example 2020-09-20 22:29:58 +08:00
289fe68e61 feat: add print_status_context 2020-09-20 16:47:34 +08:00
dd2bec2669 feat: add PrintStatusContext 2020-09-20 01:23:03 +08:00
e027f8f713 refactor: fix clippy 2020-09-19 20:12:40 +08:00
20b452656e refactor: ipv4_to_u32 2020-09-19 20:07:49 +08:00
89f45cc354 style: code style format 2020-09-19 20:00:54 +08:00
0a2302301d feat: add is_logger_level_enabled 2020-09-13 10:35:24 +08:00
68e889c542 feat: add ip address mask group 2020-09-13 02:57:44 +08:00
3e5292105b chore: !!->^ 2020-09-13 00:37:04 +08:00
745e577c7b feat: add logger level *,?,!,!! 2020-09-13 00:34:05 +08:00
6b86709137 chore: rm read_json_config 2020-09-10 01:05:27 +08:00
1917b23c27 fix: use serde 2020-09-10 00:32:42 +08:00
3db1fb8130 fix: feature use_serde 2020-09-10 00:23:04 +08:00
0576c23c5a feat: add logger level, read json config 2020-09-09 01:35:44 +08:00
79e2618e98 feat: add ip address port 2020-09-06 21:58:33 +08:00
90fc790b64 feat: add display for ip address and ip address mask 2020-09-06 18:49:40 +08:00
bd8152f59b feat: add ip address 2020-09-06 18:45:27 +08:00
c5749c8137 chore: add macro_use 2020-09-06 00:35:31 +08:00
debe4d8c2b feat: add util_net 2020-09-06 00:05:36 +08:00
0bdcbfbbde chore: loop_count 2020-08-02 14:49:20 +08:00
d8d52abd5b fix: add loop count 2020-08-02 14:44:02 +08:00
31a2910393 fix: add loop count 2020-08-02 14:43:13 +08:00
a298b6ffcb fix: fix use 2020-08-02 11:52:29 +08:00
e79908b4fe style: fix clippy 2020-08-02 11:44:56 +08:00
fe7ab57ac5 feat: add macro information/success/warning/failure/debugging 2020-08-02 11:36:03 +08:00
5ec790e192 chore: update verstion 2020-08-02 11:09:02 +08:00
2e8c8fe8c8 feat: add find_parents_exists_dir 2020-08-02 11:08:11 +08:00
328f4c503f v0.3.0 add JoinFilesReader 2020-06-21 13:19:50 +08:00
0ebd4e5630 fix print_color unwarp issue 2020-05-07 08:21:19 +08:00
ef49c85cf2 ref read_str_to_lines 2020-05-05 22:00:45 +08:00
a672cdfa45 add read_str_to_lines, tests, fix bug 2020-05-05 13:27:21 +08:00
66d3540b65 add locate_file 2020-05-04 20:54:16 +08:00
7bfffc7bb6 add MessageType#print 2020-05-02 18:53:48 +08:00
d7ffd4ca6c add print_*) 2020-05-02 13:12:53 +08:00
7853214680 fix clippy 2020-05-02 13:08:20 +08:00
57c757d770 add split_kv 2020-05-02 12:58:22 +08:00
46528c566c add split_kv 2020-05-02 12:58:13 +08:00
2f7d7d238e add resolve_file_path 2020-05-02 12:48:31 +08:00
dd0a754631 add get_read_stdin_or_file 2020-05-02 11:54:13 +08:00
d75c331fa0 ref use 2020-04-12 19:46:12 +08:00
34df5b66a2 ref is_env_on 2020-04-12 18:19:34 +08:00
d218e1edd5 change version 2020-04-12 12:26:02 +08:00
854f619518 add print message lock 2020-04-12 12:06:00 +08:00
7811e29921 update 2020-04-12 00:55:37 +08:00
b8da734669 add util_file::read_file_content 2020-04-12 00:39:16 +08:00
25 changed files with 1868 additions and 191 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Generated by Cargo # Generated by Cargo
# will have compiled files and executables # will have compiled files and executables
/target/ target/
.idea/
Cargo.lock Cargo.lock
# These are backup files generated by rustfmt # These are backup files generated by rustfmt

1
CNAME Normal file
View File

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

View File

@@ -1,14 +1,22 @@
[package] [package]
name = "rust_util" name = "rust_util"
version = "0.2.1" version = "0.6.51"
authors = ["Hatter Jiang <jht5945@gmail.com>"] authors = ["Hatter Jiang <jht5945@gmail.com>"]
edition = "2018" edition = "2018"
description = "Hatter's Rust Util" description = "Hatter's Rust Util"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
[features]
default = [] #["serde", "serde_json"]
# use_serde = ["serde", "serde_json"]
use_clap = ["clap"]
[dependencies] [dependencies]
libc = "0.2.65" libc = "0.2.82"
term = "0.5.2" term = "0.7.0"
term_size = "0.3.1" term_size = "0.3.2"
lazy_static = "1.3.0" lazy_static = "1.4.0"
clap = { version = "2.0", optional = true }
# serde = { version = "1.0", features = ["derive"], optional = true }
# serde_json = { version = "1.0", optional = true }

View File

@@ -3,12 +3,18 @@
Config `Cargo.toml`: Config `Cargo.toml`:
``` ```
[dependencies]
rust_util = "0.6"
--OR--
[dependencies] [dependencies]
rust_util = { git = "https://github.com/jht5945/rust_util" } rust_util = { git = "https://github.com/jht5945/rust_util" }
``` ```
Use in `*.rs`: Use in `*.rs`:
``` ```
#[macro_use]
extern crate rust_util; extern crate rust_util;
... ...
@@ -21,4 +27,27 @@ Update git crate:
$ cargo update $ cargo update
``` ```
Run example:
```
$ cargo run --example log
```
<br>
ENV `LOGGER_LEVEL` can be:
- `debug` or `*`
- `info` or `?` -- default
- `ok` or `#`
- `warn` or `!`
- `error` or `^`
## Update Log
* Nov 28, 2020 v0.6.19
* add util_git
* Nov 28, 2020 v0.6.18
* add util_term
* Jun 21, 2020 v0.3.0
* add struct `JoinFilesReader`

1
_config.yml Normal file
View File

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

11
demo/test_clap/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "test_clap"
version = "0.1.0"
authors = ["Hatter Jiang <jht5945@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rust_util = { version = "0.6", path = "../../", features = ["use_clap"] }
clap = "2.0"

View File

@@ -0,0 +1,30 @@
use clap::{App, SubCommand, ArgMatches};
use rust_util::util_clap::Command;
use rust_util::util_clap::CommandError;
use rust_util::util_clap::CommandExecutor;
struct TestCommand{}
impl Command for TestCommand {
fn name(&self) -> &str { "test" }
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name()).about("Test subcommand")
}
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)
}
}
fn main() {
let mut c = CommandExecutor::new(None);
c.add(Box::new(TestCommand{}));
c.run().unwrap();
}

47
examples/log.rs Normal file
View File

@@ -0,0 +1,47 @@
#[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 *
info or ? -- default
ok or #
warn or !
error or ^"##);
debugging!("Hello {}", "world!");
information!("Hello {}", "world!");
success!("Hello {}", "world!");
warning!("Hello {}", "world!");
failure!("Hello {}", "world!");
println!("{:?}", test_opt_result());
set_logger_std_out(false);
information!("Std err!");
warning!("Std err!");
set_logger_std_out(true);
simple_error!("helloworld {}", 1)
}
fn test_opt_result() -> XResult<()> {
let a = Err(SimpleError::new("test".into()));
opt_result!(a, "error: {}")
}

16
justfile Normal file
View File

@@ -0,0 +1,16 @@
_:
@just --list
# example log
log:
cargo run --example log
# publish
publish:
cargo publish
# test all
test:
cargo test
cd demo/test_clap; cargo build; cargo test

View File

@@ -1,34 +1,128 @@
#[macro_use] #[macro_use] extern crate lazy_static;
extern crate lazy_static;
extern crate term; extern crate term;
use std::{ use std::error::Error;
io::{Error, ErrorKind}, use std::io::{Error as IoError, ErrorKind};
}; use std::fmt::{Display, Formatter, Result as FmtResult};
pub mod util_io; pub mod util_io;
pub mod util_os; pub mod util_os;
pub mod util_env; pub mod util_env;
pub mod util_cmd; pub mod util_cmd;
pub mod util_msg; pub mod util_msg;
pub mod util_str;
pub mod util_size; pub mod util_size;
pub mod util_file; pub mod util_file;
pub mod util_time; pub mod util_time;
pub mod util_net;
pub mod util_term;
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) /// iff!(condition, result_when_true, result_when_false)
#[macro_export] #[macro_export] macro_rules! iff {
macro_rules! iff { ($c:expr, $t:expr, $f:expr) => ( if $c { $t } else { $f } )
($c:expr, $t:expr, $f:expr) => { }
if $c { $t } else { $f } #[macro_export] macro_rules! information {
}; ($($arg:tt)+) => ( rust_util::util_msg::when(rust_util::util_msg::MessageType::INFO, || {
rust_util::util_msg::print_info(&format!($($arg)+));
}); )
}
#[macro_export] macro_rules! success {
($($arg:tt)+) => ( rust_util::util_msg::print_ok(&format!($($arg)+)); )
}
#[macro_export] macro_rules! warning {
($($arg:tt)+) => ( rust_util::util_msg::print_warn(&format!($($arg)+)); )
}
#[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)+))
}); )
}
#[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);
} )
}
#[macro_export] macro_rules! opt_value {
($ex: expr) => ( match $ex { Some(o) => o, None => return, } )
} }
pub type XResult<T> = Result<T, Box<dyn std::error::Error>>; pub type XResult<T> = Result<T, Box<dyn Error>>;
pub fn new_box_error(m: &str) -> Box<dyn std::error::Error> { pub fn new_box_error(m: &str) -> Box<dyn Error> {
Box::new(Error::new(ErrorKind::Other, m)) Box::new(IoError::new(ErrorKind::Other, m))
} }
pub fn new_box_ioerror(m: &str) -> Box<dyn std::error::Error> { pub fn new_box_ioerror(m: &str) -> Box<dyn Error> {
Box::new(Error::new(ErrorKind::Other, m)) 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 {
Ok(o) => o,
Err(e) => return Err(rust_util::SimpleError::new(
format!("{}, file: {}, line: {}", format!($($arg)+, e), file!(), line!())
).into()),
}
)
}
#[macro_export] macro_rules! simple_error {
($($arg:tt)+) => ( Err(rust_util::SimpleError::new(
format!("{}, file: {}, line: {}", format!($($arg)+), file!(), line!())
).into()) )
}
#[derive(Debug)]
pub struct SimpleError {
pub message: String,
pub source: Option<Box<dyn Error>>,
}
impl SimpleError {
pub fn new(message: String) -> Self {
Self { message, source: None }
}
pub fn new2(message: String, source: Box<dyn Error>) -> Self {
Self { message, source: Some(source) }
}
}
impl Display for SimpleError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match &self.source {
None => write!(f, "SimpleErorr, message: {}", self.message),
Some(e) => write!(f, "SimpleErorr, message: {}, source erorr: {}", self.message, e),
}
}
}
impl Error for SimpleError {}

103
src/util_clap.rs Normal file
View File

@@ -0,0 +1,103 @@
use std::process;
use clap::{App, Arg, ArgMatches};
use crate::{XResult, util_msg};
pub type CommandError = XResult<Option<i32>>;
pub trait Command {
fn name(&self) -> &str;
fn subcommand<'a>(&self) -> App<'a, 'a>;
fn run(&self, arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError;
}
pub trait DefaultCommand {
fn process_command<'a>(&self, app: App<'a, 'a>) -> App<'a, 'a>;
fn run(&self, arg_matches: &ArgMatches) -> CommandError;
}
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"))
}
fn run(&self, arg_matches: &ArgMatches) -> CommandError {
let verbose_count = arg_matches.occurrences_of("verbose");
util_msg::print_info(&format!("Verbose count: {}", verbose_count));
util_msg::print_info("This is default command cli, please run with help (--help)");
Ok(None)
}
}
pub struct CommandExecutor {
default_cmd: Option<Box<dyn DefaultCommand>>,
commands: Vec<Box<dyn Command>>,
}
impl CommandExecutor {
pub fn new_default() -> Self {
Self::new(None)
}
pub fn new(default_cmd: Option<Box<dyn DefaultCommand>>) -> Self {
CommandExecutor {
default_cmd,
commands: Vec::new(),
}
}
pub fn add(&mut self, cmd: Box<dyn Command>) -> &mut Self {
self.commands.push(cmd);
self
}
pub fn add_commands(&mut self, cmds: Vec<Box<dyn Command>>) -> &mut Self {
for cmd in cmds.into_iter() {
self.add(cmd);
}
self
}
pub fn run(&self) -> XResult<()> {
let app = App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"));
self.run_with(app)
}
pub fn run_with<'a>(&self, mut app: App<'a, 'a>) -> XResult<()> {
if let Some(default_cmd) = &self.default_cmd {
app = default_cmd.process_command(app);
}
for command in &self.commands {
app = app.subcommand(command.subcommand());
}
let matches = app.get_matches();
for command in &self.commands {
if let Some(sub_cmd_matches) = matches.subcommand_matches(command.name()) {
match command.run(&matches, sub_cmd_matches)? {
None => return Ok(()),
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) => {
crate::util_runtime::invoke_callbacks();
process::exit(code);
}
},
}
}
}

View File

@@ -1,15 +1,48 @@
use std::io::{self, Error, ErrorKind};
use std::process::{Command, ExitStatus, Output};
use crate::util_msg::{print_debug, print_error, MessageType, print_message};
use std::{ pub fn run_command_or_exit(cmd: &str, args: &[&str]) -> Output {
io::{self, Error, ErrorKind}, let mut c = Command::new(cmd);
process::Command, c.args(args);
}; crate::util_msg::when(MessageType::DEBUG, || {
print_debug(&format!("Run command: {:?}", c));
pub fn run_command_and_wait(cmd: &mut Command) -> io::Result<()> { });
cmd.spawn()?.wait()?; let output = c.output();
Ok(()) 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_output(MessageType::ERROR, &output);
}
output
}
}
} }
pub fn extract_package_and_wait(dir: &str, file_name: &str) -> io::Result<()> { 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)));
});
}
pub fn run_command_and_wait(cmd: &mut Command) -> io::Result<ExitStatus> {
cmd.spawn()?.wait()
}
pub fn extract_package_and_wait(dir: &str, file_name: &str) -> io::Result<ExitStatus> {
let mut cmd: Command; let mut cmd: Command;
if file_name.ends_with(".zip") { if file_name.ends_with(".zip") {
cmd = Command::new("unzip"); cmd = Command::new("unzip");

View File

@@ -1,9 +1,31 @@
use std::env; use std::env;
pub fn is_env_on(var: &str) -> bool { pub fn is_env_on(var: &str) -> bool {
match env::var(var) { env_var(var).map(|val| is_on(&val)).unwrap_or(false)
Err(_) => false, }
Ok(v) => (v == "TRUE" || v == "true" || v =="YES" || v == "yes" || v == "1"),
} 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();
["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,92 +1,219 @@
use std::fs::{self, File};
use std::io::{Lines, BufReader};
use std::path::{Path, PathBuf};
use crate::{util_os, util_io, util_msg, new_box_ioerror, XResult};
use std::{ pub struct JoinFilesReader {
env, files: Vec<String>,
fs, file_ptr: usize,
path::{Path, PathBuf}, file_lines: Option<Box<Lines<BufReader<File>>>>,
}; }
use super::{ fn open_file_as_lines(f: &str) -> XResult<Lines<BufReader<File>>> {
util_os, let f = File::open(f)?;
new_box_ioerror, let br = BufReader::new(f);
XResult, use std::io::BufRead;
}; Ok(br.lines())
}
pub fn get_home_str() -> Option<String> { impl JoinFilesReader {
if util_os::is_macos_or_linux() { pub fn new(fns: &[&str]) -> XResult<Self> {
env::var("HOME").ok() let mut files: Vec<String> = vec![];
for f in fns {
files.push(f.to_string());
}
let file_ptr = 0;
let mut file_lines = None;
if !files.is_empty() {
file_lines = Some(Box::new(open_file_as_lines(&files[0])?));
}
Ok(Self { files, file_ptr, file_lines })
}
}
impl Iterator for JoinFilesReader {
type Item = XResult<String>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.file_lines {
Some(ref mut ln) => match ln.next() {
Some(r) => return Some(r.map_err(|e| e.into())),
None => {
self.file_ptr += 1;
self.file_lines = None;
if self.file_ptr >= self.files.len() {
return None;
} 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)),
}));
}
}
},
None => return None,
}
if self.file_ptr >= self.files.len() {
return None;
}
}
}
}
pub fn join_path(path1: &str, path2: &str) -> String {
let sp = if util_os::is_macos_or_linux() { "/" } else { "\\" };
if path1.ends_with(sp) {
path1.to_string() + path2
} else { } else {
None path1.to_string() + sp + path2
}
}
pub fn find_parents_exists_file(file: &str) -> Option<PathBuf> {
let mut loop_count = 0_usize;
match PathBuf::from(".").canonicalize() {
Err(_) => None,
Ok(mut path) => loop {
loop_count += 1;
if loop_count > 1000 {
util_msg::print_error("Loop count more than 1000!");
return None;
}
if path.join(file).is_file() {
return Some(path);
}
if !path.pop() {
return None;
}
}
}
}
pub fn find_parents_exists_dir(dir: &str) -> Option<PathBuf> {
let mut loop_count = 0_usize;
match PathBuf::from(".").canonicalize() {
Err(_) => None,
Ok(mut path) => loop {
loop_count += 1;
if loop_count > 1000 {
util_msg::print_error("Loop count more than 1000!");
return None;
}
if path.join(dir).is_dir() {
return Some(path);
}
if !path.pop() {
return None;
}
}
}
}
pub fn read_config(config: Option<String>, files: &[String]) -> Option<PathBuf> {
match config {
Some(config_str) => Some(PathBuf::from(config_str)),
None => locate_file(files),
}
}
pub fn locate_file(files: &[String]) -> Option<PathBuf> {
for f in files {
match PathBuf::from(&resolve_file_path(f)) {
pb if pb.is_file() => return Some(pb),
_ => (),
}
}
None
}
#[deprecated]
pub fn get_home_str() -> Option<String> {
util_os::get_user_home()
}
pub fn resolve_file_path(path: &str) -> String {
let home_path = match util_os::get_user_home() {
Some(p) => p,
None => return path.to_owned(),
};
match path {
"~" => home_path,
p if p.starts_with("~/") => home_path + &path.chars().skip(1).collect::<String>(),
p => p.to_owned(),
} }
} }
pub fn get_home_path() -> Option<PathBuf> { 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> { pub fn get_absolute_path(path: &str) -> Option<PathBuf> {
if path == "~" { match path {
return Some(PathBuf::from(get_home_str()?)); "~" => Some(PathBuf::from(util_os::get_user_home()?)),
} else if path.starts_with("~/") { path if path.starts_with("~/") => Some(PathBuf::from(&format!("{}/{}", util_os::get_user_home()?, &path[2..]))),
return Some(PathBuf::from(&format!("{}/{}", get_home_str()?, &path[2..]))); path => fs::canonicalize(path).ok(),
}
}
pub fn read_file_content(file: &str) -> XResult<String> {
match get_absolute_path(file) {
None => Err(new_box_ioerror(&format!("File not found: {}", file))),
Some(p) => util_io::read_to_string(&mut fs::File::open(p)?),
} }
fs::canonicalize(path).ok()
} }
pub fn is_symlink(path: &Path) -> bool { pub fn is_symlink(path: &Path) -> bool {
match path.symlink_metadata() { path.symlink_metadata().map(|meta| meta.file_type().is_symlink()).unwrap_or(false)
Err(_) => false,
Ok(meta) => meta.file_type().is_symlink(),
}
} }
pub fn walk_dir<FError, FProcess, FFilter>(dir: &Path, pub fn walk_dir<FError, FProcess, FFilter>(dir: &Path,
func_walk_error: &FError, func_walk_error: &FError,
func_process_file: &FProcess, func_process_file: &FProcess,
func_filter_dir: &FFilter) -> XResult<()> func_filter_dir: &FFilter) -> XResult<()>
where FError: Fn(&Path, Box<dyn std::error::Error>) -> (), where FError: Fn(&Path, Box<dyn std::error::Error>),
FProcess: Fn(&Path) -> (), FProcess: Fn(&Path),
FFilter: Fn(&Path) -> bool { FFilter: Fn(&Path) -> bool {
walk_dir_with_depth_check(&mut 0u32, dir, func_walk_error, func_process_file, func_filter_dir) 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, fn walk_dir_with_depth_check<FError, FProcess, FFilter>(depth: &mut u32, dir: &Path,
func_walk_error: &FError, func_walk_error: &FError,
func_process_file: &FProcess, func_process_file: &FProcess,
func_filter_dir: &FFilter) -> XResult<()> func_filter_dir: &FFilter) -> XResult<()>
where FError: Fn(&Path, Box<dyn std::error::Error>) -> (), where FError: Fn(&Path, Box<dyn std::error::Error>),
FProcess: Fn(&Path) -> (), FProcess: Fn(&Path),
FFilter: Fn(&Path) -> bool { FFilter: Fn(&Path) -> bool {
if *depth > 100u32 { if *depth > 100u32 {
return Err(new_box_ioerror(&format!("Depth exceed, depth: {}, path: {:?}", *depth, dir))); return Err(new_box_ioerror(&format!("Depth exceed, depth: {}, path: {:?}", *depth, dir)));
} }
let read_dir = match dir.read_dir() { let read_dir = match dir.read_dir() {
Err(err) => {
func_walk_error(&dir, Box::new(err));
return Ok(());
},
Ok(rd) => rd, Ok(rd) => rd,
Err(err) => {
func_walk_error(dir, Box::new(err));
return Ok(());
}
}; };
for dir_entry_item in read_dir { for dir_entry_item in read_dir {
let dir_entry = match dir_entry_item { let dir_entry = match dir_entry_item {
Err(err) => {
func_walk_error(&dir, Box::new(err));
continue; // Ok?
},
Ok(item) => item, Ok(item) => item,
Err(err) => {
func_walk_error(dir, Box::new(err));
continue; // Ok?
}
}; };
let path_buf = dir_entry.path(); let path_buf = dir_entry.path();
let sub_dir = path_buf.as_path(); let sub_dir = path_buf.as_path();
if sub_dir.is_file() { if sub_dir.is_file() {
func_process_file(&sub_dir); func_process_file(sub_dir);
} else if sub_dir.is_dir() { } else if sub_dir.is_dir() && func_filter_dir(sub_dir) {
if func_filter_dir(&sub_dir) { *depth += 1;
*depth += 1; if let Err(err) = walk_dir_with_depth_check(depth, sub_dir, func_walk_error, func_process_file, func_filter_dir) {
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);
func_walk_error(&sub_dir, err);
}
*depth -= 1;
} }
*depth -= 1;
} // should process else ? not file, dir } // should process else ? not file, dir
} }
Ok(()) Ok(())

177
src/util_git.rs Normal file
View File

@@ -0,0 +1,177 @@
use std::process::Command;
use crate::{util_cmd, util_msg, XResult};
const LANG: &str = "LANG";
const EN_US: &str = "en_US";
#[derive(Default, Debug, Clone)]
pub struct GitStatusChange {
pub added: Vec<String>,
pub modified: Vec<String>,
pub renamed: Vec<(String, String)>,
pub deleted: Vec<String>,
pub untracked: Vec<String>,
}
impl GitStatusChange {
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.modified.is_empty()
&& self.deleted.is_empty() && self.untracked.is_empty()
}
}
pub fn git_status_change(working_dir: Option<&str>) -> XResult<GitStatusChange> {
let git_status = git_status(working_dir)?;
parse_git_status_change(&git_status)
}
pub fn git_rev_parse_head(working_dir: Option<&str>) -> XResult<String> {
let mut cmd = new_git_command(working_dir);
cmd.args(vec!["rev-parse", "HEAD"]);
util_msg::print_info(&format!("Exec: {:?}", cmd));
let output = cmd.output()?;
let rev_parse_head = String::from_utf8(output.stdout)?;
Ok(rev_parse_head.trim().to_string())
}
pub fn git_fetch_dry_run(working_dir: Option<&str>) -> XResult<bool> {
let mut cmd = new_git_command(working_dir);
cmd.args(vec!["fetch", "--dry-run"]);
util_msg::print_info(&format!("Exec: {:?}", cmd));
let output = cmd.output()?;
let fetch_dry_run = String::from_utf8(output.stdout)?;
Ok(fetch_dry_run.trim().is_empty())
}
pub fn git_status(working_dir: Option<&str>) -> XResult<String> {
let mut cmd = new_git_command(working_dir);
cmd.arg("status");
util_msg::print_info(&format!("Exec: {:?}", cmd));
let output = cmd.output()?;
let git_status = String::from_utf8(output.stdout)?;
Ok(git_status)
}
pub fn git_branch(working_dir: Option<&str>) -> XResult<Option<String>> {
let mut cmd = new_git_command(working_dir);
cmd.arg("branch");
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('*'));
Ok(current_branch.map(|ln| ln.trim()[1..].trim().into()))
}
pub fn git_push(working_dir: Option<&str>) {
let mut cmd = new_git_command(working_dir);
cmd.arg("push");
util_msg::print_info(&format!("Exec: {:?}", cmd));
if let Err(e) = util_cmd::run_command_and_wait(&mut cmd) {
util_msg::print_error(&format!("Run git push failed: {}", e));
}
}
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);
}
util_msg::print_info(&format!("Exec: {:?}", cmd));
if let Err(e) = util_cmd::run_command_and_wait(&mut cmd) {
util_msg::print_error(&format!("Run git add failed: {}", e));
}
}
pub fn git_commit(working_dir: Option<&str>, message: &str, files: &[String]) {
let mut cmd = new_git_command(working_dir);
cmd.arg("commit");
cmd.arg("-m");
cmd.arg(message);
for f in files {
cmd.arg(f);
}
util_msg::print_info(&format!("Exec: {:?}", cmd));
if let Err(e) = util_cmd::run_command_and_wait(&mut cmd) {
util_msg::print_error(&format!("Run git commit failed: {}", e));
}
}
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') {
let ln = ln.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 let Some(deleted) = ln.strip_prefix("deleted:") {
let f = deleted.trim();
git_status_change.deleted.push(f.to_owned());
} else if let Some(modified) = ln.strip_prefix("modified:") {
let f = modified.trim();
git_status_change.modified.push(f.to_owned());
} 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();
if let (Some(fa), Some(fb)) = (fa, fb) {
git_status_change.renamed.push((fa.trim().to_owned(), fb.trim().to_owned()));
}
} else {
git_status_change.untracked.push(ln.to_owned());
}
}
}
Ok(git_status_change)
}
fn new_git_command(working_dir: Option<&str>) -> Command {
let mut cmd = Command::new("git");
cmd.env(LANG, EN_US);
if let Some(working_dir) = working_dir {
cmd.current_dir(working_dir);
}
cmd
}
#[test]
fn test_git_status() {
let git_status = r#"On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: src/util_git.rs
renamed: src/template_regex.rs -> src/chk_regex.rs
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: README.md
modified: src/lib.rs
Untracked files:
(use "git add <file>..." to include in what will be committed)
Test
H"#;
let gsc = parse_git_status_change(git_status).unwrap();
println!("{:#?}", gsc);
assert_eq!(1, gsc.added.len());
assert_eq!("src/util_git.rs", gsc.added[0]);
assert_eq!(1, gsc.modified.len());
assert_eq!("src/lib.rs", gsc.modified[0]);
assert_eq!(1, gsc.renamed.len());
assert_eq!(("src/template_regex.rs".into(), "src/chk_regex.rs".into()), gsc.renamed[0]);
assert_eq!(1, gsc.deleted.len());
assert_eq!("README.md", gsc.deleted[0]);
assert_eq!(1, gsc.untracked.len());
assert_eq!("Test", gsc.untracked[0]);
}

View File

@@ -1,18 +1,90 @@
use std::fs::File;
use std::io::{self, ErrorKind, prelude::*, Write};
use std::time::{Duration, SystemTime};
use std::{ use crate::{SimpleError, XResult};
io::{self, use crate::util_file;
ErrorKind, use crate::util_msg;
prelude::*, use crate::util_size;
},
time::{SystemTime, Duration},
};
use super::XResult;
use super::util_size::get_display_size;
use super::util_msg::print_lastline;
pub const DEFAULT_BUF_SIZE: usize = 8 * 1024; pub const DEFAULT_BUF_SIZE: usize = 8 * 1024;
pub fn stdout_or_file_write(file: Option<&str>, overwrite: bool) -> XResult<Box<dyn Write>> {
match file {
None => Ok(Box::new(io::stdout())),
Some(output) => {
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))
})?))
}
}
}
pub struct PrintStatusContext {
pub print_interval_time: Duration,
pub print_interval_bytes: i64,
pub init_print_time: SystemTime,
pub last_print_time: SystemTime,
pub total_written_bytes: i64,
}
impl PrintStatusContext {
pub fn new() -> Self {
Self::new_with(Duration::from_millis(100), 512 * 1024)
}
pub fn new_with(print_interval_time: Duration, print_interval_bytes: i64) -> Self {
Self {
print_interval_time,
print_interval_bytes,
init_print_time: SystemTime::now(),
last_print_time: SystemTime::now(),
total_written_bytes: 0,
}
}
pub fn check_print(&mut self, total: i64, written: i64) -> (bool, Duration) {
let now = SystemTime::now();
let total_cost = now.duration_since(self.init_print_time).unwrap_or_else(|_| Duration::from_millis(0));
let last_print_cost = now.duration_since(self.last_print_time).unwrap_or_else(|_| Duration::from_millis(0));
let should_update_status_line = || {
if total > written && (total - written < self.print_interval_bytes) {
return true;
}
if written > self.total_written_bytes && (written - self.total_written_bytes > self.print_interval_bytes) {
return true;
}
last_print_cost.as_millis() > self.print_interval_time.as_millis()
};
if should_update_status_line() {
self.last_print_time = now;
self.total_written_bytes = written;
(true, total_cost)
} else {
(false, total_cost)
}
}
}
impl Default for PrintStatusContext {
fn default() -> Self {
PrintStatusContext::new()
}
}
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)) {
Ok(f) => Ok(Box::new(f)),
Err(err) => Err(SimpleError::new(format!("Open file {}, erorr: {}", file, err)).into()),
}
}
}
pub fn read_to_string(read: &mut dyn Read) -> XResult<String> { pub fn read_to_string(read: &mut dyn Read) -> XResult<String> {
let mut buffer = String::new(); let mut buffer = String::new();
@@ -26,49 +98,30 @@ pub fn read_to_bytes(read: &mut dyn Read) -> XResult<Vec<u8>> {
Ok(buffer) Ok(buffer)
} }
pub fn copy_io<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, total: i64) -> io::Result<u64> 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") copy_io_with_head(reader, writer, total, "Downloading", &mut PrintStatusContext::default())
} }
pub fn print_status_last_line(head: &str, total: i64, written: i64, cost: Duration) { pub fn copy_io<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, total: i64, print_status_context: &mut PrintStatusContext)
let mut download_speed = "-".to_string(); -> io::Result<u64>
let cost_as_secs = cost.as_secs(); where R: io::Read, W: io::Write {
if cost_as_secs > 0 { copy_io_with_head(reader, writer, total, "Downloading", print_status_context)
download_speed = format!("{}/s", get_display_size((written / (cost_as_secs as i64)) as i64));
}
if total > 0 {
print_lastline(&format!("{}, Total: {}, Finished: {}, Speed: {}",
head,
get_display_size(total),
get_display_size(written),
download_speed));
} else {
print_lastline(&format!("{}, Finished: {}, Speed: {}",
head,
get_display_size(written),
download_speed));
}
} }
pub fn copy_io_with_head<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W, total: i64, head: &str) -> io::Result<u64> 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_cell = RefCell::new(0u64); let written = copy_io_callback(reader, writer, total, print_status_context, &mut |total, written, _len, print_status_context| {
let start = SystemTime::now(); print_status_last_line(head, total, written as i64, print_status_context);
let written = copy_io_callback(reader, writer, total, &|total, written, _len| {
//written_cell.replace_with(|&mut w| w + len as u64);
//let written = *written_cell.borrow();
let cost = SystemTime::now().duration_since(start.clone()).unwrap();
print_status_last_line(head, total, written as i64, cost);
}); });
println!(); println!();
written written
} }
pub fn copy_io_callback<R: ?Sized, W: ?Sized, FCallback>(reader: &mut R, writer: &mut W, total: i64, callback: &FCallback) -> io::Result<u64> 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, where R: io::Read,
W: io::Write, W: io::Write,
FCallback: Fn(i64, u64, usize) -> () { FCallback: Fn(i64, u64, usize, &mut PrintStatusContext) {
let mut written = 0u64; let mut written = 0u64;
let mut buf: [u8; DEFAULT_BUF_SIZE] = [0u8; DEFAULT_BUF_SIZE]; let mut buf: [u8; DEFAULT_BUF_SIZE] = [0u8; DEFAULT_BUF_SIZE];
loop { loop {
@@ -80,6 +133,30 @@ pub fn copy_io_callback<R: ?Sized, W: ?Sized, FCallback>(reader: &mut R, writer:
}; };
writer.write_all(&buf[..len])?; writer.write_all(&buf[..len])?;
written += len as u64; written += len as u64;
callback(total, written, len); callback(total, written, len, print_status_context);
}
}
pub fn print_status_last_line(head: &str, total: i64, written: i64, print_status_context: &mut PrintStatusContext) {
let mut download_speed = "-".to_string();
let (is_print, cost) = print_status_context.check_print(total, written);
if !is_print {
return;
}
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)));
}
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));
} else {
util_msg::print_lastline(&format!("{}, Finished: {}, Speed: {}",
head,
util_size::get_display_size(written),
download_speed));
} }
} }

View File

@@ -1,51 +1,193 @@
use std::{ use std::env;
io::{self, Write}, use std::io::{self, Write};
}; use std::sync::{Arc, Mutex, RwLock};
use std::sync::mpsc::Sender;
lazy_static! { lazy_static! {
pub static ref IS_ATTY: bool = is_atty(); 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 enum MessageType { INFO, OK, WARN, ERROR, DEBUG, } pub fn set_logger_sender(sender: Sender<String>) {
let mut logger_sender_opt = LOGGER_SENDER.write().unwrap();
logger_sender_opt.replace(sender);
}
pub fn is_atty() -> bool{ pub fn set_logger_std_out(is_std_out: bool) {
let stdout_fileno = unsafe { libc::isatty(libc::STDOUT_FILENO as i32) }; 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 }
impl MessageType {
pub fn get_u8_value(&self) -> u8 {
match self {
MessageType::DEBUG => 0,
MessageType::INFO => 1,
MessageType::OK => 2,
MessageType::WARN => 3,
MessageType::ERROR => 4,
}
}
}
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()) {
match logger_level.trim().to_lowercase().as_str() {
"debug" | "*" => MessageType::DEBUG,
"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
}
}
pub fn is_atty() -> bool {
let stdout_fileno = unsafe { libc::isatty(libc::STDOUT_FILENO) };
stdout_fileno != 0 stdout_fileno != 0
} }
pub fn print_color(color: Option<term::color::Color>, is_bold: bool, m: &str) { pub fn print_color(is_std_out: bool, color: Option<term::color::Color>, is_bold: bool, m: &str) {
let mut t = term::stdout().unwrap(); if is_std_out {
if *IS_ATTY { match term::stdout() {
if let Some(c) = color { Some(mut t) => {
t.fg(c).unwrap(); 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 => print!("{}", m),
} }
if is_bold {
t.attr(term::Attr::Bold).unwrap();
}
write!(t, "{}", m).unwrap();
t.reset().unwrap();
} else { } else {
write!(t, "{}", m).unwrap(); 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) { 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(); flush_stdout();
} }
pub fn print_message_ex(color: Option<term::color::Color>, h: &str, message: &str) { pub fn print_message_ex(color: Option<term::color::Color>, h: &str, message: &str) {
print_color(color, true, h); {
println!(" {}", message); 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(is_std_out, color, true, h);
if is_std_out {
println!(" {}", message);
} else {
eprintln!(" {}", message)
}
*lock = ();
}
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_debug(message: &str) { print_message(MessageType::DEBUG, message); }
#[inline]
pub fn is_logger_level_enabled(mt: MessageType) -> bool {
let logger_level = *LOGGER_LEVEL;
mt.get_u8_value() >= logger_level.get_u8_value()
}
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();
}
} }
pub fn print_message(mt: MessageType, message: &str) { pub fn print_message(mt: MessageType, message: &str) {
match mt { if is_logger_level_enabled(mt) {
MessageType::OK => print_message_ex(Some(term::color::GREEN), "[OK ]", message), match mt {
MessageType::WARN => print_message_ex(Some(term::color::YELLOW), "[WARN ]", message), MessageType::OK => print_message_ex(Some(term::color::GREEN), "[OK ]", message),
MessageType::ERROR => print_message_ex(Some(term::color::RED), "[ERROR]", message), MessageType::WARN => print_message_ex(Some(term::color::YELLOW), "[WARN ]", message),
MessageType::INFO => print_message_ex(None, "[INFO ]", message), MessageType::ERROR => print_message_ex(Some(term::color::RED), "[ERROR]", message),
MessageType::DEBUG => print_message_ex(Some(term::color::MAGENTA), "[DEBUG]", message), MessageType::INFO => print_message_ex(None, "[INFO ]", message),
MessageType::DEBUG => print_message_ex(Some(term::color::MAGENTA), "[DEBUG]", message),
}
}
}
impl MessageType {
pub fn print(&self, message: &str) {
print_message(*self, message);
} }
} }
@@ -80,13 +222,13 @@ pub fn get_term_width_message(message: &str, left: usize) -> String {
Some((w, _h)) => { Some((w, _h)) => {
let len = message.len(); let len = message.len();
if w > len { if w > len {
return message.to_string(); return message.to_string();
} }
let mut s = String::new(); 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("[...]");
s.push_str(&message[find_char_boundary(&message, len-10)..]); s.push_str(&message[find_char_boundary(message, len - 10)..]);
s s
}, }
} }
} }

393
src/util_net.rs Normal file
View File

@@ -0,0 +1,393 @@
use std::fmt::{self, Display, Formatter};
use std::result::Result;
use std::net::SocketAddr;
use crate::XResult;
use std::collections::HashSet;
const DEFAULT_LISTEN_ADDR: [u8; 4] = [127, 0, 0, 1];
pub struct IpAndIpMaskMatcher {
min_mask_len: u8,
ip_and_ip_mask_set: HashSet<u64>,
}
impl Default for IpAndIpMaskMatcher {
fn default() -> Self {
Self::new()
}
}
impl IpAndIpMaskMatcher {
pub fn new() -> Self {
IpAndIpMaskMatcher {
min_mask_len: 32,
ip_and_ip_mask_set: HashSet::with_capacity(128),
}
}
pub fn add_ip_address_mask(&mut self, ip_address: &IpAddress, mask_len: u8) -> bool {
if mask_len > 32 {
return false;
}
if mask_len < self.min_mask_len {
self.min_mask_len = mask_len;
}
let mask_n_ip = Self::get_mask_n_ip(ip_address.to_u32() as u64, mask_len);
self.ip_and_ip_mask_set.insert(mask_n_ip);
true
}
pub fn add_ip_address(&mut self, ip_address: &IpAddress) -> bool {
self.add_ip_address_mask(ip_address, 32)
}
pub fn contains_ip_address(&self, ip_address: &IpAddress) -> bool {
self.contains_ip_address_mask(ip_address, 32)
}
pub fn contains_ip_address_mask(&self, ip_address: &IpAddress, mask_len: u8) -> bool {
if mask_len > 32 {
return false;
}
let ip_addr_as_u64 = ip_address.to_u32() as u64;
for i_mask_len in (self.min_mask_len..=mask_len).rev() {
let mask_n_ip = Self::get_mask_n_ip(ip_addr_as_u64, i_mask_len);
if self.ip_and_ip_mask_set.contains(&mask_n_ip) {
return true;
}
}
false
}
fn get_mask_n_ip(ip_addr_as_u64: u64, mask_len: u8) -> u64 {
let mask = u64::from_be_bytes([0x00, 0x00, 0x00, mask_len, 0x00, 0x00, 0x00, 0x00]);
let ip_mask_bits = get_ipv4_mask(mask_len);
mask + (ip_addr_as_u64 & ip_mask_bits as u64)
}
}
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
pub enum IpAddress {
Ipv4([u8; 4]),
}
impl IpAddress {
pub fn parse_ipv4(addr: &str) -> Option<Self> {
parse_ipv4_addr(addr).map(IpAddress::Ipv4)
}
pub fn to_address(&self) -> String {
match self {
IpAddress::Ipv4(ipv4) => ipv4.iter().map(|p| p.to_string()).collect::<Vec<_>>().join("."),
}
}
pub fn to_u32(&self) -> u32 {
match self {
IpAddress::Ipv4(ipv4) => {
u32::from_be_bytes(*ipv4)
}
}
}
pub fn is_matches(&self, socket_addr: &SocketAddr) -> bool {
match self {
IpAddress::Ipv4(self_ipv4_octets) => IpAddressMask::Ipv4(*self_ipv4_octets, 32).is_matches(socket_addr),
}
}
}
impl Display for IpAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.to_address())
}
}
#[derive(Debug, Clone)]
pub enum IpAddressMask {
Ipv4([u8; 4], u8),
}
impl IpAddressMask {
pub fn parse_ipv4(addr: &str) -> Option<Self> {
let addr_mask_parts = addr.split('/').collect::<Vec<_>>();
let (addr_ip, mask) = if addr_mask_parts.len() == 1 {
(addr_mask_parts[0], 32)
} else if addr_mask_parts.len() == 2 {
if let Ok(mask) = addr_mask_parts[1].parse::<u8>() {
(addr_mask_parts[0], mask)
} else {
return None;
}
} else {
return None;
};
parse_ipv4_addr(addr_ip).map(|parts| IpAddressMask::Ipv4(parts, mask))
}
pub fn to_address(&self) -> String {
match self {
IpAddressMask::Ipv4(ipv4, mask) => {
format!("{}/{}", ipv4.iter().map(|p| p.to_string()).collect::<Vec<_>>().join("."), mask)
}
}
}
pub fn is_matches(&self, socket_addr: &SocketAddr) -> bool {
match socket_addr {
SocketAddr::V4(socket_addr_v4) => {
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 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
}
}
}
SocketAddr::V6(_) => false,
}
}
}
impl Display for IpAddressMask {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.to_address())
}
}
#[derive(Debug, Clone)]
pub struct IpAddressMaskGroup {
pub ip_address_mask_group: Vec<IpAddressMask>,
}
impl IpAddressMaskGroup {
pub fn parse(ip_mask_group: &[String]) -> Self {
let mut ret = vec![];
for ip_mask_addr in ip_mask_group {
if let Some(ip_mask) = IpAddressMask::parse_ipv4(ip_mask_addr) {
ret.push(ip_mask);
}
}
Self { ip_address_mask_group: ret }
}
pub fn is_empty(&self) -> bool {
self.ip_address_mask_group.is_empty()
}
pub fn is_matches(&self, socket_addr: &SocketAddr) -> bool {
self.ip_address_mask_group.iter().any(|ip_address_mask| ip_address_mask.is_matches(socket_addr))
}
pub fn is_empty_or_matches(&self, socket_addr: &SocketAddr) -> bool {
self.is_empty() || self.is_matches(socket_addr)
}
}
impl Display for IpAddressMaskGroup {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "[{}]", self.ip_address_mask_group.iter().map(|i| format!("{}", i)).collect::<Vec<_>>().join(", "))
}
}
#[derive(Debug, Clone)]
pub struct IpAddressAndPort {
pub ip: IpAddress,
pub port: u16,
}
impl IpAddressAndPort {
pub fn parse(ip_address_and_port: &str) -> Option<Self> {
if let Some((ipv4, port)) = parse_ip_and_port(ip_address_and_port) {
return Some(IpAddressAndPort {
ip: IpAddress::Ipv4(ipv4),
port,
});
}
None
}
pub fn to_address(&self) -> String {
format!("{}:{}", self.ip, self.port)
}
pub fn to_ipv4_and_port(&self) -> ([u8; 4], u16) {
match self.ip {
IpAddress::Ipv4(ipv4) => (ipv4, self.port),
}
}
}
impl Display for IpAddressAndPort {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}:{}", self.ip, self.port)
}
}
// :8080 -> 127.0.0.1:8080
fn parse_ip_and_port(listen: &str) -> Option<([u8; 4], u16)> {
let listen_addr = match listen.split(':').next() {
None => DEFAULT_LISTEN_ADDR,
Some(addr) if addr.is_empty() => DEFAULT_LISTEN_ADDR,
Some(addr) => match parse_ipv4_addr(addr) {
Some(parsed_ip_address) => parsed_ip_address,
None => return None,
},
};
let listen_port = match listen.split(':').nth(1) {
None => return None,
Some(port) => match port.parse::<u16>() {
Ok(port) => port,
Err(_) => return None,
},
};
Some((listen_addr, listen_port))
}
fn ipv4_mask(mask: u8) -> u32 {
let mut r = 0_u32;
for _ in 0..mask {
r <<= 1;
r |= 1;
}
for _ in mask..32 {
r <<= 1;
}
r
}
fn get_ipv4_mask(mask_len: u8) -> u32 {
let mut m = 0_u32;
let ml = mask_len as usize;
for _ in 0..ml {
m <<= 1;
m |= 1;
}
let ml_left = 32 - ml;
for _ in 0..ml_left {
m <<= 1;
}
m
}
fn ipv4_to_u32(ipv4: &[u8; 4]) -> u32 {
u32::from_be_bytes(*ipv4)
}
fn parse_ipv4_addr(addr: &str) -> Option<[u8; 4]> {
let addr_parts = addr.split('.').collect::<Vec<_>>();
if addr_parts.len() != 4 {
return None;
}
let parsed_addr = || -> XResult<[u8; 4]> {
Ok([addr_parts[0].parse::<u8>()?,
addr_parts[1].parse::<u8>()?,
addr_parts[2].parse::<u8>()?,
addr_parts[3].parse::<u8>()?
])
};
parsed_addr().ok()
}
#[test]
fn test_ip_address_is_matches() {
let addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), 123);
let addr2 = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 2)), 123);
assert_eq!(true, IpAddressMask::parse_ipv4("127.0.0.1").unwrap().is_matches(&addr));
assert_eq!(true, IpAddressMask::parse_ipv4("127.0.0.1/32").unwrap().is_matches(&addr));
assert_eq!(true, IpAddressMask::parse_ipv4("127.0.0.1/31").unwrap().is_matches(&addr));
assert_eq!(true, IpAddressMask::parse_ipv4("127.0.0.1/30").unwrap().is_matches(&addr));
assert_eq!(false, IpAddressMask::parse_ipv4("127.0.0.1").unwrap().is_matches(&addr2));
assert_eq!(false, IpAddressMask::parse_ipv4("127.0.0.1/32").unwrap().is_matches(&addr2));
assert_eq!(false, IpAddressMask::parse_ipv4("127.0.0.1/31").unwrap().is_matches(&addr2));
assert_eq!(true, IpAddressMask::parse_ipv4("127.0.0.1/30").unwrap().is_matches(&addr2));
}
#[test]
fn test_ip_address_port() {
let ip_address_and_port = IpAddressAndPort::parse(":80");
assert_eq!("127.0.0.1:80", format!("{}", ip_address_and_port.unwrap()));
let ip_address_and_port = IpAddressAndPort::parse("0.0.0.0:80");
assert_eq!("0.0.0.0:80", format!("{}", ip_address_and_port.unwrap()));
let ip_address_and_port = IpAddressAndPort::parse("1.1.1.1:80");
assert_eq!("1.1.1.1:80", format!("{}", ip_address_and_port.unwrap()));
}
#[test]
fn test_ip_address_mask_group_is_matches() {
let group = IpAddressMaskGroup::parse(&vec!["127.0.0.1".to_owned(), "10.0.0.0/24".to_owned()]);
let addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), 123);
assert_eq!(true, group.is_matches(&addr));
let addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 2)), 123);
assert_eq!(false, group.is_matches(&addr));
let addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 0, 2)), 123);
assert_eq!(true, group.is_matches(&addr));
let addr = SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 1, 2)), 123);
assert_eq!(false, group.is_matches(&addr));
}
#[test]
fn test_get_ipv4_mask() {
assert_eq!([0, 0, 0, 0], get_ipv4_mask(0).to_be_bytes());
assert_eq!([128, 0, 0, 0], get_ipv4_mask(1).to_be_bytes());
assert_eq!([192, 0, 0, 0], get_ipv4_mask(2).to_be_bytes());
assert_eq!([224, 0, 0, 0], get_ipv4_mask(3).to_be_bytes());
assert_eq!([240, 0, 0, 0], get_ipv4_mask(4).to_be_bytes());
assert_eq!([248, 0, 0, 0], get_ipv4_mask(5).to_be_bytes());
assert_eq!([252, 0, 0, 0], get_ipv4_mask(6).to_be_bytes());
assert_eq!([254, 0, 0, 0], get_ipv4_mask(7).to_be_bytes());
assert_eq!([255, 0, 0, 0], get_ipv4_mask(8).to_be_bytes());
assert_eq!([255, 128, 0, 0], get_ipv4_mask(9).to_be_bytes());
assert_eq!([255, 192, 0, 0], get_ipv4_mask(10).to_be_bytes());
assert_eq!([255, 224, 0, 0], get_ipv4_mask(11).to_be_bytes());
assert_eq!([255, 240, 0, 0], get_ipv4_mask(12).to_be_bytes());
assert_eq!([255, 248, 0, 0], get_ipv4_mask(13).to_be_bytes());
assert_eq!([255, 252, 0, 0], get_ipv4_mask(14).to_be_bytes());
assert_eq!([255, 254, 0, 0], get_ipv4_mask(15).to_be_bytes());
assert_eq!([255, 255, 255, 254], get_ipv4_mask(31).to_be_bytes());
assert_eq!([255, 255, 255, 255], get_ipv4_mask(32).to_be_bytes());
}
#[test]
fn test_ip_and_ip_mask_matcher() {
{
let mut matcher = IpAndIpMaskMatcher::new();
matcher.add_ip_address(&IpAddress::Ipv4([127, 0, 0, 1]));
assert!(matcher.contains_ip_address(&IpAddress::Ipv4([127, 0, 0, 1])));
assert!(!matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 31));
}
{
let mut matcher = IpAndIpMaskMatcher::new();
matcher.add_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 24);
assert!(matcher.contains_ip_address(&IpAddress::Ipv4([127, 0, 0, 1])));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 31));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 30));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 24));
assert!(!matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 23));
}
{
let mut matcher = IpAndIpMaskMatcher::new();
matcher.add_ip_address_mask(&IpAddress::Ipv4([0, 0, 0, 0]), 0);
assert!(matcher.contains_ip_address(&IpAddress::Ipv4([127, 0, 0, 1])));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 31));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 30));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 24));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([127, 0, 0, 0]), 23));
}
{
let mut matcher = IpAndIpMaskMatcher::new();
matcher.add_ip_address_mask(&IpAddress::Ipv4([192, 168, 1, 2]), 32);
assert!(matcher.contains_ip_address(&IpAddress::Ipv4([192, 168, 1, 2])));
assert!(!matcher.contains_ip_address_mask(&IpAddress::Ipv4([192, 168, 1, 2]), 31));
}
{
let mut matcher = IpAndIpMaskMatcher::new();
matcher.add_ip_address_mask(&IpAddress::Ipv4([192, 168, 1, 2]), 16);
assert!(matcher.contains_ip_address(&IpAddress::Ipv4([192, 168, 1, 2])));
assert!(matcher.contains_ip_address_mask(&IpAddress::Ipv4([192, 168, 1, 2]), 31));
}
}

View File

@@ -1,3 +1,7 @@
use std::env;
use std::path::PathBuf;
use crate::iff;
pub fn is_macos() -> bool { pub fn is_macos() -> bool {
cfg!(target_os = "macos") cfg!(target_os = "macos")
@@ -10,3 +14,13 @@ pub fn is_linux() -> bool {
pub fn is_macos_or_linux() -> bool { pub fn is_macos_or_linux() -> bool {
is_macos() || is_linux() 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

@@ -1,46 +1,64 @@
use super::XResult; use crate::XResult;
pub const SIZE_KB: i64 = 1024; pub const SIZE_KB: i64 = 1024;
pub const SIZE_MB: i64 = SIZE_KB * SIZE_KB; pub const SIZE_MB: i64 = SIZE_KB * SIZE_KB;
pub const SIZE_GB: i64 = SIZE_MB * SIZE_KB; pub const SIZE_GB: i64 = SIZE_MB * SIZE_KB;
pub const SIZE_PB: i64 = SIZE_GB * SIZE_KB; pub const SIZE_TB: i64 = SIZE_GB * SIZE_KB;
pub const SIZE_TB: i64 = SIZE_PB * SIZE_KB; pub const SIZE_PB: i64 = SIZE_TB * SIZE_KB;
pub fn parse_size(size: &str) -> XResult<i64> { pub fn parse_size(size: &str) -> XResult<i64> {
let lower_size = size.to_lowercase(); let lower_size = size.to_lowercase();
let no_last_b_size = if lower_size.ends_with('b') { let no_last_b_size = crate::iff!(
&lower_size[0..lower_size.len()-1] lower_size.ends_with('b'), &lower_size[0..lower_size.len()-1], &lower_size
} else { );
&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)
}; };
if no_last_b_size.ends_with('k') { match no_last_b_size.chars().last() {
return Ok((SIZE_KB as f64 * no_last_b_size[0..no_last_b_size.len()-1].parse::<f64>()?) as i64); Some('k') => parse_and_process_size(SIZE_KB),
} else if no_last_b_size.ends_with('m') { Some('m') => parse_and_process_size(SIZE_MB),
return Ok((SIZE_MB as f64 * no_last_b_size[0..no_last_b_size.len()-1].parse::<f64>()?) as i64); Some('g') => parse_and_process_size(SIZE_GB),
} else if no_last_b_size.ends_with('g') { Some('t') => parse_and_process_size(SIZE_TB),
return Ok((SIZE_GB as f64 * no_last_b_size[0..no_last_b_size.len()-1].parse::<f64>()?) as i64); Some('p') => parse_and_process_size(SIZE_PB),
} else if no_last_b_size.ends_with('t') { _ => Ok(no_last_b_size.parse::<i64>()?),
return Ok((SIZE_TB as f64 * no_last_b_size[0..no_last_b_size.len()-1].parse::<f64>()?) as i64);
} else if no_last_b_size.ends_with('p') {
return Ok((SIZE_PB as f64 * no_last_b_size[0..no_last_b_size.len()-1].parse::<f64>()?) as i64);
} }
Ok(no_last_b_size.parse::<i64>()?)
} }
pub fn get_display_size(size: i64) -> String { pub fn get_display_size(size: i64) -> String {
if size < SIZE_KB { if size < SIZE_KB {
size.to_string() format!("{} {}", size, crate::iff!(size == 1, "byte", "bytes" ))
} else if size < SIZE_MB { } else if size < SIZE_MB {
format!("{:.*}KB", 2, (size as f64) / 1024.) format!("{:.*}KiB", 2, (size as f64) / SIZE_KB as f64)
} else if size < SIZE_GB { } else if size < SIZE_GB {
format!("{:.*}MB", 2, (size as f64) / 1024. / 1024.) format!("{:.*}MiB", 2, (size as f64) / SIZE_MB as f64)
} else if size < SIZE_TB { } else if size < SIZE_TB {
format!("{:.*}GB", 2, (size as f64) / 1024. / 1024. / 1024.) format!("{:.*}GiB", 2, (size as f64) / SIZE_GB as f64)
} else if size < SIZE_PB { } else if size < SIZE_PB {
format!("{:.*}TB", 2, (size as f64) / 1024. / 1024. / 1024. / 1024.) format!("{:.*}TiB", 2, (size as f64) / SIZE_TB as f64)
} else { } else {
format!("{:.*}PB", 2, (size as f64) / 1024. / 1024. / 1024. / 1024. / 1024.) format!("{:.*}PiB", 2, (size as f64) / SIZE_PB as f64)
} }
} }
#[test]
fn test_parse_size() {
assert_eq!(parse_size("1").unwrap(), 1);
assert_eq!(parse_size("1k").unwrap(), 1024);
assert_eq!(parse_size("1m").unwrap(), 1024 * 1024);
assert_eq!(parse_size("1g").unwrap(), 1024 * 1024 * 1024);
assert_eq!(parse_size("1t").unwrap(), 1024 * 1024 * 1024 * 1024);
assert_eq!(parse_size("1p").unwrap(), 1024 * 1024 * 1024 * 1024 * 1024);
}
#[test]
fn test_get_display_size() {
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");
}

72
src/util_str.rs Normal file
View File

@@ -0,0 +1,72 @@
/// 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()
}
pub fn split_kv(s: &str, split: char) -> (String, String) {
let mut k = String::new();
let mut v = String::new();
let mut is_splited = false;
let cs = s.chars();
for c in cs {
if is_splited {
v.push(c);
} else if c == split {
is_splited = true;
} else {
k.push(c);
}
}
(k, v)
}
#[test]
fn test_split_kv() {
assert_eq!(("".to_owned(), "".to_owned()), split_kv("", '='));
assert_eq!(("aaaa".to_owned(), "".to_owned()), split_kv("aaaa", '='));
assert_eq!(("".to_owned(), "aaaa".to_owned()), split_kv("=aaaa", '='));
assert_eq!(("aa".to_owned(), "bb".to_owned()), split_kv("aa=bb", '='));
assert_eq!(("aa".to_owned(), "bb".to_owned()), split_kv("aa:bb", ':'));
}
#[test]
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);
assert_eq!(lines[0], "aa");
assert_eq!(lines[1], "bb");
assert_eq!(lines[2], "cc");
}
}

59
src/util_term.rs Normal file
View File

@@ -0,0 +1,59 @@
use std::io::{self, Write};
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 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 {
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 ["y", "yes"].contains(&buff.as_str()) {
return true;
}
if ["n", "no"].contains(&buff.as_str()) {
return false;
}
}
}

View File

@@ -1,9 +1,95 @@
use std::time::SystemTime; 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 { pub fn get_current_secs() -> u64 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() get_secs(&SystemTime::now())
} }
pub fn get_current_millis() -> u128 { pub fn get_current_millis() -> u128 {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis() 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> {
if t.is_empty() {
return None;
}
let parse_and_process_time = |mul: u32| {
t[..t.len() - 1].parse::<f64>().map(|ti| Duration::from_millis((ti * mul as f64) as u64)).ok()
};
match t.to_ascii_lowercase().chars().last() {
Some('s') => parse_and_process_time(1000),
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(Duration::from_millis).ok(),
}
}
#[test]
fn test_get_current_secs() {
assert_ne!(get_current_secs(), 0);
}
#[test]
fn test_get_current_millis() {
assert_ne!(get_current_millis(), 0);
}
#[test]
fn test_parse_duration() {
assert_eq!(None, parse_duration(""));
assert_eq!(None, parse_duration("X"));
assert_eq!(None, parse_duration("S"));
assert_eq!(Duration::from_millis(1), parse_duration("1").unwrap());
assert_eq!(Duration::from_millis(100), parse_duration("100").unwrap());
assert_eq!(Duration::from_millis(1000), parse_duration("1s").unwrap());
assert_eq!(Duration::from_millis(2000), parse_duration("2S").unwrap());
assert_eq!(Duration::from_millis(1500), parse_duration("1.5s").unwrap());
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());
}
#[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));
} }

92
src/util_tlv.rs Normal file
View File

@@ -0,0 +1,92 @@
use std::io::{Write, Read};
use crate::XResult;
pub struct Tlv {
pub r#type: u16,
pub length: u32,
pub value: Vec<u8>,
}
impl Tlv {
pub fn new_empty(ty: u16) -> Self {
Self {
r#type: ty,
length: 0,
value: vec![],
}
}
pub fn new(ty: u16, value: Vec<u8>) -> Self {
assert!(value.len() < u32::MAX as usize, "Value too huge");
Self {
r#type: ty,
length: value.len() as u32,
value,
}
}
pub fn compose(&self) -> Vec<u8> {
let cap = self.value.len() + 4 + 2;
let mut v = Vec::with_capacity(cap);
v.extend_from_slice(&self.r#type.to_be_bytes());
v.extend_from_slice(&self.length.to_be_bytes());
v.extend_from_slice(self.value.as_slice());
v
}
pub fn write<T>(&self, mut w: T) -> XResult<usize> where T: Write {
let mut len = w.write(&self.r#type.to_be_bytes())?;
len += w.write(&self.length.to_be_bytes())?;
len += w.write(self.value.as_slice())?;
Ok(len)
}
pub fn read<T>(mut r: T) -> XResult<Self> where T: Read {
let mut r#type = [0_u8; 2];
r.read_exact(&mut r#type)?;
let mut length = [0_u8; 4];
r.read_exact(&mut length)?;
let len = u32::from_be_bytes(length);
let mut value = vec![0_u8; len as usize];
r.read_exact(&mut value)?;
Ok(Self {
r#type: u16::from_be_bytes(r#type),
length: len,
value,
})
}
}
#[test]
fn test_tlv() {
{
let tlv = Tlv::new_empty(0);
assert_eq!([0, 0, 0, 0, 0, 0], tlv.compose().as_slice());
}
{
let tlv = Tlv::new_empty(1);
assert_eq!([0, 1, 0, 0, 0, 0], tlv.compose().as_slice());
}
{
let tlv = Tlv::new_empty(256);
assert_eq!([1, 0, 0, 0, 0, 0], tlv.compose().as_slice());
}
{
let tlv = Tlv::new(1, vec![0]);
assert_eq!([0, 1, 0, 0, 0, 1, 0], tlv.compose().as_slice());
}
{
let tlv = Tlv::new(2, vec![1, 2]);
assert_eq!([0, 2, 0, 0, 0, 2, 1, 2], tlv.compose().as_slice());
}
{
let tlv = Tlv::new(2, vec![1, 2]);
let bs = tlv.compose().clone();
let tlv2 = Tlv::read(bs.as_slice()).unwrap();
assert_eq!(bs, tlv2.compose());
assert_eq!(2, tlv2.r#type);
assert_eq!(2, tlv2.length);
assert_eq!([1, 2], tlv2.value.as_slice());
}
}