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! 🌙")
|
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/),
|
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).
|
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
|
## `0.1.2` - January 24th, 2023
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -5,7 +5,11 @@ use clap::{CommandFactory, Parser};
|
||||||
|
|
||||||
use lune::Lune;
|
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_SELENE_FILE_NAME: &str = "lune.yml";
|
||||||
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
const LUNE_LUAU_FILE_NAME: &str = "luneTypes.d.luau";
|
||||||
|
@ -21,6 +25,10 @@ pub struct Cli {
|
||||||
script_path: Option<String>,
|
script_path: Option<String>,
|
||||||
/// Arguments to pass to the file as vararg (...)
|
/// Arguments to pass to the file as vararg (...)
|
||||||
script_args: Vec<String>,
|
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
|
/// Pass this flag to download the Selene type
|
||||||
/// definitions file to the current directory
|
/// definitions file to the current directory
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
|
@ -69,7 +77,34 @@ impl Cli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list() -> Self {
|
||||||
|
Self {
|
||||||
|
list: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn run(self) -> Result<ExitCode> {
|
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
|
// Download definition files, if wanted
|
||||||
let download_types_requested = self.download_selene_types || self.download_luau_types;
|
let download_types_requested = self.download_selene_types || self.download_luau_types;
|
||||||
if download_types_requested {
|
if download_types_requested {
|
||||||
|
@ -159,6 +194,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list() -> Result<()> {
|
||||||
|
smol::block_on(async {
|
||||||
|
Cli::list().run().await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn download_selene_types() -> Result<()> {
|
fn download_selene_types() -> Result<()> {
|
||||||
smol::block_on(async {
|
smol::block_on(async {
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
|
const LUNE_COMMENT_PREFIX: &str = "-->";
|
||||||
|
|
||||||
pub fn find_luau_file_path(path: &str) -> Option<PathBuf> {
|
pub fn find_luau_file_path(path: &str) -> Option<PathBuf> {
|
||||||
let file_path = PathBuf::from(path);
|
let file_path = PathBuf::from(path);
|
||||||
if let Some(ext) = file_path.extension() {
|
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)
|
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 files;
|
||||||
pub mod github;
|
pub mod github;
|
||||||
|
pub mod listing;
|
||||||
|
|
Loading…
Reference in a new issue