use std::io::Write;
use sysinfo::{System, ProcessesToUpdate, ProcessRefreshKind, get_current_pid};
use termcolor::{Buffer, BufferWriter, WriteColor, ColorSpec, Color};
use http_types::Url;
use boomack::client::json::pprint_json;
use boomack::client::api::{
ClientRequest,
ClientRequestMethod,
ClientRequestBody,
build_request_url,
};
use super::{CliConfig};
fn request_method_name(client_request: &ClientRequest) -> &str {
match client_request.method {
ClientRequestMethod::GET => "GET",
ClientRequestMethod::POST => "POST",
ClientRequestMethod::PUT => "PUT",
ClientRequestMethod::DELETE => "DELETE",
}
}
type IoResult = Result<(), std::io::Error>;
fn reset_color(buf: &mut Buffer) -> IoResult {
buf.set_color(ColorSpec::new().set_reset(true))?;
IoResult::Ok(())
}
fn set_color(buf: &mut Buffer, color: Color, intense: bool, dimmed: bool) -> IoResult {
buf.set_color(ColorSpec::new()
.set_fg(Some(color))
.set_intense(intense)
.set_dimmed(dimmed))?;
IoResult::Ok(())
}
fn print_request_plain(cfg: &CliConfig, client_request: ClientRequest) -> IoResult {
let method = request_method_name(&client_request);
let host_url_str = cfg.api.server.get_api_url();
let host_url = Url::parse(&host_url_str).unwrap();
let bw = BufferWriter::stdout(cfg.color_choice());
let mut buf = bw.buffer();
set_color(&mut buf, Color::Blue, true, false)?;
write!(&mut buf, "{} ", method)?;
set_color(&mut buf, Color::Cyan, true, false)?;
write!(&mut buf, "/v1/{} ", client_request.route)?;
set_color(&mut buf, Color::White, false, false)?;
write!(&mut buf, "HTTP/1.1")?;
buf.set_color(ColorSpec::new().set_reset(true))?;
writeln!(&mut buf, "")?;
fn write_header(buf: &mut Buffer, name: &str, value: &str, color: Color, intense: bool) -> IoResult {
set_color(buf, Color::White, false, false)?;
write!(buf, "{}: ", name)?;
set_color(buf, color, intense, false)?;
write!(buf, "{}", value)?;
reset_color(buf)?;
writeln!(buf, "")?;
IoResult::Ok(())
}
write_header(&mut buf, "Host", &format!("{}:{}",
host_url.host_str().unwrap(),
host_url.port_or_known_default().unwrap()),
Color::Cyan, true)?;
for (name, value) in &client_request.headers {
write_header(&mut buf, name, value, Color::Yellow, true)?;
}
if matches!(client_request.body, ClientRequestBody::Json(_)) {
write_header(&mut buf, "Content-Type", "application/json", Color::Yellow, true)?;
}
match client_request.body {
ClientRequestBody::None => {},
ClientRequestBody::Json(data) => {
writeln!(&mut buf, "")?;
buf.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_intense(true))?;
write!(&mut buf, "{}", pprint_json(&data))?;
buf.set_color(ColorSpec::new().set_reset(true))?;
writeln!(&mut buf, "")?;
},
ClientRequestBody::FileContent(path) => {
writeln!(&mut buf, "")?;
buf.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
write!(&mut buf, "<FILE CONTENT: {}>", path.to_string_lossy())?;
buf.set_color(ColorSpec::new().set_reset(true))?;
writeln!(&mut buf, "")?;
},
ClientRequestBody::StdIn => {
writeln!(&mut buf, "")?;
buf.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
write!(&mut buf, "<STDIN>")?;
buf.set_color(ColorSpec::new().set_reset(true))?;
writeln!(&mut buf, "")?;
}
}
bw.print(&buf)?;
IoResult::Ok(())
}
fn parent_exe() -> Option<String> {
if let Ok(pid) = get_current_pid() {
let mut sys = System::new();
sys.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]), false, ProcessRefreshKind::nothing());
if let Some(proc) = sys.process(pid) {
if let Some(parent_pid) = proc.parent() {
sys.refresh_processes_specifics(
ProcessesToUpdate::Some(&[parent_pid]), false, ProcessRefreshKind::nothing());
if let Some(parent_proc) = sys.process(parent_pid) {
if let Some(parent_path) = parent_proc.exe() {
return Some(parent_path.to_string_lossy().to_string());
}
}
}
}
}
return None;
}
fn is_in_cmd(parent: &str) -> bool {
if parent.ends_with("cmd.exe") {
return true;
}
return false;
}
fn is_in_powershell(parent: &str) -> bool {
if parent.ends_with("powershell.exe") ||
parent.ends_with("pwsh.exe") ||
parent.ends_with("pwsh") {
return true;
}
return false;
}
enum ParentShell {
Unknown,
PowerShell,
Cmd,
}
fn get_parent_shell() -> ParentShell {
if let Some(process_parent) = parent_exe() {
if is_in_cmd(&process_parent) {
return ParentShell::Cmd;
}
if is_in_powershell(&process_parent) {
return ParentShell::PowerShell;
}
}
return ParentShell::Unknown;
}
fn print_request_curl(cfg: &CliConfig, client_request: ClientRequest) -> IoResult {
let bw = BufferWriter::stdout(cfg.color_choice());
let mut buf = bw.buffer();
fn flag(buf: &mut Buffer, name: &str, value: &str, color: Color, intense: bool) -> IoResult {
set_color(buf, Color::White, false, false)?;
write!(buf, " {}", name)?;
set_color(buf, color, intense, false)?;
write!(buf, " {}", value)?;
reset_color(buf)?;
Ok(())
}
reset_color(&mut buf)?;
set_color(&mut buf, Color::White, true, false)?;
write!(&mut buf, "curl")?;
if !matches!(&client_request.method, ClientRequestMethod::GET) {
let method = request_method_name(&client_request);
flag(&mut buf, "-X", method, Color::Blue, true)?;
}
if let Some(timeout) = cfg.api.client.timeout {
flag(&mut buf, "--connect-timeout", &timeout.to_string(), Color::White, false)?;
}
for (name, value) in &client_request.headers {
flag(&mut buf, "-H", &format!("\"{}: {}\"", name, value), Color::Yellow, true)?;
}
match client_request.body {
ClientRequestBody::None => {},
ClientRequestBody::Json(data) => {
flag(&mut buf, "-H", "\"Content-Type: application/json\"", Color::Yellow, true)?;
let mut json = serde_json::to_string(&data).unwrap();
match get_parent_shell() {
ParentShell::Cmd => {
json = json.replace("\\\"", "\\\\\"");
json = json.replace('"', "\"\"");
},
ParentShell::PowerShell => {
json = json.replace('`', "``");
json = json.replace('"', "\\`\"");
},
_ => {
json = json.replace('\\', "\\\\");
json = json.replace('"', "\\\"");
},
}
flag(&mut buf, "-d", &format!("\"{}\"", json), Color::Green, true)?;
},
ClientRequestBody::FileContent(path) => {
flag(&mut buf, " --data-binary", &format!(" \"@{}\"", path.to_string_lossy()), Color::Green, false)?;
},
ClientRequestBody::StdIn => {
flag(&mut buf, " --data-binary", " @-", Color::Green, false)?;
},
}
let request_url = build_request_url(&cfg.api, &client_request.route);
set_color(&mut buf, Color::Cyan, true, false)?;
write!(&mut buf, " {}", request_url)?;
reset_color(&mut buf)?;
writeln!(&mut buf, "")?;
bw.print(&buf)?;
IoResult::Ok(())
}
pub fn print_request(cfg: &CliConfig, r: ClientRequest) ->IoResult {
if cfg.use_curl_syntax() {
print_request_curl(cfg, r)
} else {
print_request_plain(cfg, r)
}
}