pretty print type analysis & add exit code
update readme
This commit is contained in:
parent
7796c6bc82
commit
bd5f469682
3 changed files with 141 additions and 39 deletions
|
@ -5,3 +5,4 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
laddertypes = { git = "https://github.com/michaelsippel/lib-laddertypes.git" }
|
laddertypes = { git = "https://github.com/michaelsippel/lib-laddertypes.git" }
|
||||||
|
tiny-ansi = "0.1.0"
|
||||||
|
|
67
README.md
67
README.md
|
@ -1,46 +1,69 @@
|
||||||
# ltsh
|
# ltsh
|
||||||
small utility to perform a type-check on shell-pipelines
|
|
||||||
|
**(highly experimental)**
|
||||||
|
|
||||||
|
tiny utility program for type-analysis of shell pipelines based on ladder-typing
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[~]$ ltsh <<< 'echo -n $PATH | xargs stat -c %x | sort -n'
|
[~]$ ltsh <<< 'echo -n $PATH | xargs stat -c %Y | sort -n'
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
--- BEGIN TYPE-ANALYSIS ---
|
--- BEGIN TYPE-ANALYSIS ---
|
||||||
|
|
||||||
* unknown stdin-type for `echo -n $PATH`
|
* unknown stdin-type of `echo -n $PATH`
|
||||||
|
|
||||||
* !====> TYPE MISMATCH !! <====!
|
* typecheck error
|
||||||
——————————
|
echo -n $PATH | xargs stat -c %Y
|
||||||
....`echo -n $PATH` outputs
|
<Seq Path> | <Seq Path>
|
||||||
<Seq Path~<Seq PathSegment~<Seq Char>>~<SepSeq Char '/'>~<Seq Char>>~<SepSeq Char ':'>~<Seq Char>
|
<Seq <Seq PathSegment>> | <Seq <Seq PathSegment>>
|
||||||
———————————
|
<Seq <Seq <Seq Char>>> | <Seq <Seq <Seq Char>>>
|
||||||
.... `xargs stat -c %x` expects
|
<Seq <SepSeq Char '/'>> | <Seq <SepSeq Char '/'>>
|
||||||
<Seq Path~<Seq PathSegment~<Seq Char>>~<SepSeq Char '/'>~<Seq Char>>~<SepSeq Char '\n'>~<Seq Char>
|
<Seq <Seq Char>> | <Seq <Seq Char>>
|
||||||
——————————
|
<SepSeq Char ':'> | <SepSeq Char '\n'>
|
||||||
|
<Seq Char> | <Seq Char>
|
||||||
|
|
||||||
* !====> TYPE MISMATCH !! <====!
|
* typecheck ok
|
||||||
——————————
|
xargs stat -c %Y | sort -n
|
||||||
....`xargs stat -c %x` outputs
|
<Seq Date> |
|
||||||
<Seq Date~ISO-8601~<Seq Char>>~<SepSeq Char '\n'>~<Seq Char>
|
<Seq <TimeSince UnixEpoch>> |
|
||||||
———————————
|
<Seq <Duration Seconds>> |
|
||||||
.... `sort -n` expects
|
<Seq ℕ> | <Seq ℕ>
|
||||||
<Seq ℕ>~<Seq <PosInt 10 BigEndian>~<Seq <Digit 10>~Char>>~<SepSeq Char '\n'>~<Seq Char>
|
<Seq <PosInt 10 BigEndian>> | <Seq <PosInt 10 BigEndian>>
|
||||||
——————————
|
<Seq <Seq <Digit 10>>> | <Seq <Seq <Digit 10>>>
|
||||||
|
<Seq <Seq Char>> | <Seq <Seq Char>>
|
||||||
|
<SepSeq Char '\n'> | <SepSeq Char '\n'>
|
||||||
|
<Seq Char> | <Seq Char>
|
||||||
|
|
||||||
--- END TYPE-ANALYSIS ---
|
--- END TYPE-ANALYSIS ---
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Use as Zsh-extension
|
### Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/michaelsippel/ltsh
|
||||||
|
cd ltsh
|
||||||
|
cargo install --path .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use as Zsh-Extension
|
||||||
To automatically check every pipeline entered during interactive shell
|
To automatically check every pipeline entered during interactive shell
|
||||||
use, add the following hook to your `.zshrc`:
|
use, add the following hook to your `.zshrc`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
preexec() {
|
preexec() {
|
||||||
ltsh <<< "$1"
|
if ! ltsh <<< "${1}";
|
||||||
|
then
|
||||||
|
echo "\e[33;1m"
|
||||||
|
echo "!! ltsh discoverd a type incompatibility. !!"
|
||||||
|
echo "!! abort [CTRL-C] or continue regardless [RET] !!"
|
||||||
|
echo "\e[0m"
|
||||||
|
read -s keys
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -51,5 +74,5 @@ preexec() {
|
||||||
* regex-based typedb implementation (slow & incapable)
|
* regex-based typedb implementation (slow & incapable)
|
||||||
|
|
||||||
|
|
||||||
### License
|
## License
|
||||||
[GPLv3](COPYING)
|
[GPLv3](COPYING)
|
||||||
|
|
110
src/main.rs
110
src/main.rs
|
@ -1,6 +1,7 @@
|
||||||
use {
|
use {
|
||||||
laddertypes::*,
|
laddertypes::*,
|
||||||
std::io::BufRead
|
std::io::BufRead,
|
||||||
|
tiny_ansi::TinyAnsi
|
||||||
};
|
};
|
||||||
|
|
||||||
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\
|
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\
|
||||||
|
@ -26,17 +27,17 @@ pub fn get_type_str(cmd: &str, item: &str) -> Option<String> {
|
||||||
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\
|
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let mut success = true;
|
||||||
let mut dict = TypeDict::new();
|
let mut dict = TypeDict::new();
|
||||||
|
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
for pipeline in std::io::BufReader::new(stdin).lines() {
|
for pipeline in std::io::BufReader::new(stdin).lines() {
|
||||||
let mut last_cmd = String::new();
|
let mut last_cmd = String::new();
|
||||||
let mut last_stdout_type_str : Option<String> = None;
|
|
||||||
let mut last_stdout_type : Option<TypeTerm> = None;
|
let mut last_stdout_type : Option<TypeTerm> = None;
|
||||||
|
|
||||||
let pipeline_str = pipeline.expect("");
|
let pipeline_str = pipeline.expect("");
|
||||||
|
|
||||||
eprintln!("--- BEGIN TYPE-ANALYSIS ---\n");
|
eprintln!("{}", "--- BEGIN TYPE-ANALYSIS ---\n".blue());
|
||||||
for cmd in pipeline_str.split("|") {
|
for cmd in pipeline_str.split("|") {
|
||||||
|
|
||||||
let cmd = cmd.trim()
|
let cmd = cmd.trim()
|
||||||
|
@ -49,36 +50,113 @@ fn main() {
|
||||||
let it = dict.parse(&intype_str).expect("parse error");
|
let it = dict.parse(&intype_str).expect("parse error");
|
||||||
|
|
||||||
if let Some(last) = last_stdout_type {
|
if let Some(last) = last_stdout_type {
|
||||||
if last.is_syntactic_subtype_of( &it ).is_ok() {
|
match last.is_syntactic_subtype_of( &it ) {
|
||||||
eprintln!("* typecheck OK!");
|
Ok(first_match) => {
|
||||||
eprintln!(" ——————————\n .... `{}` outputs\n{} ——————————\n .... `{}` expects\n{} ——————————\n",
|
eprintln!("{} typecheck {}", "*".bright_blue().bold(), "ok".bold().green());
|
||||||
last_cmd, last_stdout_type_str.unwrap(), cmd, intype_str);
|
|
||||||
|
let rl = last.get_lnf_vec().iter().map(|t| dict.unparse(t)).collect::<Vec<_>>();
|
||||||
|
let rr = it.get_lnf_vec().iter().map(|t| dict.unparse(t)).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let c1_width = usize::max(rl.iter().map(|s| s.chars().count()).max().unwrap_or(0), last_cmd.chars().count());
|
||||||
|
for _ in last_cmd.chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!("{}{}{}", last_cmd.on_black().bright_blue(), " | ".on_black().yellow().bold(), cmd.on_black().bright_blue());
|
||||||
|
|
||||||
|
for i in 0 .. rl.len() {
|
||||||
|
if i < first_match {
|
||||||
|
eprint!("{}", rl[i].bright_black());
|
||||||
|
|
||||||
|
for _ in rl[i].chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!(" {}", "|".bright_black());
|
||||||
} else {
|
} else {
|
||||||
eprintln!("* !====> TYPE MISMATCH !! <====!");
|
eprint!("{}", rl[i].green());
|
||||||
eprintln!(" ——————————\n ....`{}` outputs\n{} ———————————\n .... `{}` expects\n{} ——————————\n",
|
|
||||||
last_cmd, last_stdout_type_str.unwrap(), cmd, intype_str);
|
if i-first_match < rr.len() {
|
||||||
|
for _ in rl[i].chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!(" | {}", rr[i-first_match].green());
|
||||||
|
} else {
|
||||||
|
eprintln!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("");
|
||||||
|
}
|
||||||
|
Err((first_match, first_mismatch)) => {
|
||||||
|
success = false;
|
||||||
|
|
||||||
|
eprintln!("{} typecheck {}", "*".bright_blue().bold(), "error".bold().red());
|
||||||
|
|
||||||
|
let rl = last.get_lnf_vec().iter().map(|t| dict.unparse(t)).collect::<Vec<_>>();
|
||||||
|
let rr = it.get_lnf_vec().iter().map(|t| dict.unparse(t)).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let c1_width = usize::max(rl.iter().map(|s| s.chars().count()).max().unwrap_or(0), last_cmd.chars().count());
|
||||||
|
for _ in last_cmd.chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!("{}{}{}", last_cmd.on_black().bright_blue(), " | ".on_black().yellow().bold(), cmd.on_black().bright_blue());
|
||||||
|
|
||||||
|
for i in 0 .. rl.len() {
|
||||||
|
if i < first_match {
|
||||||
|
eprint!("{}", &rl[i].bright_black());
|
||||||
|
|
||||||
|
for _ in rl[i].chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!(" {}", "|".bright_black());
|
||||||
|
} else {
|
||||||
|
if i < first_mismatch {
|
||||||
|
eprint!("{}", rl[i].green());
|
||||||
|
|
||||||
|
if i - first_match < rr.len() {
|
||||||
|
for _ in rl[i].chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!(" | {}", rr[i-first_match].green());
|
||||||
|
} else {
|
||||||
|
eprintln!("");
|
||||||
|
}
|
||||||
|
} else if i > first_mismatch {
|
||||||
|
eprint!("{}", rl[i].bright_black());
|
||||||
|
|
||||||
|
if i - first_match < rr.len() {
|
||||||
|
for _ in rl[i].chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!(" | {}", rr[i-first_match].bright_black());
|
||||||
|
} else {
|
||||||
|
eprintln!("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprint!("{}", rl[i].red());
|
||||||
|
|
||||||
|
if i - first_match < rr.len() {
|
||||||
|
for _ in rl[i].chars().count() .. c1_width { eprint!(" "); }
|
||||||
|
eprintln!(" | {}", rr[i-first_match].red());
|
||||||
|
} else {
|
||||||
|
eprintln!("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("* unknown stdin-type for `{}`\n", cmd);
|
eprintln!("{} {} stdin-type of `{}`\n", "*".bold().bright_blue(), "unknown".yellow(), cmd.on_black().bright_blue());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(outtype_str) = get_type_str(&cmd, "<1") {
|
if let Some(outtype_str) = get_type_str(&cmd, "<1") {
|
||||||
let it = dict.parse(&outtype_str).expect("parse error");
|
let it = dict.parse(&outtype_str).expect("parse error");
|
||||||
|
|
||||||
last_stdout_type_str = Some(outtype_str);
|
|
||||||
last_stdout_type = Some(it);
|
last_stdout_type = Some(it);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("* unknown stdout-type for `{}`\n", cmd);
|
eprintln!("{} {} stdout-type of `{}`\n", "*".bold().bright_blue(), "unknown".yellow(), cmd.on_black().bright_blue());
|
||||||
last_stdout_type_str = None;
|
|
||||||
last_stdout_type = None;
|
last_stdout_type = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_cmd = cmd;
|
last_cmd = cmd;
|
||||||
}
|
}
|
||||||
|
eprintln!("{}", "--- END TYPE-ANALYSIS ---".blue());
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("--- END TYPE-ANALYSIS ---\n");
|
std::process::exit(
|
||||||
|
if success {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\
|
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\
|
||||||
|
|
Loading…
Reference in a new issue