Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ jobs:
- run: mise r build
- run: mise r test
- run: mise r lint
- name: check wasm build
run: rustup target add wasm32-wasip1 && cargo build --target wasm32-wasip1 -p usage-cli
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ serde_with = "3"
tera = "1"
thiserror = "2"
usage-lib = { workspace = true, features = ["clap", "docs", "unstable_choices_env"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
xx = "2"

[target.'cfg(unix)'.dependencies]
Expand Down
17 changes: 13 additions & 4 deletions cli/src/cli/complete_word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use std::collections::BTreeMap;
use std::env;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
#[cfg(not(target_arch = "wasm32"))]
use std::process::Command;
use std::sync::Arc;

use clap::Args;
use itertools::Itertools;
use miette::IntoDiagnostic;
use regex::Regex;
use std::sync::LazyLock;
use xx::process::check_status;
use xx::{regex, XXError, XXResult};

use usage::{Spec, SpecArg, SpecCommand, SpecComplete, SpecFlag};

Expand Down Expand Up @@ -283,7 +283,8 @@ impl CompleteWord {
trace!("run: {run}");
let stdout = sh(&run)?;
// trace!("stdout: {stdout}");
let re = regex!(r"[^\\]:");
static COLON_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[^\\]:").unwrap());
let re = &*COLON_RE;
return Ok(stdout
.lines()
.map(|l| {
Expand Down Expand Up @@ -372,7 +373,10 @@ fn zsh_escape(s: &str) -> String {
.replace(']', "\\]")
}

fn sh(script: &str) -> XXResult<String> {
#[cfg(not(target_arch = "wasm32"))]
fn sh(script: &str) -> xx::XXResult<String> {
use xx::process::check_status;
use xx::XXError;
let output = Command::new("sh")
.arg("-c")
.arg(script)
Expand All @@ -387,3 +391,8 @@ fn sh(script: &str) -> XXResult<String> {
let stdout = String::from_utf8(output.stdout).expect("stdout is not utf-8");
Ok(stdout)
}

#[cfg(target_arch = "wasm32")]
fn sh(_script: &str) -> miette::Result<String> {
Err(miette::miette!("shell execution is not supported on wasm"))
}
5 changes: 4 additions & 1 deletion cli/src/cli/generate/fig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,10 @@ impl Fig {
pub fn run(&self) -> miette::Result<()> {
let write = |path: &PathBuf, md: &str| -> miette::Result<()> {
println!("writing to {}", path.display());
xx::file::write(path, format!("{}\n", md.trim()))?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| miette::miette!("{e}"))?;
}
std::fs::write(path, format!("{}\n", md.trim())).map_err(|e| miette::miette!("{e}"))?;
Ok(())
};
let spec = generate::file_or_spec(&self.file, &self.spec)?;
Expand Down
5 changes: 4 additions & 1 deletion cli/src/cli/generate/manpage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ impl Manpage {

if let Some(out_file) = &self.out_file {
println!("writing to {}", out_file.display());
xx::file::write(out_file, &manpage)?;
if let Some(parent) = out_file.parent() {
std::fs::create_dir_all(parent).map_err(|e| miette::miette!("{e}"))?;
}
std::fs::write(out_file, &manpage).map_err(|e| miette::miette!("{e}"))?;
} else {
print!("{}", manpage);
}
Expand Down
8 changes: 6 additions & 2 deletions cli/src/cli/generate/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ impl Markdown {
pub fn run(&self) -> miette::Result<()> {
let write = |path: &PathBuf, md: &str| -> miette::Result<()> {
println!("writing to {}", path.display());
xx::file::write(
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| miette::miette!("{e}"))?;
}
std::fs::write(
path,
format!(
"<!-- @generated by usage-cli from usage spec -->\n{}\n",
md.trim()
),
)?;
)
.map_err(|e| miette::miette!("{e}"))?;
Ok(())
};
let spec = parse_file_or_stdin(&self.file)?;
Expand Down
1 change: 1 addition & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[macro_use]
extern crate log;
extern crate miette;
#[cfg(not(target_arch = "wasm32"))]
extern crate xx;

use miette::Result;
Expand Down
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ strum = { version = "0.28", features = ["derive"] }
tera = { version = "1", optional = true }
thiserror = "2"
versions = "7"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
xx = "2"

[features]
Expand Down
32 changes: 20 additions & 12 deletions lib/src/docs/markdown/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use crate::docs::markdown::tera::TERA;
use crate::docs::models::Spec;
use crate::error::UsageErr;
use itertools::Itertools;
use regex::Regex;
use serde::Serialize;
use std::collections::HashMap;
use xx::regex;
use std::sync::LazyLock;

#[derive(Debug, Clone)]
pub struct MarkdownRenderer {
Expand Down Expand Up @@ -88,22 +89,29 @@ impl MarkdownRenderer {
return line.to_string();
}
// replace '<' with '&lt;' but not inside code blocks
xx::regex!(r"(`[^`]*`)|(<)")
.replace_all(line, |caps: &regex::Captures| {
if caps.get(1).is_some() {
caps.get(1).unwrap().as_str().to_string()
} else {
"&lt;".to_string()
}
})
.to_string()
{
static RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(`[^`]*`)|(<)").unwrap());
&RE
}
.replace_all(line, |caps: &regex::Captures| {
if caps.get(1).is_some() {
caps.get(1).unwrap().as_str().to_string()
} else {
"&lt;".to_string()
}
})
.to_string()
})
.join("\n");
Ok(value.into())
},
);
let path_re =
regex!(r"https://(github.com/[^/]+/[^/]+|gitlab.com/[^/]+/[^/]+/-)/blob/[^/]+/");
static PATH_RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"https://(github.com/[^/]+/[^/]+|gitlab.com/[^/]+/[^/]+/-)/blob/[^/]+/")
.unwrap()
});
let path_re = &*PATH_RE;
tera.register_function("source_code_link", |args: &HashMap<String, tera::Value>| {
let spec = args.get("spec").unwrap().as_object().unwrap();
let cmd = args.get("cmd").unwrap().as_object().unwrap();
Expand Down
1 change: 1 addition & 0 deletions lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub enum UsageErr {
#[diagnostic(transparent)]
KdlError(#[from] kdl::KdlError),

#[cfg(not(target_arch = "wasm32"))]
#[error(transparent)]
#[diagnostic(transparent)]
XXError(#[from] xx::error::XXError),
Expand Down
12 changes: 12 additions & 0 deletions lib/src/sh.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
#[cfg(not(target_arch = "wasm32"))]
use std::process::Command;
#[cfg(not(target_arch = "wasm32"))]
use xx::process::check_status;
#[cfg(not(target_arch = "wasm32"))]
use xx::{XXError, XXResult};

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn sh(script: &str) -> XXResult<String> {
#[cfg(unix)]
let (shell, flag) = ("sh", "-c");
Expand All @@ -23,3 +27,11 @@ pub(crate) fn sh(script: &str) -> XXResult<String> {
let stdout = String::from_utf8(output.stdout).expect("stdout is not utf-8");
Ok(stdout)
}

#[cfg(target_arch = "wasm32")]
pub(crate) fn sh(_script: &str) -> std::result::Result<String, crate::error::UsageErr> {
Err(crate::error::UsageErr::IO(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"shell execution is not supported on wasm",
)))
}
20 changes: 13 additions & 7 deletions lib/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ pub mod mount;
use indexmap::IndexMap;
use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue};
use log::{info, warn};
use regex::Regex;
use serde::Serialize;
use std::fmt::{Display, Formatter};
use std::iter::once;
use std::path::Path;
use std::str::FromStr;
use xx::file;
use std::sync::LazyLock;

use crate::error::UsageErr;
use crate::spec::cmd::{SpecCommand, SpecExample};
Expand Down Expand Up @@ -102,7 +103,7 @@ impl Spec {
/// If `bin` is not specified in the spec, it defaults to the filename.
#[must_use = "parsing result should be used"]
pub fn parse_script(file: &Path) -> Result<Spec, UsageErr> {
let raw = extract_usage_from_comments(&file::read_to_string(file)?);
let raw = extract_usage_from_comments(&std::fs::read_to_string(file)?);
let ctx = ParsingContext::new(file, &raw);
let mut spec = Self::parse(&ctx, &raw)?;
if spec.bin.is_empty() {
Expand Down Expand Up @@ -303,11 +304,12 @@ fn check_usage_version(version: &str) {
}

fn split_script(file: &Path) -> Result<String, UsageErr> {
let full = file::read_to_string(file)?;
let full = std::fs::read_to_string(file)?;
// If file has a shebang and USAGE comments, extract the spec from comments
if full.starts_with("#!") {
let usage_regex = xx::regex!(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])");
if full.lines().any(|l| usage_regex.is_match(l)) {
static USAGE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])").unwrap());
if full.lines().any(|l| USAGE_RE.is_match(l)) {
return Ok(extract_usage_from_comments(&full));
}
}
Expand All @@ -316,8 +318,12 @@ fn split_script(file: &Path) -> Result<String, UsageErr> {
}

fn extract_usage_from_comments(full: &str) -> String {
let usage_regex = xx::regex!(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])(.*)$");
let blank_comment_regex = xx::regex!(r"^(?:#|//|::)\s*$");
static USAGE_CAPTURE_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])(.*)$").unwrap());
static BLANK_COMMENT_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(?:#|//|::)\s*$").unwrap());
let usage_regex = &*USAGE_CAPTURE_RE;
let blank_comment_regex = &*BLANK_COMMENT_RE;
let mut usage = vec![];
let mut found = false;
for line in full.lines() {
Expand Down
Loading