2023-01-24 07:05:54 +00:00
|
|
|
use std::{fs::read_to_string, 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-01-21 03:01:02 +00:00
|
|
|
|
|
|
|
use crate::utils::{files::find_parse_file_path, github::Client as GithubClient};
|
2023-01-19 01:47:14 +00:00
|
|
|
|
2023-01-24 16:53:06 +00:00
|
|
|
const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
|
|
|
|
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
|
|
|
|
2023-01-19 01:47:14 +00:00
|
|
|
/// Lune CLI
|
2023-01-19 18:18:15 +00:00
|
|
|
#[derive(Parser, Debug, Default)]
|
2023-01-19 01:47:14 +00:00
|
|
|
#[command(author, version, about, long_about = None)]
|
|
|
|
pub struct Cli {
|
2023-01-19 05:36:44 +00:00
|
|
|
/// Path to the file to run, or the name
|
|
|
|
/// of a luau file in a lune directory
|
2023-01-19 18:18:15 +00:00
|
|
|
///
|
|
|
|
/// Can be omitted when downloading type definitions
|
|
|
|
script_path: Option<String>,
|
2023-01-19 05:36:44 +00:00
|
|
|
/// Arguments to pass to the file as vararg (...)
|
|
|
|
script_args: Vec<String>,
|
|
|
|
/// Pass this flag to download the Selene type
|
|
|
|
/// definitions file to the current directory
|
|
|
|
#[clap(long)]
|
|
|
|
download_selene_types: bool,
|
|
|
|
/// Pass this flag to download the Luau type
|
|
|
|
/// definitions file to the current directory
|
|
|
|
#[clap(long)]
|
|
|
|
download_luau_types: bool,
|
|
|
|
}
|
|
|
|
|
2023-01-24 16:53:06 +00:00
|
|
|
#[allow(dead_code)]
|
2023-01-19 01:47:14 +00:00
|
|
|
impl Cli {
|
2023-01-19 05:23:06 +00:00
|
|
|
pub fn from_path<S>(path: S) -> Self
|
|
|
|
where
|
|
|
|
S: Into<String>,
|
|
|
|
{
|
2023-01-19 01:47:14 +00:00
|
|
|
Self {
|
2023-01-19 18:18:15 +00:00
|
|
|
script_path: Some(path.into()),
|
2023-01-19 05:36:44 +00:00
|
|
|
..Default::default()
|
2023-01-19 05:23:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_path_with_args<S, A>(path: S, args: A) -> Self
|
|
|
|
where
|
|
|
|
S: Into<String>,
|
|
|
|
A: Into<Vec<String>>,
|
|
|
|
{
|
|
|
|
Self {
|
2023-01-19 18:18:15 +00:00
|
|
|
script_path: Some(path.into()),
|
2023-01-19 05:36:44 +00:00
|
|
|
script_args: args.into(),
|
|
|
|
..Default::default()
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 16:53:06 +00:00
|
|
|
pub fn download_selene_types() -> Self {
|
|
|
|
Self {
|
|
|
|
download_selene_types: true,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn download_luau_types() -> Self {
|
|
|
|
Self {
|
|
|
|
download_luau_types: true,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 07:05:54 +00:00
|
|
|
pub async fn run(self) -> Result<ExitCode> {
|
2023-01-19 18:18:15 +00:00
|
|
|
// Download definition files, if wanted
|
|
|
|
let download_types_requested = self.download_selene_types || self.download_luau_types;
|
|
|
|
if download_types_requested {
|
2023-01-23 02:14:13 +00:00
|
|
|
let client = GithubClient::new();
|
2023-01-24 18:48:37 +00:00
|
|
|
let release = client.fetch_release_for_this_version().await?;
|
2023-01-19 18:18:15 +00:00
|
|
|
if self.download_selene_types {
|
|
|
|
println!("Downloading Selene type definitions...");
|
|
|
|
client
|
2023-01-24 16:53:06 +00:00
|
|
|
.fetch_release_asset(&release, LUNE_SELENE_FILE_NAME)
|
2023-01-24 18:48:37 +00:00
|
|
|
.await?;
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
|
|
|
if self.download_luau_types {
|
|
|
|
println!("Downloading Luau type definitions...");
|
|
|
|
client
|
2023-01-24 16:53:06 +00:00
|
|
|
.fetch_release_asset(&release, LUNE_LUAU_FILE_NAME)
|
2023-01-24 18:48:37 +00:00
|
|
|
.await?;
|
2023-01-19 18:18:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if self.script_path.is_none() {
|
2023-01-21 02:05:51 +00:00
|
|
|
// Only downloading types without running a script is completely
|
|
|
|
// fine, and we should just exit the program normally afterwards
|
|
|
|
if download_types_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-20 20:21:20 +00:00
|
|
|
let file_contents = read_to_string(&file_path)?;
|
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-01-22 20:23:56 +00:00
|
|
|
let lune = Lune::new().with_args(self.script_args).with_all_globals();
|
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}");
|
|
|
|
ExitCode::from(1)
|
|
|
|
}
|
|
|
|
Ok(code) => code,
|
|
|
|
})
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-24 16:53:06 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-01-24 19:05:19 +00:00
|
|
|
use std::env::{current_dir, set_current_dir};
|
2023-01-24 18:48:37 +00:00
|
|
|
|
2023-01-24 19:05:19 +00:00
|
|
|
use anyhow::{bail, Context, Result};
|
2023-01-24 16:53:06 +00:00
|
|
|
use serde_json::Value;
|
2023-01-24 19:05:19 +00:00
|
|
|
use smol::fs::{create_dir_all, read_to_string, remove_file};
|
2023-01-24 18:48:37 +00:00
|
|
|
|
|
|
|
use super::{Cli, LUNE_LUAU_FILE_NAME, LUNE_SELENE_FILE_NAME};
|
|
|
|
|
2023-01-24 16:53:06 +00:00
|
|
|
async fn run_cli(cli: Cli) -> Result<()> {
|
2023-01-24 19:05:19 +00:00
|
|
|
let path = current_dir()
|
|
|
|
.context("Failed to get current dir")?
|
|
|
|
.join("bin");
|
|
|
|
create_dir_all(&path)
|
|
|
|
.await
|
|
|
|
.context("Failed to create bin dir")?;
|
|
|
|
set_current_dir(&path).context("Failed to set current dir")?;
|
2023-01-24 16:53:06 +00:00
|
|
|
cli.run().await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-01-24 19:05:19 +00:00
|
|
|
async fn ensure_file_exists_and_is_not_json(file_name: &str) -> Result<()> {
|
|
|
|
match read_to_string(file_name)
|
|
|
|
.await
|
|
|
|
.context("Failed to read definitions file")
|
|
|
|
{
|
|
|
|
Ok(file_contents) => match serde_json::from_str::<Value>(&file_contents) {
|
|
|
|
Err(_) => {
|
|
|
|
remove_file(file_name)
|
|
|
|
.await
|
|
|
|
.context("Failed to remove definitions file")?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Ok(_) => bail!("Downloading selene definitions returned json, expected luau"),
|
|
|
|
},
|
|
|
|
Err(e) => bail!("Failed to download selene definitions!\n{e}"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 16:53:06 +00:00
|
|
|
#[test]
|
|
|
|
fn download_selene_types() -> Result<()> {
|
|
|
|
smol::block_on(async {
|
|
|
|
run_cli(Cli::download_selene_types()).await?;
|
2023-01-24 19:05:19 +00:00
|
|
|
ensure_file_exists_and_is_not_json(LUNE_SELENE_FILE_NAME).await?;
|
|
|
|
Ok(())
|
2023-01-24 16:53:06 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn download_luau_types() -> Result<()> {
|
|
|
|
smol::block_on(async {
|
|
|
|
run_cli(Cli::download_luau_types()).await?;
|
2023-01-24 19:05:19 +00:00
|
|
|
ensure_file_exists_and_is_not_json(LUNE_LUAU_FILE_NAME).await?;
|
|
|
|
Ok(())
|
2023-01-24 16:53:06 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|