mirror of
https://github.com/jht5945/rust_util.git
synced 2026-01-12 23:30:05 +08:00
Compare commits
82 Commits
328f4c503f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
254585bb90
|
|||
|
e13b2a3db4
|
|||
|
e2b258ca09
|
|||
|
4b596db8de
|
|||
|
af4b91d4e9
|
|||
|
c1ef4c4b53
|
|||
|
ca597cf0d6
|
|||
|
c56b7deaaf
|
|||
|
16e859c785
|
|||
|
29519b3a43
|
|||
|
32a2b529fd
|
|||
|
5cdad86ea6
|
|||
| 7e5bec260f | |||
|
30e9384505
|
|||
|
80ff02e6b8
|
|||
| 7aa8f4c43c | |||
| efbbf4261a | |||
| 568a2173d5 | |||
| c69cb1c35b | |||
| 019a410750 | |||
| 8d4b8aa1e0 | |||
| c8a0f22425 | |||
| 53720edc00 | |||
| e89d6be38d | |||
| 7e752d95b5 | |||
| b4c789aebc | |||
| b91e6e060a | |||
| 6ed6a9d26f | |||
| 4d6b7b3180 | |||
| 4b39ac48fd | |||
| cb1b8187fa | |||
| 50159a0ca8 | |||
| aa8b4ed447 | |||
| a805d7cb1c | |||
| a8ae90ab4d | |||
| da71d5c0c2 | |||
| 797d75bf71 | |||
| 5df135fda9 | |||
| 249d7ee36e | |||
| 19d967ef0e | |||
| 9d9423cfec | |||
| f0bd9a719a | |||
| 1a3c8cbeb4 | |||
| 17c06e304d | |||
| 59705779d1 | |||
| 0001f67f0d | |||
| c61c8d37e4 | |||
| 0c38427bcb | |||
| 6881cebf72 | |||
| a55ad86546 | |||
| 56f84533aa | |||
| 807bf490fb | |||
| 6b4f7bf3ef | |||
| 6e821a4cd9 | |||
| 755dc5619d | |||
| 90ffe7c7d9 | |||
| 289fe68e61 | |||
| dd2bec2669 | |||
| e027f8f713 | |||
| 20b452656e | |||
| 89f45cc354 | |||
| 0a2302301d | |||
| 68e889c542 | |||
| 3e5292105b | |||
| 745e577c7b | |||
| 6b86709137 | |||
| 1917b23c27 | |||
| 3db1fb8130 | |||
| 0576c23c5a | |||
| 79e2618e98 | |||
| 90fc790b64 | |||
| bd8152f59b | |||
| c5749c8137 | |||
| debe4d8c2b | |||
| 0bdcbfbbde | |||
| d8d52abd5b | |||
| 31a2910393 | |||
| a298b6ffcb | |||
| e79908b4fe | |||
| fe7ab57ac5 | |||
| 5ec790e192 | |||
| 2e8c8fe8c8 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
18
Cargo.toml
18
Cargo.toml
@@ -1,14 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rust_util"
|
name = "rust_util"
|
||||||
version = "0.3.0"
|
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 }
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -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,9 +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
|
## 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
|
* Jun 21, 2020 v0.3.0
|
||||||
* add struct `JoinFilesReader`
|
* add struct `JoinFilesReader`
|
||||||
|
|
||||||
|
|||||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
||||||
11
demo/test_clap/Cargo.toml
Normal file
11
demo/test_clap/Cargo.toml
Normal 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"
|
||||||
30
demo/test_clap/src/main.rs
Normal file
30
demo/test_clap/src/main.rs
Normal 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
47
examples/log.rs
Normal 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
16
justfile
Normal 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
|
||||||
|
|
||||||
121
src/lib.rs
121
src/lib.rs
@@ -1,8 +1,9 @@
|
|||||||
#[macro_use]
|
#[macro_use] extern crate lazy_static;
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate term;
|
extern crate term;
|
||||||
|
|
||||||
use std::io::{ Error, ErrorKind, };
|
use std::error::Error;
|
||||||
|
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;
|
||||||
@@ -13,21 +14,115 @@ 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
103
src/util_clap.rs
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub fn is_env_on(var: &str) -> bool {
|
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 {
|
pub fn is_on(val: &str) -> bool {
|
||||||
let lower_val = val.to_lowercase();
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
149
src/util_file.rs
149
src/util_file.rs
@@ -1,18 +1,7 @@
|
|||||||
|
use std::fs::{self, File};
|
||||||
use std::{
|
use std::io::{Lines, BufReader};
|
||||||
env,
|
use std::path::{Path, PathBuf};
|
||||||
fs::{self, File},
|
use crate::{util_os, util_io, util_msg, new_box_ioerror, XResult};
|
||||||
io::{Lines, BufReader},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
iff,
|
|
||||||
util_os,
|
|
||||||
util_io,
|
|
||||||
new_box_ioerror,
|
|
||||||
XResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct JoinFilesReader {
|
pub struct JoinFilesReader {
|
||||||
files: Vec<String>,
|
files: Vec<String>,
|
||||||
@@ -21,14 +10,13 @@ pub struct JoinFilesReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn open_file_as_lines(f: &str) -> XResult<Lines<BufReader<File>>> {
|
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);
|
let br = BufReader::new(f);
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
Ok(br.lines())
|
Ok(br.lines())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JoinFilesReader {
|
impl JoinFilesReader {
|
||||||
|
|
||||||
pub fn new(fns: &[&str]) -> XResult<Self> {
|
pub fn new(fns: &[&str]) -> XResult<Self> {
|
||||||
let mut files: Vec<String> = vec![];
|
let mut files: Vec<String> = vec![];
|
||||||
for f in fns {
|
for f in fns {
|
||||||
@@ -39,11 +27,7 @@ impl JoinFilesReader {
|
|||||||
if !files.is_empty() {
|
if !files.is_empty() {
|
||||||
file_lines = Some(Box::new(open_file_as_lines(&files[0])?));
|
file_lines = Some(Box::new(open_file_as_lines(&files[0])?));
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self { files, file_ptr, file_lines })
|
||||||
files,
|
|
||||||
file_ptr,
|
|
||||||
file_lines,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,10 +47,11 @@ impl Iterator for JoinFilesReader {
|
|||||||
} else {
|
} else {
|
||||||
// if open file failed, will not continue read files
|
// 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]) {
|
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,
|
None => return None,
|
||||||
}
|
}
|
||||||
@@ -77,6 +62,62 @@ impl Iterator for JoinFilesReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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> {
|
pub fn locate_file(files: &[String]) -> Option<PathBuf> {
|
||||||
for f in files {
|
for f in files {
|
||||||
match PathBuf::from(&resolve_file_path(f)) {
|
match PathBuf::from(&resolve_file_path(f)) {
|
||||||
@@ -87,13 +128,15 @@ pub fn locate_file(files: &[String]) -> Option<PathBuf> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deprecated]
|
||||||
pub fn get_home_str() -> Option<String> {
|
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 {
|
pub fn resolve_file_path(path: &str) -> String {
|
||||||
let home_path = match get_home_str() {
|
let home_path = match util_os::get_user_home() {
|
||||||
Some(p) => p, None => return path.to_owned(),
|
Some(p) => p,
|
||||||
|
None => return path.to_owned(),
|
||||||
};
|
};
|
||||||
match path {
|
match path {
|
||||||
"~" => home_path,
|
"~" => home_path,
|
||||||
@@ -103,13 +146,13 @@ pub fn resolve_file_path(path: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
match path {
|
match path {
|
||||||
"~" => Some(PathBuf::from(get_home_str()?)),
|
"~" => Some(PathBuf::from(util_os::get_user_home()?)),
|
||||||
path if path.starts_with("~/") => Some(PathBuf::from(&format!("{}/{}", get_home_str()?, &path[2..]))),
|
path if path.starts_with("~/") => Some(PathBuf::from(&format!("{}/{}", util_os::get_user_home()?, &path[2..]))),
|
||||||
path => fs::canonicalize(path).ok(),
|
path => fs::canonicalize(path).ok(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,47 +169,49 @@ pub fn is_symlink(path: &Path) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
Ok(rd) => rd, Err(err) => {
|
Ok(rd) => rd,
|
||||||
func_walk_error(&dir, Box::new(err));
|
Err(err) => {
|
||||||
|
func_walk_error(dir, Box::new(err));
|
||||||
return Ok(());
|
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 {
|
||||||
Ok(item) => item, Err(err) => {
|
Ok(item) => item,
|
||||||
func_walk_error(&dir, Box::new(err));
|
Err(err) => {
|
||||||
|
func_walk_error(dir, Box::new(err));
|
||||||
continue; // Ok?
|
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() && func_filter_dir(&sub_dir) {
|
} else if sub_dir.is_dir() && 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
|
||||||
|
|||||||
177
src/util_git.rs
Normal file
177
src/util_git.rs
Normal 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]);
|
||||||
|
}
|
||||||
164
src/util_io.rs
164
src/util_io.rs
@@ -1,28 +1,87 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, ErrorKind, prelude::*, Write};
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use std::{
|
use crate::{SimpleError, XResult};
|
||||||
fs::File,
|
use crate::util_file;
|
||||||
io::{self,
|
use crate::util_msg;
|
||||||
ErrorKind,
|
use crate::util_size;
|
||||||
prelude::*,
|
|
||||||
},
|
|
||||||
time::{SystemTime, Duration},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{ XResult, new_box_ioerror, };
|
|
||||||
use super::util_size::get_display_size;
|
|
||||||
use super::util_msg::print_lastline;
|
|
||||||
use super::util_file::resolve_file_path;
|
|
||||||
|
|
||||||
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>> {
|
pub fn get_read_stdin_or_file(file: &str) -> XResult<Box<dyn Read>> {
|
||||||
if file.is_empty() {
|
if file.is_empty() {
|
||||||
Ok(Box::new(io::stdin()))
|
Ok(Box::new(io::stdin()))
|
||||||
} else {
|
} else {
|
||||||
match File::open(&resolve_file_path(file)) {
|
match File::open(util_file::resolve_file_path(file)) {
|
||||||
Ok(f) => Ok(Box::new(f)),
|
Ok(f) => Ok(Box::new(f)),
|
||||||
Err(err) => Err(new_box_ioerror(&format!("Open file {}, erorr: {}", file, err))),
|
Err(err) => Err(SimpleError::new(format!("Open file {}, erorr: {}", file, err)).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,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 {
|
||||||
@@ -93,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
195
src/util_msg.rs
195
src/util_msg.rs
@@ -1,66 +1,187 @@
|
|||||||
use std::{
|
use std::env;
|
||||||
io::{self, Write},
|
use std::io::{self, Write};
|
||||||
sync::{Arc, Mutex},
|
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(()));
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum MessageType { INFO, OK, WARN, ERROR, DEBUG, }
|
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 {
|
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
|
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) {
|
||||||
match term::stdout() {
|
if is_std_out {
|
||||||
Some(mut t) => {
|
match term::stdout() {
|
||||||
if *IS_ATTY {
|
Some(mut t) => {
|
||||||
if let Some(c) = color {
|
if *IS_ATTY {
|
||||||
t.fg(c).ok();
|
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) {
|
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) {
|
||||||
|
{
|
||||||
|
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();
|
let mut lock = PRINT_MESSAGE_LOCK.lock().unwrap();
|
||||||
print_color(color, true, h);
|
print_color(is_std_out, color, true, h);
|
||||||
println!(" {}", message);
|
if is_std_out {
|
||||||
|
println!(" {}", message);
|
||||||
|
} else {
|
||||||
|
eprintln!(" {}", message)
|
||||||
|
}
|
||||||
*lock = ();
|
*lock = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_ok (message: &str) { print_message(MessageType::OK, message); }
|
pub fn print_ex(message: &str, new_line: bool) {
|
||||||
pub fn print_warn (message: &str) { print_message(MessageType::WARN, message); }
|
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_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); }
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,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
393
src/util_net.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
24
src/util_runtime.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
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;
|
||||||
@@ -9,39 +9,35 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,11 +54,11 @@ fn test_parse_size() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_display_size() {
|
fn test_get_display_size() {
|
||||||
assert_eq!(get_display_size(0), "0");
|
assert_eq!(get_display_size(0), "0 bytes");
|
||||||
assert_eq!(get_display_size(111), "111");
|
assert_eq!(get_display_size(111), "111 bytes");
|
||||||
assert_eq!(get_display_size(1024), "1.00KB");
|
assert_eq!(get_display_size(1024), "1.00KiB");
|
||||||
assert_eq!(get_display_size(1024 * 1024), "1.00MB");
|
assert_eq!(get_display_size(1024 * 1024), "1.00MiB");
|
||||||
assert_eq!(get_display_size(1024 * 1024 * 1024), "1.00GB");
|
assert_eq!(get_display_size(1024 * 1024 * 1024), "1.00GiB");
|
||||||
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024), "1.00TB");
|
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024), "1.00TiB");
|
||||||
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024 * 1024), "1.00PB");
|
assert_eq!(get_display_size(1024 * 1024 * 1024 * 1024 * 1024), "1.00PiB");
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,6 @@
|
|||||||
|
|
||||||
/// Split string to lines, splited by '\r', '\n' or "\r\n"
|
/// Split string to lines, splited by '\r', '\n' or "\r\n"
|
||||||
pub fn read_str_to_lines(s: &str) -> Vec<String> {
|
pub fn read_str_to_lines(s: &str) -> Vec<String> {
|
||||||
s.lines().map(|ln| ln.to_owned()).collect()
|
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) {
|
pub fn split_kv(s: &str, split: char) -> (String, String) {
|
||||||
@@ -61,25 +38,30 @@ fn test_read_str_to_lines() {
|
|||||||
let s = "";
|
let s = "";
|
||||||
let lines = read_str_to_lines(s);
|
let lines = read_str_to_lines(s);
|
||||||
assert_eq!(lines.len(), 0);
|
assert_eq!(lines.len(), 0);
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
let s = "\n";
|
let s = "\n";
|
||||||
let lines = read_str_to_lines(s);
|
let lines = read_str_to_lines(s);
|
||||||
assert_eq!(lines.len(), 1);
|
assert_eq!(lines.len(), 1);
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
let s = "\r";
|
let s = "\r";
|
||||||
let lines = read_str_to_lines(s);
|
let lines = read_str_to_lines(s);
|
||||||
assert_eq!(lines.len(), 1);
|
assert_eq!(lines.len(), 1);
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
let s = "\r\n";
|
let s = "\r\n";
|
||||||
let lines = read_str_to_lines(s);
|
let lines = read_str_to_lines(s);
|
||||||
assert_eq!(lines.len(), 1);
|
assert_eq!(lines.len(), 1);
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
let s = "aa\r\nbb";
|
let s = "aa\r\nbb";
|
||||||
let lines = read_str_to_lines(s);
|
let lines = read_str_to_lines(s);
|
||||||
assert_eq!(lines.len(), 2);
|
assert_eq!(lines.len(), 2);
|
||||||
assert_eq!(lines[0], "aa");
|
assert_eq!(lines[0], "aa");
|
||||||
assert_eq!(lines[1], "bb");
|
assert_eq!(lines[1], "bb");
|
||||||
} {
|
}
|
||||||
|
{
|
||||||
let s = "aa\r\nbb\ncc";
|
let s = "aa\r\nbb\ncc";
|
||||||
let lines = read_str_to_lines(s);
|
let lines = read_str_to_lines(s);
|
||||||
assert_eq!(lines.len(), 3);
|
assert_eq!(lines.len(), 3);
|
||||||
|
|||||||
59
src/util_term.rs
Normal file
59
src/util_term.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
92
src/util_tlv.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user