目录

doe — Versatile Rust Utility Crate

Crates.io docs.rs MIT

doe bundles encoding helpers, cryptographic primitives, clipboard, keyboard & mouse simulation, screenshot capture, HTTP & JSON utilities, date/time, Excel & DOCX manipulation, structured logging, an async runtime, and more — every feature opt-in, minimal dependencies.

Installation

[dependencies]
doe = { version = "1", features = ["ctf", "crypto", "date"] }

Feature flags

Feature What it enables
ctf Hex, base64, URL, hashing, regex, Chinese text, HTML parsing
clip Clipboard read & write
keyboard Keyboard input simulation
mouse Mouse movement, click, drag, scroll
kcm Keyboard + clipboard + mouse combined
crypto AES, RSA, SHA-1/2/3, Blake3, SM2/3/4, MD5, RIPEMD-160
screenshot Screen capture — Windows GDI / macOS CoreGraphics / Linux X11+Wayland
images Image resize, padding, ICO & ICNS conversion
date Date/time formatting, Excel serial-date conversion
xlsx Read & write .xlsx files
docx Read & write .docx files
exiftool EXIF metadata extraction
httprs Simplified reqwest HTTP client (sync & async)
json json! macro, to_json_value, as_bytes
asyncrs Tokio runtime + block_on + async command
logger Tracing-subscriber with file appender & env-filter
sqlserver Deadpool Tiberius SQL Server pool
axumserver Axum HTTP server (CORS, TLS, rate-limit)
ip_addr Local IP / interface discovery
process Enumerate & kill processes
sys_random Cryptographically secure random bytes & UUID v4
full All of the above

Always available (no feature needed): utils (hex/base64/percent/temp), color, fs, system/command, rand, macros, traits, timer, zoml, structs, DynError<T>.

Quick start

use doe::utils; // always available
assert_eq!(utils::hex::encode(b"hi"), "6869");
assert_eq!(utils::base64::encode_standard(b"hi"), "aGk=");

Examples

utils — encoding & temp (no feature)

use doe::utils;

// Hex
assert_eq!(utils::hex::encode(b"hello"), "68656c6c6f");
assert_eq!(utils::hex::decode("68656C6C6F").unwrap(), b"hello");

// Base64 (standard, no-pad, URL-safe)
assert_eq!(utils::base64::encode_standard(b"hello"), "aGVsbG8=");
assert_eq!(utils::base64::encode_standard_no_pad(b"hello"), "aGVsbG8");
assert_eq!(utils::base64::encode_url_safe(b">?"), "Pj8=");
assert_eq!(utils::base64::decode_standard("aGVsbG8=").unwrap(), b"hello");

// Percent-encoding
let enc = utils::percent::encode("a b", utils::percent::EncodeSet::NonAlphanumeric);
assert_eq!(enc, "a%20b");
assert_eq!(utils::percent::decode(&enc).unwrap(), "a b");

// RAII temporary directory
{
    let tmp = utils::temp::TempDir::new().unwrap();
    std::fs::write(tmp.path().join("x.txt"), b"data").unwrap();
} // cleaned up automatically

color — RGB / hex (no feature)

use doe::color;

assert_eq!(color::rgb_to_hex((255, 87, 51)), "#FF5733");
assert_eq!(color::hex_to_rgb("#FF5733").unwrap(), (255, 87, 51));

fs — file-system (no feature)

use doe::fs;
use std::path::Path;

// Walk directories
let files   = fs::walk_dir_get_files(Path::new(".")).unwrap();
let folders = fs::walk_dir_get_folders(Path::new(".")).unwrap();

// Copy / move single files
fs::copy_file(Path::new("a.txt"), Path::new("b.txt")).unwrap();
fs::move_file(Path::new("b.txt"), Path::new("c.txt")).unwrap();

// Copy / move entire folders
fs::copy_folder(Path::new("src"), Path::new("backup")).unwrap();
fs::move_folder(Path::new("old"), Path::new("new")).unwrap();

// Append & remove read-only
fs::append_data_to_file("log.txt", b"new line\n".to_vec()).unwrap();
fs::set_not_readonly("readonly.txt").unwrap();

system / command — shell (no feature)

use doe::{system, command};

system("echo hello").unwrap();               // fire & forget
let out = command("echo hello").unwrap();    // capture output
println!("stdout: {}", out.stdout);
println!("stderr: {}", out.stderr);
assert!(out.status.success());

rand — LCG PRNG (no feature)

use doe::rand::LCG;

let mut rng = LCG::new();
println!("random u32: {}", rng.random());
println!("random f32: {}", rng.random_f32());

macros — utility macros (no feature)

use doe::*;

vec![1, 2, 3].dprintln();    // debug-print with file:line prefix
"hello".dprintln();

if has_powershell!() {
    println!("running on Windows with PowerShell");
}

traits — common traits (no feature)

use doe::*;
use doe::traits::*;

// DebugPrint
"hello".dprintln();          // [src/main.rs:5] "hello"
vec![1,2,3].dprintln();

// Sleep — call .sleep(secs) on any value
let x = 42.sleep(0.5);       // waits 500 ms, then x == 42

// Spawn — .spawn() convenience
let handle = (|| println!("in thread")).spawn();
handle.join().unwrap();

timer — scheduled tasks (no feature)

use doe::run_timer;
use std::time::{Duration, Instant};

run_timer(
    Instant::now(),
    Duration::from_secs(1),  // run every 1s
    Duration::from_secs(5),  // stop after 5s
    Box::new(|| println!("tick")),
);

zoml — ZOML / CSV (no feature)

use doe::zoml;

let csv = "name,age\nAlice,30\nBob,25";
let zoml = zoml::csv_to_zoml(csv).unwrap();
let back = zoml::zoml_to_csv(&zoml).unwrap();
assert_eq!(csv, back);

structs — Bfn / Bts (no feature)

use doe::structs::*;

// Bfn: buffered file read/write
let bfn = Bfn::new();
bfn.write("data.txt", "hello world").unwrap();
assert_eq!(bfn.read("data.txt").unwrap(), "hello world");

crypto (-F crypto)

use doe::crypto;

// SHA family
assert_eq!(crypto::md5(b"hello"), "5d41402abc4b2a76b9719d911017c592");
println!("sha1:   {}", crypto::sha1::sha1(b"hello"));
println!("sha256: {}", crypto::sha2::sha_256(b"hello"));
println!("sha512: {}", crypto::sha2::sha_512(b"hello"));
println!("blake3: {}", crypto::blake3(b"hello"));
println!("blake3_xof: {}", crypto::blake3_xof(b"hello", 64));

// AES-256-CBC
let key = b"12345678123456781234567812345678";
let iv  = b"1234567812345678";
let enc = crypto::aes::aes_128::aes_cbc_encrypt(key, iv, b"secret").unwrap();
let dec = crypto::aes::aes_128::aes_cbc_decrypt(key, iv, &enc).unwrap();
assert_eq!(dec, b"secret");

// AES-GCM (authenticated encryption)
let key = b"0123456789abcdef0123456789abcdef";
let nonce = b"0123456789abcdef";
let aad = b"additional data";
let enc = crypto::aes::aes_128::aes_gcm_encrypt(key, nonce, aad, b"secret").unwrap();
let dec = crypto::aes::aes_128::aes_gcm_decrypt(key, nonce, aad, &enc).unwrap();
assert_eq!(dec, b"secret");

// RSA
let (priv_key, pub_key) = crypto::rsa::generate_keypair(2048);
let enc = crypto::rsa::encrypt(&pub_key, b"message").unwrap();
let dec = crypto::rsa::decrypt(&priv_key, &enc).unwrap();
assert_eq!(dec, b"message");

// SM2 sign & verify
let sig = crypto::sm2::sm2_sign(
    b"private_key_32_bytes____",
    b"message to sign",
).unwrap();
let ok = crypto::sm2::sm2_verify(
    b"public_key_64_bytes_____",
    b"message to sign",
    &sig,
).unwrap();
assert!(ok);

ctf — encoding & text (-F ctf)

use doe::ctf::*;

// Hex
assert_eq!(hex_encode(b"hi"), "6869");
assert_eq!(hex_decode("6869").unwrap(), b"hi");

// Base64
assert_eq!(base64_encode(b"hi"), "aGk");
assert_eq!(base64_decode("aGk"), b"hi");

// URL encode / decode
assert_eq!(url_encode("a b"), "a%20b");
assert_eq!(url_decode("a%20b"), "a b");

// Chinese detection & conversion
assert!(contains_chinese("hello 测试"));
assert!(!contains_chinese("hello"));
assert_eq!(chinese_to_unicode("学习").unwrap(), "\u5b66\u4e60");

// Pinyin conversion
let py = convert_to_pinyin_with_non_chinese("测试 test");
println!("pinyin: {py}"); // ce_shi_test

// HTML parsing
let html = r#"<html><body><a href="/x">link</a></body></html>"#;
let links = parse_html(html, "a", Some("href"));
assert_eq!(links, vec!["/x"]);

// Find URLs / PNG URLs in text
let urls = find_urls("visit https://example.com now");
assert!(urls.contains(&"https://example.com".to_string()));

// CtfParty trait (on &str and String)
use doe::ctf::{CtfParty, Base64Mode};
assert_eq!("hello".to_b64(Base64Mode::Strict), "aGVsbG8=");
assert_eq!("hello".md5(), "5d41402abc4b2a76b9719d911017c592");
assert_eq!("hello".sha1(), "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
assert_eq!("HELLO".rot(13), "URYYB");
assert_eq!("hello".leet(), "h3ll0");

// Validation
assert!("192.168.1.1".is_ipv4());
assert!("test@example.com".is_email(doe::ctf::EmailMode::Light));
assert!("example.com".is_domain());

clipboard (-F clip)

use doe::clipboard;

clipboard::set_clipboard("copied text").unwrap();
let text = clipboard::get_clipboard().unwrap();
assert_eq!(text, "copied text");

keyboard (-F keyboard)

use doe::keyboard::{key_click, key_press, key_release, KeyCode};

key_click("a");              // press + release
key_press("ctrl+c");         // hold Ctrl, tap C
key_release("ctrl+c");       // release

// Virtual-key codes
key_press(KeyCode::A);
key_press(KeyCode::ENTER);
key_press(KeyCode::F1);

mouse (-F mouse)

use doe::mouse;

// Move & click
mouse::move_to(500, 300);
mouse::press("left");

// Right-click at position
mouse::move_and_press_right(800, 900);

// Drag
mouse::mouse_drag((100, 100).into(), (200, 200).into());

// Scroll
mouse::scrool(2);    // scroll up
mouse::scrool(-2);   // scroll down

screenshot (-F screenshot)

use doe::screenshot;

// Capture all screens to PNG files
screenshot::capture_all();  // → screenshot_0.png, screenshot_1.png, …

// Crop a region from the primary screen
if let Some(img) = screenshot::capture_area(10, 10, 200, 100) {
    img.save("cropped.png").unwrap();
}

// Read a pixel from an image
if let Some((r, g, b)) = screenshot::get_rgb_from_position("screenshot_0.png", 0, 0) {
    println!("#{r:02X}{g:02X}{b:02X}");
}

// Crop an existing image
use image::{ImageBuffer, Rgba};
let img = ImageBuffer::new(100, 100);
let cropped = screenshot::cut_area(img, 10, 10, 50, 50).unwrap();

images (-F images)

use doe::images;

// Add white padding
images::image_add_padding(
    "in.png", [255u8, 255, 255, 255], 10, 10, "out.png",
);

// Resize
images::image_resize("big.png", 64, 64, "thumb.png");

// Convert formats
images::convert_to_ico("icon.png", "icon.ico");
images::images_to_icns(
    &["16.png", "32.png", "64.png", "128.png"],
    "app.icns",
).unwrap();

date (-F date)

use doe::date;

println!("now:     {}", date::now_str());
println!("year:    {}", date::year());
println!("month:   {}", date::month());
println!("day:     {}", date::day());
println!("hours:   {}", date::hours());
println!("minutes: {}", date::minutes());
println!("seconds: {}", date::seconds());

// Recent 7 days
let days = date::get_recent_seven_days();
for d in &days { println!("{d}"); }

// Excel serial date conversions
let excel_val = date::normal_date_to_excel_date("2024-01-01").unwrap();
assert_eq!(excel_val, 45292);
let iso = date::excel_date_to_normal_date(45292).unwrap();
assert_eq!(iso, "2024-01-01");

xlsx (-F xlsx)

use doe::xlsx;
use doe::cellvalue;

// Set cell values
xlsx::xlsx_set_values("demo.xlsx", "Sheet1", &[
    cellvalue!("A1", "Name",   "string"),
    cellvalue!("B1", "42",     "number"),
    cellvalue!("C1", "=A1&B1", "formula"),
]).unwrap();

// Read a cell value
let val = xlsx::xlsx_get_cell_value_str("demo.xlsx", "Sheet1", "A1").unwrap();
println!("A1 = {val}");

// Read sheet names
let names = xlsx::xlsx_get_sheet_names("demo.xlsx").unwrap();
println!("sheets: {names:?}");

// Replace values & save
xlsx::xlsx_replace_values_save(
    "template.xlsx", "Sheet1",
    &[("{{name}}".to_string(), "Alice".to_string())],
    "output.xlsx",
).unwrap();

// Column name helpers
assert_eq!(xlsx::num_to_col(0), "A");
assert_eq!(xlsx::col_to_num("D"), 3);

docx (-F docx)

use doe::docx;

// Replace placeholder text
docx::docx_replace("template.docx", "{{name}}", "Alice").unwrap();

// Extract all text content
let content = docx::docx_get_content("doc.docx").unwrap();
println!("{content}");

// Remove read-only protection
docx::docx_remove_read_only("protected.docx").unwrap();

exiftool (-F exiftool)

use doe::exiftool;

// Read all metadata
let meta = exiftool::run("photo.jpg").unwrap();
if let Some(date) = meta.get("EXIF:DateTimeOriginal") {
    println!("taken on: {date}");
}

// Filter by keyword
let camera = exiftool::run_filtered("photo.jpg", "camera").unwrap();
for (k, v) in camera {
    println!("{k} = {v}");
}

// Get a single tag
let model = exiftool::get_tag("photo.jpg", "EXIF:Model").unwrap();
println!("camera: {model:?}");

httprs (-F httprs)

use doe::httprs;

// Sync client with custom headers
let headers = httprs::headers![
    "Content-Type"  => "application/json",
    "Authorization" => "Bearer my-token",
];
let client = httprs::sync_client_with_headers(headers);
let body = client.sync_get_bytes("https://httpbin.org/get").unwrap();
println!("{}", String::from_utf8_lossy(&body));

// Async client
use doe::httprs::{async_client_with_headers, ClientAsyncSend};
// #[tokio::main]
// async fn main() {
//     let client = async_client_with_headers(headers).await;
//     let body = client.async_get_bytes("https://httpbin.org/get").await;
// }

json (-F json)

use doe::json::{json, ToJsonValue, ValueToBytes};

// Build JSON
let val = json!({
    "name": "Alice",
    "age": 30,
    "active": true,
    "tags": ["rust", "developer"],
});
println!("{}", val);

// Serialise to bytes
let bytes: Vec<u8> = val.as_bytes();

// Deserialise from a string
let parsed = r#"{"key":"value"}"#.to_json_value();

asyncrs (-F asyncrs)

use doe::asyncrs;

asyncrs::block_on(async {
    // Run shell command asynchronously
    let output = asyncrs::command("echo hello").await;
    println!("{}", String::from_utf8_lossy(&output.stdout));
});

// Custom multi-thread runtime
let rt = doe::asyncrs::runtime::Builder::new_multi_thread()
    .enable_all()
    .build()
    .unwrap();
rt.block_on(async {
    println!("running on a custom runtime");
});

logger (-F logger)

use doe::logger;

// Initialise (terminal + file appender + env filter)
logger::init();

tracing::info!("server started on port 8080");
tracing::warn!(user_id = 42, "rate limit approaching");
tracing::error!(error = "timeout", "database connection failed");

ip_addr (-F ip_addr)

use doe::ip_addr;

let addrs = ip_addr::get_addrs().unwrap();
for addr in addrs {
    println!("{addr:?}");
}

process (-F process)

use doe::process;

// List all running processes
let procs = process::get_all_process_name_and_pid();
for p in &procs {
    println!("{:>6}  {}", p.pid, p.name);
}

// Kill by name or PID
process::kill_process_by_name("notepad.exe");
process::kill_process_by_pid(1234);

sys_random (-F sys_random)

use doe::sys_random;

// Cryptographically secure random bytes
let mut buf = [0u8; 32];
sys_random::get_system_random_bytes(&mut buf).unwrap();
println!("random bytes: {buf:02X?}");

// UUID v4
let id = sys_random::uuid();
println!("UUID: {id}"); // e.g. "550e8400-e29b-41d4-a716-446655440000"

axumserver (-F axumserver)

use doe::axumserver;
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    // Build routes
    let app = Router::new()
        .route("/", get(|| async { "Hello from doe!" }))
        .route("/health", get(|| async { "OK" }));

    // Serve on 0.0.0.0:3000 with CORS & TLS
    axumserver::server_app_default_ip(app).await.unwrap();
}

DynError — error alias (always available)

use doe::DynError;

fn read_config(path: &str) -> DynError<String> {
    let content = std::fs::read_to_string(path)?;
    Ok(content)
}

let config = read_config("config.toml");

MSRV

1.73 — required for OnceLock and div_ceil used in utils.

License

MIT © Andrew (dnrops@outlook.com)

关于
8.7 MB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号