Skip to content
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

Add fso subcommand #32

Merged
merged 9 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
linters:
disable:
- err113
- nlreturn
- typecheck
- wsl
Expand Down
68 changes: 62 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
```

Expand Down Expand Up @@ -463,15 +471,15 @@ 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
```

> 💥 is: error: unknown flag -1, did you mean one of "-h", "-a"?

We can use `--` before the expected value to get around this. 😅

```
```bash
$ is cli output stdout 'bash -c' -a 'date|wc -l' gt -- -1
```

Expand Down Expand Up @@ -502,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
Expand Down Expand Up @@ -835,6 +890,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`.

Expand Down
7 changes: 7 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
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\""`
}

// OSCmd type is configuration for OS level checks.
//
//nolint:lll
Expand Down
7 changes: 5 additions & 2 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
45 changes: 27 additions & 18 deletions cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
"github.com/stretchr/testify/assert"
)

const tmux = "./testdata/bin/tmux"

//nolint:paralleltest,nolintlint
func TestCliVersion(t *testing.T) {
t.Parallel()
const command = "tmux"
t.Setenv("PATH", prependPath("testdata/bin"))

type test struct {
Cmp VersionCmp
Error bool
Expand All @@ -24,16 +25,20 @@ func TestCliVersion(t *testing.T) {

//nolint:godox
tests := []test{
{VersionCmp{tmux, ops.Ne, "1", major, minor, patch}, false, true},
{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{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 {
Expand All @@ -53,25 +58,27 @@ func TestCliVersion(t *testing.T) {
}
}

//nolint:paralleltest,nolintlint
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)
Expand All @@ -85,19 +92,21 @@ func TestCliAge(t *testing.T) {
}
}

//nolint:paralleltest,nolintlint
func TestCliOutput(t *testing.T) {
t.Parallel()
t.Setenv("PATH", prependPath("testdata/bin"))
type test struct {
Cmp OutputCmp
Error bool
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},
Expand Down
15 changes: 15 additions & 0 deletions fso.go
Original file line number Diff line number Diff line change
@@ -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.Age.Name != "" {
return runAge(ctx, r.Age.Name, r.Age.Op, r.Age.Val, r.Age.Unit)
}
return errors.New("unimplemented command")
}
42 changes: 42 additions & 0 deletions fso_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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()
const tmux = "testdata/bin/tmux"
{
ctx := types.Context{Debug: true}
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{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{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{Age: AgeCmp{"tmuxxx", ops.Lt, "1", "d"}}
err := cmd.Run(&ctx)
assert.Error(t, err)
assert.False(t, ctx.Success)
}
}
26 changes: 20 additions & 6 deletions known_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"

Expand All @@ -10,9 +12,21 @@ import (
"github.com/stretchr/testify/assert"
)

//nolint:unparam
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")
}

//nolint:paralleltest,nolintlint
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
Expand Down Expand Up @@ -55,10 +69,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 {
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 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\""`
Expand Down
Loading