2023-01-27 00:36:06 +00:00
|
|
|
use std::process::ExitCode;
|
2023-01-19 01:47:14 +00:00
|
|
|
|
2023-01-21 03:01:02 +00:00
|
|
|
use anyhow::Result;
|
2023-01-19 18:18:15 +00:00
|
|
|
use clap::{CommandFactory, Parser};
|
2023-01-19 01:47:14 +00:00
|
|
|
|
2023-01-22 20:23:56 +00:00
|
|
|
use lune::Lune;
|
2023-02-03 19:15:20 +00:00
|
|
|
use tokio::fs::{read_to_string, write};
|
2023-01-21 03:01:02 +00:00
|
|
|
|
2023-01-27 00:36:06 +00:00
|
|
|
use crate::{
|
|
|
|
gen::generate_docs_json_from_definitions,
|
|
|
|
utils::{
|
|
|
|
files::find_parse_file_path,
|
|
|
|
listing::{find_lune_scripts, print_lune_scripts, sort_lune_scripts},
|
|
|
|
},
|
2023-01-25 19:42:10 +00:00
|
|
|
};
|
2023-01-19 01:47:14 +00:00
|
|
|
|
2023-02-15 21:36:26 +00:00
|
|
|
pub(crate) const FILE_NAME_SELENE_TYPES: &str = "lune.yml";
|
|
|
|
pub(crate) const FILE_NAME_LUAU_TYPES: &str = "luneTypes.d.luau";
|
|
|
|
pub(crate) const FILE_NAME_DOCS: &str = "luneDocs.json";
|
2023-01-24 16:53:06 +00:00
|
|
|
|
2023-02-15 21:36:26 +00:00
|
|
|
pub(crate) const FILE_CONTENTS_SELENE_TYPES: &str = include_str!("../../../lune.yml");
|
|
|
|
pub(crate) const FILE_CONTENTS_LUAU_TYPES: &str = include_str!("../../../luneTypes.d.luau");
|
|
|
|
|
|
|
|
/// A Luau script runner
|
|
|
|
#[derive(Parser, Debug, Default, Clone)]
|
|
|
|
#[command(version, long_about = None)]
|
2023-01-27 00:36:06 +00:00
|
|
|
#[allow(clippy::struct_excessive_bools)]
|
2023-01-19 01:47:14 +00:00
|
|
|
pub struct Cli {
|
2023-02-15 21:36:26 +00:00
|
|
|
/// Script name or full path to the file to run
|
2023-01-19 18:18:15 +00:00
|
|
|
script_path: Option<String>,
|
2023-02-15 21:36:26 +00:00
|
|
|
/// Arguments to pass to the script, stored in process.args
|
2023-01-19 05:36:44 +00:00
|
|
|
script_args: Vec<String>,
|
2023-02-15 21:36:26 +00:00
|
|
|
/// List scripts found inside of a nearby `lune` directory
|
2023-01-25 19:42:10 +00:00
|
|
|
#[clap(long, short = 'l')]
|
|
|
|
list: bool,
|
2023-02-15 21:36:26 +00:00
|
|
|
/// Generate a Selene type definitions file in the current dir
|
2023-01-19 05:36:44 +00:00
|
|
|
#[clap(long)]
|
2023-02-15 21:36:26 +00:00
|
|
|
generate_selene_types: bool,
|
|
|
|
/// Generate a Luau type definitions file in the current dir
|
2023-01-19 05:36:44 +00:00
|
|
|
#[clap(long)]
|
2023-02-15 21:36:26 +00:00
|
|
|
generate_luau_types: bool,
|
|
|
|
/// Generate a Lune documentation file for Luau LSP
|
2023-01-27 00:36:06 +00:00
|
|
|
#[clap(long)]
|
|
|
|
generate_docs_file: bool,
|
2023-02-15 21:36:26 +00:00
|
|
|
/// Generate the full Lune wiki directory
|
|
|
|
#[clap(long, hide = true)]
|
|
|
|
generate_wiki_dir: bool,
|
2023-01-19 05:36:44 +00:00
|
|
|
}
|
|
|
|
|
2023-01-24 16:53:06 +00:00
|
|
|
#[allow(dead_code)]
|
2023-01-19 01:47:14 +00:00
|
|
|
impl Cli {
|
2023-02-15 21:36:26 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_path<S>(mut self, path: S) -> Self
|
2023-01-19 05:23:06 +00:00
|
|
|
where
|
|
|
|
S: Into<String>,
|
|
|
|
{
|
2023-02-15 21:36:26 +00:00
|
|
|
self.script_path = Some(path.into());
|
|
|
|
self
|
2023-01-19 05:23:06 +00:00
|
|
|
}
|
|
|
|
|
2023-02-15 21:36:26 +00:00
|
|
|
pub fn with_args<A>(mut self, args: A) -> Self
|
2023-01-19 05:23:06 +00:00
|
|
|
where
|
|
|
|
A: Into<Vec<String>>,
|
|
|
|
{
|
2023-02-15 21:36:26 +00:00
|
|
|
self.script_args = args.into();
|
|
|
|
self
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
|
2023-02-15 21:36:26 +00:00
|
|
|
pub fn generate_selene_types(mut self) -> Self {
|
|
|
|
self.generate_selene_types = true;
|
|
|
|
self
|
2023-01-24 16:53:06 +00:00
|
|
|
}
|
|
|
|
|
2023-02-15 21:36:26 +00:00
|
|
|
pub fn generate_luau_types(mut self) -> Self {
|
|
|
|
self.generate_luau_types = true;
|
|
|
|
self
|
2023-01-24 16:53:06 +00:00
|
|
|
}
|
|
|
|
|
2023-02-15 21:36:26 +00:00
|
|
|
pub fn generate_docs_file(mut self) -> Self {
|
|
|
|
self.generate_docs_file = true;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn list(mut self) -> Self {
|
|
|
|
self.list = true;
|
|
|
|
self
|
2023-01-25 19:42:10 +00:00
|
|
|
}
|
|
|
|
|
2023-01-24 07:05:54 +00:00
|
|
|
pub async fn run(self) -> Result<ExitCode> {
|
2023-01-25 19:42:10 +00:00
|
|
|
// List files in `lune` and `.lune` directories, if wanted
|
|
|
|
// This will also exit early and not run anything else
|
|
|
|
if self.list {
|
|
|
|
match find_lune_scripts().await {
|
|
|
|
Ok(scripts) => {
|
|
|
|
let sorted = sort_lune_scripts(scripts);
|
|
|
|
if sorted.is_empty() {
|
|
|
|
println!("No scripts found.");
|
|
|
|
} else {
|
|
|
|
print!("Available scripts:");
|
|
|
|
print_lune_scripts(sorted)?;
|
|
|
|
}
|
|
|
|
return Ok(ExitCode::SUCCESS);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{e}");
|
|
|
|
return Ok(ExitCode::FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-02-15 21:36:26 +00:00
|
|
|
// Generate (save) definition files, if wanted
|
|
|
|
let generate_file_requested =
|
|
|
|
self.generate_selene_types || self.generate_luau_types || self.generate_docs_file;
|
|
|
|
if generate_file_requested {
|
|
|
|
if self.generate_selene_types {
|
|
|
|
generate_and_save_file(FILE_NAME_SELENE_TYPES, "Selene type definitions", || {
|
|
|
|
Ok(FILE_CONTENTS_SELENE_TYPES.to_string())
|
|
|
|
})
|
|
|
|
.await?;
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
2023-02-15 21:36:26 +00:00
|
|
|
if self.generate_luau_types {
|
|
|
|
generate_and_save_file(FILE_NAME_LUAU_TYPES, "Luau type definitions", || {
|
|
|
|
Ok(FILE_CONTENTS_LUAU_TYPES.to_string())
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
if self.generate_docs_file {
|
|
|
|
generate_and_save_file(FILE_NAME_DOCS, "Luau LSP documentation", || {
|
|
|
|
let docs = &generate_docs_json_from_definitions(
|
|
|
|
FILE_CONTENTS_LUAU_TYPES,
|
|
|
|
"roblox/global",
|
|
|
|
)?;
|
|
|
|
Ok(serde_json::to_string_pretty(docs)?)
|
|
|
|
})
|
|
|
|
.await?;
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
2023-01-27 00:36:06 +00:00
|
|
|
}
|
2023-01-19 18:18:15 +00:00
|
|
|
if self.script_path.is_none() {
|
2023-02-15 21:36:26 +00:00
|
|
|
// Only generating typedefs without running a script is completely
|
2023-01-21 02:05:51 +00:00
|
|
|
// fine, and we should just exit the program normally afterwards
|
2023-02-15 21:36:26 +00:00
|
|
|
if generate_file_requested {
|
2023-01-24 07:05:54 +00:00
|
|
|
return Ok(ExitCode::SUCCESS);
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
2023-01-21 02:05:51 +00:00
|
|
|
// HACK: We know that we didn't get any arguments here but since
|
|
|
|
// script_path is optional clap will not error on its own, to fix
|
|
|
|
// we will duplicate the cli command and make arguments required,
|
|
|
|
// which will then fail and print out the normal help message
|
|
|
|
let cmd = Cli::command();
|
|
|
|
cmd.arg_required_else_help(true).get_matches();
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
2023-01-19 01:47:14 +00:00
|
|
|
// Parse and read the wanted file
|
2023-01-19 18:18:15 +00:00
|
|
|
let file_path = find_parse_file_path(&self.script_path.unwrap())?;
|
2023-01-27 00:36:06 +00:00
|
|
|
let file_contents = read_to_string(&file_path).await?;
|
2023-01-21 03:01:02 +00:00
|
|
|
// Display the file path relative to cwd with no extensions in stack traces
|
|
|
|
let file_display_name = file_path.with_extension("").display().to_string();
|
|
|
|
// Create a new lune object with all globals & run the script
|
2023-02-10 11:14:28 +00:00
|
|
|
let lune = Lune::new().with_all_globals_and_args(self.script_args);
|
2023-01-24 07:05:54 +00:00
|
|
|
let result = lune.run(&file_display_name, &file_contents).await;
|
|
|
|
Ok(match result {
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{e}");
|
2023-02-06 00:13:58 +00:00
|
|
|
ExitCode::FAILURE
|
2023-01-24 07:05:54 +00:00
|
|
|
}
|
|
|
|
Ok(code) => code,
|
|
|
|
})
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-15 21:36:26 +00:00
|
|
|
|
|
|
|
async fn generate_and_save_file(
|
|
|
|
file_path: &str,
|
|
|
|
display_name: &str,
|
|
|
|
f: impl Fn() -> Result<String>,
|
|
|
|
) -> Result<()> {
|
|
|
|
#[cfg(test)]
|
|
|
|
use crate::tests::fmt_path_relative_to_workspace_root;
|
|
|
|
match f() {
|
|
|
|
Ok(file_contents) => {
|
|
|
|
write(file_path, file_contents).await?;
|
|
|
|
#[cfg(not(test))]
|
|
|
|
println!("Generated {display_name} file at '{file_path}'");
|
|
|
|
#[cfg(test)]
|
|
|
|
println!(
|
|
|
|
"Generated {display_name} file at '{}'",
|
|
|
|
fmt_path_relative_to_workspace_root(file_path)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
#[cfg(not(test))]
|
|
|
|
println!("Failed to generate {display_name} file at '{file_path}'\n{e}");
|
|
|
|
#[cfg(test)]
|
|
|
|
println!(
|
|
|
|
"Failed to generate {display_name} file at '{}'\n{e}",
|
|
|
|
fmt_path_relative_to_workspace_root(file_path)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|