From 7bd5514ed431452bf061d94386061f3d81bdcbb8 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Sat, 25 May 2024 10:36:21 -0400 Subject: [PATCH 1/9] Add fso --- api.go | 7 +++++++ fso.go | 15 +++++++++++++++ fso_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 4 files changed, 64 insertions(+) create mode 100644 fso.go create mode 100644 fso_test.go diff --git a/api.go b/api.go index c336c47..78b4439 100644 --- a/api.go +++ b/api.go @@ -42,6 +42,13 @@ type CLICmd struct { Output OutputCmp `cmd:"" help:"Check output of a command. e.g. \"is cli output stdout \"uname -a\" like \"Kernel Version 22.5\""` } +// FSOCmd type is configuration for FSO checks. +// +//nolint:lll,govet +type FSOCmd struct { + LastModifiedTime AgeCmp `cmd:"" help:"Check last modified time of fso (2h, 4d). e.g. \"is fso last-modified-time /tmp/log.txt gt 1 d\""` +} + // OSCmd type is configuration for OS level checks. // //nolint:lll diff --git a/fso.go b/fso.go new file mode 100644 index 0000000..b97a1be --- /dev/null +++ b/fso.go @@ -0,0 +1,15 @@ +// Package main contains the logic for the "fso" command +package main + +import ( + "errors" + + "github.com/oalders/is/types" +) + +func (r *FSOCmd) Run(ctx *types.Context) error { + if r.LastModifiedTime.Name != "" { + return runAge(ctx, r.LastModifiedTime.Name, r.LastModifiedTime.Op, r.LastModifiedTime.Val, r.LastModifiedTime.Unit) + } + return errors.New("unimplemented command") +} diff --git a/fso_test.go b/fso_test.go new file mode 100644 index 0000000..4128396 --- /dev/null +++ b/fso_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "testing" + + "github.com/oalders/is/ops" + "github.com/oalders/is/types" + "github.com/stretchr/testify/assert" +) + +func TestFSOLastModifiedTime(t *testing.T) { + t.Parallel() + { + ctx := types.Context{Debug: true} + cmd := FSOCmd{LastModifiedTime: AgeCmp{tmux, ops.Gt, "1", "s"}} + err := cmd.Run(&ctx) + assert.NoError(t, err) + assert.True(t, ctx.Success) + } + { + ctx := types.Context{Debug: true} + cmd := FSOCmd{LastModifiedTime: AgeCmp{tmux, ops.Lt, "100000", "days"}} + err := cmd.Run(&ctx) + assert.NoError(t, err) + assert.True(t, ctx.Success) + } + { + ctx := types.Context{Debug: true} + cmd := FSOCmd{LastModifiedTime: AgeCmp{tmux, ops.Lt, "1.1", "d"}} + err := cmd.Run(&ctx) + assert.Error(t, err) + assert.False(t, ctx.Success) + } + { + ctx := types.Context{Debug: true} + cmd := FSOCmd{LastModifiedTime: AgeCmp{"tmuxxx", ops.Lt, "1", "d"}} + err := cmd.Run(&ctx) + assert.Error(t, err) + assert.False(t, ctx.Success) + } +} diff --git a/main.go b/main.go index 785cb13..3e31d45 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ func main() { Arch ArchCmd `cmd:"" help:"Check arch e.g. \"is arch like x64\""` CLI CLICmd `cmd:"" help:"Check cli version. e.g. \"is cli version tmux gte 3\""` Debug bool `help:"turn on debugging statements"` + FSO FSOCmd `cmd:"" help:"Check fso (file system object). e.g. \"is fso last-modified-time gte 3 days\""` Known KnownCmd `cmd:""` OS OSCmd `cmd:"" help:"Check OS attributes. e.g. \"is os name eq darwin\""` There ThereCmd `cmd:"" help:"Check if command exists. e.g. \"is there git\""` From 8c7dd00ed7e69d521147dcc5aad0c3ad733a4824 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Wed, 5 Jun 2024 23:50:02 -0400 Subject: [PATCH 2/9] prepend test dir to $PATH in test for known --- cli_test.go | 35 ++++++++++++++++++----------------- known_test.go | 24 ++++++++++++++++++------ 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/cli_test.go b/cli_test.go index ecbd9b8..d0d8b64 100644 --- a/cli_test.go +++ b/cli_test.go @@ -8,10 +8,10 @@ import ( "github.com/stretchr/testify/assert" ) -const tmux = "./testdata/bin/tmux" - func TestCliVersion(t *testing.T) { - t.Parallel() + const command = "tmux" + t.Setenv("PATH", prependPath("testdata/bin")) + type test struct { Cmp VersionCmp Error bool @@ -24,16 +24,16 @@ func TestCliVersion(t *testing.T) { //nolint:godox tests := []test{ - {VersionCmp{tmux, ops.Ne, "1", major, minor, patch}, false, true}, + {VersionCmp{command, ops.Ne, "1", major, minor, patch}, false, true}, {VersionCmp{"tmuxzzz", ops.Ne, "1", major, minor, patch}, true, false}, - {VersionCmp{tmux, ops.Eq, "1", major, minor, patch}, false, false}, - {VersionCmp{tmux, ops.Eq, "zzz", major, minor, patch}, true, false}, - {VersionCmp{tmux, ops.Unlike, "zzz", major, minor, patch}, false, true}, - {VersionCmp{tmux, ops.Like, "", major, minor, patch}, false, true}, // FIXME - {VersionCmp{tmux, ops.Like, "3.*", major, minor, patch}, false, true}, - {VersionCmp{tmux, ops.Eq, "3", true, minor, patch}, false, true}, - {VersionCmp{tmux, ops.Eq, "3", major, true, patch}, false, true}, - {VersionCmp{tmux, ops.Eq, "0", major, minor, true}, false, true}, + {VersionCmp{command, ops.Eq, "1", major, minor, patch}, false, false}, + {VersionCmp{command, ops.Eq, "zzz", major, minor, patch}, true, false}, + {VersionCmp{command, ops.Unlike, "zzz", major, minor, patch}, false, true}, + {VersionCmp{command, ops.Like, "", major, minor, patch}, false, true}, // FIXME + {VersionCmp{command, ops.Like, "3.*", major, minor, patch}, false, true}, + {VersionCmp{command, ops.Eq, "3", true, minor, patch}, false, true}, + {VersionCmp{command, ops.Eq, "3", major, true, patch}, false, true}, + {VersionCmp{command, ops.Eq, "0", major, minor, true}, false, true}, } for _, test := range tests { @@ -54,24 +54,25 @@ func TestCliVersion(t *testing.T) { } func TestCliAge(t *testing.T) { - t.Parallel() + t.Setenv("PATH", prependPath("testdata/bin")) + const command = "tmux" { ctx := types.Context{Debug: true} - cmd := CLICmd{Age: AgeCmp{tmux, ops.Gt, "1", "s"}} + cmd := CLICmd{Age: AgeCmp{command, ops.Gt, "1", "s"}} err := cmd.Run(&ctx) assert.NoError(t, err) assert.True(t, ctx.Success) } { ctx := types.Context{Debug: true} - cmd := CLICmd{Age: AgeCmp{tmux, ops.Lt, "100000", "days"}} + cmd := CLICmd{Age: AgeCmp{command, ops.Lt, "100000", "days"}} err := cmd.Run(&ctx) assert.NoError(t, err) assert.True(t, ctx.Success) } { ctx := types.Context{Debug: true} - cmd := CLICmd{Age: AgeCmp{tmux, ops.Lt, "1.1", "d"}} + cmd := CLICmd{Age: AgeCmp{command, ops.Lt, "1.1", "d"}} err := cmd.Run(&ctx) assert.Error(t, err) assert.False(t, ctx.Success) @@ -86,7 +87,7 @@ func TestCliAge(t *testing.T) { } func TestCliOutput(t *testing.T) { - t.Parallel() + t.Setenv("PATH", prependPath("testdata/bin")) type test struct { Cmp OutputCmp Error bool diff --git a/known_test.go b/known_test.go index 74fd71b..77d6665 100644 --- a/known_test.go +++ b/known_test.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "os" + "path/filepath" "runtime" "testing" @@ -10,9 +12,19 @@ import ( "github.com/stretchr/testify/assert" ) +func prependPath(path string) string { + wd, err := os.Getwd() + if err != nil { + panic(err) + } + + return filepath.Join(wd, path) + string(os.PathListSeparator) + os.Getenv("PATH") +} + func TestKnownCmd(t *testing.T) { - t.Parallel() - const tmux = "testdata/bin/tmux" + t.Setenv("PATH", prependPath("testdata/bin")) + + const command = "semver" type testableOS struct { Attr string Error bool @@ -55,10 +67,10 @@ func TestKnownCmd(t *testing.T) { } cliTests := []testableCLI{ {KnownCmd{CLI: KnownCLI{attr.Version, "gitzzz"}}, false, false}, - {KnownCmd{CLI: KnownCLI{attr.Version, tmux}}, false, true}, - {KnownCmd{CLI: KnownCLI{attr.Version, tmux}, Major: true}, false, true}, - {KnownCmd{CLI: KnownCLI{attr.Version, tmux}, Minor: true}, false, true}, - {KnownCmd{CLI: KnownCLI{attr.Version, tmux}, Patch: true}, false, true}, + {KnownCmd{CLI: KnownCLI{attr.Version, command}}, false, true}, + {KnownCmd{CLI: KnownCLI{attr.Version, command}, Major: true}, false, true}, + {KnownCmd{CLI: KnownCLI{attr.Version, command}, Minor: true}, false, true}, + {KnownCmd{CLI: KnownCLI{attr.Version, command}, Patch: true}, false, true}, } for _, test := range cliTests { From 6d51e84d7f9f9540861306a602602ebf80a56d32 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Thu, 6 Jun 2024 11:46:47 -0400 Subject: [PATCH 3/9] Add some exact matches on testdata/bin tmux output --- cli_test.go | 10 +++++++++- known_test.go | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cli_test.go b/cli_test.go index d0d8b64..20f318c 100644 --- a/cli_test.go +++ b/cli_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) +//nolint:paralleltest,nolintlint func TestCliVersion(t *testing.T) { const command = "tmux" t.Setenv("PATH", prependPath("testdata/bin")) @@ -24,6 +25,10 @@ func TestCliVersion(t *testing.T) { //nolint:godox tests := []test{ + {VersionCmp{command, ops.Eq, "3.3a", major, minor, patch}, false, true}, + {VersionCmp{command, ops.Gt, "3.2a", major, minor, patch}, false, true}, + {VersionCmp{command, ops.Lt, "3.3b", major, minor, patch}, false, true}, + {VersionCmp{command, ops.Lt, "4", major, minor, patch}, false, true}, {VersionCmp{command, ops.Ne, "1", major, minor, patch}, false, true}, {VersionCmp{"tmuxzzz", ops.Ne, "1", major, minor, patch}, true, false}, {VersionCmp{command, ops.Eq, "1", major, minor, patch}, false, false}, @@ -53,6 +58,7 @@ func TestCliVersion(t *testing.T) { } } +//nolint:paralleltest,nolintlint func TestCliAge(t *testing.T) { t.Setenv("PATH", prependPath("testdata/bin")) const command = "tmux" @@ -86,6 +92,7 @@ func TestCliAge(t *testing.T) { } } +//nolint:paralleltest,nolintlint func TestCliOutput(t *testing.T) { t.Setenv("PATH", prependPath("testdata/bin")) type test struct { @@ -94,11 +101,12 @@ func TestCliOutput(t *testing.T) { Success bool } - command := "./testdata/bin/tmux" + command := "tmux" args := []string{"-V"} const optimistic = "optimistic" tests := []test{ + {OutputCmp{"stdout", command, ops.Eq, "tmux 3.3a", args, optimistic}, false, true}, {OutputCmp{"stdout", command, ops.Ne, "1", args, optimistic}, false, true}, {OutputCmp{"stdout", command, ops.Eq, "1", args, optimistic}, false, false}, {OutputCmp{"stderr", command, ops.Like, "xxx", args, optimistic}, false, false}, diff --git a/known_test.go b/known_test.go index 77d6665..de32686 100644 --- a/known_test.go +++ b/known_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) +//nolint:unparam func prependPath(path string) string { wd, err := os.Getwd() if err != nil { @@ -21,6 +22,7 @@ func prependPath(path string) string { return filepath.Join(wd, path) + string(os.PathListSeparator) + os.Getenv("PATH") } +//nolint:paralleltest,nolintlint func TestKnownCmd(t *testing.T) { t.Setenv("PATH", prependPath("testdata/bin")) From 90f1951cf61e9c114d6a78b63836829674bce276 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Thu, 6 Jun 2024 23:47:46 -0400 Subject: [PATCH 4/9] s/last-modified-time/age/ --- api.go | 2 +- fso.go | 4 ++-- fso_test.go | 9 +++++---- main.go | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index 78b4439..5b05816 100644 --- a/api.go +++ b/api.go @@ -46,7 +46,7 @@ type CLICmd struct { // //nolint:lll,govet type FSOCmd struct { - LastModifiedTime AgeCmp `cmd:"" help:"Check last modified time of fso (2h, 4d). e.g. \"is fso last-modified-time /tmp/log.txt gt 1 d\""` + Age AgeCmp `cmd:"" help:"Check age (last modified time) of an fso (2h, 4d). e.g. \"is fso age /tmp/log.txt gt 1 d\""` } // OSCmd type is configuration for OS level checks. diff --git a/fso.go b/fso.go index b97a1be..4922f0a 100644 --- a/fso.go +++ b/fso.go @@ -8,8 +8,8 @@ import ( ) func (r *FSOCmd) Run(ctx *types.Context) error { - if r.LastModifiedTime.Name != "" { - return runAge(ctx, r.LastModifiedTime.Name, r.LastModifiedTime.Op, r.LastModifiedTime.Val, r.LastModifiedTime.Unit) + if r.Age.Name != "" { + return runAge(ctx, r.Age.Name, r.Age.Op, r.Age.Val, r.Age.Unit) } return errors.New("unimplemented command") } diff --git a/fso_test.go b/fso_test.go index 4128396..f286929 100644 --- a/fso_test.go +++ b/fso_test.go @@ -10,30 +10,31 @@ import ( func TestFSOLastModifiedTime(t *testing.T) { t.Parallel() + const tmux = "testdata/bin/tmux" { ctx := types.Context{Debug: true} - cmd := FSOCmd{LastModifiedTime: AgeCmp{tmux, ops.Gt, "1", "s"}} + cmd := FSOCmd{Age: AgeCmp{tmux, ops.Gt, "1", "s"}} err := cmd.Run(&ctx) assert.NoError(t, err) assert.True(t, ctx.Success) } { ctx := types.Context{Debug: true} - cmd := FSOCmd{LastModifiedTime: AgeCmp{tmux, ops.Lt, "100000", "days"}} + cmd := FSOCmd{Age: AgeCmp{tmux, ops.Lt, "100000", "days"}} err := cmd.Run(&ctx) assert.NoError(t, err) assert.True(t, ctx.Success) } { ctx := types.Context{Debug: true} - cmd := FSOCmd{LastModifiedTime: AgeCmp{tmux, ops.Lt, "1.1", "d"}} + cmd := FSOCmd{Age: AgeCmp{tmux, ops.Lt, "1.1", "d"}} err := cmd.Run(&ctx) assert.Error(t, err) assert.False(t, ctx.Success) } { ctx := types.Context{Debug: true} - cmd := FSOCmd{LastModifiedTime: AgeCmp{"tmuxxx", ops.Lt, "1", "d"}} + cmd := FSOCmd{Age: AgeCmp{"tmuxxx", ops.Lt, "1", "d"}} err := cmd.Run(&ctx) assert.Error(t, err) assert.False(t, ctx.Success) diff --git a/main.go b/main.go index 3e31d45..a93dd18 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ func main() { Arch ArchCmd `cmd:"" help:"Check arch e.g. \"is arch like x64\""` CLI CLICmd `cmd:"" help:"Check cli version. e.g. \"is cli version tmux gte 3\""` Debug bool `help:"turn on debugging statements"` - FSO FSOCmd `cmd:"" help:"Check fso (file system object). e.g. \"is fso last-modified-time gte 3 days\""` + FSO FSOCmd `cmd:"" help:"Check fso (file system object). e.g. \"is fso age gte 3 days\""` Known KnownCmd `cmd:""` OS OSCmd `cmd:"" help:"Check OS attributes. e.g. \"is os name eq darwin\""` There ThereCmd `cmd:"" help:"Check if command exists. e.g. \"is there git\""` From e89cc0ecd76b0f6e3deb5140a9f43425ee811da4 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Fri, 7 Jun 2024 00:01:16 -0400 Subject: [PATCH 5/9] Don't search $PATH for "fso age" --- cli.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cli.go b/cli.go index a60e3fa..70209c2 100644 --- a/cli.go +++ b/cli.go @@ -32,7 +32,7 @@ func execCommand(ctx *types.Context, stream, cmd string, args []string) (string, // Run "is cli ...". func (r *CLICmd) Run(ctx *types.Context) error { if r.Age.Name != "" { - return runAge(ctx, r.Age.Name, r.Age.Op, r.Age.Val, r.Age.Unit) + return runCliAge(ctx, r.Age.Name, r.Age.Op, r.Age.Val, r.Age.Unit) } if r.Version.Name != "" { output, err := parser.CLIOutput(ctx, r.Version.Name) @@ -96,12 +96,15 @@ func compareAge(ctx *types.Context, modTime, targetTime time.Time, operator, pat } } -func runAge(ctx *types.Context, name, ageOperator, ageValue, ageUnit string) error { +func runCliAge(ctx *types.Context, name, ageOperator, ageValue, ageUnit string) error { path, err := exec.LookPath(name) if err != nil { return errors.Join(errors.New("could not find command"), err) } + return runAge(ctx, path, ageOperator, ageValue, ageUnit) +} +func runAge(ctx *types.Context, path, ageOperator, ageValue, ageUnit string) error { info, err := os.Stat(path) if err != nil { return errors.Join(errors.New("could not stat command"), err) From 43967c0a41871ade07a17772e89981af016e5be3 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Sat, 8 Jun 2024 00:04:35 -0400 Subject: [PATCH 6/9] Tidy README markdown --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6f94a90..588d44d 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,13 @@ $ is known cli version --major zsh ### Has gofumpt been modified in the last week? ```text -$ is cli age gofumpt lt 7 d +is cli age gofumpt lt 7 d +``` + +### Has a file been modified in the last hour? + +```text +is fso age ./stats.txt lt 1 h ``` ### echo the OS name @@ -183,7 +189,9 @@ is os version-codename unlike ventura 🚨 Leaky abstraction alert! -Regex patterns are passed directly to Golang's `regexp.MatchString`. We can take advantage of this when crafting regexes. For instance, for a case insensitive search: +Regex patterns are passed directly to Golang's `regexp.MatchString`. We can +take advantage of this when crafting regexes. For instance, for a case +insensitive search: ```text is cli output stdout date like "(?i)wed" @@ -392,14 +400,14 @@ Optional argument to command. Can be used more than once. Let's match on the results of `uname -m -n`. -``` +```bash is cli output stdout uname --arg="-m" --arg="-n" eq "olafs-mbp-2.lan x86_64" ``` If our args don't contain special characters or spaces, we may not need to quote them. Let's match on the results of `cat README.md`. -``` +```bash is cli output stdout cat --arg README.md like "an inspector for your environment" ``` @@ -463,7 +471,7 @@ is cli output stdout "bash -c" -a "date|wc -l" eq 1 Passing negative integers as expected values is a bit tricky, since we don't want them to be interpreted as flags. -``` +```bash $ is cli output stdout 'bash -c' -a 'date|wc -l' gt -1 ``` @@ -471,7 +479,7 @@ $ is cli output stdout 'bash -c' -a 'date|wc -l' gt -1 We can use `--` before the expected value to get around this. 😅 -``` +```bash $ is cli output stdout 'bash -c' -a 'date|wc -l' gt -- -1 ``` @@ -835,6 +843,7 @@ $ is known cli version --minor tmux $ is known cli version --patch tmux 0 ``` + Please see the docs on `os version` for more information on `--major`, `--minor` and `--patch`. From 5d0b3c918d0f54d58d6719281aa56b3b0f35403c Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Sat, 8 Jun 2024 00:04:43 -0400 Subject: [PATCH 7/9] Document fso age --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index 588d44d..e8345a4 100644 --- a/README.md +++ b/README.md @@ -510,6 +510,53 @@ in some cases try to do an optimistic comparison. That is, it will try a string comparison first and then a numeric comparison. Hopefully this will "do the right thing" for you. If not, please open an issue. +### fso + +`fso` is short for filesystem object (file, directory, link, etc). This command +is very similar to `cli age`. The difference between `cli age` and `fso age` is +that `fso` will not search your `$PATH`. You may provide either a relative or +an absolute path. + +#### age + +Compare against the last modified date of a file. + +```bash +is cli age /tmp/programs.csv lt 18 hours +``` + +Compare against the last modified date of a directory. + +```bash +is cli age ~./local/cache gt 1 d +``` + +Supported comparisons are: + +* `lt` +* `gt` + +Supported units are: + +* `s` +* `second` +* `seconds` +* `m` +* `minute` +* `minutes` +* `h` +* `hour` +* `hours` +* `d` +* `day` +* `days` + +Note that `d|day|days` is shorthand for 24 hours. DST offsets are not taken +into account here. + +The `--debug` flag can give us some helpful information when troubleshooting +date math. + ### os Information specific to the current operating system From 1a7acecfdd6878a74a0c8543a71720e18982bb47 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Sat, 8 Jun 2024 18:20:09 -0400 Subject: [PATCH 8/9] err113 is now an unknown linter --- .golangci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yaml b/.golangci.yaml index c340052..440af97 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,7 +1,6 @@ --- linters: disable: - - err113 - nlreturn - typecheck - wsl From feb4b340ff24fc90dec22bac1c2c802232758596 Mon Sep 17 00:00:00 2001 From: Olaf Alders Date: Sat, 8 Jun 2024 18:23:14 -0400 Subject: [PATCH 9/9] Update linter policy exclusions --- api.go | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index 5b05816..710ef2c 100644 --- a/api.go +++ b/api.go @@ -44,7 +44,7 @@ type CLICmd struct { // FSOCmd type is configuration for FSO checks. // -//nolint:lll,govet +//nolint:lll type FSOCmd struct { Age AgeCmp `cmd:"" help:"Check age (last modified time) of an fso (2h, 4d). e.g. \"is fso age /tmp/log.txt gt 1 d\""` } diff --git a/main.go b/main.go index a93dd18..61fcb34 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ func main() { Arch ArchCmd `cmd:"" help:"Check arch e.g. \"is arch like x64\""` CLI CLICmd `cmd:"" help:"Check cli version. e.g. \"is cli version tmux gte 3\""` Debug bool `help:"turn on debugging statements"` - FSO FSOCmd `cmd:"" help:"Check fso (file system object). e.g. \"is fso age gte 3 days\""` + FSO FSOCmd `cmd:"" help:"Check fso (file system object). e.g. \"is fso age gte 3 days\""` //nolint:lll Known KnownCmd `cmd:""` OS OSCmd `cmd:"" help:"Check OS attributes. e.g. \"is os name eq darwin\""` There ThereCmd `cmd:"" help:"Check if command exists. e.g. \"is there git\""`