diff --git a/CHANGELOG.md b/CHANGELOG.md
index baf5ccb..658561a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,21 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### Added
+
+- Added support for running scripts by passing absolute file paths in the CLI
+ - This does not have the restriction of scripts having to use the `.luau` or `.lua` extension, since it is presumed that if you pass an absolute path you know exactly what you are doing
+
+### Changed
+
+- Improved error messages for passing invalid file names / file paths substantially - they now include helpful formatting to make file names distinct from file extensions, and give suggestions on how to solve the problem
+
+### Fixed
+
+- Fixed the CLI being a bit too picky about file names when trying to run files in `lune` or `.lune` directories
+
## `0.5.4` - March 7th, 2023
### Added
diff --git a/docs/pages/Getting Started - 3 Running Scripts.md b/docs/pages/Getting Started - 3 Running Scripts.md
index fa220fd..edc27e5 100644
--- a/docs/pages/Getting Started - 3 Running Scripts.md
+++ b/docs/pages/Getting Started - 3 Running Scripts.md
@@ -46,4 +46,4 @@ Runs a script passed to Lune using stdin. Occasionally useful for running script
---
-**_[1]_** _Lune also supports files with the `.lua` extension but using the `.luau` extension is highly recommended. Additionally, if you don't want Lune to look in sub-directories you can provide a full file path with the file extension included, instead of only the file name._
+**_[1]_** _Lune also supports files with the `.lua` extension but using the `.luau` extension is highly recommended. Additionally, if you don't want Lune to look in sub-directories or try to find files with `.lua` / `.luau` extensions at all, you can provide an absolute file path. This will disable all file path parsing and checks, and just run the file directly._
diff --git a/packages/cli/src/cli.rs b/packages/cli/src/cli.rs
index cadeba9..b8a2892 100644
--- a/packages/cli/src/cli.rs
+++ b/packages/cli/src/cli.rs
@@ -15,7 +15,7 @@ use crate::{
generate_selene_defs_from_definitions, generate_wiki_dir_from_definitions,
},
utils::{
- files::find_parse_file_path,
+ files::discover_script_file_path_including_lune_dirs,
listing::{find_lune_scripts, print_lune_scripts, sort_lune_scripts},
},
};
@@ -168,7 +168,7 @@ impl Cli {
.context("Failed to read script contents from stdin")?;
("stdin".to_string(), stdin_contents)
} else {
- let file_path = find_parse_file_path(&script_path)?;
+ let file_path = discover_script_file_path_including_lune_dirs(&script_path)?;
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();
diff --git a/packages/cli/src/utils/files.rs b/packages/cli/src/utils/files.rs
index 863e0b7..88f4796 100644
--- a/packages/cli/src/utils/files.rs
+++ b/packages/cli/src/utils/files.rs
@@ -1,43 +1,136 @@
-use std::path::{PathBuf, MAIN_SEPARATOR};
+use std::{
+ fs::Metadata,
+ path::{PathBuf, MAIN_SEPARATOR},
+};
-use anyhow::{bail, Result};
+use anyhow::{anyhow, Result};
+use console::style;
+use lazy_static::lazy_static;
const LUNE_COMMENT_PREFIX: &str = "-->";
-pub fn find_luau_file_path(path: &str) -> Option {
+lazy_static! {
+ static ref ERR_MESSAGE_HELP_NOTE: String = format!(
+ "To run this file, either:\n{}\n{}",
+ format_args!(
+ "{} rename it to use a {} or {} extension",
+ style("-").dim(),
+ style(".luau").blue(),
+ style(".lua").blue()
+ ),
+ format_args!(
+ "{} pass it as an absolute path instead of relative",
+ style("-").dim()
+ ),
+ );
+}
+
+/**
+ Discovers a script file path based on a given script name.
+
+ Script discovery is done in several steps here for the best possible user experience:
+
+ 1. If we got a file that definitely exists, make sure it is either
+ - using an absolute path
+ - has the lua or luau extension
+ 2. If we got a directory, let the user know
+ 3. If we got an absolute path, don't check any extensions, just let the user know it didn't exist
+ 4. If we got a relative path with no extension, also look for a file with a lua or luau extension
+ 5. No other options left, the file simply did not exist
+
+ This behavior ensures that users can do pretty much whatever they want if they pass in an absolute
+ path, and that they then have control over script discovery behavior, whereas if they pass in
+ a relative path we will instead try to be as permissive as possible for user-friendliness
+*/
+pub fn discover_script_file_path(path: &str) -> Result {
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,
+ // NOTE: We use metadata directly here to try to
+ // avoid accessing the file path more than once
+ let file_meta = file_path.metadata();
+ let is_file = file_meta.as_ref().map_or(false, Metadata::is_file);
+ let is_dir = file_meta.as_ref().map_or(false, Metadata::is_dir);
+ let is_abs = file_path.is_absolute();
+ let ext = file_path.extension();
+ if is_file {
+ if is_abs {
+ Ok(file_path)
+ } else if let Some(ext) = file_path.extension() {
+ match ext {
+ e if e == "lua" || e == "luau" => Ok(file_path),
+ _ => Err(anyhow!(
+ "A file was found at {} but it uses the '{}' file extension\n{}",
+ style(file_path.display()).green(),
+ style(ext.to_string_lossy()).blue(),
+ *ERR_MESSAGE_HELP_NOTE
+ )),
+ }
+ } else {
+ Err(anyhow!(
+ "A file was found at {} but it has no file extension\n{}",
+ style(file_path.display()).green(),
+ *ERR_MESSAGE_HELP_NOTE
+ ))
+ }
+ } else if is_dir {
+ Err(anyhow!(
+ "No file was found at {}, found a directory",
+ style(file_path.display()).yellow()
+ ))
+ } else if is_abs {
+ Err(anyhow!(
+ "No file was found at {}",
+ style(file_path.display()).yellow()
+ ))
+ } else if ext.is_none() {
+ let file_path_lua = file_path.with_extension("lua");
+ let file_path_luau = file_path.with_extension("luau");
+ if file_path_lua.is_file() {
+ Ok(file_path_lua)
+ } else if file_path_luau.is_file() {
+ Ok(file_path_luau)
+ } else {
+ Err(anyhow!(
+ "No file was found at {}",
+ style(file_path.display()).yellow()
+ ))
}
} 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
- }
- }
+ Err(anyhow!(
+ "No file was found at {}",
+ style(file_path.display()).yellow()
+ ))
}
}
-pub fn find_parse_file_path(path: &str) -> Result {
- 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)
+/**
+ Discovers a script file path based on a given script name, and tries to
+ find scripts in `lune` and `.lune` folders if one was not directly found.
+
+ Note that looking in `lune` and `.lune` folders is automatically
+ disabled if the given script name is an absolute path.
+
+ Behavior is otherwise exactly the same as for `discover_script_file_path`.
+*/
+pub fn discover_script_file_path_including_lune_dirs(path: &str) -> Result {
+ match discover_script_file_path(path) {
+ Ok(path) => Ok(path),
+ Err(e) => {
+ // If we got any absolute path it means the user has also
+ // told us to not look in any special relative directories
+ // so we should error right away with the first err message
+ if PathBuf::from(path).is_absolute() {
+ return Err(e);
+ }
+ // Otherwise we take a look in either relative lune or .lune directories
+ let res_lune = discover_script_file_path(&format!("lune{MAIN_SEPARATOR}{path}"));
+ let res_dotlune = discover_script_file_path(&format!(".lune{MAIN_SEPARATOR}{path}"));
+ match res_lune.or(res_dotlune) {
+ // NOTE: The first error message is generally more
+ // descriptive than the ones for the lune subfolders
+ Err(_) => Err(e),
+ Ok(path) => Ok(path),
+ }
}
- } else {
- bail!("Invalid file path: '{}'", path)
}
}