Merge branch 'master' into conditional-style

This commit is contained in:
Filip Bachul 2022-07-31 17:18:19 +02:00
commit 5751ed2ded
5 changed files with 203 additions and 15 deletions

View File

@ -278,6 +278,9 @@ Style strings are a list of words, separated by whitespace. The words are not ca
- `underline`
- `dimmed`
- `inverted`
- `blink`
- `hidden`
- `strikethrough`
- `bg:<color>`
- `fg:<color>`
- `<color>`
@ -297,3 +300,9 @@ A color specifier can be one of the following:
- A number between 0-255. This specifies an [8-bit ANSI Color Code](https://i.stack.imgur.com/KTSQa.png).
If multiple colors are specified for foreground/background, the last one in the string will take priority.
Not every style string will be displayed correctly by every terminal. In particular, the following known quirks exist:
- Many terminals disable support for `blink` by default
- `hidden` is not supported on iTerm (https://gitlab.com/gnachman/iterm2/-/issues/4564).
- `strikethrough` is not supported by the default macOS Terminal.app

View File

@ -152,6 +152,24 @@ format = '''
\$'''
```
### Negative matching
Many modules have `detect_extensions`, `detect_files`, and `detect_folders` variables. These take
lists of strings to match or not match. "Negative" options, those which should not be matched, are
indicated with a leading "!" character. The presence of _any_ negative indicator in the directory
will result in the module not being matched.
Extensions are matched against both the characters after the last dot in a filename, and the
characters after the first dot in a filename. For example, `foo.bar.tar.gz` will be matched
against `bar.tar.gz` and `gz` in the `detect_extensions` variable. Files whose name begins with a
dot are not considered to have extensions at all.
To see how this works in practice, you could match TypeScript but not MPEG Transport Stream files thus:
```toml
detect_extensions = ["ts", "!video.ts", "!audio.ts"]
```
## Prompt
This is the list of prompt-wide configuration options.
@ -209,11 +227,9 @@ $git_status\
$hg_branch\
$docker_context\
$package\
$buf\
$c\
$cmake\
$cobol\
$container\
$daml\
$dart\
$deno\
@ -236,6 +252,7 @@ $php\
$pulumi\
$purescript\
$python\
$raku\
$rlang\
$red\
$ruby\
@ -246,6 +263,7 @@ $terraform\
$vlang\
$vagrant\
$zig\
$buf\
$nix_shell\
$conda\
$spack\
@ -264,6 +282,7 @@ $jobs\
$battery\
$time\
$status\
$container\
$shell\
$character"""
```

View File

@ -268,6 +268,7 @@ where
- 'bold'
- 'italic'
- 'inverted'
- 'blink'
- '<color>' (see the `parse_color_string` doc for valid color strings)
*/
pub fn parse_style_string(style_string: &str) -> Option<ansi_term::Style> {
@ -293,6 +294,9 @@ pub fn parse_style_string(style_string: &str) -> Option<ansi_term::Style> {
"italic" => Some(style.italic()),
"dimmed" => Some(style.dimmed()),
"inverted" => Some(style.reverse()),
"blink" => Some(style.blink()),
"hidden" => Some(style.hidden()),
"strikethrough" => Some(style.strikethrough()),
// When the string is supposed to be a color:
// Decide if we yield none, reset background or set color.
color_string => {
@ -626,6 +630,69 @@ mod tests {
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_blink_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD bLiNk");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.is_bold);
assert!(mystyle.is_italic);
assert!(mystyle.is_underline);
assert!(mystyle.is_dimmed);
assert!(mystyle.is_blink);
assert_eq!(
mystyle,
ansi_term::Style::new()
.bold()
.italic()
.underline()
.dimmed()
.blink()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_hidden_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD hIDDen");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.is_bold);
assert!(mystyle.is_italic);
assert!(mystyle.is_underline);
assert!(mystyle.is_dimmed);
assert!(mystyle.is_hidden);
assert_eq!(
mystyle,
ansi_term::Style::new()
.bold()
.italic()
.underline()
.dimmed()
.hidden()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_bold_italic_underline_green_dimmed_strikethrough_silly_caps() {
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD StRiKEthROUgh");
let mystyle = <StyleWrapper>::from_config(&config).unwrap().0;
assert!(mystyle.is_bold);
assert!(mystyle.is_italic);
assert!(mystyle.is_underline);
assert!(mystyle.is_dimmed);
assert!(mystyle.is_strikethrough);
assert_eq!(
mystyle,
ansi_term::Style::new()
.bold()
.italic()
.underline()
.dimmed()
.strikethrough()
.fg(Color::Green)
);
}
#[test]
fn table_get_styles_plain_and_broken_styles() {
// Test a "plain" style with no formatting

View File

@ -397,10 +397,27 @@ impl DirContents {
folders.insert(path);
} else {
if !path.to_string_lossy().starts_with('.') {
// Extract the file extensions (yes, that's plural) from a filename.
// Why plural? Consider the case of foo.tar.gz. It's a compressed
// tarball (tar.gz), and it's a gzipped file (gz). We should be able
// to match both.
// find the minimal extension on a file. ie, the gz in foo.tar.gz
// NB the .to_string_lossy().to_string() here looks weird but is
// required to convert it from a Cow.
path.extension()
.map(|ext| extensions.insert(ext.to_string_lossy().to_string()));
// find the full extension on a file. ie, the tar.gz in foo.tar.gz
path.file_name().map(|file_name| {
file_name
.to_string_lossy()
.split_once('.')
.map(|(_, after)| extensions.insert(after.to_string()))
});
}
if let Some(file_name) = path.file_name() {
// this .to_string_lossy().to_string() is also required
file_names.insert(file_name.to_string_lossy().to_string());
}
files.insert(path);
@ -432,24 +449,47 @@ impl DirContents {
self.file_names.contains(name)
}
pub fn has_any_file_name(&self, names: &[&str]) -> bool {
names.iter().any(|name| self.has_file_name(name))
}
pub fn has_folder(&self, path: &str) -> bool {
self.folders.contains(Path::new(path))
}
pub fn has_any_folder(&self, paths: &[&str]) -> bool {
paths.iter().any(|path| self.has_folder(path))
}
pub fn has_extension(&self, ext: &str) -> bool {
self.extensions.contains(ext)
}
pub fn has_any_extension(&self, exts: &[&str]) -> bool {
exts.iter().any(|ext| self.has_extension(ext))
pub fn has_any_positive_file_name(&self, names: &[&str]) -> bool {
names
.iter()
.any(|name| !name.starts_with('!') && self.has_file_name(name))
}
pub fn has_any_positive_folder(&self, paths: &[&str]) -> bool {
paths
.iter()
.any(|path| !path.starts_with('!') && self.has_folder(path))
}
pub fn has_any_positive_extension(&self, exts: &[&str]) -> bool {
exts.iter()
.any(|ext| !ext.starts_with('!') && self.has_extension(ext))
}
pub fn has_no_negative_file_name(&self, names: &[&str]) -> bool {
!names
.iter()
.any(|name| name.starts_with('!') && self.has_file_name(&name[1..]))
}
pub fn has_no_negative_folder(&self, paths: &[&str]) -> bool {
!paths
.iter()
.any(|path| path.starts_with('!') && self.has_folder(&path[1..]))
}
pub fn has_no_negative_extension(&self, exts: &[&str]) -> bool {
!exts
.iter()
.any(|ext| ext.starts_with('!') && self.has_extension(&ext[1..]))
}
}
@ -516,9 +556,16 @@ impl<'a> ScanDir<'a> {
/// based on the current `PathBuf` check to see
/// if any of this criteria match or exist and returning a boolean
pub fn is_match(&self) -> bool {
self.dir_contents.has_any_extension(self.extensions)
|| self.dir_contents.has_any_folder(self.folders)
|| self.dir_contents.has_any_file_name(self.files)
// if there exists a file with a file/folder/ext we've said we don't want,
// fail the match straight away
self.dir_contents.has_no_negative_extension(self.extensions)
&& self.dir_contents.has_no_negative_file_name(self.files)
&& self.dir_contents.has_no_negative_folder(self.folders)
&& (self
.dir_contents
.has_any_positive_extension(self.extensions)
|| self.dir_contents.has_any_positive_file_name(self.files)
|| self.dir_contents.has_any_positive_folder(self.folders))
}
}
@ -726,6 +773,50 @@ mod tests {
.is_match());
node.close()?;
let tarballs = testdir(&["foo.tgz", "foo.tar.gz"])?;
let tarballs_dc = DirContents::from_path(tarballs.path())?;
assert!(ScanDir {
dir_contents: &tarballs_dc,
files: &[],
extensions: &["tar.gz"],
folders: &[],
}
.is_match());
tarballs.close()?;
let dont_match_ext = testdir(&["foo.js", "foo.ts"])?;
let dont_match_ext_dc = DirContents::from_path(dont_match_ext.path())?;
assert!(!ScanDir {
dir_contents: &dont_match_ext_dc,
files: &[],
extensions: &["js", "!notfound", "!ts"],
folders: &[],
}
.is_match());
dont_match_ext.close()?;
let dont_match_file = testdir(&["goodfile", "evilfile"])?;
let dont_match_file_dc = DirContents::from_path(dont_match_file.path())?;
assert!(!ScanDir {
dir_contents: &dont_match_file_dc,
files: &["goodfile", "!notfound", "!evilfile"],
extensions: &[],
folders: &[],
}
.is_match());
dont_match_file.close()?;
let dont_match_folder = testdir(&["gooddir/somefile", "evildir/somefile"])?;
let dont_match_folder_dc = DirContents::from_path(dont_match_folder.path())?;
assert!(!ScanDir {
dir_contents: &dont_match_folder_dc,
files: &[],
extensions: &[],
folders: &["gooddir", "!notfound", "!evildir"],
}
.is_match());
dont_match_folder.close()?;
Ok(())
}

View File

@ -500,6 +500,8 @@ mod tests {
fn make_known_tempdir(root: &Path) -> io::Result<(TempDir, String)> {
fs::create_dir_all(root)?;
let dir = TempDir::new_in(root)?;
// the .to_string_lossy().to_string() here looks weird but is required
// to convert it from a Cow.
let path = dir
.path()
.file_name()