Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
result
|
||||
348
Cargo.lock
generated
Normal file
348
Cargo.lock
generated
Normal file
@@ -0,0 +1,348 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "condexec"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"regex",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "condexec"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = ["h7x4@nani.wtf"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.49", features = ["derive"] }
|
||||
clap_complete = "4.5.59"
|
||||
regex = "1.12.2"
|
||||
rustix = { version = "1.1.2", features = ["process"] }
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 h7x4 <h7x4@nani.wtf>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# condexec
|
||||
|
||||
This is a utility for conditionally running commands
|
||||
|
||||
It is meant as a replacement for shells in the cases where you only need the shell for running `if [ ... ]`/`if [[ ... ]]]`/ `if test ...`.
|
||||
39
default.nix
Normal file
39
default.nix
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
lib
|
||||
, rustPlatform
|
||||
}:
|
||||
let
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
in
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = cargoToml.package.name;
|
||||
version = cargoToml.package.version;
|
||||
src = builtins.filterSource (path: type: let
|
||||
baseName = baseNameOf (toString path);
|
||||
in !(lib.any (b: b) [
|
||||
(!(lib.cleanSourceFilter path type))
|
||||
(type == "directory" && lib.elem baseName [
|
||||
".direnv"
|
||||
".git"
|
||||
"target"
|
||||
"result"
|
||||
])
|
||||
(type == "regular" && lib.elem baseName [
|
||||
"flake.nix"
|
||||
"flake.lock"
|
||||
"default.nix"
|
||||
"module.nix"
|
||||
".envrc"
|
||||
])
|
||||
])) ./.;
|
||||
|
||||
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
|
||||
meta = with lib; {
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [ h7x4 ];
|
||||
platforms = platforms.unix;
|
||||
mainProgram = "condexec";
|
||||
};
|
||||
}
|
||||
48
flake.lock
generated
Normal file
48
flake.lock
generated
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1760284886,
|
||||
"narHash": "sha256-TK9Kr0BYBQ/1P5kAsnNQhmWWKgmZXwUQr4ZMjCzWf2c=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cf3f5c4def3c7b5f1fc012b3d839575dbe552d43",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1760323082,
|
||||
"narHash": "sha256-SKhC9tyt+gVgQHnZGMVPSdptlDYNqApT56JF5t8RwBY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "c73e6874fe8dce0bab82c0387b510875f1eff9f8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
57
flake.nix
Normal file
57
flake.nix
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, rust-overlay }:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
forAllSystems = f: lib.genAttrs systems (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [
|
||||
(import rust-overlay)
|
||||
];
|
||||
};
|
||||
|
||||
rust-bin = rust-overlay.lib.mkRustBin { } pkgs.buildPackages;
|
||||
toolchain = rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" "rust-std" ];
|
||||
};
|
||||
in f system pkgs toolchain);
|
||||
in {
|
||||
devShells = forAllSystems (system: pkgs: toolchain: {
|
||||
default = pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
toolchain
|
||||
pkgs.cargo-edit
|
||||
];
|
||||
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
};
|
||||
});
|
||||
|
||||
overlays = {
|
||||
default = self.overlays.condexec;
|
||||
condexec = final: prev: {
|
||||
inherit (self.packages.${prev.system}) condexec;
|
||||
};
|
||||
};
|
||||
|
||||
packages = forAllSystems (system: pkgs: _: {
|
||||
default = self.packages.${system}.condexec;
|
||||
condexec = pkgs.callPackage ./default.nix { };
|
||||
});
|
||||
};
|
||||
}
|
||||
762
src/main.rs
Normal file
762
src/main.rs
Normal file
@@ -0,0 +1,762 @@
|
||||
use std::io::IsTerminal;
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use regex::Regex;
|
||||
use rustix::fd::{FromRawFd, OwnedFd};
|
||||
use rustix::process::{getegid, geteuid};
|
||||
|
||||
const DELIMITER: char = ':';
|
||||
|
||||
fn parse_tuple<T>(s: &str) -> Result<(T, T), String>
|
||||
where
|
||||
T: std::str::FromStr,
|
||||
<T as std::str::FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
if s.matches(DELIMITER).count() != 1 {
|
||||
return Err("Expected exactly one delimiter".to_string());
|
||||
}
|
||||
|
||||
let mut parts = s.split(DELIMITER);
|
||||
let first = parts
|
||||
.next()
|
||||
.ok_or_else(|| "Missing first part".to_string())?
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse first part: {e}").to_string())?;
|
||||
let second = parts
|
||||
.next()
|
||||
.ok_or_else(|| "Missing second part".to_string())?
|
||||
.parse()
|
||||
.map_err(|e| format!("Failed to parse second part: {e}").to_string())?;
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None, disable_help_flag = true)]
|
||||
struct Args {
|
||||
/// Display this help and exit
|
||||
#[arg(long = "help", action = clap::ArgAction::Help)]
|
||||
help: Option<bool>,
|
||||
|
||||
// A combined boolean expression, consisting of the the options below combined with (), ! (not), -a (and), and -o (or).
|
||||
// #[arg(long = "expr", value_name = "EXPRESSION")]
|
||||
// expression: Vec<String>,
|
||||
|
||||
/// Negate the result
|
||||
#[arg(short = '!', long = "not", alias = "neg")]
|
||||
negate: bool,
|
||||
|
||||
/// Use OR to combine tests (default is AND)
|
||||
#[arg(short = 'o', long = "or")]
|
||||
or: bool,
|
||||
|
||||
/// The command to execute if the expression is true
|
||||
#[arg(trailing_var_arg = true, value_name = "COMMAND")]
|
||||
command: Vec<String>,
|
||||
|
||||
/// The length of STRING is nonzero
|
||||
#[arg(short = 'n', value_name = "STRING")]
|
||||
nonzero: Vec<String>,
|
||||
|
||||
/// The length of STRING is zero
|
||||
#[arg(short = 'z', value_name = "STRING")]
|
||||
zero: Vec<String>,
|
||||
|
||||
/// INTEGER1 is equal to INTEGER2
|
||||
#[arg(long = "ieq", value_name = "INTEGER1:INTEGER2", value_parser = parse_tuple::<i64>)]
|
||||
equals_int: Vec<(i64, i64)>,
|
||||
|
||||
/// INTEGER1 is greater than or equal to INTEGER2
|
||||
#[arg(long = "ige", value_name = "INTEGER1:INTEGER2", value_parser = parse_tuple::<i64>)]
|
||||
greater_equals_int: Vec<(i64, i64)>,
|
||||
|
||||
/// INTEGER1 is greater than INTEGER2
|
||||
#[arg(long = "igt", value_name = "INTEGER1:INTEGER2", value_parser = parse_tuple::<i64>)]
|
||||
greater_than_int: Vec<(i64, i64)>,
|
||||
|
||||
/// INTEGER1 is less than or equal to INTEGER2
|
||||
#[arg(long = "ile", value_name = "INTEGER1:INTEGER2", value_parser = parse_tuple::<i64>)]
|
||||
less_equal_int: Vec<(i64, i64)>,
|
||||
|
||||
/// INTEGER1 is less than INTEGER2
|
||||
#[arg(long = "ilt", value_name = "INTEGER1:INTEGER2", value_parser = parse_tuple::<i64>)]
|
||||
less_than_int: Vec<(i64, i64)>,
|
||||
|
||||
/// INTEGER1 is not equal to INTEGER2
|
||||
#[arg(long = "ine", value_name = "INTEGER1:INTEGER2", value_parser = parse_tuple::<i64>)]
|
||||
not_equal_int: Vec<(i64, i64)>,
|
||||
|
||||
/// STRING1 is equal to STRING2
|
||||
#[arg(long = "eq", value_name = "STRING1:STRING2", value_parser = parse_tuple::<String>)]
|
||||
equals_str: Vec<(String, String)>,
|
||||
|
||||
/// STRING1 is greater than or equal to STRING2 in the current locale
|
||||
#[arg(long = "ge", value_name = "STRING1:STRING2", value_parser = parse_tuple::<String>)]
|
||||
greater_equals_str: Vec<(String, String)>,
|
||||
|
||||
/// STRING1 is greater than STRING2 in the current locale
|
||||
#[arg(long = "gt", value_name = "STRING1:STRING2", value_parser = parse_tuple::<String>)]
|
||||
greater_than_str: Vec<(String, String)>,
|
||||
|
||||
/// STRING1 is less than or equal to STRING2 in the current locale
|
||||
#[arg(long = "le", value_name = "STRING1:STRING2", value_parser = parse_tuple::<String>)]
|
||||
less_equal_str: Vec<(String, String)>,
|
||||
|
||||
/// STRING1 is less than STRING2 in the current locale
|
||||
#[arg(long = "lt", value_name = "STRING1:STRING2", value_parser = parse_tuple::<String>)]
|
||||
less_than_str: Vec<(String, String)>,
|
||||
|
||||
/// STRING1 is not equal to STRING2
|
||||
#[arg(long = "ne", value_name = "STRING1:STRING2", value_parser = parse_tuple::<String>)]
|
||||
not_equal_str: Vec<(String, String)>,
|
||||
|
||||
/// STRING1 matches REGEX
|
||||
#[arg(long = "match", value_name = "STRING:REGEX", value_parser = parse_tuple::<String>)]
|
||||
matches_regex: Vec<(String, String)>,
|
||||
|
||||
/// FILE1 and FILE2 have the same device and inode numbers
|
||||
#[arg(long = "ef", value_name = "FILE1:FILE2", value_parser = parse_tuple::<PathBuf>)]
|
||||
equal_file: Vec<(PathBuf, PathBuf)>,
|
||||
|
||||
/// FILE1 is newer (modification date) than FILE2
|
||||
#[arg(long = "nt", value_name = "FILE1:FILE2", value_parser = parse_tuple::<PathBuf>)]
|
||||
newer_than: Vec<(PathBuf, PathBuf)>,
|
||||
|
||||
/// FILE1 is older than FILE2
|
||||
#[arg(long = "ot", value_name = "FILE1:FILE2", value_parser = parse_tuple::<PathBuf>)]
|
||||
older_than: Vec<(PathBuf, PathBuf)>,
|
||||
|
||||
/// File exists and is block special
|
||||
#[arg(short = 'b', value_name = "FILE")]
|
||||
block_special: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is character special
|
||||
#[arg(short = 'c', value_name = "FILE")]
|
||||
char_special: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is a directory
|
||||
#[arg(short = 'd', value_name = "FILE")]
|
||||
directory: Vec<PathBuf>,
|
||||
|
||||
/// File exists
|
||||
#[arg(short = 'e', value_name = "FILE")]
|
||||
exists: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is a regular file
|
||||
#[arg(short = 'f', value_name = "FILE")]
|
||||
regular_file: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is set-group-ID
|
||||
#[arg(short = 'g', value_name = "FILE")]
|
||||
set_group_id: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is owned by the effective group ID
|
||||
#[arg(short = 'G', value_name = "FILE")]
|
||||
owned_by_effective_group: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is a symbolic link
|
||||
#[arg(short = 'h', value_name = "FILE", alias = "L")]
|
||||
symbolic_link: Vec<PathBuf>,
|
||||
|
||||
/// File exists and has its sticky bit set
|
||||
#[arg(short = 'k', value_name = "FILE")]
|
||||
sticky_bit: Vec<PathBuf>,
|
||||
|
||||
/// File exists and has been modified since it was last read
|
||||
#[arg(short = 'N', value_name = "FILE")]
|
||||
modified_since_last_read: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is owned by the effective user ID
|
||||
#[arg(short = 'O', value_name = "FILE")]
|
||||
owned_by_effective_user: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is a named pipe
|
||||
#[arg(short = 'p', value_name = "FILE")]
|
||||
named_pipe: Vec<PathBuf>,
|
||||
|
||||
/// File exists and the user has read access
|
||||
#[arg(short = 'r', value_name = "FILE")]
|
||||
read_access: Vec<PathBuf>,
|
||||
|
||||
/// File exists and has a size greater than zero
|
||||
#[arg(short = 's', value_name = "FILE")]
|
||||
size_greater_than_zero: Vec<PathBuf>,
|
||||
|
||||
/// File exists and is a socket
|
||||
#[arg(short = 'S', value_name = "FILE")]
|
||||
is_socket: Vec<PathBuf>,
|
||||
|
||||
/// File descriptor FD is opened on a terminal
|
||||
#[arg(short = 't', value_name = "FD")]
|
||||
fd_on_terminal: Vec<i32>,
|
||||
|
||||
/// File exists and its set-user-ID bit is set
|
||||
#[arg(short = 'u', value_name = "FILE")]
|
||||
set_user_id: Vec<PathBuf>,
|
||||
|
||||
/// File exists and the user has write access
|
||||
#[arg(short = 'w', value_name = "FILE")]
|
||||
write_access: Vec<PathBuf>,
|
||||
|
||||
/// File exists and the user has execute (or search) access
|
||||
#[arg(short = 'x', value_name = "FILE")]
|
||||
execute_access: Vec<PathBuf>,
|
||||
|
||||
/// Environment variable VAR is declared
|
||||
#[arg(short = 'v', value_name = "VAR")]
|
||||
env_var_declared: Vec<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
let results = process_results(&args);
|
||||
if results.should_execute() {
|
||||
if args.command.is_empty() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let command = &args.command[0];
|
||||
let command_args = &args.command[1..];
|
||||
std::process::Command::new(command)
|
||||
.args(command_args)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.map(|status| std::process::exit(status.code().unwrap_or(1)))?;
|
||||
} else {
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// TODO: This is made as an intermediary step, to allow for nice debug-printing.
|
||||
/// However, no such debug printing is actually implemented.
|
||||
#[derive(Debug)]
|
||||
struct TestResults<'a> {
|
||||
or: bool,
|
||||
negate: bool,
|
||||
// expression: Vec<ExpressionResult>,
|
||||
nonzero: Vec<(&'a str, bool)>,
|
||||
zero: Vec<(&'a str, bool)>,
|
||||
equals_int: Vec<((i64, i64), bool)>,
|
||||
greater_equals_int: Vec<((i64, i64), bool)>,
|
||||
greater_than_int: Vec<((i64, i64), bool)>,
|
||||
less_equal_int: Vec<((i64, i64), bool)>,
|
||||
less_than_int: Vec<((i64, i64), bool)>,
|
||||
not_equal_int: Vec<((i64, i64), bool)>,
|
||||
equals_str: Vec<((&'a str, &'a str), bool)>,
|
||||
greater_equals_str: Vec<((&'a str, &'a str), bool)>,
|
||||
greater_than_str: Vec<((&'a str, &'a str), bool)>,
|
||||
less_equal_str: Vec<((&'a str, &'a str), bool)>,
|
||||
less_than_str: Vec<((&'a str, &'a str), bool)>,
|
||||
not_equal_str: Vec<((&'a str, &'a str), bool)>,
|
||||
matches_regex: Vec<((&'a str, &'a str), bool)>,
|
||||
equal_file: Vec<((&'a Path, &'a Path), bool)>,
|
||||
newer_than: Vec<((&'a Path, &'a Path), bool)>,
|
||||
older_than: Vec<((&'a Path, &'a Path), bool)>,
|
||||
block_special: Vec<(&'a Path, bool)>,
|
||||
char_special: Vec<(&'a Path, bool)>,
|
||||
directory: Vec<(&'a Path, bool)>,
|
||||
exists: Vec<(&'a Path, bool)>,
|
||||
regular_file: Vec<(&'a Path, bool)>,
|
||||
set_group_id: Vec<(&'a Path, bool)>,
|
||||
owned_by_effective_group: Vec<(&'a Path, bool)>,
|
||||
symbolic_link: Vec<(&'a Path, bool)>,
|
||||
sticky_bit: Vec<(&'a Path, bool)>,
|
||||
modified_since_last_read: Vec<(&'a Path, bool)>,
|
||||
owned_by_effective_user: Vec<(&'a Path, bool)>,
|
||||
named_pipe: Vec<(&'a Path, bool)>,
|
||||
read_access: Vec<(&'a Path, bool)>,
|
||||
size_greater_than_zero: Vec<(&'a Path, bool)>,
|
||||
is_socket: Vec<(&'a Path, bool)>,
|
||||
fd_on_terminal: Vec<(i32, bool)>,
|
||||
set_user_id: Vec<(&'a Path, bool)>,
|
||||
write_access: Vec<(&'a Path, bool)>,
|
||||
execute_access: Vec<(&'a Path, bool)>,
|
||||
env_var_declared: Vec<(String, bool)>,
|
||||
}
|
||||
|
||||
impl TestResults<'_> {
|
||||
fn should_execute(&self) -> bool {
|
||||
let all_tests = [
|
||||
&self.nonzero.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.zero.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.equals_int.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.greater_equals_int
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.greater_than_int
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.less_equal_int
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.less_than_int
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.not_equal_int
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.equals_str.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.greater_equals_str
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.greater_than_str
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.less_equal_str
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.less_than_str
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.not_equal_str
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.matches_regex
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.equal_file.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.newer_than.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.older_than.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.block_special
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.char_special.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.directory.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.exists.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.regular_file.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.set_group_id.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.owned_by_effective_group
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.symbolic_link
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.sticky_bit.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.modified_since_last_read
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.owned_by_effective_user
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.named_pipe.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.read_access.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.size_greater_than_zero
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.is_socket.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.fd_on_terminal
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self.set_user_id.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self.write_access.iter().map(|(_, r)| r).collect::<Vec<_>>(),
|
||||
&self
|
||||
.execute_access
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
&self
|
||||
.env_var_declared
|
||||
.iter()
|
||||
.map(|(_, r)| r)
|
||||
.collect::<Vec<_>>(),
|
||||
];
|
||||
|
||||
match (self.or, self.negate) {
|
||||
(true, true) => !all_tests.into_iter().flatten().any(|&&r| r),
|
||||
(true, false) => all_tests.into_iter().flatten().any(|&&r| r),
|
||||
(false, true) => !all_tests.into_iter().flatten().all(|&&r| r),
|
||||
(false, false) => all_tests.into_iter().flatten().all(|&&r| r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This should store parse-tree and sub-results
|
||||
// struct ExpressionResult {
|
||||
|
||||
// }
|
||||
|
||||
const S_ISVTX: u32 = 0o1000;
|
||||
const S_ISGID: u32 = 0o2000;
|
||||
const S_ISUID: u32 = 0o4000;
|
||||
|
||||
macro_rules! with_file_meta {
|
||||
($file:expr, $meta:ident, $result:expr) => {{
|
||||
let meta = std::fs::metadata($file);
|
||||
let result = match meta {
|
||||
Ok($meta) => $result,
|
||||
Err(_) => false,
|
||||
};
|
||||
($file.as_path(), result)
|
||||
}};
|
||||
}
|
||||
|
||||
enum Permission {
|
||||
Read = 0b100,
|
||||
Write = 0b010,
|
||||
Execute = 0b001,
|
||||
}
|
||||
|
||||
fn has_permission(meta: &std::fs::Metadata, permission: Permission) -> bool {
|
||||
let mode = meta.mode();
|
||||
let user_id = meta.uid();
|
||||
let group_id = meta.gid();
|
||||
let euid = geteuid().as_raw();
|
||||
let egid = getegid().as_raw();
|
||||
|
||||
let perm_bits = match (euid == user_id, egid == group_id) {
|
||||
(true, _) => (mode >> 6) & 0o7,
|
||||
(false, true) => (mode >> 3) & 0o7,
|
||||
(false, false) => mode & 0o7,
|
||||
};
|
||||
|
||||
(perm_bits & (permission as u32)) != 0
|
||||
}
|
||||
|
||||
fn process_results(args: &Args) -> TestResults<'_> {
|
||||
let nonzero = args
|
||||
.nonzero
|
||||
.iter()
|
||||
.map(|s| (s.as_str(), !s.is_empty()))
|
||||
.collect();
|
||||
|
||||
let zero = args
|
||||
.zero
|
||||
.iter()
|
||||
.map(|s| (s.as_str(), s.is_empty()))
|
||||
.collect();
|
||||
|
||||
let equals_int = args
|
||||
.equals_int
|
||||
.iter()
|
||||
.map(|(a, b)| ((*a, *b), a == b))
|
||||
.collect();
|
||||
|
||||
let greater_equals_int = args
|
||||
.greater_equals_int
|
||||
.iter()
|
||||
.map(|(a, b)| ((*a, *b), a >= b))
|
||||
.collect();
|
||||
|
||||
let greater_than_int = args
|
||||
.greater_than_int
|
||||
.iter()
|
||||
.map(|(a, b)| ((*a, *b), a > b))
|
||||
.collect();
|
||||
|
||||
let less_equal_int = args
|
||||
.less_equal_int
|
||||
.iter()
|
||||
.map(|(a, b)| ((*a, *b), a <= b))
|
||||
.collect();
|
||||
|
||||
let less_than_int = args
|
||||
.less_than_int
|
||||
.iter()
|
||||
.map(|(a, b)| ((*a, *b), a < b))
|
||||
.collect();
|
||||
|
||||
let not_equal_int = args
|
||||
.not_equal_int
|
||||
.iter()
|
||||
.map(|(a, b)| ((*a, *b), a != b))
|
||||
.collect();
|
||||
|
||||
let equals_str = args
|
||||
.equals_str
|
||||
.iter()
|
||||
.map(|(a, b)| ((a.as_str(), b.as_str()), a == b))
|
||||
.collect();
|
||||
|
||||
let greater_equals_str = args
|
||||
.greater_equals_str
|
||||
.iter()
|
||||
.map(|(a, b)| ((a.as_str(), b.as_str()), a >= b))
|
||||
.collect();
|
||||
|
||||
let greater_than_str = args
|
||||
.greater_than_str
|
||||
.iter()
|
||||
.map(|(a, b)| ((a.as_str(), b.as_str()), a > b))
|
||||
.collect();
|
||||
|
||||
let less_equal_str = args
|
||||
.less_equal_str
|
||||
.iter()
|
||||
.map(|(a, b)| ((a.as_str(), b.as_str()), a <= b))
|
||||
.collect();
|
||||
|
||||
let less_than_str = args
|
||||
.less_than_str
|
||||
.iter()
|
||||
.map(|(a, b)| ((a.as_str(), b.as_str()), a < b))
|
||||
.collect();
|
||||
|
||||
let not_equal_str = args
|
||||
.not_equal_str
|
||||
.iter()
|
||||
.map(|(a, b)| ((a.as_str(), b.as_str()), a != b))
|
||||
.collect();
|
||||
|
||||
let matches_regex = args
|
||||
.matches_regex
|
||||
.iter()
|
||||
.map(|(s, regex)| {
|
||||
(
|
||||
(s.as_str(), regex.as_str()),
|
||||
Regex::new(regex).is_ok_and(|re| re.is_match(s)),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let equal_file = args
|
||||
.equal_file
|
||||
.iter()
|
||||
.map(|(file1, file2)| {
|
||||
let meta1 = std::fs::metadata(file1);
|
||||
let meta2 = std::fs::metadata(file2);
|
||||
let result = match (meta1, meta2) {
|
||||
(Ok(m1), Ok(m2)) => m1.ino() == m2.ino() && m1.dev() == m2.dev(),
|
||||
_ => false,
|
||||
};
|
||||
((file1.as_path(), file2.as_path()), result)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let newer_than = args
|
||||
.newer_than
|
||||
.iter()
|
||||
.map(|(file1, file2)| {
|
||||
let meta1 = std::fs::metadata(file1);
|
||||
let meta2 = std::fs::metadata(file2);
|
||||
let result = match (meta1, meta2) {
|
||||
(Ok(m1), Ok(m2)) => m1.modified().ok() > m2.modified().ok(),
|
||||
_ => false,
|
||||
};
|
||||
((file1.as_path(), file2.as_path()), result)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let older_than = args
|
||||
.older_than
|
||||
.iter()
|
||||
.map(|(file1, file2)| {
|
||||
let meta1 = std::fs::metadata(file1);
|
||||
let meta2 = std::fs::metadata(file2);
|
||||
let result = match (meta1, meta2) {
|
||||
(Ok(m1), Ok(m2)) => m1.modified().ok() < m2.modified().ok(),
|
||||
_ => false,
|
||||
};
|
||||
((file1.as_path(), file2.as_path()), result)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let block_special = args
|
||||
.block_special
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.file_type().is_block_device()))
|
||||
.collect();
|
||||
|
||||
let char_special = args
|
||||
.char_special
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.file_type().is_char_device()))
|
||||
.collect();
|
||||
|
||||
let directory = args
|
||||
.directory
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.is_dir()))
|
||||
.collect();
|
||||
|
||||
let exists = args
|
||||
.exists
|
||||
.iter()
|
||||
.map(|file| (file.as_path(), file.exists()))
|
||||
.collect();
|
||||
|
||||
let regular_file = args
|
||||
.regular_file
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.is_file()))
|
||||
.collect();
|
||||
|
||||
let set_group_id = args
|
||||
.set_group_id
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, (m.mode() & S_ISGID) != 0))
|
||||
.collect();
|
||||
|
||||
let owned_by_effective_group = args
|
||||
.owned_by_effective_group
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.gid() == getegid().as_raw()))
|
||||
.collect();
|
||||
|
||||
let symbolic_link = args
|
||||
.symbolic_link
|
||||
.iter()
|
||||
.map(|file| {
|
||||
let meta = std::fs::symlink_metadata(file);
|
||||
let result = match meta {
|
||||
Ok(m) => m.file_type().is_symlink(),
|
||||
Err(_) => false,
|
||||
};
|
||||
(file.as_path(), result)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let sticky_bit = args
|
||||
.sticky_bit
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, (m.mode() & S_ISVTX) != 0))
|
||||
.collect();
|
||||
|
||||
let modified_since_last_read = args
|
||||
.modified_since_last_read
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.mtime() > m.atime()))
|
||||
.collect();
|
||||
|
||||
let owned_by_effective_user = args
|
||||
.owned_by_effective_user
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.uid() == geteuid().as_raw()))
|
||||
.collect();
|
||||
|
||||
let named_pipe = args
|
||||
.named_pipe
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.file_type().is_fifo()))
|
||||
.collect();
|
||||
|
||||
let read_access = args
|
||||
.read_access
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, has_permission(&m, Permission::Read)))
|
||||
.collect();
|
||||
|
||||
let size_greater_than_zero = args
|
||||
.size_greater_than_zero
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.len() > 0))
|
||||
.collect();
|
||||
|
||||
let is_socket = args
|
||||
.is_socket
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, m.file_type().is_socket()))
|
||||
.collect();
|
||||
|
||||
let fd_on_terminal = args
|
||||
.fd_on_terminal
|
||||
.iter()
|
||||
.map(|fd| (*fd, unsafe { OwnedFd::from_raw_fd(*fd) }.is_terminal()))
|
||||
.collect();
|
||||
|
||||
let set_user_id = args
|
||||
.set_user_id
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, (m.mode() & S_ISUID) != 0))
|
||||
.collect();
|
||||
|
||||
let write_access = args
|
||||
.write_access
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, has_permission(&m, Permission::Write)))
|
||||
.collect();
|
||||
|
||||
let execute_access = args
|
||||
.execute_access
|
||||
.iter()
|
||||
.map(|file| with_file_meta!(file, m, has_permission(&m, Permission::Execute)))
|
||||
.collect();
|
||||
|
||||
let env_var_declared = args
|
||||
.env_var_declared
|
||||
.iter()
|
||||
.map(|var| (var.to_string(), std::env::var(var).is_ok()))
|
||||
.collect();
|
||||
|
||||
TestResults {
|
||||
or: args.or,
|
||||
negate: args.negate,
|
||||
nonzero,
|
||||
zero,
|
||||
equals_int,
|
||||
greater_equals_int,
|
||||
greater_than_int,
|
||||
less_equal_int,
|
||||
less_than_int,
|
||||
not_equal_int,
|
||||
equals_str,
|
||||
greater_equals_str,
|
||||
greater_than_str,
|
||||
less_equal_str,
|
||||
less_than_str,
|
||||
not_equal_str,
|
||||
matches_regex,
|
||||
equal_file,
|
||||
newer_than,
|
||||
older_than,
|
||||
block_special,
|
||||
char_special,
|
||||
directory,
|
||||
exists,
|
||||
regular_file,
|
||||
set_group_id,
|
||||
owned_by_effective_group,
|
||||
symbolic_link,
|
||||
sticky_bit,
|
||||
modified_since_last_read,
|
||||
owned_by_effective_user,
|
||||
named_pipe,
|
||||
read_access,
|
||||
size_greater_than_zero,
|
||||
is_socket,
|
||||
fd_on_terminal,
|
||||
set_user_id,
|
||||
write_access,
|
||||
execute_access,
|
||||
env_var_declared,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user