mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 04:50:36 +00:00
Refactor process args & env, improve errors
This commit is contained in:
parent
b6822e603e
commit
bce6958c46
12 changed files with 310 additions and 227 deletions
|
@ -15,10 +15,12 @@ module.sayHello()
|
|||
Using arguments given to the program
|
||||
]==]
|
||||
|
||||
local arg: string? = ...
|
||||
if arg then
|
||||
print("\nGot an argument while running hello_lune:")
|
||||
print(arg)
|
||||
if #process.args > 0 then
|
||||
print("\nGot arguments while running hello_lune:")
|
||||
console.log(process.args)
|
||||
if #process.args > 3 then
|
||||
error("Too many arguments!")
|
||||
end
|
||||
end
|
||||
|
||||
--[==[
|
||||
|
@ -31,14 +33,13 @@ end
|
|||
]==]
|
||||
|
||||
print("\nReading current environment 🔎")
|
||||
local vars = process.getEnvVars()
|
||||
table.sort(vars)
|
||||
|
||||
assert(table.find(vars, "PATH") ~= nil, "Missing PATH")
|
||||
assert(table.find(vars, "PWD") ~= nil, "Missing PWD")
|
||||
-- Environment variables can be read directly
|
||||
assert(process.env.PATH ~= nil, "Missing PATH")
|
||||
assert(process.env.PWD ~= nil, "Missing PWD")
|
||||
|
||||
for _, key in vars do
|
||||
local value = process.getEnvVar(key)
|
||||
-- And they can also be accessed using generalized iteration (not pairs!)
|
||||
for key, value in process.env do
|
||||
local box = if value and value ~= "" then "✅" else "❌"
|
||||
print(string.format("[%s] %s", box, key))
|
||||
end
|
||||
|
@ -85,7 +86,7 @@ end
|
|||
|
||||
-- NOTE: We skip the ping example in GitHub Actions
|
||||
-- since the ping command does not work in azure
|
||||
if not process.getEnvVar("GITHUB_ACTIONS") then
|
||||
if not process.env.GITHUB_ACTIONS then
|
||||
--[==[
|
||||
EXAMPLE #5
|
||||
|
||||
|
|
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added `process.args` for inspecting values given to Lune when running (read only)
|
||||
- Added `process.env` which is a plain table where you can get & set environment variables
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved error formatting & added proper file name to stack traces
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `...` for process arguments, use `process.args` instead
|
||||
- Removed individual functions for getting & setting environment variables, use `process.env` instead
|
||||
|
||||
## `0.0.3` - January 19th, 2023
|
||||
|
||||
### Added
|
||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -516,6 +516,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"clap",
|
||||
"mlua",
|
||||
"os_str_bytes",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -668,6 +669,9 @@ name = "os_str_bytes"
|
|||
version = "6.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
|
|
|
@ -22,6 +22,7 @@ panic = "abort" # Remove extra panic info
|
|||
anyhow = { version = "1.0.68" }
|
||||
clap = { version = "4.1.1", features = ["derive"] }
|
||||
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
|
||||
os_str_bytes = "6.4.1"
|
||||
reqwest = { version = "0.11.13", features = ["gzip", "deflate"] }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.91" }
|
||||
|
|
|
@ -98,9 +98,8 @@ type net = {
|
|||
|
||||
```lua
|
||||
type process = {
|
||||
getEnvVars: () -> { string },
|
||||
getEnvVar: (key: string) -> string?,
|
||||
setEnvVar: (key: string, value: string) -> (),
|
||||
args: { string },
|
||||
env: { [string]: string? },
|
||||
exit: (code: number?) -> (),
|
||||
spawn: (program: string, params: { string }?) -> {
|
||||
ok: boolean,
|
||||
|
|
10
lune.yml
10
lune.yml
|
@ -63,15 +63,7 @@ globals:
|
|||
net.request:
|
||||
args:
|
||||
- type: any
|
||||
# Process
|
||||
process.getEnvVars:
|
||||
process.getEnvVar:
|
||||
args:
|
||||
- type: string
|
||||
process.setEnvVar:
|
||||
args:
|
||||
- type: string
|
||||
- type: string
|
||||
# Processs
|
||||
process.exit:
|
||||
args:
|
||||
- required: false
|
||||
|
|
|
@ -41,9 +41,8 @@ declare net: {
|
|||
}
|
||||
|
||||
declare process: {
|
||||
getEnvVars: () -> { string },
|
||||
getEnvVar: (key: string) -> string?,
|
||||
setEnvVar: (key: string, value: string) -> (),
|
||||
args: { string },
|
||||
env: { [string]: string? },
|
||||
exit: (code: number?) -> (),
|
||||
spawn: (program: string, params: { string }?) -> {
|
||||
ok: boolean,
|
||||
|
|
14
src/cli.rs
14
src/cli.rs
|
@ -4,7 +4,7 @@ use std::{
|
|||
};
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use mlua::{Lua, MultiValue, Result, ToLua};
|
||||
use mlua::{Lua, Result};
|
||||
|
||||
use crate::{
|
||||
lune::{console::LuneConsole, fs::LuneFs, net::LuneNet, process::LuneProcess},
|
||||
|
@ -97,23 +97,19 @@ impl Cli {
|
|||
}
|
||||
// Parse and read the wanted file
|
||||
let file_path = find_parse_file_path(&self.script_path.unwrap())?;
|
||||
let file_contents = read_to_string(file_path)?;
|
||||
let file_contents = read_to_string(&file_path)?;
|
||||
// Create a new lua state and add in all lune globals
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
globals.set("console", LuneConsole::new())?;
|
||||
globals.set("fs", LuneFs::new())?;
|
||||
globals.set("net", LuneNet::new())?;
|
||||
globals.set("process", LuneProcess::new())?;
|
||||
globals.set("process", LuneProcess::new(self.script_args))?;
|
||||
lua.sandbox(true)?;
|
||||
// Load & call the file with the given args
|
||||
let lua_args = self
|
||||
.script_args
|
||||
.iter()
|
||||
.map(|value| value.to_owned().to_lua(&lua))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
lua.load(&file_contents)
|
||||
.call_async(MultiValue::from_vec(lua_args))
|
||||
.set_name(file_path.with_extension("").display().to_string())?
|
||||
.exec_async()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,28 +1,9 @@
|
|||
use std::{
|
||||
fmt::Write,
|
||||
io::{self, Write as IoWrite},
|
||||
use mlua::{Lua, MultiValue, Result, UserData, UserDataMethods};
|
||||
|
||||
use crate::utils::{
|
||||
flush_stdout, pretty_format_multi_value, print_color, print_label, print_style,
|
||||
};
|
||||
|
||||
use mlua::{Lua, MultiValue, Result, UserData, UserDataMethods, Value};
|
||||
|
||||
const MAX_FORMAT_DEPTH: usize = 4;
|
||||
|
||||
const INDENT: &str = " ";
|
||||
|
||||
const COLOR_RESET: &str = "\x1B[0m";
|
||||
const COLOR_BLACK: &str = "\x1B[30m";
|
||||
const COLOR_RED: &str = "\x1B[31m";
|
||||
const COLOR_GREEN: &str = "\x1B[32m";
|
||||
const COLOR_YELLOW: &str = "\x1B[33m";
|
||||
const COLOR_BLUE: &str = "\x1B[34m";
|
||||
const COLOR_PURPLE: &str = "\x1B[35m";
|
||||
const COLOR_CYAN: &str = "\x1B[36m";
|
||||
const COLOR_WHITE: &str = "\x1B[37m";
|
||||
|
||||
const STYLE_RESET: &str = "\x1B[22m";
|
||||
const STYLE_BOLD: &str = "\x1B[1m";
|
||||
const STYLE_DIM: &str = "\x1B[2m";
|
||||
|
||||
pub struct LuneConsole();
|
||||
|
||||
impl LuneConsole {
|
||||
|
@ -45,134 +26,6 @@ impl UserData for LuneConsole {
|
|||
}
|
||||
}
|
||||
|
||||
fn flush_stdout() -> Result<()> {
|
||||
io::stdout().flush().map_err(mlua::Error::external)
|
||||
}
|
||||
|
||||
fn can_be_plain_lua_table_key(s: &mlua::String) -> bool {
|
||||
let str = s.to_string_lossy().to_string();
|
||||
let first_char = str.chars().next().unwrap();
|
||||
if first_char.is_alphabetic() {
|
||||
str.chars().all(|c| c == '_' || c.is_alphanumeric())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) -> anyhow::Result<()> {
|
||||
// TODO: Handle tables with cyclic references
|
||||
// TODO: Handle other types like function, userdata, ...
|
||||
match &value {
|
||||
Value::Nil => write!(buffer, "nil")?,
|
||||
Value::Boolean(true) => write!(buffer, "{}true{}", COLOR_YELLOW, COLOR_RESET)?,
|
||||
Value::Boolean(false) => write!(buffer, "{}false{}", COLOR_YELLOW, COLOR_RESET)?,
|
||||
Value::Number(n) => write!(buffer, "{}{}{}", COLOR_BLUE, n, COLOR_RESET)?,
|
||||
Value::Integer(i) => write!(buffer, "{}{}{}", COLOR_BLUE, i, COLOR_RESET)?,
|
||||
Value::String(s) => write!(
|
||||
buffer,
|
||||
"{}\"{}\"{}",
|
||||
COLOR_GREEN,
|
||||
s.to_string_lossy()
|
||||
.replace('"', r#"\""#)
|
||||
.replace('\n', r#"\n"#),
|
||||
COLOR_RESET
|
||||
)?,
|
||||
Value::Table(ref tab) => {
|
||||
if depth >= MAX_FORMAT_DEPTH {
|
||||
write!(buffer, "{}{{ ... }}{}", STYLE_DIM, STYLE_RESET)?;
|
||||
} else {
|
||||
let depth_indent = INDENT.repeat(depth);
|
||||
write!(buffer, "{}{{{}", STYLE_DIM, STYLE_RESET)?;
|
||||
for pair in tab.clone().pairs::<Value, Value>() {
|
||||
let (key, value) = pair?;
|
||||
match &key {
|
||||
Value::String(s) if can_be_plain_lua_table_key(s) => write!(
|
||||
buffer,
|
||||
"\n{}{}{} {}={} ",
|
||||
depth_indent,
|
||||
INDENT,
|
||||
s.to_string_lossy(),
|
||||
STYLE_DIM,
|
||||
STYLE_RESET
|
||||
)?,
|
||||
_ => {
|
||||
write!(buffer, "\n{}{}[", depth_indent, INDENT)?;
|
||||
pretty_format_value(buffer, &key, depth)?;
|
||||
write!(buffer, "] {}={} ", STYLE_DIM, STYLE_RESET)?;
|
||||
}
|
||||
}
|
||||
pretty_format_value(buffer, &value, depth + 1)?;
|
||||
write!(buffer, "{},{}", STYLE_DIM, STYLE_RESET)?;
|
||||
}
|
||||
write!(buffer, "\n{}{}}}{}", depth_indent, STYLE_DIM, STYLE_RESET)?;
|
||||
}
|
||||
}
|
||||
_ => write!(buffer, "?")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pretty_format_multi_value(multi: &MultiValue) -> Result<String> {
|
||||
let mut buffer = String::new();
|
||||
let mut counter = 0;
|
||||
for value in multi {
|
||||
counter += 1;
|
||||
if let Value::String(s) = value {
|
||||
write!(buffer, "{}", s.to_string_lossy()).map_err(mlua::Error::external)?
|
||||
} else {
|
||||
pretty_format_value(&mut buffer, value, 0).map_err(mlua::Error::external)?;
|
||||
}
|
||||
if counter < multi.len() {
|
||||
write!(&mut buffer, " ").map_err(mlua::Error::external)?;
|
||||
}
|
||||
}
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn print_style<S: AsRef<str>>(s: S) -> Result<()> {
|
||||
print!(
|
||||
"{}",
|
||||
match s.as_ref() {
|
||||
"reset" => STYLE_RESET,
|
||||
"bold" => STYLE_BOLD,
|
||||
"dim" => STYLE_DIM,
|
||||
_ => {
|
||||
return Err(mlua::Error::RuntimeError(format!(
|
||||
"The style '{}' is not a valid style name",
|
||||
s.as_ref()
|
||||
)));
|
||||
}
|
||||
}
|
||||
);
|
||||
flush_stdout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_color<S: AsRef<str>>(s: S) -> Result<()> {
|
||||
print!(
|
||||
"{}",
|
||||
match s.as_ref() {
|
||||
"reset" => COLOR_RESET,
|
||||
"black" => COLOR_BLACK,
|
||||
"red" => COLOR_RED,
|
||||
"green" => COLOR_GREEN,
|
||||
"yellow" => COLOR_YELLOW,
|
||||
"blue" => COLOR_BLUE,
|
||||
"purple" => COLOR_PURPLE,
|
||||
"cyan" => COLOR_CYAN,
|
||||
"white" => COLOR_WHITE,
|
||||
_ => {
|
||||
return Err(mlua::Error::RuntimeError(format!(
|
||||
"The color '{}' is not a valid color name",
|
||||
s.as_ref()
|
||||
)));
|
||||
}
|
||||
}
|
||||
);
|
||||
flush_stdout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn console_reset_color(_: &Lua, _: ()) -> Result<()> {
|
||||
print_color("reset")?;
|
||||
flush_stdout()?;
|
||||
|
@ -207,10 +60,7 @@ fn console_log(_: &Lua, args: MultiValue) -> Result<()> {
|
|||
}
|
||||
|
||||
fn console_info(_: &Lua, args: MultiValue) -> Result<()> {
|
||||
print!(
|
||||
"{}[{}INFO{}{}]{} ",
|
||||
STYLE_BOLD, COLOR_CYAN, COLOR_RESET, STYLE_BOLD, STYLE_RESET
|
||||
);
|
||||
print_label("info")?;
|
||||
let s = pretty_format_multi_value(&args)?;
|
||||
println!("{}", s);
|
||||
flush_stdout()?;
|
||||
|
@ -218,10 +68,7 @@ fn console_info(_: &Lua, args: MultiValue) -> Result<()> {
|
|||
}
|
||||
|
||||
fn console_warn(_: &Lua, args: MultiValue) -> Result<()> {
|
||||
print!(
|
||||
"{}[{}WARN{}{}]{} ",
|
||||
STYLE_BOLD, COLOR_YELLOW, COLOR_RESET, STYLE_BOLD, STYLE_RESET
|
||||
);
|
||||
print_label("warn")?;
|
||||
let s = pretty_format_multi_value(&args)?;
|
||||
println!("{}", s);
|
||||
flush_stdout()?;
|
||||
|
@ -229,10 +76,7 @@ fn console_warn(_: &Lua, args: MultiValue) -> Result<()> {
|
|||
}
|
||||
|
||||
fn console_error(_: &Lua, args: MultiValue) -> Result<()> {
|
||||
eprint!(
|
||||
"{}[{}ERROR{}{}]{} ",
|
||||
STYLE_BOLD, COLOR_RED, COLOR_RESET, STYLE_BOLD, STYLE_RESET
|
||||
);
|
||||
print_label("error")?;
|
||||
let s = pretty_format_multi_value(&args)?;
|
||||
eprintln!("{}", s);
|
||||
flush_stdout()?;
|
||||
|
|
|
@ -1,53 +1,111 @@
|
|||
use std::{
|
||||
env::{self, VarError},
|
||||
env,
|
||||
process::{exit, Stdio},
|
||||
};
|
||||
|
||||
use mlua::{Error, Lua, Result, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{
|
||||
Error, Function, Lua, MetaMethod, Result, Table, UserData, UserDataFields, UserDataMethods,
|
||||
Value,
|
||||
};
|
||||
use os_str_bytes::RawOsString;
|
||||
use tokio::process::Command;
|
||||
|
||||
pub struct LuneProcess();
|
||||
pub struct LuneProcess {
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl LuneProcess {
|
||||
pub fn new() -> Self {
|
||||
Self()
|
||||
pub fn new(args: Vec<String>) -> Self {
|
||||
Self { args }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for LuneProcess {
|
||||
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("args", |lua, this| {
|
||||
// TODO: Use the same strategy as env uses below to avoid
|
||||
// copying each time args are accessed? is it worth it?
|
||||
let tab = lua.create_table()?;
|
||||
for arg in &this.args {
|
||||
tab.push(arg.to_owned())?;
|
||||
}
|
||||
Ok(tab)
|
||||
});
|
||||
fields.add_field_method_get("env", |lua, _| {
|
||||
let meta = lua.create_table()?;
|
||||
meta.raw_set(
|
||||
MetaMethod::Index.name(),
|
||||
lua.create_function(process_env_get)?,
|
||||
)?;
|
||||
meta.raw_set(
|
||||
MetaMethod::NewIndex.name(),
|
||||
lua.create_function(process_env_set)?,
|
||||
)?;
|
||||
meta.raw_set(
|
||||
MetaMethod::Iter.name(),
|
||||
lua.create_function(process_env_iter)?,
|
||||
)?;
|
||||
let tab = lua.create_table()?;
|
||||
tab.set_metatable(Some(meta));
|
||||
tab.set_readonly(true);
|
||||
Ok(tab)
|
||||
})
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_function("getEnvVars", process_get_env_vars);
|
||||
methods.add_function("getEnvVar", process_get_env_var);
|
||||
methods.add_function("setEnvVar", process_set_env_var);
|
||||
methods.add_function("exit", process_exit);
|
||||
methods.add_async_function("spawn", process_spawn);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_get_env_vars(_: &Lua, _: ()) -> Result<Vec<String>> {
|
||||
let mut vars = Vec::new();
|
||||
for (key, _) in env::vars() {
|
||||
vars.push(key);
|
||||
}
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
fn process_get_env_var(lua: &Lua, key: String) -> Result<Value> {
|
||||
match env::var(&key) {
|
||||
Ok(value) => Ok(Value::String(lua.create_string(&value)?)),
|
||||
Err(VarError::NotPresent) => Ok(Value::Nil),
|
||||
Err(VarError::NotUnicode(_)) => Err(Error::external(format!(
|
||||
"The env var '{}' contains invalid utf8",
|
||||
&key
|
||||
))),
|
||||
fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result<Value<'lua>> {
|
||||
match env::var_os(key) {
|
||||
Some(value) => {
|
||||
let raw_value = RawOsString::new(value);
|
||||
Ok(Value::String(lua.create_string(raw_value.as_raw_bytes())?))
|
||||
}
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_set_env_var(_: &Lua, (key, value): (String, String)) -> Result<()> {
|
||||
env::set_var(key, value);
|
||||
fn process_env_set(_: &Lua, (_, key, value): (Value, String, String)) -> Result<()> {
|
||||
// Make sure key is valid, otherwise set_var will panic
|
||||
if key.is_empty() {
|
||||
return Err(Error::RuntimeError("Key must not be empty".to_string()));
|
||||
} else if key.contains('=') {
|
||||
return Err(Error::RuntimeError(
|
||||
"Key must not contain the equals character '='".to_string(),
|
||||
));
|
||||
} else if key.contains('\0') {
|
||||
return Err(Error::RuntimeError(
|
||||
"Key must not contain the NUL character".to_string(),
|
||||
));
|
||||
}
|
||||
// Make sure value is valid, otherwise set_var will panic
|
||||
if value.contains('\0') {
|
||||
return Err(Error::RuntimeError(
|
||||
"Value must not contain the NUL character".to_string(),
|
||||
));
|
||||
}
|
||||
env::set_var(&key, &value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_env_iter<'lua>(lua: &'lua Lua, (_, _): (Value<'lua>, ())) -> Result<Function<'lua>> {
|
||||
let mut vars = env::vars_os();
|
||||
lua.create_function_mut(move |lua, _: ()| match vars.next() {
|
||||
Some((key, value)) => {
|
||||
let raw_key = RawOsString::new(key);
|
||||
let raw_value = RawOsString::new(value);
|
||||
Ok((
|
||||
Value::String(lua.create_string(raw_key.as_raw_bytes())?),
|
||||
Value::String(lua.create_string(raw_value.as_raw_bytes())?),
|
||||
))
|
||||
}
|
||||
None => Ok((Value::Nil, Value::Nil)),
|
||||
})
|
||||
}
|
||||
|
||||
fn process_exit(_: &Lua, exit_code: Option<i32>) -> Result<()> {
|
||||
if let Some(code) = exit_code {
|
||||
exit(code);
|
||||
|
|
|
@ -8,7 +8,7 @@ mod lune;
|
|||
mod utils;
|
||||
|
||||
use cli::Cli;
|
||||
use utils::pretty_print_luau_error;
|
||||
use utils::{pretty_print_luau_error, print_label};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
|
@ -17,7 +17,8 @@ async fn main() -> Result<()> {
|
|||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
eprintln!();
|
||||
eprintln!("[ERROR]");
|
||||
print_label("ERROR").unwrap();
|
||||
eprintln!();
|
||||
pretty_print_luau_error(&e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
174
src/utils.rs
174
src/utils.rs
|
@ -1,12 +1,35 @@
|
|||
use std::env::current_dir;
|
||||
use std::{
|
||||
env::current_dir,
|
||||
fmt::Write,
|
||||
io::{self, Write as IoWrite},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use mlua::{MultiValue, Value};
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderValue},
|
||||
Client,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const MAX_FORMAT_DEPTH: usize = 4;
|
||||
|
||||
const INDENT: &str = " ";
|
||||
|
||||
const COLOR_RESET: &str = "\x1B[0m";
|
||||
const COLOR_BLACK: &str = "\x1B[30m";
|
||||
const COLOR_RED: &str = "\x1B[31m";
|
||||
const COLOR_GREEN: &str = "\x1B[32m";
|
||||
const COLOR_YELLOW: &str = "\x1B[33m";
|
||||
const COLOR_BLUE: &str = "\x1B[34m";
|
||||
const COLOR_PURPLE: &str = "\x1B[35m";
|
||||
const COLOR_CYAN: &str = "\x1B[36m";
|
||||
const COLOR_WHITE: &str = "\x1B[37m";
|
||||
|
||||
const STYLE_RESET: &str = "\x1B[22m";
|
||||
const STYLE_BOLD: &str = "\x1B[1m";
|
||||
const STYLE_DIM: &str = "\x1B[2m";
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct GithubReleaseAsset {
|
||||
id: u64,
|
||||
|
@ -138,6 +161,155 @@ pub fn get_github_user_agent_header() -> String {
|
|||
format!("{}-{}-cli", github_owner, github_repo)
|
||||
}
|
||||
|
||||
// TODO: Separate utils out into github & formatting
|
||||
|
||||
pub fn flush_stdout() -> mlua::Result<()> {
|
||||
io::stdout().flush().map_err(mlua::Error::external)
|
||||
}
|
||||
|
||||
pub fn print_label<S: AsRef<str>>(s: S) -> mlua::Result<()> {
|
||||
print!(
|
||||
"{}[{}{}{}{}]{} ",
|
||||
STYLE_BOLD,
|
||||
match s.as_ref().to_ascii_lowercase().as_str() {
|
||||
"info" => COLOR_BLUE,
|
||||
"warn" => COLOR_YELLOW,
|
||||
"error" => COLOR_RED,
|
||||
_ => COLOR_WHITE,
|
||||
},
|
||||
s.as_ref().to_ascii_uppercase(),
|
||||
COLOR_RESET,
|
||||
STYLE_BOLD,
|
||||
STYLE_RESET
|
||||
);
|
||||
flush_stdout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_style<S: AsRef<str>>(s: S) -> mlua::Result<()> {
|
||||
print!(
|
||||
"{}",
|
||||
match s.as_ref() {
|
||||
"reset" => STYLE_RESET,
|
||||
"bold" => STYLE_BOLD,
|
||||
"dim" => STYLE_DIM,
|
||||
_ => {
|
||||
return Err(mlua::Error::RuntimeError(format!(
|
||||
"The style '{}' is not a valid style name",
|
||||
s.as_ref()
|
||||
)));
|
||||
}
|
||||
}
|
||||
);
|
||||
flush_stdout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_color<S: AsRef<str>>(s: S) -> mlua::Result<()> {
|
||||
print!(
|
||||
"{}",
|
||||
match s.as_ref() {
|
||||
"reset" => COLOR_RESET,
|
||||
"black" => COLOR_BLACK,
|
||||
"red" => COLOR_RED,
|
||||
"green" => COLOR_GREEN,
|
||||
"yellow" => COLOR_YELLOW,
|
||||
"blue" => COLOR_BLUE,
|
||||
"purple" => COLOR_PURPLE,
|
||||
"cyan" => COLOR_CYAN,
|
||||
"white" => COLOR_WHITE,
|
||||
_ => {
|
||||
return Err(mlua::Error::RuntimeError(format!(
|
||||
"The color '{}' is not a valid color name",
|
||||
s.as_ref()
|
||||
)));
|
||||
}
|
||||
}
|
||||
);
|
||||
flush_stdout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn can_be_plain_lua_table_key(s: &mlua::String) -> bool {
|
||||
let str = s.to_string_lossy().to_string();
|
||||
let first_char = str.chars().next().unwrap();
|
||||
if first_char.is_alphabetic() {
|
||||
str.chars().all(|c| c == '_' || c.is_alphanumeric())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) -> anyhow::Result<()> {
|
||||
// TODO: Handle tables with cyclic references
|
||||
// TODO: Handle other types like function, userdata, ...
|
||||
match &value {
|
||||
Value::Nil => write!(buffer, "nil")?,
|
||||
Value::Boolean(true) => write!(buffer, "{}true{}", COLOR_YELLOW, COLOR_RESET)?,
|
||||
Value::Boolean(false) => write!(buffer, "{}false{}", COLOR_YELLOW, COLOR_RESET)?,
|
||||
Value::Number(n) => write!(buffer, "{}{}{}", COLOR_BLUE, n, COLOR_RESET)?,
|
||||
Value::Integer(i) => write!(buffer, "{}{}{}", COLOR_BLUE, i, COLOR_RESET)?,
|
||||
Value::String(s) => write!(
|
||||
buffer,
|
||||
"{}\"{}\"{}",
|
||||
COLOR_GREEN,
|
||||
s.to_string_lossy()
|
||||
.replace('"', r#"\""#)
|
||||
.replace('\n', r#"\n"#),
|
||||
COLOR_RESET
|
||||
)?,
|
||||
Value::Table(ref tab) => {
|
||||
if depth >= MAX_FORMAT_DEPTH {
|
||||
write!(buffer, "{}{{ ... }}{}", STYLE_DIM, STYLE_RESET)?;
|
||||
} else {
|
||||
let depth_indent = INDENT.repeat(depth);
|
||||
write!(buffer, "{}{{{}", STYLE_DIM, STYLE_RESET)?;
|
||||
for pair in tab.clone().pairs::<Value, Value>() {
|
||||
let (key, value) = pair?;
|
||||
match &key {
|
||||
Value::String(s) if can_be_plain_lua_table_key(s) => write!(
|
||||
buffer,
|
||||
"\n{}{}{} {}={} ",
|
||||
depth_indent,
|
||||
INDENT,
|
||||
s.to_string_lossy(),
|
||||
STYLE_DIM,
|
||||
STYLE_RESET
|
||||
)?,
|
||||
_ => {
|
||||
write!(buffer, "\n{}{}[", depth_indent, INDENT)?;
|
||||
pretty_format_value(buffer, &key, depth)?;
|
||||
write!(buffer, "] {}={} ", STYLE_DIM, STYLE_RESET)?;
|
||||
}
|
||||
}
|
||||
pretty_format_value(buffer, &value, depth + 1)?;
|
||||
write!(buffer, "{},{}", STYLE_DIM, STYLE_RESET)?;
|
||||
}
|
||||
write!(buffer, "\n{}{}}}{}", depth_indent, STYLE_DIM, STYLE_RESET)?;
|
||||
}
|
||||
}
|
||||
_ => write!(buffer, "?")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pretty_format_multi_value(multi: &MultiValue) -> mlua::Result<String> {
|
||||
let mut buffer = String::new();
|
||||
let mut counter = 0;
|
||||
for value in multi {
|
||||
counter += 1;
|
||||
if let Value::String(s) = value {
|
||||
write!(buffer, "{}", s.to_string_lossy()).map_err(mlua::Error::external)?
|
||||
} else {
|
||||
pretty_format_value(&mut buffer, value, 0).map_err(mlua::Error::external)?;
|
||||
}
|
||||
if counter < multi.len() {
|
||||
write!(&mut buffer, " ").map_err(mlua::Error::external)?;
|
||||
}
|
||||
}
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn pretty_print_luau_error(e: &mlua::Error) {
|
||||
match e {
|
||||
mlua::Error::RuntimeError(e) => {
|
||||
|
|
Loading…
Reference in a new issue