mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 21:10: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
|
Using arguments given to the program
|
||||||
]==]
|
]==]
|
||||||
|
|
||||||
local arg: string? = ...
|
if #process.args > 0 then
|
||||||
if arg then
|
print("\nGot arguments while running hello_lune:")
|
||||||
print("\nGot an argument while running hello_lune:")
|
console.log(process.args)
|
||||||
print(arg)
|
if #process.args > 3 then
|
||||||
|
error("Too many arguments!")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--[==[
|
--[==[
|
||||||
|
@ -31,14 +33,13 @@ end
|
||||||
]==]
|
]==]
|
||||||
|
|
||||||
print("\nReading current environment 🔎")
|
print("\nReading current environment 🔎")
|
||||||
local vars = process.getEnvVars()
|
|
||||||
table.sort(vars)
|
|
||||||
|
|
||||||
assert(table.find(vars, "PATH") ~= nil, "Missing PATH")
|
-- Environment variables can be read directly
|
||||||
assert(table.find(vars, "PWD") ~= nil, "Missing PWD")
|
assert(process.env.PATH ~= nil, "Missing PATH")
|
||||||
|
assert(process.env.PWD ~= nil, "Missing PWD")
|
||||||
|
|
||||||
for _, key in vars do
|
-- And they can also be accessed using generalized iteration (not pairs!)
|
||||||
local value = process.getEnvVar(key)
|
for key, value in process.env do
|
||||||
local box = if value and value ~= "" then "✅" else "❌"
|
local box = if value and value ~= "" then "✅" else "❌"
|
||||||
print(string.format("[%s] %s", box, key))
|
print(string.format("[%s] %s", box, key))
|
||||||
end
|
end
|
||||||
|
@ -85,7 +86,7 @@ end
|
||||||
|
|
||||||
-- NOTE: We skip the ping example in GitHub Actions
|
-- NOTE: We skip the ping example in GitHub Actions
|
||||||
-- since the ping command does not work in azure
|
-- 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
|
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/),
|
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).
|
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
|
## `0.0.3` - January 19th, 2023
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -516,6 +516,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"mlua",
|
"mlua",
|
||||||
|
"os_str_bytes",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -668,6 +669,9 @@ name = "os_str_bytes"
|
||||||
version = "6.4.1"
|
version = "6.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
|
|
|
@ -22,6 +22,7 @@ panic = "abort" # Remove extra panic info
|
||||||
anyhow = { version = "1.0.68" }
|
anyhow = { version = "1.0.68" }
|
||||||
clap = { version = "4.1.1", features = ["derive"] }
|
clap = { version = "4.1.1", features = ["derive"] }
|
||||||
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
|
mlua = { version = "0.8.7", features = ["luau", "async", "serialize"] }
|
||||||
|
os_str_bytes = "6.4.1"
|
||||||
reqwest = { version = "0.11.13", features = ["gzip", "deflate"] }
|
reqwest = { version = "0.11.13", features = ["gzip", "deflate"] }
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = { version = "1.0.91" }
|
serde_json = { version = "1.0.91" }
|
||||||
|
|
|
@ -98,9 +98,8 @@ type net = {
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
type process = {
|
type process = {
|
||||||
getEnvVars: () -> { string },
|
args: { string },
|
||||||
getEnvVar: (key: string) -> string?,
|
env: { [string]: string? },
|
||||||
setEnvVar: (key: string, value: string) -> (),
|
|
||||||
exit: (code: number?) -> (),
|
exit: (code: number?) -> (),
|
||||||
spawn: (program: string, params: { string }?) -> {
|
spawn: (program: string, params: { string }?) -> {
|
||||||
ok: boolean,
|
ok: boolean,
|
||||||
|
|
10
lune.yml
10
lune.yml
|
@ -63,15 +63,7 @@ globals:
|
||||||
net.request:
|
net.request:
|
||||||
args:
|
args:
|
||||||
- type: any
|
- type: any
|
||||||
# Process
|
# Processs
|
||||||
process.getEnvVars:
|
|
||||||
process.getEnvVar:
|
|
||||||
args:
|
|
||||||
- type: string
|
|
||||||
process.setEnvVar:
|
|
||||||
args:
|
|
||||||
- type: string
|
|
||||||
- type: string
|
|
||||||
process.exit:
|
process.exit:
|
||||||
args:
|
args:
|
||||||
- required: false
|
- required: false
|
||||||
|
|
|
@ -41,9 +41,8 @@ declare net: {
|
||||||
}
|
}
|
||||||
|
|
||||||
declare process: {
|
declare process: {
|
||||||
getEnvVars: () -> { string },
|
args: { string },
|
||||||
getEnvVar: (key: string) -> string?,
|
env: { [string]: string? },
|
||||||
setEnvVar: (key: string, value: string) -> (),
|
|
||||||
exit: (code: number?) -> (),
|
exit: (code: number?) -> (),
|
||||||
spawn: (program: string, params: { string }?) -> {
|
spawn: (program: string, params: { string }?) -> {
|
||||||
ok: boolean,
|
ok: boolean,
|
||||||
|
|
14
src/cli.rs
14
src/cli.rs
|
@ -4,7 +4,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::{CommandFactory, Parser};
|
use clap::{CommandFactory, Parser};
|
||||||
use mlua::{Lua, MultiValue, Result, ToLua};
|
use mlua::{Lua, Result};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
lune::{console::LuneConsole, fs::LuneFs, net::LuneNet, process::LuneProcess},
|
lune::{console::LuneConsole, fs::LuneFs, net::LuneNet, process::LuneProcess},
|
||||||
|
@ -97,23 +97,19 @@ impl Cli {
|
||||||
}
|
}
|
||||||
// Parse and read the wanted file
|
// Parse and read the wanted file
|
||||||
let file_path = find_parse_file_path(&self.script_path.unwrap())?;
|
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
|
// Create a new lua state and add in all lune globals
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
let globals = lua.globals();
|
let globals = lua.globals();
|
||||||
globals.set("console", LuneConsole::new())?;
|
globals.set("console", LuneConsole::new())?;
|
||||||
globals.set("fs", LuneFs::new())?;
|
globals.set("fs", LuneFs::new())?;
|
||||||
globals.set("net", LuneNet::new())?;
|
globals.set("net", LuneNet::new())?;
|
||||||
globals.set("process", LuneProcess::new())?;
|
globals.set("process", LuneProcess::new(self.script_args))?;
|
||||||
lua.sandbox(true)?;
|
lua.sandbox(true)?;
|
||||||
// Load & call the file with the given args
|
// 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)
|
lua.load(&file_contents)
|
||||||
.call_async(MultiValue::from_vec(lua_args))
|
.set_name(file_path.with_extension("").display().to_string())?
|
||||||
|
.exec_async()
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,9 @@
|
||||||
use std::{
|
use mlua::{Lua, MultiValue, Result, UserData, UserDataMethods};
|
||||||
fmt::Write,
|
|
||||||
io::{self, Write as IoWrite},
|
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();
|
pub struct LuneConsole();
|
||||||
|
|
||||||
impl 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<()> {
|
fn console_reset_color(_: &Lua, _: ()) -> Result<()> {
|
||||||
print_color("reset")?;
|
print_color("reset")?;
|
||||||
flush_stdout()?;
|
flush_stdout()?;
|
||||||
|
@ -207,10 +60,7 @@ fn console_log(_: &Lua, args: MultiValue) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn console_info(_: &Lua, args: MultiValue) -> Result<()> {
|
fn console_info(_: &Lua, args: MultiValue) -> Result<()> {
|
||||||
print!(
|
print_label("info")?;
|
||||||
"{}[{}INFO{}{}]{} ",
|
|
||||||
STYLE_BOLD, COLOR_CYAN, COLOR_RESET, STYLE_BOLD, STYLE_RESET
|
|
||||||
);
|
|
||||||
let s = pretty_format_multi_value(&args)?;
|
let s = pretty_format_multi_value(&args)?;
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
flush_stdout()?;
|
flush_stdout()?;
|
||||||
|
@ -218,10 +68,7 @@ fn console_info(_: &Lua, args: MultiValue) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn console_warn(_: &Lua, args: MultiValue) -> Result<()> {
|
fn console_warn(_: &Lua, args: MultiValue) -> Result<()> {
|
||||||
print!(
|
print_label("warn")?;
|
||||||
"{}[{}WARN{}{}]{} ",
|
|
||||||
STYLE_BOLD, COLOR_YELLOW, COLOR_RESET, STYLE_BOLD, STYLE_RESET
|
|
||||||
);
|
|
||||||
let s = pretty_format_multi_value(&args)?;
|
let s = pretty_format_multi_value(&args)?;
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
flush_stdout()?;
|
flush_stdout()?;
|
||||||
|
@ -229,10 +76,7 @@ fn console_warn(_: &Lua, args: MultiValue) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn console_error(_: &Lua, args: MultiValue) -> Result<()> {
|
fn console_error(_: &Lua, args: MultiValue) -> Result<()> {
|
||||||
eprint!(
|
print_label("error")?;
|
||||||
"{}[{}ERROR{}{}]{} ",
|
|
||||||
STYLE_BOLD, COLOR_RED, COLOR_RESET, STYLE_BOLD, STYLE_RESET
|
|
||||||
);
|
|
||||||
let s = pretty_format_multi_value(&args)?;
|
let s = pretty_format_multi_value(&args)?;
|
||||||
eprintln!("{}", s);
|
eprintln!("{}", s);
|
||||||
flush_stdout()?;
|
flush_stdout()?;
|
||||||
|
|
|
@ -1,53 +1,111 @@
|
||||||
use std::{
|
use std::{
|
||||||
env::{self, VarError},
|
env,
|
||||||
process::{exit, Stdio},
|
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;
|
use tokio::process::Command;
|
||||||
|
|
||||||
pub struct LuneProcess();
|
pub struct LuneProcess {
|
||||||
|
args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LuneProcess {
|
impl LuneProcess {
|
||||||
pub fn new() -> Self {
|
pub fn new(args: Vec<String>) -> Self {
|
||||||
Self()
|
Self { args }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for LuneProcess {
|
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) {
|
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_function("exit", process_exit);
|
||||||
methods.add_async_function("spawn", process_spawn);
|
methods.add_async_function("spawn", process_spawn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_get_env_vars(_: &Lua, _: ()) -> Result<Vec<String>> {
|
fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result<Value<'lua>> {
|
||||||
let mut vars = Vec::new();
|
match env::var_os(key) {
|
||||||
for (key, _) in env::vars() {
|
Some(value) => {
|
||||||
vars.push(key);
|
let raw_value = RawOsString::new(value);
|
||||||
|
Ok(Value::String(lua.create_string(raw_value.as_raw_bytes())?))
|
||||||
}
|
}
|
||||||
Ok(vars)
|
None => Ok(Value::Nil),
|
||||||
}
|
|
||||||
|
|
||||||
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_set_env_var(_: &Lua, (key, value): (String, String)) -> Result<()> {
|
fn process_env_set(_: &Lua, (_, key, value): (Value, String, String)) -> Result<()> {
|
||||||
env::set_var(key, value);
|
// 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(())
|
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<()> {
|
fn process_exit(_: &Lua, exit_code: Option<i32>) -> Result<()> {
|
||||||
if let Some(code) = exit_code {
|
if let Some(code) = exit_code {
|
||||||
exit(code);
|
exit(code);
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod lune;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use cli::Cli;
|
use cli::Cli;
|
||||||
use utils::pretty_print_luau_error;
|
use utils::{pretty_print_luau_error, print_label};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
@ -17,7 +17,8 @@ async fn main() -> Result<()> {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("[ERROR]");
|
print_label("ERROR").unwrap();
|
||||||
|
eprintln!();
|
||||||
pretty_print_luau_error(&e);
|
pretty_print_luau_error(&e);
|
||||||
std::process::exit(1);
|
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 anyhow::{bail, Context, Result};
|
||||||
|
use mlua::{MultiValue, Value};
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{HeaderMap, HeaderValue},
|
header::{HeaderMap, HeaderValue},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
pub struct GithubReleaseAsset {
|
pub struct GithubReleaseAsset {
|
||||||
id: u64,
|
id: u64,
|
||||||
|
@ -138,6 +161,155 @@ pub fn get_github_user_agent_header() -> String {
|
||||||
format!("{}-{}-cli", github_owner, github_repo)
|
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) {
|
pub fn pretty_print_luau_error(e: &mlua::Error) {
|
||||||
match e {
|
match e {
|
||||||
mlua::Error::RuntimeError(e) => {
|
mlua::Error::RuntimeError(e) => {
|
||||||
|
|
Loading…
Reference in a new issue