mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Implement list subcommand
This commit is contained in:
parent
8b7990b9a9
commit
92781a521c
6 changed files with 189 additions and 1 deletions
|
@ -1,3 +1,5 @@
|
|||
--> A walkthrough of all of the basic Lune features.
|
||||
|
||||
print("Hello, lune! 🌙")
|
||||
|
||||
--[==[
|
||||
|
|
|
@ -5,6 +5,12 @@ 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 a `--list` subcommand to list scripts found in the `lune` or `.lune` directory.
|
||||
|
||||
## `0.1.2` - January 24th, 2023
|
||||
|
||||
### Added
|
||||
|
|
|
@ -5,7 +5,11 @@ use clap::{CommandFactory, Parser};
|
|||
|
||||
use lune::Lune;
|
||||
|
||||
use crate::utils::{files::find_parse_file_path, github::Client as GithubClient};
|
||||
use crate::utils::{
|
||||
files::find_parse_file_path,
|
||||
github::Client as GithubClient,
|
||||
listing::{find_lune_scripts, print_lune_scripts, sort_lune_scripts},
|
||||
};
|
||||
|
||||
const LUNE_SELENE_FILE_NAME: &str = "lune.yml";
|
||||
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
||||
|
@ -21,6 +25,10 @@ pub struct Cli {
|
|||
script_path: Option<String>,
|
||||
/// Arguments to pass to the file as vararg (...)
|
||||
script_args: Vec<String>,
|
||||
/// Pass this flag to list scripts inside of
|
||||
/// nearby `lune` and / or `.lune` directories
|
||||
#[clap(long, short = 'l')]
|
||||
list: bool,
|
||||
/// Pass this flag to download the Selene type
|
||||
/// definitions file to the current directory
|
||||
#[clap(long)]
|
||||
|
@ -69,7 +77,34 @@ impl Cli {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn list() -> Self {
|
||||
Self {
|
||||
list: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(self) -> Result<ExitCode> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Download definition files, if wanted
|
||||
let download_types_requested = self.download_selene_types || self.download_luau_types;
|
||||
if download_types_requested {
|
||||
|
@ -159,6 +194,14 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() -> Result<()> {
|
||||
smol::block_on(async {
|
||||
Cli::list().run().await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn download_selene_types() -> Result<()> {
|
||||
smol::block_on(async {
|
||||
|
|
|
@ -2,6 +2,8 @@ use std::path::{PathBuf, MAIN_SEPARATOR};
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
const LUNE_COMMENT_PREFIX: &str = "-->";
|
||||
|
||||
pub fn find_luau_file_path(path: &str) -> Option<PathBuf> {
|
||||
let file_path = PathBuf::from(path);
|
||||
if let Some(ext) = file_path.extension() {
|
||||
|
@ -38,3 +40,27 @@ pub fn find_parse_file_path(path: &str) -> Result<PathBuf> {
|
|||
bail!("Invalid file path: '{}'", path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_lune_description_from_file(contents: &str) -> Option<String> {
|
||||
let mut comment_lines = Vec::new();
|
||||
for line in contents.lines() {
|
||||
if let Some(stripped) = line.strip_prefix(LUNE_COMMENT_PREFIX) {
|
||||
comment_lines.push(stripped);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if comment_lines.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let shortest_indent = comment_lines.iter().fold(usize::MAX, |acc, line| {
|
||||
let first_alphanumeric = line.find(char::is_alphanumeric).unwrap();
|
||||
acc.min(first_alphanumeric)
|
||||
});
|
||||
let unindented_lines = comment_lines
|
||||
.iter()
|
||||
.map(|line| &line[shortest_indent..])
|
||||
.collect();
|
||||
Some(unindented_lines)
|
||||
}
|
||||
}
|
||||
|
|
110
src/cli/utils/listing.rs
Normal file
110
src/cli/utils/listing.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
use std::{cmp::Ordering, fmt::Write as _};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use smol::{fs, io, prelude::*};
|
||||
|
||||
use super::files::parse_lune_description_from_file;
|
||||
|
||||
// TODO: Use some crate for this instead
|
||||
pub const COLOR_RESET: &str = if cfg!(test) { "" } else { "\x1B[0m" };
|
||||
pub const COLOR_BLUE: &str = if cfg!(test) { "" } else { "\x1B[34m" };
|
||||
|
||||
pub const STYLE_RESET: &str = if cfg!(test) { "" } else { "\x1B[22m" };
|
||||
pub const STYLE_DIM: &str = if cfg!(test) { "" } else { "\x1B[2m" };
|
||||
|
||||
pub async fn find_lune_scripts() -> Result<Vec<(String, String)>> {
|
||||
let mut lune_dir = fs::read_dir("lune").await;
|
||||
if lune_dir.is_err() {
|
||||
lune_dir = fs::read_dir(".lune").await;
|
||||
}
|
||||
match lune_dir {
|
||||
Ok(mut dir) => {
|
||||
let mut files = Vec::new();
|
||||
while let Some(entry) = dir.next().await.transpose()? {
|
||||
let meta = entry.metadata().await?;
|
||||
if meta.is_file() {
|
||||
let contents = fs::read_to_string(entry.path()).await?;
|
||||
files.push((entry, meta, contents));
|
||||
}
|
||||
}
|
||||
let parsed: Vec<_> = files
|
||||
.iter()
|
||||
.map(|(entry, _, contents)| {
|
||||
let file_path = entry.path().with_extension("");
|
||||
let file_name = file_path.file_name().unwrap().to_string_lossy();
|
||||
let description = parse_lune_description_from_file(contents);
|
||||
(file_name.to_string(), description.unwrap_or_default())
|
||||
})
|
||||
.collect();
|
||||
Ok(parsed)
|
||||
}
|
||||
Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => {
|
||||
bail!("No lune directory was found.")
|
||||
}
|
||||
Err(e) => {
|
||||
bail!("Failed to read lune files!\n{e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sort_lune_scripts(scripts: Vec<(String, String)>) -> Vec<(String, String)> {
|
||||
let mut sorted = scripts;
|
||||
sorted.sort_by(|left, right| {
|
||||
// Prefer scripts that have a description
|
||||
let left_has_desc = !left.1.is_empty();
|
||||
let right_has_desc = !right.1.is_empty();
|
||||
if left_has_desc == right_has_desc {
|
||||
// If both have a description or both
|
||||
// have no description, we sort by name
|
||||
left.0.cmp(&right.0)
|
||||
} else if left_has_desc {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
sorted
|
||||
}
|
||||
|
||||
pub fn print_lune_scripts(scripts: Vec<(String, String)>) -> Result<()> {
|
||||
let longest_file_name_len = scripts
|
||||
.iter()
|
||||
.fold(0, |acc, (file_name, _)| acc.max(file_name.len()));
|
||||
let script_with_description_exists = scripts.iter().any(|(_, desc)| !desc.is_empty());
|
||||
// Pre-calculate some strings that will be used often
|
||||
let prefix = format!("{STYLE_DIM}>{STYLE_RESET} ");
|
||||
let separator = format!("{STYLE_DIM}-{STYLE_RESET}");
|
||||
// Write the entire output to a buffer, doing this instead of using individual
|
||||
// println! calls will ensure that no output get mixed up in between these lines
|
||||
let mut buffer = String::new();
|
||||
if script_with_description_exists {
|
||||
for (file_name, description) in scripts {
|
||||
if description.is_empty() {
|
||||
write!(&mut buffer, "\n{prefix}{file_name}")?;
|
||||
} else {
|
||||
let mut lines = description.lines();
|
||||
let first_line = lines.next().unwrap_or_default();
|
||||
let file_spacing = " ".repeat(file_name.len());
|
||||
let line_spacing = " ".repeat(longest_file_name_len - file_name.len());
|
||||
write!(
|
||||
&mut buffer,
|
||||
"\n{prefix}{file_name}{line_spacing} {separator} {COLOR_BLUE}{first_line}{COLOR_RESET}"
|
||||
)?;
|
||||
for line in lines {
|
||||
write!(
|
||||
&mut buffer,
|
||||
"\n{prefix}{file_spacing}{line_spacing} {COLOR_BLUE}{line}{COLOR_RESET}"
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (file_name, _) in scripts {
|
||||
write!(&mut buffer, "\n{prefix}{file_name}")?;
|
||||
}
|
||||
}
|
||||
// Finally, print the entire buffer out
|
||||
// with an ending newline added to it
|
||||
println!("{buffer}");
|
||||
Ok(())
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod files;
|
||||
pub mod github;
|
||||
pub mod listing;
|
||||
|
|
Loading…
Reference in a new issue