chore: add app_tools

This commit is contained in:
Jesús Pérez Lorenzo 2021-09-01 20:12:27 +01:00
parent 6bcb904c42
commit c4723576b7
5 changed files with 373 additions and 0 deletions

10
app_tools/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
/target
target
Cargo.lock
.cache
.temp
.env
*.log
.DS_Store
logs
tmp

29
app_tools/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "app_tools"
version = "0.1.0"
authors = ["JesusPerez <jpl@jesusperez.pro>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.40"
base64 = "0.13.0"
envmnt = "0.9.0"
rand = "0.8.3"
regex = "1.4.3"
reqwest = "0.11.3"
json = "0.12.4"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0.125"
serde_json = "1.0.64"
serde_yaml = "0.8.17"
slab = "0.4.3"
sthash = "0.2.9"
tempfile = "3.2.0"
tera = "1.8.0"
thiserror = "1.0.24"
toml = "0.5.8"
yaml-rust = "0.4"
uuid = { version = "0.8", features = ["serde", "v4"] }

52
app_tools/README.md Normal file
View File

@ -0,0 +1,52 @@
# App Tools Utils library
Several functions to help in applications
Utility functions for variety of tasks
```rust
fn run_command(cmd: &str, arg1: &str, arg2: &str) -> anyhow::Result<()>
```
```rust
fn from_base64(source: &str) -> String
```
FromStr for RGB
Trim newline
this function is critical for base64 content as usually adds a newline at the end, causing decode not work properly with code.
**trim_newline** should be called to clean content before to be decoded.
```rust
fn trim_newline(s: &mut String)
```
```rust
fn hash_from_data(path: &str, ctx: &mut tera::Context, data_hash: &mut HashMap<String, String>, load_extend: bool) -> anyhow::Result<()>
```
**get_json_val** is a simple match Option to remove quotes in json values and returned as a String
```rust
get_json_val(json_value: &serde_json::Value, dflt_val: String) -> String
```
**get_toml_val** is a simple match Option to remove quotes in json values and returned as a String
```rust
get_toml_val(toml_value: &toml::Value, dflt_val: String) -> String
```
**get_toml_val** is a simple match Option to remove quotes in json values and returned as a String
```rust
get_yaml_val(yaml_value: &serde_yaml::Value, dflt_val: String) -> String
```
Read a **file_name** from a path (root / file_name) ( "" file_name_full_path )
format of reading can be matched with `file_type`
```rust
read_path_file(path: &str, file_name: &str, file_type: &str) -> anyhow::Result<String>
````

9
app_tools/TODO.md Normal file
View File

@ -0,0 +1,9 @@
### App Env library
- [ ] Implement tests for **config**
- [ ] Extend **config** to support other **data stores**
- [ ] Add encryption to **config**
- [ ] Complete implementation for this types by moving some code here.

273
app_tools/src/lib.rs Normal file
View File

@ -0,0 +1,273 @@
/*! Utility functions for variety of tasks */
//
/*! Zterton */
//
// Copyright 2020, Jesús Pérez Lorenzo
use std::collections::HashMap;
use std::str::FromStr;
use std::fs;
use anyhow::anyhow;
use regex::Regex;
use std::process::Command;
use serde::Deserialize;
#[derive(PartialEq, Default, Clone, Debug)]
struct Commit {
hash: String,
message: String,
}
/// A very simple Operating System Command runner capturing output
/// Allows two arguments, they can be empty
///```rust
/// let res =
/// match run_command("pwd", "", "") {
/// Ok(_) => format!("{} {}", "pwd","Done"),
/// Err(e) => format!("{}",e),
/// };
/// println!("Result: {}",res);
/// ```
#[allow(dead_code)]
#[allow(clippy::missing_errors_doc, clippy::filter_map)]
pub fn run_command(cmd: &str, arg1: &str, arg2: &str) -> anyhow::Result<()> {
let output = match arg2 {
"" => match arg1 {
"" => Command::new(cmd).output()?,
_ => Command::new(cmd).arg(arg1).output()?,
},
_ => Command::new(cmd).arg(arg1).arg(arg2).output()?,
};
if !output.status.success() {
//dbg!(&output);
let code = output.status.code().unwrap_or(-1);
// let code = match output.status.code() {
// Some(val) => val,
// None => -1,
// };
let stderr = String::from_utf8(output.stderr)?;
let msg = format!(
"Command {} executed with failing errors({}): {}",
&cmd, &code, &stderr
);
println!("{}", &msg);
return Err(anyhow!(msg));
// error_chain::bail!("Command {} executed with failing error code", &cmd);
}
let pattern = Regex::new(
r"(?x)
([0-9a-fA-F]+) # commit hash
(.*) # The commit message",
)?;
String::from_utf8(output.stdout)?
.lines()
.filter_map(|line| pattern.captures(line))
.map(|cap| Commit {
hash: cap[1].to_string(),
message: cap[2].trim().to_string(),
})
.take(5)
.for_each(|x| println!("{:?}", x));
Ok(())
}
#[must_use]
pub fn from_base64(source: &str) -> String {
match base64::decode(source) {
Ok(res_deco) =>
String::from_utf8(res_deco).unwrap_or_else(
|e|{ println!("Error utf8 decode: {}", e); "".to_string() }
),
Err(e) => {
println!("Error 64 decode: {}", e);
source.to_owned()
}
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl FromStr for RGB {
type Err = std::num::ParseIntError;
// Parses a color hex code of the form '#rRgGbB..' into an
// instance of 'RGB'
fn from_str(hex_code: &str) -> Result<Self, Self::Err> {
// u8::from_str_radix(src: &str, radix: u32) converts a string
// slice in a given base to u8
let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?;
let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?;
let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?;
Ok(RGB { r, g, b })
}
}
impl RGB {
#[must_use]
pub fn as_u8(&self) -> [u8; 3] {
[self.r,self.g,self.b]
}
}
/// Trim newline
/// this function is critical for base64 content as usually adds a newline at the end, causing decode not work properly with code.
/// `trim_newline` should be called to clean content before to be decoded.
pub fn trim_newline(s: &mut String) {
if s.ends_with('\n') {
s.pop();
if s.ends_with('\r') {
s.pop();
}
}
}
//
#[allow(clippy::missing_errors_doc,clippy::implicit_hasher)]
pub fn hash_from_data(path: &str, ctx: &mut tera::Context, data_hash: &mut HashMap<String, String>, load_extend: bool) -> anyhow::Result<()> {
// {HashMap<String, String>> {
let mut check_value = | key: String, v: String | {
if let Some(first_char) = v.chars().next() {
if first_char == '@' {
let field_file = &v[1..];
let _ = hash_from_data(field_file, ctx, data_hash, load_extend);
// dbg!("{}",&data_hash);
return;
}
}
data_hash.insert(key, v);
};
let vars_content = fs::read_to_string(&path)?;
//let mut data_hash: HashMap<String, String> = HashMap::new();
if let Some(extension) = path.split('.').last() {
//println!("File {} extension {}", &path, &extension);
match extension {
"json" => {
let data: HashMap<String, serde_json::Value> = serde_json::from_str(&vars_content)?;
if load_extend {
match tera::Context::from_serialize(data) {
Ok(res) => {
ctx.extend(res);
},
Err(e) => {
println!("Error loading {}: {}",&path, e);
},
}
} else {
for (key, value) in data {
let val = get_json_val(&value, "".into());
check_value(key,val);
}
}
}
"toml" => {
// As a table content
let data: toml::Value = toml::from_str(&vars_content)?;
// let data = &vars_content.parse::<toml::Value>()?;
if load_extend {
match tera::Context::from_serialize(data) {
Ok(res) => {
ctx.extend(res);
},
Err(e) => {
println!("Error loading {}: {}",&path, e);
},
}
} else if let Some(table_data) = data.as_table() {
for (key, value) in table_data {
println!("-{}-{}",&key,&value);
let val = get_toml_val(&value, "".into());
check_value(key.into(),val);
}
}
}
"yaml" => {
let data: HashMap<String, serde_yaml::Value> = serde_yaml::from_str(&vars_content)?;
if load_extend {
match tera::Context::from_serialize(data) {
Ok(res) => {
ctx.extend(res);
},
Err(e) => {
println!("Error loading {}: {}",&path, e);
},
}
} else {
for (key, value) in data {
let val = get_yaml_val(&value, "".into());
check_value(key,val);
}
}
}
_ => {
println!(
"File {} format extension {} not defined ",
&path, &extension
);
}
}
} else {
println!("File {} unknown extension", &path);
}
// dbg!(&data_hash);
Ok(()) // data_hash)
}
/// `get_json_val` is a simple match Option to remove quotes in json values and returned as a String
#[must_use]
pub fn get_json_val(json_value: &serde_json::Value, dflt_val: String) -> String {
match json_value.as_str() {
Some(val) => val.to_string(),
None => dflt_val,
}
}
/// `get_toml_val` is a simple match Option to remove quotes in json values and returned as a String
#[must_use]
pub fn get_toml_val(toml_value: &toml::Value, dflt_val: String) -> String {
match toml_value.as_str() {
Some(val) => val.to_string(),
None => dflt_val,
}
}
/// `get_toml_val` is a simple match Option to remove quotes in json values and returned as a String
#[must_use]
pub fn get_yaml_val(yaml_value: &serde_yaml::Value, dflt_val: String) -> String {
match yaml_value.as_str() {
Some(val) => val.to_string(),
None => dflt_val,
}
}
/// Read a `file_name` from a path (root / `file_name`) ( "" `file_name_full_path` )
/// format of reading can be matched with `file_type`
/// TODO implement other types to read
#[allow(clippy::missing_errors_doc)]
pub fn read_path_file(path: &str, file_name: &str, file_type: &str) -> anyhow::Result<String> {
let mut result = String::from("");
let mut root_path = format!("{}/", path);
if root_path.as_str() == "/" {
root_path = String::from("");
}
//println!("{}",format!("{}{}", &root_path.as_str(), &file_name));
match file_type {
"content" => {
result = fs::read_to_string(format!("{}{}", &root_path.as_str(), &file_name).as_str())?
}
_ => println!(
"File {}{} type {} not defined ",
&root_path, &file_name, &file_type
),
}
Ok(result)
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}