Split into lune lib & lune cli for easier external usage

This commit is contained in:
Filip Tibell 2023-01-20 22:01:02 -05:00
parent 2565e02e11
commit c90f40cf30
No known key found for this signature in database
16 changed files with 183 additions and 103 deletions

View file

@ -10,7 +10,11 @@ categories = ["command-line-interface"]
[[bin]] [[bin]]
name = "lune" name = "lune"
path = "src/main.rs" path = "src/cli/main.rs"
[lib]
name = "lune"
path = "src/lib/lib.rs"
[profile.release] [profile.release]
strip = true # Automatically strip symbols from the binary. strip = true # Automatically strip symbols from the binary.

View file

@ -1,15 +1,11 @@
use std::{ use std::fs::read_to_string;
fs::read_to_string,
path::{PathBuf, MAIN_SEPARATOR},
};
use anyhow::Result;
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use mlua::{Lua, Result};
use crate::{ use lune::Lune;
globals::{console::Console, fs::Fs, net::Net, process::Process},
utils::github::Client as GithubClient, use crate::utils::{files::find_parse_file_path, github::Client as GithubClient};
};
/// Lune CLI /// Lune CLI
#[derive(Parser, Debug, Default)] #[derive(Parser, Debug, Default)]
@ -97,62 +93,14 @@ 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 // Display the file path relative to cwd with no extensions in stack traces
let lua = Lua::new(); let file_display_name = file_path.with_extension("").display().to_string();
let globals = lua.globals(); // Create a new lune object with all globals & run the script
globals.set("console", Console::new())?; Lune::new()?
globals.set("fs", Fs::new())?; .with_args(self.script_args)?
globals.set("net", Net::new())?; .with_default_globals()?
globals.set("process", Process::new(self.script_args))?; .run_with_name(&file_contents, &file_display_name)
lua.sandbox(true)?;
// Load & call the file with the given args
lua.load(&file_contents)
.set_name(file_path.with_extension("").display().to_string())?
.exec_async()
.await?; .await?;
Ok(()) Ok(())
} }
} }
fn find_luau_file_path(path: &str) -> Option<PathBuf> {
let file_path = PathBuf::from(path);
if let Some(ext) = file_path.extension() {
match ext {
e if e == "lua" || e == "luau" && file_path.exists() => Some(file_path),
_ => None,
}
} else {
let file_path_lua = PathBuf::from(path).with_extension("lua");
if file_path_lua.exists() {
Some(file_path_lua)
} else {
let file_path_luau = PathBuf::from(path).with_extension("luau");
if file_path_luau.exists() {
Some(file_path_luau)
} else {
None
}
}
}
}
fn find_parse_file_path(path: &str) -> Result<PathBuf> {
let parsed_file_path = find_luau_file_path(path)
.or_else(|| find_luau_file_path(&format!("lune{MAIN_SEPARATOR}{path}")))
.or_else(|| find_luau_file_path(&format!(".lune{MAIN_SEPARATOR}{path}")));
if let Some(file_path) = parsed_file_path {
if file_path.exists() {
Ok(file_path)
} else {
Err(mlua::Error::RuntimeError(format!(
"File does not exist at path: '{}'",
path
)))
}
} else {
Err(mlua::Error::RuntimeError(format!(
"Invalid file path: '{}'",
path
)))
}
}

View file

@ -1,29 +1,19 @@
#![deny(clippy::all, clippy::cargo, clippy::pedantic)] #![deny(clippy::all, clippy::cargo, clippy::pedantic)]
#![allow(clippy::needless_pass_by_value, clippy::match_bool)] #![allow(clippy::needless_pass_by_value, clippy::match_bool)]
use anyhow::Result;
use clap::Parser; use clap::Parser;
use mlua::Result;
mod cli; mod cli;
mod globals;
mod utils; mod utils;
use cli::Cli; use cli::Cli;
use utils::formatting::{pretty_print_luau_error, print_label};
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match cli.run().await { cli.run().await?;
Ok(_) => Ok(()), Ok(())
Err(e) => {
eprintln!();
print_label("ERROR").unwrap();
eprintln!();
pretty_print_luau_error(&e);
std::process::exit(1);
}
}
} }
#[tokio::test] #[tokio::test]

40
src/cli/utils/files.rs Normal file
View file

@ -0,0 +1,40 @@
use std::path::{PathBuf, MAIN_SEPARATOR};
use anyhow::{bail, Result};
pub fn find_luau_file_path(path: &str) -> Option<PathBuf> {
let file_path = PathBuf::from(path);
if let Some(ext) = file_path.extension() {
match ext {
e if e == "lua" || e == "luau" && file_path.exists() => Some(file_path),
_ => None,
}
} else {
let file_path_lua = PathBuf::from(path).with_extension("lua");
if file_path_lua.exists() {
Some(file_path_lua)
} else {
let file_path_luau = PathBuf::from(path).with_extension("luau");
if file_path_luau.exists() {
Some(file_path_luau)
} else {
None
}
}
}
}
pub fn find_parse_file_path(path: &str) -> Result<PathBuf> {
let parsed_file_path = find_luau_file_path(path)
.or_else(|| find_luau_file_path(&format!("lune{MAIN_SEPARATOR}{path}")))
.or_else(|| find_luau_file_path(&format!(".lune{MAIN_SEPARATOR}{path}")));
if let Some(file_path) = parsed_file_path {
if file_path.exists() {
Ok(file_path)
} else {
bail!("File does not exist at path: '{}'", path)
}
} else {
bail!("Invalid file path: '{}'", path)
}
}

View file

@ -4,6 +4,8 @@ use anyhow::{bail, Context, Result};
use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use lune::utils::net::{get_github_owner_and_repo, get_request_user_agent_header};
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
pub struct ReleaseAsset { pub struct ReleaseAsset {
id: u64, id: u64,
@ -38,7 +40,7 @@ impl Client {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(
"User-Agent", "User-Agent",
HeaderValue::from_str(&get_github_user_agent_header())?, HeaderValue::from_str(&get_request_user_agent_header())?,
); );
headers.insert( headers.insert(
"Accept", "Accept",
@ -118,17 +120,3 @@ impl Client {
Ok(()) Ok(())
} }
} }
pub fn get_github_owner_and_repo() -> (String, String) {
let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY")
.strip_prefix("https://github.com/")
.unwrap()
.split_once('/')
.unwrap();
(github_owner.to_owned(), github_repo.to_owned())
}
pub fn get_github_user_agent_header() -> String {
let (github_owner, github_repo) = get_github_owner_and_repo();
format!("{github_owner}-{github_repo}-cli")
}

2
src/cli/utils/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod files;
pub mod github;

View file

@ -1,4 +0,0 @@
pub mod console;
pub mod fs;
pub mod net;
pub mod process;

View file

@ -12,6 +12,12 @@ impl Console {
} }
} }
impl Default for Console {
fn default() -> Self {
Self::new()
}
}
impl UserData for Console { impl UserData for Console {
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("resetColor", console_reset_color); methods.add_function("resetColor", console_reset_color);

View file

@ -11,6 +11,12 @@ impl Fs {
} }
} }
impl Default for Fs {
fn default() -> Self {
Self::new()
}
}
impl UserData for Fs { impl UserData for Fs {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("readFile", fs_read_file); methods.add_async_function("readFile", fs_read_file);

9
src/lib/globals/mod.rs Normal file
View file

@ -0,0 +1,9 @@
mod console;
mod fs;
mod net;
mod process;
pub use console::Console;
pub use fs::Fs;
pub use net::Net;
pub use process::Process;

View file

@ -6,7 +6,7 @@ use reqwest::{
Method, Method,
}; };
use crate::utils::github::get_github_user_agent_header; use crate::utils::net::get_request_user_agent_header;
pub struct Net(); pub struct Net();
@ -16,6 +16,12 @@ impl Net {
} }
} }
impl Default for Net {
fn default() -> Self {
Self::new()
}
}
impl UserData for Net { impl UserData for Net {
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("jsonEncode", net_json_encode); methods.add_function("jsonEncode", net_json_encode);
@ -98,7 +104,7 @@ async fn net_request<'lua>(lua: &'lua Lua, config: Value<'lua>) -> Result<Value<
}; };
header_map.insert( header_map.insert(
"User-Agent", "User-Agent",
HeaderValue::from_str(&get_github_user_agent_header()).map_err(Error::external)?, HeaderValue::from_str(&get_request_user_agent_header()).map_err(Error::external)?,
); );
// Create a client to send a request with // Create a client to send a request with
// FUTURE: Try to reuse this client // FUTURE: Try to reuse this client

View file

@ -14,6 +14,12 @@ pub struct Process {
args: Vec<String>, args: Vec<String>,
} }
impl Default for Process {
fn default() -> Self {
Self::new(vec![])
}
}
impl Process { impl Process {
pub fn new(args: Vec<String>) -> Self { pub fn new(args: Vec<String>) -> Self {
Self { args } Self { args }

60
src/lib/lib.rs Normal file
View file

@ -0,0 +1,60 @@
use anyhow::Result;
use mlua::Lua;
pub mod globals;
pub mod utils;
use crate::{
globals::{Console, Fs, Net, Process},
utils::formatting::{pretty_print_luau_error, print_label},
};
pub struct Lune {
lua: Lua,
args: Vec<String>,
}
impl Lune {
pub fn new() -> Result<Self> {
let lua = Lua::new();
lua.sandbox(true)?;
Ok(Self { lua, args: vec![] })
}
pub fn with_args(mut self, args: Vec<String>) -> Result<Self> {
self.args = args;
Ok(self)
}
pub fn with_default_globals(self) -> Result<Self> {
{
let globals = self.lua.globals();
globals.set("console", Console::new())?;
globals.set("fs", Fs())?;
globals.set("net", Net::new())?;
globals.set("process", Process::new(self.args.clone()))?;
}
Ok(self)
}
pub async fn run(&self, chunk: &str) -> Result<()> {
self.handle_result(self.lua.load(chunk).exec_async().await)
}
pub async fn run_with_name(&self, chunk: &str, name: &str) -> Result<()> {
self.handle_result(self.lua.load(chunk).set_name(name)?.exec_async().await)
}
fn handle_result(&self, result: mlua::Result<()>) -> Result<()> {
match result {
Ok(_) => Ok(()),
Err(e) => {
eprintln!();
print_label("ERROR").unwrap();
eprintln!();
pretty_print_luau_error(&e);
std::process::exit(1);
}
}
}
}

View file

@ -107,8 +107,8 @@ pub fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) ->
Value::Nil => write!(buffer, "nil")?, Value::Nil => write!(buffer, "nil")?,
Value::Boolean(true) => write!(buffer, "{COLOR_YELLOW}true{COLOR_RESET}")?, Value::Boolean(true) => write!(buffer, "{COLOR_YELLOW}true{COLOR_RESET}")?,
Value::Boolean(false) => write!(buffer, "{COLOR_YELLOW}false{COLOR_RESET}")?, Value::Boolean(false) => write!(buffer, "{COLOR_YELLOW}false{COLOR_RESET}")?,
Value::Number(n) => write!(buffer, "{COLOR_BLUE}{n}{COLOR_RESET}")?, Value::Number(n) => write!(buffer, "{COLOR_CYAN}{n}{COLOR_RESET}")?,
Value::Integer(i) => write!(buffer, "{COLOR_BLUE}{i}{COLOR_RESET}")?, Value::Integer(i) => write!(buffer, "{COLOR_CYAN}{i}{COLOR_RESET}")?,
Value::String(s) => write!( Value::String(s) => write!(
buffer, buffer,
"{}\"{}\"{}", "{}\"{}\"{}",
@ -122,6 +122,7 @@ pub fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) ->
if depth >= MAX_FORMAT_DEPTH { if depth >= MAX_FORMAT_DEPTH {
write!(buffer, "{STYLE_DIM}{{ ... }}{STYLE_RESET}")?; write!(buffer, "{STYLE_DIM}{{ ... }}{STYLE_RESET}")?;
} else { } else {
let mut is_empty = false;
let depth_indent = INDENT.repeat(depth); let depth_indent = INDENT.repeat(depth);
write!(buffer, "{STYLE_DIM}{{{STYLE_RESET}")?; write!(buffer, "{STYLE_DIM}{{{STYLE_RESET}")?;
for pair in tab.clone().pairs::<Value, Value>() { for pair in tab.clone().pairs::<Value, Value>() {
@ -144,8 +145,13 @@ pub fn pretty_format_value(buffer: &mut String, value: &Value, depth: usize) ->
} }
pretty_format_value(buffer, &value, depth + 1)?; pretty_format_value(buffer, &value, depth + 1)?;
write!(buffer, "{STYLE_DIM},{STYLE_RESET}")?; write!(buffer, "{STYLE_DIM},{STYLE_RESET}")?;
is_empty = false;
}
if is_empty {
write!(buffer, " {STYLE_DIM}}}{STYLE_RESET}")?;
} else {
write!(buffer, "\n{depth_indent}{STYLE_DIM}}}{STYLE_RESET}")?;
} }
write!(buffer, "\n{depth_indent}{STYLE_DIM}}}{STYLE_RESET}")?;
} }
} }
_ => write!(buffer, "?")?, _ => write!(buffer, "?")?,

View file

@ -1,2 +1,2 @@
pub mod formatting; pub mod formatting;
pub mod github; pub mod net;

13
src/lib/utils/net.rs Normal file
View file

@ -0,0 +1,13 @@
pub fn get_github_owner_and_repo() -> (String, String) {
let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY")
.strip_prefix("https://github.com/")
.unwrap()
.split_once('/')
.unwrap();
(github_owner.to_owned(), github_repo.to_owned())
}
pub fn get_request_user_agent_header() -> String {
let (github_owner, github_repo) = get_github_owner_and_repo();
format!("{github_owner}-{github_repo}-cli")
}