2023-01-27 00:36:06 +00:00
|
|
|
use std::process::ExitCode;
|
2023-01-19 01:47:14 +00:00
|
|
|
|
2023-03-07 21:07:53 +00:00
|
|
|
use anyhow::{Context, 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-03-07 21:07:53 +00:00
|
|
|
use tokio::{
|
|
|
|
fs::{read as read_to_vec, write},
|
|
|
|
io::{stdin, AsyncReadExt},
|
|
|
|
};
|
2023-01-21 03:01:02 +00:00
|
|
|
|
2023-01-27 00:36:06 +00:00
|
|
|
use crate::{
|
2023-02-16 11:28:17 +00:00
|
|
|
gen::{
|
2023-02-22 13:25:50 +00:00
|
|
|
generate_docs_json_from_definitions, generate_luau_defs_from_definitions,
|
|
|
|
generate_selene_defs_from_definitions, generate_wiki_dir_from_definitions,
|
2023-02-16 11:28:17 +00:00
|
|
|
},
|
2023-01-27 00:36:06 +00:00
|
|
|
utils::{
|
2023-03-08 11:09:45 +00:00
|
|
|
files::discover_script_file_path_including_lune_dirs,
|
2023-01-27 00:36:06 +00:00
|
|
|
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-16 11:28:17 +00:00
|
|
|
pub(crate) const FILE_CONTENTS_LUAU_TYPES: &str = include_str!("../../../docs/luneTypes.d.luau");
|
2023-02-15 21:36:26 +00:00
|
|
|
|
|
|
|
/// 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 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,
|
2023-02-16 11:28:17 +00:00
|
|
|
/// Generate a Selene type definitions file in the current dir
|
|
|
|
#[clap(long)]
|
|
|
|
generate_selene_types: bool,
|
2023-02-15 21:36:26 +00:00
|
|
|
/// 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
|
2023-02-16 11:28:17 +00:00
|
|
|
let generate_file_requested = self.generate_luau_types
|
|
|
|
|| self.generate_selene_types
|
2023-02-16 10:52:23 +00:00
|
|
|
|| self.generate_docs_file
|
|
|
|
|| self.generate_wiki_dir;
|
2023-02-15 21:36:26 +00:00
|
|
|
if generate_file_requested {
|
|
|
|
if self.generate_luau_types {
|
|
|
|
generate_and_save_file(FILE_NAME_LUAU_TYPES, "Luau type definitions", || {
|
2023-02-22 13:25:50 +00:00
|
|
|
generate_luau_defs_from_definitions(FILE_CONTENTS_LUAU_TYPES)
|
2023-02-15 21:36:26 +00:00
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
}
|
2023-02-16 11:28:17 +00:00
|
|
|
if self.generate_selene_types {
|
|
|
|
generate_and_save_file(FILE_NAME_SELENE_TYPES, "Selene type definitions", || {
|
|
|
|
generate_selene_defs_from_definitions(FILE_CONTENTS_LUAU_TYPES)
|
|
|
|
})
|
|
|
|
.await?;
|
|
|
|
}
|
2023-02-15 21:36:26 +00:00
|
|
|
if self.generate_docs_file {
|
|
|
|
generate_and_save_file(FILE_NAME_DOCS, "Luau LSP documentation", || {
|
2023-02-16 10:52:23 +00:00
|
|
|
generate_docs_json_from_definitions(FILE_CONTENTS_LUAU_TYPES, "roblox/global")
|
2023-02-15 21:36:26 +00:00
|
|
|
})
|
|
|
|
.await?;
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
2023-02-16 10:52:23 +00:00
|
|
|
if self.generate_wiki_dir {
|
|
|
|
generate_wiki_dir_from_definitions(FILE_CONTENTS_LUAU_TYPES).await?;
|
|
|
|
}
|
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-03-07 21:07:53 +00:00
|
|
|
// Figure out if we should read from stdin or from a file,
|
|
|
|
// reading from stdin is marked by passing a single "-"
|
|
|
|
// (dash) as the script name to run to the cli
|
|
|
|
let script_path = self.script_path.unwrap();
|
|
|
|
let (script_display_name, script_contents) = if script_path == "-" {
|
|
|
|
let mut stdin_contents = Vec::new();
|
|
|
|
stdin()
|
|
|
|
.read_to_end(&mut stdin_contents)
|
|
|
|
.await
|
|
|
|
.context("Failed to read script contents from stdin")?;
|
|
|
|
("stdin".to_string(), stdin_contents)
|
|
|
|
} else {
|
2023-03-08 11:09:45 +00:00
|
|
|
let file_path = discover_script_file_path_including_lune_dirs(&script_path)?;
|
2023-03-07 21:07:53 +00:00
|
|
|
let file_contents = read_to_vec(&file_path).await?;
|
|
|
|
// NOTE: We skip the extension here to remove it from stack traces
|
|
|
|
let file_display_name = file_path.with_extension("").display().to_string();
|
|
|
|
(file_display_name, file_contents)
|
|
|
|
};
|
2023-01-21 03:01:02 +00:00
|
|
|
// Create a new lune object with all globals & run the script
|
2023-02-17 18:20:17 +00:00
|
|
|
let lune = Lune::new().with_args(self.script_args);
|
2023-03-07 21:07:53 +00:00
|
|
|
let result = lune.run(&script_display_name, &script_contents).await;
|
2023-01-24 07:05:54 +00:00
|
|
|
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(())
|
|
|
|
}
|