-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement builtin glob function #488
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
use std::mem::swap; | ||
|
||
use heraclitus_compiler::prelude::*; | ||
use itertools::Itertools; | ||
use crate::docs::module::DocumentationModule; | ||
use crate::modules::expression::expr::Expr; | ||
use crate::modules::types::{Type, Typed}; | ||
use crate::translate::module::TranslateModule; | ||
use crate::utils::metadata::{ParserMetadata, TranslateMetadata}; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct GlobInvocation { | ||
args: Vec<Expr>, | ||
} | ||
|
||
impl Typed for GlobInvocation { | ||
fn get_type(&self) -> Type { | ||
Type::Array(Box::new(Type::Text)) | ||
} | ||
} | ||
|
||
impl SyntaxModule<ParserMetadata> for GlobInvocation { | ||
syntax_name!("Glob Invocation"); | ||
|
||
fn new() -> Self { | ||
GlobInvocation { | ||
args: vec![], | ||
} | ||
} | ||
|
||
fn parse(&mut self, meta: &mut ParserMetadata) -> SyntaxResult { | ||
token(meta, "glob")?; | ||
token(meta, "(")?; | ||
let mut new_is_escaped_ctx = true; | ||
swap(&mut new_is_escaped_ctx, &mut meta.context.is_escaped_ctx); | ||
loop { | ||
let tok = meta.get_current_token(); | ||
let mut arg = Expr::new(); | ||
syntax(meta, &mut arg)?; | ||
if arg.get_type() != Type::Text { | ||
return error!(meta, tok, "Expected string"); | ||
} | ||
self.args.push(arg); | ||
match token(meta, ")") { | ||
Ok(_) => break, | ||
Err(_) => token(meta, ",")?, | ||
}; | ||
} | ||
swap(&mut new_is_escaped_ctx, &mut meta.context.is_escaped_ctx); | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl TranslateModule for GlobInvocation { | ||
fn translate(&self, meta: &mut TranslateMetadata) -> String { | ||
self.args.iter().map(|arg| arg.translate(meta)).join(" ") | ||
} | ||
|
||
fn conditional(&self, name: &str) -> Option<String> { | ||
Some(format!("[ -e \"${{{}}}\" ]", name)) | ||
} | ||
} | ||
|
||
impl DocumentationModule for GlobInvocation { | ||
fn document(&self, _meta: &ParserMetadata) -> String { | ||
"".to_string() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import * from "std/text" | ||
|
||
// Output | ||
// === | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These separators are necessary because if the script creates no output (as in this case) the unit test actually behaves as if we had written:
|
||
// === | ||
// === | ||
|
||
main { | ||
let tmpdir = unsafe $ mktemp -d $ | ||
unsafe { | ||
$ touch {tmpdir}/1st\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/2nd\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/file1.txt $ | ||
$ touch {tmpdir}/file2.txt $ | ||
$ touch {tmpdir}/other1.csv $ | ||
$ touch {tmpdir}/other2.csv $ | ||
} | ||
|
||
echo "===" | ||
loop file in glob("{tmpdir}/missing*") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo file | ||
} | ||
echo "===" | ||
loop index, file in glob("{tmpdir}/missing*") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo "{index}: {file}" | ||
} | ||
echo "===" | ||
|
||
unsafe $ rm -rf {tmpdir} $ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import * from "std/text" | ||
|
||
// Output | ||
// === | ||
// /tmp/tmpdir/file1.txt | ||
// /tmp/tmpdir/file2.txt | ||
// /tmp/tmpdir/other1.csv | ||
// /tmp/tmpdir/other2.csv | ||
// === | ||
// 0: /tmp/tmpdir/file1.txt | ||
// 1: /tmp/tmpdir/file2.txt | ||
// 2: /tmp/tmpdir/other1.csv | ||
// 3: /tmp/tmpdir/other2.csv | ||
// === | ||
|
||
main { | ||
let tmpdir = unsafe $ mktemp -d $ | ||
unsafe { | ||
$ touch {tmpdir}/1st\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/2nd\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/file1.txt $ | ||
$ touch {tmpdir}/file2.txt $ | ||
$ touch {tmpdir}/other1.csv $ | ||
$ touch {tmpdir}/other2.csv $ | ||
} | ||
|
||
echo "===" | ||
loop file in glob("{tmpdir}/missing*", "{tmpdir}/file*", "{tmpdir}/*.csv") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo file | ||
} | ||
echo "===" | ||
loop index, file in glob("{tmpdir}/missing*", "{tmpdir}/file*", "{tmpdir}/*.csv") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo "{index}: {file}" | ||
} | ||
echo "===" | ||
|
||
unsafe $ rm -rf {tmpdir} $ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import * from "std/text" | ||
|
||
// Output | ||
// === | ||
// /tmp/tmpdir/file1.txt | ||
// /tmp/tmpdir/file2.txt | ||
// === | ||
// 0: /tmp/tmpdir/file1.txt | ||
// 1: /tmp/tmpdir/file2.txt | ||
// === | ||
|
||
main { | ||
let tmpdir = unsafe $ mktemp -d $ | ||
unsafe { | ||
$ touch {tmpdir}/1st\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/2nd\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/file1.txt $ | ||
$ touch {tmpdir}/file2.txt $ | ||
$ touch {tmpdir}/other1.csv $ | ||
$ touch {tmpdir}/other2.csv $ | ||
} | ||
|
||
echo "===" | ||
loop file in glob("{tmpdir}/file*") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo file | ||
} | ||
echo "===" | ||
loop index, file in glob("{tmpdir}/file*") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo "{index}: {file}" | ||
} | ||
echo "===" | ||
|
||
unsafe $ rm -rf {tmpdir} $ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import * from "std/text" | ||
|
||
// Output | ||
// === | ||
// /tmp/tmpdir/1st file with spaces.txt | ||
// /tmp/tmpdir/2nd file with spaces.txt | ||
// === | ||
// 0: /tmp/tmpdir/1st file with spaces.txt | ||
// 1: /tmp/tmpdir/2nd file with spaces.txt | ||
// === | ||
|
||
main { | ||
let tmpdir = unsafe $ mktemp -d $ | ||
unsafe { | ||
$ touch {tmpdir}/1st\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/2nd\ file\ with\ spaces.txt $ | ||
$ touch {tmpdir}/file1.txt $ | ||
$ touch {tmpdir}/file2.txt $ | ||
$ touch {tmpdir}/other1.csv $ | ||
$ touch {tmpdir}/other2.csv $ | ||
} | ||
|
||
echo "===" | ||
loop file in glob("{tmpdir}/*with spaces*") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The glob builtin does not expect the calling code to escape spaces in its own string literals; it has to do this itself, to prevent injection attacks. |
||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo file | ||
} | ||
echo "===" | ||
loop index, file in glob("{tmpdir}/*with spaces*") { | ||
file = replace_regex(file, tmpdir, "/tmp/tmpdir", true) | ||
echo "{index}: {file}" | ||
} | ||
echo "===" | ||
|
||
unsafe $ rm -rf {tmpdir} $ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note, this will only work with string literals:
I do not see any way around this (short of calling
find
, which has its own problems) because Bash glob expansion does not work with quoted globs.