diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 580bbae..42d0051 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.22 + go-version: 1.18 - name: Configure run: | git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 870d41f..3f86c5a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.22 + go-version: 1.18 - name: Configure run: | git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" @@ -41,7 +41,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.22 + go-version: 1.18 - name: Configure run: | git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" @@ -52,7 +52,7 @@ jobs: - name: Release if: startsWith(github.ref, 'refs/tags/') run: | - goreleaser release -f macos.goreleaser.yml --skip=validate --clean + goreleaser release -f macos.goreleaser.yml --skip-validate --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} @@ -66,7 +66,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.22 + go-version: 1.18 - name: Configure run: | git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" @@ -75,7 +75,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: version: latest - args: release -f linux.goreleaser.yml --skip=validate --clean + args: release -f linux.goreleaser.yml --skip-validate --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} \ No newline at end of file diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index 8adcce4..ffcebc6 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -4,36 +4,33 @@ import ( "context" "flag" "fmt" - "log" - "net" - "os" - "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/dynamodb" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/commandctrl" - "github.com/lmika/dynamo-browse/internal/common/ui/logging" - "github.com/lmika/dynamo-browse/internal/common/ui/osstyle" - "github.com/lmika/dynamo-browse/internal/common/workspaces" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/dynamo" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/inputhistorystore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/pasteboardprovider" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/settingstore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/workspacestore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/inputhistory" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/itemrenderer" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" - keybindings_service "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/keybindings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/tables" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/viewsnapshot" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/keybindings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" + "github.com/lmika/audax/internal/common/ui/commandctrl" + "github.com/lmika/audax/internal/common/ui/logging" + "github.com/lmika/audax/internal/common/ui/osstyle" + "github.com/lmika/audax/internal/common/workspaces" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/inputhistorystore" + "github.com/lmika/audax/internal/dynamo-browse/providers/settingstore" + "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" + "github.com/lmika/audax/internal/dynamo-browse/services/inputhistory" + "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" + "github.com/lmika/audax/internal/dynamo-browse/services/jobs" + keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/tables" + "github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot" + "github.com/lmika/audax/internal/dynamo-browse/ui" + "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles" bus "github.com/lmika/events" "github.com/lmika/gopkgs/cli" + "log" + "net" + "os" ) func main() { @@ -43,7 +40,6 @@ func main() { var flagRO = flag.Bool("ro", false, "enable readonly mode") var flagDefaultLimit = flag.Int("default-limit", 0, "default limit for queries and scans") var flagWorkspace = flag.String("w", "", "workspace file") - var flagQuery = flag.String("q", "", "run query") flag.Parse() ctx := context.Background() @@ -88,7 +84,6 @@ func main() { resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws) settingStore := settingstore.New(ws) inputHistoryStore := inputhistorystore.NewInputHistoryStore(ws) - pasteboardProvider := pasteboardprovider.New() if *flagRO { if err := settingStore.SetReadOnly(*flagRO); err != nil { @@ -110,58 +105,19 @@ func main() { state := controllers.NewState() jobsController := controllers.NewJobsController(jobsService, eventBus, false) - tableReadController := controllers.NewTableReadController( - state, - tableService, - workspaceService, - itemRendererService, - jobsController, - inputHistoryService, - eventBus, - pasteboardProvider, - scriptManagerService, - *flagTable, - ) + tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, itemRendererService, jobsController, inputHistoryService, eventBus, *flagTable) tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore) - columnsController := controllers.NewColumnsController(tableReadController, eventBus) - exportController := controllers.NewExportController(state, tableService, jobsController, columnsController, pasteboardProvider) + columnsController := controllers.NewColumnsController(eventBus) + exportController := controllers.NewExportController(state, columnsController) settingsController := controllers.NewSettingsController(settingStore, eventBus) keyBindings := keybindings.Default() - scriptController := controllers.NewScriptController(scriptManagerService, tableReadController, jobsController, settingsController, eventBus) - - if *flagQuery != "" { - if *flagTable == "" { - cli.Fatalf("-t will need to be set for -q") - } - - ctx := context.Background() - - query, err := queryexpr.Parse(*flagQuery) - if err != nil { - cli.Fatalf("query: %v", err) - } - - ti, err := tableService.Describe(ctx, *flagTable) - if err != nil { - cli.Fatalf("cannot describe table: %v", err) - } - - rs, err := tableService.ScanOrQuery(ctx, ti, query, nil) - if err != nil { - cli.Fatalf("cannot execute query: %v", err) - } - if err := exportController.ExportToWriter(os.Stdout, rs); err != nil { - cli.Fatalf("cannot export results of query: %v", err) - } - return - } + scriptController := controllers.NewScriptController(scriptManagerService, tableReadController, settingsController, eventBus) keyBindingService := keybindings_service.NewService(keyBindings) keyBindingController := controllers.NewKeyBindingController(keyBindingService, scriptController) commandController := commandctrl.NewCommandController(inputHistoryService) commandController.AddCommandLookupExtension(scriptController) - commandController.SetCommandCompletionProvider(columnsController) model := ui.NewModel( tableReadController, @@ -175,7 +131,6 @@ func main() { scriptController, eventBus, keyBindingController, - pasteboardProvider, keyBindings, ) diff --git a/go.mod b/go.mod index 96398dc..4562fef 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,17 @@ -module github.com/lmika/dynamo-browse +module github.com/lmika/audax -go 1.22 - -toolchain go1.22.0 +go 1.18 require ( github.com/alecthomas/participle/v2 v2.0.0-beta.5 github.com/asdine/storm v2.1.2+incompatible - github.com/aws/aws-sdk-go-v2 v1.18.1 - github.com/aws/aws-sdk-go-v2/config v1.18.27 - github.com/aws/aws-sdk-go-v2/credentials v1.13.26 + github.com/aws/aws-sdk-go-v2 v1.17.4 + github.com/aws/aws-sdk-go-v2/config v1.13.1 + github.com/aws/aws-sdk-go-v2/credentials v1.8.0 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.12 github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.39 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.11 - github.com/aws/aws-sdk-go-v2/service/sqs v1.23.2 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.3 + github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0 github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0 github.com/brianvoe/gofakeit/v6 v6.15.0 github.com/calyptia/go-bubble-table v0.2.1 @@ -29,7 +27,7 @@ require ( github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 github.com/muesli/reflow v0.3.0 github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.8.1 golang.design/x/clipboard v0.6.2 golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a ) @@ -37,68 +35,38 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/Sereal/Sereal v0.0.0-20220220040404-e0d1e550e879 // indirect - github.com/anthonynsimon/bild v0.13.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudformation v1.30.0 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.8 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.27.1 // indirect - github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.26.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.14.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ebs v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/service/ec2 v1.102.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ecs v1.27.4 // indirect - github.com/aws/aws-sdk-go-v2/service/eks v1.27.14 // indirect - github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.2 // indirect - github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.19.2 // indirect - github.com/aws/aws-sdk-go-v2/service/glue v1.52.0 // indirect - github.com/aws/aws-sdk-go-v2/service/iam v1.21.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect - github.com/aws/aws-sdk-go-v2/service/kinesis v1.17.14 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.22.2 // indirect - github.com/aws/aws-sdk-go-v2/service/lambda v1.37.0 // indirect - github.com/aws/aws-sdk-go-v2/service/rds v1.46.0 // indirect - github.com/aws/aws-sdk-go-v2/service/redshift v1.28.0 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.28.3 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sfn v1.18.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sns v1.20.13 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 // indirect - github.com/aws/aws-sdk-go-v2/service/wafv2 v1.35.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 // indirect github.com/aws/smithy-go v1.13.5 // indirect github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.4.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgx/v5 v5.0.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.13.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/risor-io/risor v1.4.0 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect @@ -108,13 +76,13 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/wI2L/jsondiff v0.3.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/exp/shiny v0.0.0-20230213192124-5e25df0256eb // indirect - golang.org/x/image v0.5.0 // indirect + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect + golang.org/x/text v0.3.8 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7d71b0d..ce47c1b 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= @@ -8,9 +7,6 @@ github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJz github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo= github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= -github.com/anthonynsimon/bild v0.13.0 h1:mN3tMaNds1wBWi1BrJq0ipDBhpkooYfu7ZFSMhXt1C8= -github.com/anthonynsimon/bild v0.13.0/go.mod h1:tpzzp0aYkAsMi1zmfhimaDyX1xjn2OUc1AJZK/TF0AE= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asdine/storm v2.1.2+incompatible h1:dczuIkyqwY2LrtXPz8ixMrU/OFgZp71kbKTHGrXYt/Q= github.com/asdine/storm v2.1.2+incompatible/go.mod h1:RarYDc9hq1UPLImuiXK3BIWPJLdIygvV3PsInK0FbVQ= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -19,126 +15,44 @@ github.com/aws/aws-sdk-go-v2 v1.13.0/go.mod h1:L6+ZpqHaLbAaxsqV0L4cvxZY7QupWJB4f github.com/aws/aws-sdk-go-v2 v1.16.1/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo= -github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno= github.com/aws/aws-sdk-go-v2/config v1.13.1 h1:yLv8bfNoT4r+UvUKQKqRtdnvuWGMK5a82l4ru9Jvnuo= github.com/aws/aws-sdk-go-v2/config v1.13.1/go.mod h1:Ba5Z4yL/UGbjQUzsiaN378YobhFo0MLfueXGiOsYtEs= -github.com/aws/aws-sdk-go-v2/config v1.18.27 h1:Az9uLwmssTE6OGTpsFqOnaGpLnKDqNYOJzWuC6UAYzA= -github.com/aws/aws-sdk-go-v2/config v1.18.27/go.mod h1:0My+YgmkGxeqjXZb5BYme5pc4drjTnM+x1GJ3zv42Nw= github.com/aws/aws-sdk-go-v2/credentials v1.8.0 h1:8Ow0WcyDesGNL0No11jcgb1JAtE+WtubqXjgxau+S0o= github.com/aws/aws-sdk-go-v2/credentials v1.8.0/go.mod h1:gnMo58Vwx3Mu7hj1wpcG8DI0s57c9o42UQ6wgTQT5to= -github.com/aws/aws-sdk-go-v2/credentials v1.13.26 h1:qmU+yhKmOCyujmuPY7tf5MxR/RKyZrOPO3V4DobiTUk= -github.com/aws/aws-sdk-go-v2/credentials v1.13.26/go.mod h1:GoXt2YC8jHUBbA4jr+W3JiemnIbkXOfxSXcisUsZ3os= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.12 h1:ama2cD4WaH6+8Gq/M/g+ZumPmmqCyanr+6Sm+iJVxfA= github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.10.12/go.mod h1:tPnUO5mS3JThpwfq4Q8iPd745s7yh6fGPqDUEBw+Wv4= github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.39 h1:PhgfvgqwMFQKwOcxLV7V3lNDVnR3ZUWzoB6T9oCFpR4= github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.4.39/go.mod h1:/GkvC7uHpK50ilKkKx9I2gZiI/ieZbKjS2aah1rT9uE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 h1:NITDuUZO34mqtOwFWZiXo7yAHj7kf+XPE+EiKuCBNUI= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0/go.mod h1:I6/fHT/fH460v09eg2gVrd8B/IqskhNdpcLH0WNO3QI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4/go.mod h1:XHgQ7Hz2WY2GAn//UXHofLfPXWh+s62MbMOijrg12Lw= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.8/go.mod h1:LnTQMTqbKsbtt+UI5+wPsB7jedW+2ZgozoPG8k6cMxg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.2/go.mod h1:1x4ZP3Z8odssdhuLI+/1Tqw6Pt/VAaP4Tr8EUxHvPXE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 h1:ixotxbfTCFpqbuwFv/RcZwyzhkxPSYDYEMcj4niB5Uk= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 h1:wscW+pnn3J1OYnanMnza5ZVYXLX4cKk5rAvUAl4Qu+c= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26/go.mod h1:MtYiox5gvyB+OyP0Mr0Sm/yzbEAIPL9eijj/ouHAPw0= -github.com/aws/aws-sdk-go-v2/service/cloudformation v1.30.0 h1:XbDkc4FLeg1RfnqeblfbJvaEabqq9ByZl4zqyPFkfSc= -github.com/aws/aws-sdk-go-v2/service/cloudformation v1.30.0/go.mod h1:SwQFcCs9Rog8hSHm+81KBkAK+UKLXErA/1ChaEI8mLE= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.8 h1:loRDtQ0vT0+JCB0hQBCfv95tttEzJ1rqSaTDy5cpy0A= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.26.8/go.mod h1:YTd4wGn2beCF9wkSTpEcupk79zDFYJk2Ca76B8YyvJg= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.27.1 h1:Qw1G/M7eanpm6s/URkG1UuRLKEnRnpUvkUb7NMVvWb8= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.27.1/go.mod h1:oKRYqorIUkfAVmX03+lpv3tW5WelDpaliqzTwmCj/k8= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.26.2 h1:PWGu2JhCb/XJlJ7SSFJq76pxk4xWsN76nZxh7TzMHx0= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.26.2/go.mod h1:2KOZkkzMDZCo/aLzPhys06mHNkiU74u85aMJA3PLRvg= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.3 h1:MxOpCZ+o9+AIeQHi2ocW7H4D7p0LhEkmetETVvDnkvg= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.3/go.mod h1:nkpC9xkh+3vdxmhqN8Ac10pgV14DsJDLzUsV2CcS+44= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.11 h1:tLTGNAsazbfjfjW1k/i43kyCcyTTTTFaD93H7JbSbbs= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.19.11/go.mod h1:W1oiFegjVosgjIwb2Vv45jiCQT1ee8x85u8EyZRYLes= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.14.3 h1:B+bkmCnNJi194pu9aTtYUe8f4EPXafC+xfU+zciVxdg= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.14.3/go.mod h1:bRphLmXQD9Ux4jLcFEwyrWdmuPTj2Lh8VGl9wILuJII= -github.com/aws/aws-sdk-go-v2/service/ebs v1.16.14 h1:DosI4CvEUo6/V21pDspzYkOa2X3Zwy5XS/cbPFiqDv0= -github.com/aws/aws-sdk-go-v2/service/ebs v1.16.14/go.mod h1:yVTqVHjnrbAj6FvhTQfjNgwQbjPbDUUvA1x4IpXFmrE= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.102.0 h1:P4dyjm49F2kKws0FpouBC6fjVImACXKt752+CWa01lM= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.102.0/go.mod h1:tIctCeX9IbzsUTKHt53SVEcgyfxV2ElxJeEB+QUbc4M= -github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13 h1:hF7MUVNjubetjggZDtn3AmqCJzD7EUi//tSdxMYPm7U= -github.com/aws/aws-sdk-go-v2/service/ecr v1.18.13/go.mod h1:XwEFO35g0uN/SftK0asWxh8Rk6DOx37R83TmWe2tzEE= -github.com/aws/aws-sdk-go-v2/service/ecs v1.27.4 h1:F1N0Eh5EGRRY9QpF+tMTkx8Wb59DkQWE91Xza/9dk1c= -github.com/aws/aws-sdk-go-v2/service/ecs v1.27.4/go.mod h1:0irnFofeEZwT7uTjSkNVcSQJbWRqZ9BRoxhKjt1BObM= -github.com/aws/aws-sdk-go-v2/service/eks v1.27.14 h1:47HQVuJXgwvuoc4AT3rVdm77H0qGFbFnsuE4PRT+xX0= -github.com/aws/aws-sdk-go-v2/service/eks v1.27.14/go.mod h1:QxuWcm9rlLkW3aEV8tiDzqZewnNSNUZfnqJvo1Nv9A0= -github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.2 h1:IC9XLGcT3yEkziTlX7PX54km7cHJnltlV7Ppwq2+7ik= -github.com/aws/aws-sdk-go-v2/service/elasticache v1.27.2/go.mod h1:+oJhn/SIud310/2LLSVmlNZmExmlYPaGCLmUsnq5JZc= -github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.19.2 h1:Zam6yofBgdtP13laNoeA+DA9wlKJNooU8p3CWw6xLaI= -github.com/aws/aws-sdk-go-v2/service/elasticsearchservice v1.19.2/go.mod h1:dehjpZ00q0RJcBUOUEysaj7zHK2rHSS4ePp89MsFiaI= -github.com/aws/aws-sdk-go-v2/service/glue v1.52.0 h1:ukSf8ZdoZ6AygsUWIjj177wLOXljxBspBaNMgvx6fRA= -github.com/aws/aws-sdk-go-v2/service/glue v1.52.0/go.mod h1:wMCE0B6l8eHb57l2DMYCGxt0rHIfcu3RvIY7SAfc+Fs= -github.com/aws/aws-sdk-go-v2/service/iam v1.21.0 h1:8hEpu60CWlrp7iEBUFRZhgPoX6+gadaGL1sD4LoRYS0= -github.com/aws/aws-sdk-go-v2/service/iam v1.21.0/go.mod h1:aQZ8BI+reeaY7RI/QQp7TKCSUHOesTdrzzylp3CW85c= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 h1:zZSLP3v3riMOP14H7b4XP0uyfREDQOYv2cqIrvTXDNQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29/go.mod h1:z7EjRjVwZ6pWcWdI2H64dKttvzaP99jRIj5hphW0M5U= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.22 h1:6zEryIiJOSk5/OcVHzkPDwzNBQ2atYCTShyA7TqkuxA= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.22/go.mod h1:moeOz5SKfY0p6pNIChdPIQdfaUfWI67+OVe0/r6+aGY= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.28 h1:/D994rtMQd1jQ2OY+7tvUlMlrv1L1c7Xtma/FhkbVtY= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.28/go.mod h1:3bJI2pLY3ilrqO5EclusI1GbjFJh1iXYrhOItf2sjKw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 h1:4QAOB3KrvI1ApJK14sliGr3Ie2pjyvNypn/lfzDHfUw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 h1:dBL3StFxHtpBzJJ/mNEsjXVgfO+7jR0dAIEwLqMapEA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3/go.mod h1:f1QyiAsvIv4B49DmCqrhlXqyaR+0IxMmyX+1P+AnzOM= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.17.14 h1:oSw0SQN9cKeYvCUYfPul7bH11b8E9I9BnoVUme3iSaU= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.17.14/go.mod h1:omXkSCk1T1difhE8wVaecXNeerY6jmpFFu49ngjEDQk= -github.com/aws/aws-sdk-go-v2/service/kms v1.22.2 h1:jwmtdM1/l1DRNy5jQrrYpsQm8zwetkgeqhAqefDr1yI= -github.com/aws/aws-sdk-go-v2/service/kms v1.22.2/go.mod h1:aNfh11Smy55o65PB3MyKbkM8BFyFUcZmj1k+4g8eNfg= -github.com/aws/aws-sdk-go-v2/service/lambda v1.37.0 h1:xzyM5ZR9kZW0/Bkw5EiihOy6B+BYclp5K+yb6OHjc7s= -github.com/aws/aws-sdk-go-v2/service/lambda v1.37.0/go.mod h1:Q8zQi5nZpjUF/H55dKEpKfEvFWJkgZzjjqvDb2AR5b4= -github.com/aws/aws-sdk-go-v2/service/rds v1.46.0 h1:uv2LAciZRd5lEXzJo2u92tdZh/JxcVL7YLC51D4NLG4= -github.com/aws/aws-sdk-go-v2/service/rds v1.46.0/go.mod h1:goBDR4OPrsnKpYyU0GHGcEnlTmL8O+eKGsWeyOAFJ5M= -github.com/aws/aws-sdk-go-v2/service/redshift v1.28.0 h1:tmhg03t7nNVSFqhxb8YpHqq8H1wwwrfEQv/rL7NkTAE= -github.com/aws/aws-sdk-go-v2/service/redshift v1.28.0/go.mod h1:x9am33DT5lVKUb0DH1UVbX+iFfpIqAKx6DAqB5Qu6jU= -github.com/aws/aws-sdk-go-v2/service/route53 v1.28.3 h1:nJbE4+tHd8xpM1RB1ZF0/xTJnFd/ATz42ZC35lwXx0w= -github.com/aws/aws-sdk-go-v2/service/route53 v1.28.3/go.mod h1:Cd4MnFoV+6fELBrgWXJ4Y09FrSkn/VjNPkOr1Yr1muU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0 h1:lEmQ1XSD9qLk+NZXbgvLJI/IiTz7OIR2TYUTFH25EI4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.36.0/go.mod h1:aVbf0sko/TsLWHx30c/uVu7c62+0EAJ3vbxaJga0xCw= -github.com/aws/aws-sdk-go-v2/service/sfn v1.18.0 h1:1AIwJvCywFO4nGtHj7ZtKb9mhLpB5hToyjtE5OO6o/I= -github.com/aws/aws-sdk-go-v2/service/sfn v1.18.0/go.mod h1:41VgIwo6R/QE8DnFZ4RrP+f2w9xTzB77h3NRu/BzXyE= -github.com/aws/aws-sdk-go-v2/service/sns v1.20.13 h1:+ADGcDhddHTKyu6Qp3oZKootryteS7D3ODo2ZPDBgjQ= -github.com/aws/aws-sdk-go-v2/service/sns v1.20.13/go.mod h1:rWrvp9i8y/lX94lS7Kn/0iu9RY6vXzeKRqS/knVX8/c= github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0 h1:dzWS4r8E9bA0TesHM40FSAtedwpTVCuTsLI8EziSqyk= github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0/go.mod h1:IBTQMG8mtyj37OWg7vIXcg714Ntcb/LlYou/rZpvV1k= -github.com/aws/aws-sdk-go-v2/service/sqs v1.23.2 h1:Y2vfLiY3HmaMisuwx6fS2kMRYbajRXXB+9vesGVPseY= -github.com/aws/aws-sdk-go-v2/service/sqs v1.23.2/go.mod h1:TaV67b6JMD1988x/uMDop/JnMFK6v5d4Ru+sDmFg+ww= github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0 h1:p22U2yL/AeRToERGcZv1R26Yci5VQnWIrpzcZdG54cg= github.com/aws/aws-sdk-go-v2/service/ssm v1.24.0/go.mod h1:chcyLYBEVRac/7rWJsD6cUHUR2osROwavvNqCplfwog= github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 h1:1qLJeQGBmNQW3mBNzK2CFmrQNmoXWrscPqsrAaU1aTA= github.com/aws/aws-sdk-go-v2/service/sso v1.9.0/go.mod h1:vCV4glupK3tR7pw7ks7Y4jYRL86VvxS+g5qk04YeWrU= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 h1:2qTR7IFk7/0IN/adSFhYu9Xthr0zVFTgBrmPldILn80= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w= github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 h1:ksiDXhvNYg0D2/UFkLejsaz3LqpW5yjNQ8Nx9Sn2c0E= github.com/aws/aws-sdk-go-v2/service/sts v1.14.0/go.mod h1:u0xMJKDvvfocRjiozsoZglVNXRG19043xzp3r2ivLIk= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg= -github.com/aws/aws-sdk-go-v2/service/wafv2 v1.35.1 h1:FtzLuTf9HPECIcKdBMtA16ZwZWOIj/r57Z3QuWuYfqc= -github.com/aws/aws-sdk-go-v2/service/wafv2 v1.35.1/go.mod h1:RBpb9oTsEgAUfyaTAT2hFC83DxtLxj+SQpcbhaXiHnU= github.com/aws/smithy-go v1.10.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= @@ -162,18 +76,11 @@ github.com/cloudcmds/tamarin v1.0.0 h1:PhrJ74FCUJo24/nIPXnQe9E3WVEIYo4aG58pICOMD github.com/cloudcmds/tamarin v1.0.0/go.mod h1:U1aHBoAFtJbI9jzgaj8TUo9C6vfzUKzn1OhWKIdigVM= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= @@ -190,19 +97,13 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.0.4 h1:r5O6y84qHX/z/HZV40JBdx2obsHz7/uRj5b+CcYEdeY= github.com/jackc/pgx/v5 v5.0.4/go.mod h1:U0ynklHtgg43fue9Ly30w3OCSTDPlXjig9ghrNGaguQ= -github.com/jackc/pgx/v5 v5.4.1 h1:oKfB/FhuVtit1bBM3zNRRsZ925ZkMN3HXL+LgLUM9lE= -github.com/jackc/pgx/v5 v5.4.1/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -211,7 +112,6 @@ github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc h1:ZQrgZFsLzkw7o3CoD github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -229,14 +129,11 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -244,8 +141,6 @@ github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA= github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= @@ -259,47 +154,27 @@ github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/risor-io/risor v0.8.0 h1:G0fpHMGztvocKVu8egkKNbvLy4Rsjkuk+0zReu2JSn8= -github.com/risor-io/risor v0.8.0/go.mod h1:lvatEIYxs6HL+X/Bm0R+Mq4Z9a5Y036mniw6DwUnqs0= -github.com/risor-io/risor v1.1.1 h1:J8rIZX/0HXhg/t2+QygksvP65XCWhg5QxRZrwZabhxE= -github.com/risor-io/risor v1.1.1/go.mod h1:0UMw7ZMbUKSPFgQyuHCFe7UuBUewBKX4K3By4ba1CBA= -github.com/risor-io/risor v1.4.0 h1:G17pWgq+N06jWvnaJVwos89tC5C4VMjqwGYRrTWleRM= -github.com/risor-io/risor v1.4.0/go.mod h1:+s/FeK0CdsTCCNZsHSp8EJa3u3mMrhqtNGLCv/GcW8Y= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -307,58 +182,42 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/wI2L/jsondiff v0.3.0 h1:iTzQ9u/d86GE9RsBzVHX88f2EA1vQUboHwLhSQFc1s4= github.com/wI2L/jsondiff v0.3.0/go.mod h1:y1IMzNNjlSsk3IUoJdRJO7VRBtzMvRgyo4Vu0LdHpTc= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= golang.design/x/clipboard v0.6.2 h1:a3Np4qfKnLWwfFJQhUWU3IDeRfmVuqWl+QPtP4CSYGw= golang.design/x/clipboard v0.6.2/go.mod h1:kqBSweBP0/im4SZGGjLrppH0D400Hnfo5WbFKSNK8N4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a h1:tlXy25amD5A7gOfbXdqCGN5k8ESEed/Ee1E5RcrYnqU= golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp/shiny v0.0.0-20230213192124-5e25df0256eb h1:gdeQX7xJSkTNF+Sw7++XNIOo4pGL0CjQv3N2Vm1Erxk= golang.org/x/exp/shiny v0.0.0-20230213192124-5e25df0256eb/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= -golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 h1:3In5TnfvnuXTF/uflgpYxSCEGP2NdYT37KsPh3VjZYU= golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -372,19 +231,13 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220919170432-7a66f970e087 h1:tPwmk4vmvVCMdr98VgL4JH+qZxPL8fqlUOHnyOM8N3w= golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -392,14 +245,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/common/maputils/map.go b/internal/common/maputils/map.go index 9708bbe..bffa7a1 100644 --- a/internal/common/maputils/map.go +++ b/internal/common/maputils/map.go @@ -8,16 +8,6 @@ func Values[K comparable, T any](ts map[K]T) []T { return values } -func MapValues[K comparable, T, U any](ts map[K]T, fn func(t T) U) map[K]U { - us := make(map[K]U) - - for k, t := range ts { - us[k] = fn(t) - } - - return us -} - func MapValuesWithError[K comparable, T, U any](ts map[K]T, fn func(t T) (U, error)) (map[K]U, error) { us := make(map[K]U) diff --git a/internal/common/sliceutils/map.go b/internal/common/sliceutils/map.go index 0864a56..43b69e7 100644 --- a/internal/common/sliceutils/map.go +++ b/internal/common/sliceutils/map.go @@ -9,14 +9,6 @@ func All[T any](ts []T, predicate func(t T) bool) bool { return true } -func Generate[U any](from, to int, fn func(t int) U) []U { - us := make([]U, to-from+1) - for i := from; i <= to; i++ { - us[i-from] = fn(i) - } - return us -} - func Map[T, U any](ts []T, fn func(t T) U) []U { us := make([]U, len(ts)) for i, t := range ts { @@ -47,22 +39,3 @@ func Filter[T any](ts []T, fn func(t T) bool) []T { } return us } - -func FindFirst[T any](ts []T, fn func(t T) bool) (returnedT T, found bool) { - for _, t := range ts { - if fn(t) { - return t, true - } - } - return returnedT, false -} - -func FindLast[T any](ts []T, fn func(t T) bool) (returnedT T, found bool) { - for i := len(ts) - 1; i >= 0; i-- { - t := ts[i] - if fn(t) { - return t, true - } - } - return returnedT, false -} diff --git a/internal/common/ui/commandctrl/commandctrl.go b/internal/common/ui/commandctrl/commandctrl.go index c0d857f..6c08f96 100644 --- a/internal/common/ui/commandctrl/commandctrl.go +++ b/internal/common/ui/commandctrl/commandctrl.go @@ -11,17 +11,16 @@ import ( "path/filepath" "strings" - "github.com/lmika/dynamo-browse/internal/common/ui/events" + "github.com/lmika/audax/internal/common/ui/events" "github.com/lmika/shellwords" ) const commandsCategory = "commands" type CommandController struct { - historyProvider IterProvider - commandList *CommandList - lookupExtensions []CommandLookupExtension - completionProvider CommandCompletionProvider + historyProvider IterProvider + commandList *CommandList + lookupExtensions []CommandLookupExtension } func NewCommandController(historyProvider IterProvider) *CommandController { @@ -41,10 +40,6 @@ func (c *CommandController) AddCommandLookupExtension(ext CommandLookupExtension c.lookupExtensions = append(c.lookupExtensions, ext) } -func (c *CommandController) SetCommandCompletionProvider(provider CommandCompletionProvider) { - c.completionProvider = provider -} - func (c *CommandController) Prompt() tea.Msg { return events.PromptForInputMsg{ Prompt: ":", @@ -52,24 +47,6 @@ func (c *CommandController) Prompt() tea.Msg { OnDone: func(value string) tea.Msg { return c.Execute(value) }, - // TEMP - OnTabComplete: func(value string) (string, bool) { - if c.completionProvider == nil { - return "", false - } - - if strings.HasPrefix(value, "sa ") || strings.HasPrefix(value, "da ") { - tokens := shellwords.Split(strings.TrimSpace(value)) - lastToken := tokens[len(tokens)-1] - - options := c.completionProvider.AttributesWithPrefix(lastToken) - if len(options) == 1 { - return value[:len(value)-len(lastToken)] + options[0], true - } - } - return "", false - }, - // END TEMP } } diff --git a/internal/common/ui/commandctrl/commandctrl_test.go b/internal/common/ui/commandctrl/commandctrl_test.go index b21783e..b300418 100644 --- a/internal/common/ui/commandctrl/commandctrl_test.go +++ b/internal/common/ui/commandctrl/commandctrl_test.go @@ -2,11 +2,11 @@ package commandctrl_test import ( "context" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/services" "testing" - "github.com/lmika/dynamo-browse/internal/common/ui/commandctrl" + "github.com/lmika/audax/internal/common/ui/commandctrl" "github.com/stretchr/testify/assert" ) diff --git a/internal/common/ui/commandctrl/iface.go b/internal/common/ui/commandctrl/iface.go index 1cb834a..671bc74 100644 --- a/internal/common/ui/commandctrl/iface.go +++ b/internal/common/ui/commandctrl/iface.go @@ -2,7 +2,7 @@ package commandctrl import ( "context" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" + "github.com/lmika/audax/internal/dynamo-browse/services" ) type IterProvider interface { diff --git a/internal/common/ui/commandctrl/types.go b/internal/common/ui/commandctrl/types.go index 7861e09..c8a9058 100644 --- a/internal/common/ui/commandctrl/types.go +++ b/internal/common/ui/commandctrl/types.go @@ -19,7 +19,3 @@ type CommandList struct { type CommandLookupExtension interface { LookupCommand(name string) Command } - -type CommandCompletionProvider interface { - AttributesWithPrefix(prefix string) []string -} diff --git a/internal/common/ui/dispatcher/context.go b/internal/common/ui/dispatcher/context.go index a7abf8d..deb7559 100644 --- a/internal/common/ui/dispatcher/context.go +++ b/internal/common/ui/dispatcher/context.go @@ -2,7 +2,7 @@ package dispatcher import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/uimodels" + "github.com/lmika/audax/internal/common/ui/uimodels" ) type DispatcherContext struct { diff --git a/internal/common/ui/dispatcher/dispatcher.go b/internal/common/ui/dispatcher/dispatcher.go index eca48a5..45350ae 100644 --- a/internal/common/ui/dispatcher/dispatcher.go +++ b/internal/common/ui/dispatcher/dispatcher.go @@ -4,8 +4,8 @@ import ( "context" "sync" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/common/ui/uimodels" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/common/ui/uimodels" "github.com/pkg/errors" ) diff --git a/internal/common/ui/events/commands.go b/internal/common/ui/events/commands.go index 68fdd5d..19e30bc 100644 --- a/internal/common/ui/events/commands.go +++ b/internal/common/ui/events/commands.go @@ -2,7 +2,7 @@ package events import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" + "github.com/lmika/audax/internal/dynamo-browse/services" "log" ) @@ -54,8 +54,3 @@ type MessageWithMode interface { MessageWithStatus ModeMessage() string } - -type MessageWithRightMode interface { - MessageWithStatus - RightModeMessage() string -} diff --git a/internal/common/ui/events/errors.go b/internal/common/ui/events/errors.go index 127c3b3..c34d73f 100644 --- a/internal/common/ui/events/errors.go +++ b/internal/common/ui/events/errors.go @@ -2,7 +2,7 @@ package events import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" + "github.com/lmika/audax/internal/dynamo-browse/services" ) // Error indicates that an error occurred @@ -21,9 +21,8 @@ type ModeMessage string // PromptForInput indicates that the context is requesting a line of input type PromptForInputMsg struct { - Prompt string - History services.HistoryProvider - OnDone func(value string) tea.Msg - OnCancel func() tea.Msg - OnTabComplete func(value string) (string, bool) + Prompt string + History services.HistoryProvider + OnDone func(value string) tea.Msg + OnCancel func() tea.Msg } diff --git a/internal/dynamo-browse/controllers/columns.go b/internal/dynamo-browse/controllers/columns.go index 23dbd35..b58f97b 100644 --- a/internal/dynamo-browse/controllers/columns.go +++ b/internal/dynamo-browse/controllers/columns.go @@ -2,25 +2,21 @@ package controllers import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/columns" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/evaluators" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/columns" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" bus "github.com/lmika/events" - "strings" ) type ColumnsController struct { - tr *TableReadController - // State colModel *columns.Columns resultSet *models.ResultSet } -func NewColumnsController(tr *TableReadController, eventBus *bus.Bus) *ColumnsController { - cc := &ColumnsController{tr: tr} +func NewColumnsController(eventBus *bus.Bus) *ColumnsController { + cc := &ColumnsController{} eventBus.On(newResultSetEvent, cc.onNewResultSet) return cc @@ -83,7 +79,7 @@ func (cc *ColumnsController) AddColumn(afterIndex int) tea.Msg { newCol := columns.Column{ Name: colExpr.String(), - Evaluator: queryexpr.ExprFieldValueEvaluator{Expr: colExpr}, + Evaluator: columns.ExprFieldValueEvaluator{Expr: colExpr}, } if afterIndex >= len(cc.colModel.Columns)-1 { @@ -119,44 +115,3 @@ func (cc *ColumnsController) DeleteColumn(afterIndex int) tea.Msg { return ColumnsUpdated{} } - -func (cc *ColumnsController) SortByColumn(index int) tea.Msg { - if index >= len(cc.colModel.Columns) { - return nil - } - - column := cc.colModel.Columns[index] - newCriteria := models.SortCriteria{ - Fields: []models.SortField{ - {Field: column.Evaluator, Asc: true}, - }, - } - if ff := cc.SortCriteria().FirstField(); evaluators.Equals(ff.Field, column.Evaluator) { - newCriteria.Fields[0].Asc = !ff.Asc - } - - cc.SetSortCriteria(newCriteria) - return ColumnsUpdated{} -} - -func (c *ColumnsController) AttributesWithPrefix(prefix string) []string { - options := make([]string, 0) - for _, col := range c.resultSet.Columns() { - if strings.HasPrefix(col, prefix) { - options = append(options, col) - } - } - return options -} - -func (cc *ColumnsController) SortCriteria() models.SortCriteria { - if cc.resultSet == nil { - return models.SortCriteria{} - } - - return cc.resultSet.SortCriteria() -} - -func (cc *ColumnsController) SetSortCriteria(criteria models.SortCriteria) { - cc.tr.SortResultSet(criteria) -} diff --git a/internal/dynamo-browse/controllers/commands.go b/internal/dynamo-browse/controllers/commands.go index 97585f2..16390e2 100644 --- a/internal/dynamo-browse/controllers/commands.go +++ b/internal/dynamo-browse/controllers/commands.go @@ -2,7 +2,7 @@ package controllers import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" + "github.com/lmika/audax/internal/common/ui/events" ) type promptSequence struct { diff --git a/internal/dynamo-browse/controllers/events.go b/internal/dynamo-browse/controllers/events.go index 7af1c41..8805de4 100644 --- a/internal/dynamo-browse/controllers/events.go +++ b/internal/dynamo-browse/controllers/events.go @@ -2,12 +2,8 @@ package controllers import ( "fmt" - "strings" - "time" - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/relitems" + "github.com/lmika/audax/internal/dynamo-browse/models" ) type SetTableItemView struct { @@ -46,24 +42,6 @@ func (rs NewResultSet) ModeMessage() string { return modeLine } -func (rs NewResultSet) RightModeMessage() string { - var sb strings.Builder - - itemCountStr := applyToN("", len(rs.ResultSet.Items()), "item", "items", "") - if rs.currentFilter != "" { - sb.WriteString(fmt.Sprintf("%d of %v", rs.filteredCount, itemCountStr)) - } else { - sb.WriteString(itemCountStr) - } - - if !rs.ResultSet.Created.IsZero() { - sb.WriteString(" • ") - sb.WriteString(rs.ResultSet.Created.Format(time.Kitchen)) - } - - return sb.String() -} - func (rs NewResultSet) StatusMessage() string { if rs.statusMessage != "" { return rs.statusMessage @@ -91,9 +69,3 @@ func (rs ResultSetUpdated) StatusMessage() string { type ShowColumnOverlay struct{} type HideColumnOverlay struct{} - -type ShowRelatedItemsOverlay struct { - Items []relitems.RelatedItem - OnSelected func(item relitems.RelatedItem) tea.Msg -} -type HideRelatedItemsOverlay struct{} diff --git a/internal/dynamo-browse/controllers/export.go b/internal/dynamo-browse/controllers/export.go index 7f4ce8e..a755ae9 100644 --- a/internal/dynamo-browse/controllers/export.go +++ b/internal/dynamo-browse/controllers/export.go @@ -1,146 +1,57 @@ package controllers import ( - "bytes" - "context" "encoding/csv" - "fmt" - "io" - "os" - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/columns" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "github.com/pkg/errors" + "os" ) type ExportController struct { - state *State - tableService TableReadService - jobController *JobsController - columns *ColumnsController - pasteboardProvider services.PasteboardProvider + state *State + columns *ColumnsController } -func NewExportController( - state *State, - tableService TableReadService, - jobsController *JobsController, - columns *ColumnsController, - pasteboardProvider services.PasteboardProvider, -) *ExportController { - return &ExportController{state, tableService, jobsController, columns, pasteboardProvider} +func NewExportController(state *State, columns *ColumnsController) *ExportController { + return &ExportController{state, columns} } -func (c *ExportController) ExportCSV(filename string, opts ExportOptions) tea.Msg { +func (c *ExportController) ExportCSV(filename string) tea.Msg { resultSet := c.state.ResultSet() if resultSet == nil { return events.Error(errors.New("no result set")) } - return NewJob(c.jobController, fmt.Sprintf("Exporting to %v…", filename), func(ctx context.Context) (int, error) { - f, err := os.Create(filename) - if err != nil { - return 0, errors.Wrapf(err, "cannot export to '%v'", filename) - } - defer f.Close() - - cw := csv.NewWriter(f) - defer cw.Flush() - - columns := c.columns.Columns().VisibleColumns() - - colNames := make([]string, len(columns)) - for i, c := range columns { - colNames[i] = c.Name - } - if err := cw.Write(colNames); err != nil { - return 0, errors.Wrapf(err, "cannot export to '%v'", filename) - } - - totalRows := 0 - row := make([]string, len(columns)) - for { - for _, item := range resultSet.Items() { - for i, col := range columns { - row[i], _ = attrutils.AttributeToString(col.Evaluator.EvaluateForItem(item)) - } - if err := cw.Write(row); err != nil { - return 0, errors.Wrapf(err, "cannot export to '%v'", filename) - } - } - totalRows += len(resultSet.Items()) - - if !opts.AllResults || !resultSet.HasNextPage() { - break - } - - jobs.PostUpdate(ctx, fmt.Sprintf("exported %d items", totalRows)) - resultSet, err = c.tableService.NextPage(ctx, resultSet) - if err != nil { - return 0, errors.Wrapf(err, "cannot get next page while exporting to '%v'", filename) - } - } - - return totalRows, nil - }).OnDone(func(rows int) tea.Msg { - return events.StatusMsg(applyToN("Exported ", rows, "item", "items", " to "+filename)) - }).Submit() -} - -func (c *ExportController) ExportCSVToClipboard() tea.Msg { - var bts bytes.Buffer - - resultSet := c.state.ResultSet() - if resultSet == nil { - return errors.New("no result set") + f, err := os.Create(filename) + if err != nil { + return events.Error(errors.Wrapf(err, "cannot export to '%v'", filename)) } + defer f.Close() - if err := c.exportCSV(&bts, c.columns.Columns().VisibleColumns(), resultSet); err != nil { - return events.Error(err) - } - - if err := c.pasteboardProvider.WriteText(bts.Bytes()); err != nil { - return events.Error(err) - } - return nil -} - -// TODO: this really needs to be a service! -func (c *ExportController) ExportToWriter(w io.Writer, resultSet *models.ResultSet) error { - return c.exportCSV(w, columns.NewColumnsFromResultSet(resultSet).Columns, resultSet) -} - -func (c *ExportController) exportCSV(w io.Writer, cols []columns.Column, resultSet *models.ResultSet) error { - cw := csv.NewWriter(w) + cw := csv.NewWriter(f) defer cw.Flush() - colNames := make([]string, len(cols)) - for i, c := range cols { + columns := c.columns.Columns().VisibleColumns() + + colNames := make([]string, len(columns)) + for i, c := range columns { colNames[i] = c.Name } if err := cw.Write(colNames); err != nil { - return errors.Wrap(err, "cannot export to clipboard") + return events.Error(errors.Wrapf(err, "cannot export to '%v'", filename)) } - row := make([]string, len(cols)) + row := make([]string, len(columns)) for _, item := range resultSet.Items() { - for i, col := range cols { + for i, col := range columns { row[i], _ = attrutils.AttributeToString(col.Evaluator.EvaluateForItem(item)) } if err := cw.Write(row); err != nil { - return errors.Wrap(err, "cannot export to clipboard") + return events.Error(errors.Wrapf(err, "cannot export to '%v'", filename)) } } return nil } - -type ExportOptions struct { - // AllResults returns all results from the table - AllResults bool -} diff --git a/internal/dynamo-browse/controllers/export_test.go b/internal/dynamo-browse/controllers/export_test.go index ce62fc3..f70a387 100644 --- a/internal/dynamo-browse/controllers/export_test.go +++ b/internal/dynamo-browse/controllers/export_test.go @@ -1,9 +1,6 @@ package controllers_test import ( - "fmt" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" "github.com/stretchr/testify/assert" "os" "strings" @@ -17,7 +14,7 @@ func TestExportController_ExportCSV(t *testing.T) { tempFile := tempFile(t) invokeCommand(t, srv.readController.Init()) - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommand(t, srv.exportController.ExportCSV(tempFile)) bts, err := os.ReadFile(tempFile) assert.NoError(t, err) @@ -30,66 +27,13 @@ func TestExportController_ExportCSV(t *testing.T) { }, "")) }) - t.Run("should export all pages of the results", func(t *testing.T) { - pageLimits := []int{5, 10, 50} - - for _, pageLimit := range pageLimits { - t.Run(fmt.Sprintf("page size %d", pageLimit), func(t *testing.T) { - t.Run("all results", func(t *testing.T) { - srv := newService(t, serviceConfig{tableName: "count-to-30", defaultLimit: 5}) - - tempFile := tempFile(t) - - expected := append([]string{ - "pk,sk,num\n", - }, sliceutils.Generate(1, 30, func(i int) string { - return fmt.Sprintf("NUM,NUM#%02d,%d\n", i, i) - })...) - - invokeCommand(t, srv.readController.Init()) - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{ - AllResults: true, - })) - - bts, err := os.ReadFile(tempFile) - assert.NoError(t, err) - - assert.Equal(t, strings.Join(expected, ""), string(bts)) - }) - - t.Run("with query", func(t *testing.T) { - srv := newService(t, serviceConfig{tableName: "count-to-30", defaultLimit: 5}) - - tempFile := tempFile(t) - - expected := append([]string{ - "pk,sk,num\n", - }, sliceutils.Generate(1, 15, func(i int) string { - return fmt.Sprintf("NUM,NUM#%02d,%d\n", i, i) - })...) - - invokeCommand(t, srv.readController.Init()) - invokeCommandWithPrompt(t, srv.readController.PromptForQuery(), "num<=15") - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{ - AllResults: true, - })) - - bts, err := os.ReadFile(tempFile) - assert.NoError(t, err) - - assert.Equal(t, strings.Join(expected, ""), string(bts)) - }) - }) - } - }) - t.Run("should return error if result set is not set", func(t *testing.T) { srv := newService(t, serviceConfig{tableName: "non-existant-table"}) tempFile := tempFile(t) invokeCommandExpectingError(t, srv.readController.Init()) - invokeCommandExpectingError(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommandExpectingError(t, srv.exportController.ExportCSV(tempFile)) }) t.Run("should honour new columns in CSV file", func(t *testing.T) { @@ -104,7 +48,7 @@ func TestExportController_ExportCSV(t *testing.T) { invokeCommandWithPrompt(t, srv.columnsController.AddColumn(1), "address.street") invokeCommand(t, srv.columnsController.ShiftColumnLeft(1)) - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommand(t, srv.exportController.ExportCSV(tempFile)) bts, err := os.ReadFile(tempFile) assert.NoError(t, err) @@ -127,7 +71,7 @@ func TestExportController_ExportCSV(t *testing.T) { invokeCommand(t, srv.columnsController.ToggleVisible(1)) invokeCommand(t, srv.columnsController.ToggleVisible(2)) - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommand(t, srv.exportController.ExportCSV(tempFile)) bts, err := os.ReadFile(tempFile) assert.NoError(t, err) diff --git a/internal/dynamo-browse/controllers/iface.go b/internal/dynamo-browse/controllers/iface.go index 7fa5239..6f00ad5 100644 --- a/internal/dynamo-browse/controllers/iface.go +++ b/internal/dynamo-browse/controllers/iface.go @@ -2,12 +2,10 @@ package controllers import ( "context" - "io/fs" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/relitems" + "github.com/lmika/audax/internal/dynamo-browse/models" + "io/fs" ) type TableReadService interface { @@ -35,7 +33,3 @@ type CustomKeyBindingSource interface { UnbindKey(key string) Rebind(bindingName string, newKey string) error } - -type RelatedItemSupplier interface { - RelatedItemOfItem(context.Context, *models.ResultSet, int) ([]relitems.RelatedItem, error) -} diff --git a/internal/dynamo-browse/controllers/jobbuilder.go b/internal/dynamo-browse/controllers/jobbuilder.go index 2128c44..7455b58 100644 --- a/internal/dynamo-browse/controllers/jobbuilder.go +++ b/internal/dynamo-browse/controllers/jobbuilder.go @@ -3,8 +3,8 @@ package controllers import ( "context" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/services/jobs" ) func NewJob[T any](jc *JobsController, description string, job func(ctx context.Context) (T, error)) JobBuilder[T] { @@ -51,9 +51,6 @@ func (jb JobBuilder[T]) executeJob(ctx context.Context) tea.Msg { if jb.onEither != nil { return jb.onEither(res, err) } else if err == nil { - if jb.onDone == nil { - return nil - } return jb.onDone(res) } else { if jb.onErr != nil { diff --git a/internal/dynamo-browse/controllers/jobs.go b/internal/dynamo-browse/controllers/jobs.go index 3dc8fea..063d9e5 100644 --- a/internal/dynamo-browse/controllers/jobs.go +++ b/internal/dynamo-browse/controllers/jobs.go @@ -2,8 +2,8 @@ package controllers import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/services/jobs" bus "github.com/lmika/events" "log" ) diff --git a/internal/dynamo-browse/controllers/keybinding.go b/internal/dynamo-browse/controllers/keybinding.go index 043248f..9081026 100644 --- a/internal/dynamo-browse/controllers/keybinding.go +++ b/internal/dynamo-browse/controllers/keybinding.go @@ -3,8 +3,8 @@ package controllers import ( "fmt" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/keybindings" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/services/keybindings" "github.com/pkg/errors" ) diff --git a/internal/dynamo-browse/controllers/scripts.go b/internal/dynamo-browse/controllers/scripts.go index f706ddd..50daf23 100644 --- a/internal/dynamo-browse/controllers/scripts.go +++ b/internal/dynamo-browse/controllers/scripts.go @@ -3,24 +3,21 @@ package controllers import ( "context" "fmt" - "log" - "strings" - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/commandctrl" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/relitems" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/common/ui/commandctrl" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" bus "github.com/lmika/events" "github.com/pkg/errors" + "log" + "strings" ) type ScriptController struct { scriptManager *scriptmanager.Service tableReadController *TableReadController - jobController *JobsController settingsController *SettingsController eventBus *bus.Bus sendMsg func(msg tea.Msg) @@ -29,14 +26,12 @@ type ScriptController struct { func NewScriptController( scriptManager *scriptmanager.Service, tableReadController *TableReadController, - jobController *JobsController, settingsController *SettingsController, eventBus *bus.Bus, ) *ScriptController { sc := &ScriptController{ scriptManager: scriptManager, tableReadController: tableReadController, - jobController: jobController, settingsController: settingsController, eventBus: eventBus, } @@ -66,6 +61,13 @@ func (sc *ScriptController) Init() { } else { log.Printf("warn: script lookup paths are invalid: %v", err) } + sc.scriptManager.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowShellCommands: true, + AllowEnv: true, + }, + }) } func (sc *ScriptController) SetMessageSender(sendMsg func(msg tea.Msg)) { @@ -167,6 +169,7 @@ func (s *sessionImpl) SetResultSet(ctx context.Context, newResultSet *models.Res } func (s *sessionImpl) Query(ctx context.Context, query string, opts scriptmanager.QueryOptions) (*models.ResultSet, error) { + // Parse the query expr, err := queryexpr.Parse(query) if err != nil { @@ -179,22 +182,12 @@ func (s *sessionImpl) Query(ctx context.Context, query string, opts scriptmanage if opts.ValuePlaceholders != nil { expr = expr.WithValueParams(opts.ValuePlaceholders) } - if opts.IndexName != "" { - expr = expr.WithIndex(opts.IndexName) - } - return s.sc.doQuery(ctx, expr, opts) -} - -func (s *ScriptController) doQuery(ctx context.Context, expr *queryexpr.QueryExpr, opts scriptmanager.QueryOptions) (*models.ResultSet, error) { // Get the table info - var ( - tableInfo *models.TableInfo - err error - ) + var tableInfo *models.TableInfo tableName := opts.TableName - currentResultSet := s.tableReadController.state.ResultSet() + currentResultSet := s.sc.tableReadController.state.ResultSet() if tableName != "" { // Table specified. If it's the same as the current table, then use the existing table info @@ -203,7 +196,7 @@ func (s *ScriptController) doQuery(ctx context.Context, expr *queryexpr.QueryExp } // Otherwise, describe the table - tableInfo, err = s.tableReadController.tableService.Describe(ctx, tableName) + tableInfo, err = s.sc.tableReadController.tableService.Describe(ctx, tableName) if err != nil { return nil, errors.Wrapf(err, "cannot describe table '%v'", tableName) } @@ -215,7 +208,7 @@ func (s *ScriptController) doQuery(ctx context.Context, expr *queryexpr.QueryExp tableInfo = currentResultSet.TableInfo } - newResultSet, err := s.tableReadController.tableService.ScanOrQuery(ctx, tableInfo, expr, nil) + newResultSet, err := s.sc.tableReadController.tableService.ScanOrQuery(ctx, tableInfo, expr, nil) if err != nil { return nil, err } @@ -251,31 +244,3 @@ func (sc *ScriptController) LookupBinding(theKey string) string { func (sc *ScriptController) UnbindKey(key string) { sc.scriptManager.UnbindKey(key) } - -func (c *ScriptController) LookupRelatedItems(idx int) (res tea.Msg) { - rs := c.tableReadController.state.ResultSet() - - relItems, err := c.scriptManager.RelatedItemOfItem(context.Background(), rs, idx) - if err != nil { - return events.Error(err) - } else if len(relItems) == 0 { - return events.StatusMsg("No related items available") - } - - return ShowRelatedItemsOverlay{ - Items: relItems, - OnSelected: func(item relitems.RelatedItem) tea.Msg { - if item.OnSelect != nil { - return item.OnSelect() - } - - return NewJob(c.jobController, "Running query…", func(ctx context.Context) (*models.ResultSet, error) { - return c.doQuery(ctx, item.Query, scriptmanager.QueryOptions{ - TableName: item.Table, - }) - }).OnDone(func(rs *models.ResultSet) tea.Msg { - return c.tableReadController.setResultSetAndFilter(rs, "", true, resultSetUpdateQuery) - }).Submit() - }, - } -} diff --git a/internal/dynamo-browse/controllers/scripts_test.go b/internal/dynamo-browse/controllers/scripts_test.go index ffcb8a8..06ee419 100644 --- a/internal/dynamo-browse/controllers/scripts_test.go +++ b/internal/dynamo-browse/controllers/scripts_test.go @@ -1,13 +1,12 @@ package controllers_test import ( + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/stretchr/testify/assert" "testing" "time" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/stretchr/testify/assert" ) func TestScriptController_RunScript(t *testing.T) { @@ -54,7 +53,7 @@ func TestScriptController_RunScript(t *testing.T) { srv := newService(t, serviceConfig{ tableName: "alpha-table", scriptFS: testScriptFile(t, "test.tm", ` - rs := session.query('pk="abc"') + rs := session.query('pk="abc"').unwrap() ui.print(rs.length) `), }) @@ -73,7 +72,7 @@ func TestScriptController_RunScript(t *testing.T) { srv := newService(t, serviceConfig{ tableName: "alpha-table", scriptFS: testScriptFile(t, "test.tm", ` - rs := session.query('pk!="abc"', { table: "count-to-30" }) + rs := session.query('pk!="abc"', { table: "count-to-30" }).unwrap() ui.print(rs.length) `), }) @@ -94,7 +93,7 @@ func TestScriptController_RunScript(t *testing.T) { srv := newService(t, serviceConfig{ tableName: "alpha-table", scriptFS: testScriptFile(t, "test.tm", ` - rs := session.query('pk="abc"') + rs := session.query('pk="abc"').unwrap() session.set_result_set(rs) `), }) @@ -113,7 +112,7 @@ func TestScriptController_RunScript(t *testing.T) { srv := newService(t, serviceConfig{ tableName: "alpha-table", scriptFS: testScriptFile(t, "test.tm", ` - rs := session.query('pk="abc"') + rs := session.query('pk="abc"').unwrap() rs[0].set_attr("pk", "131") session.set_result_set(rs) `), @@ -136,35 +135,22 @@ func TestScriptController_RunScript(t *testing.T) { func TestScriptController_LookupCommand(t *testing.T) { t.Run("should schedule the script on a separate go-routine", func(t *testing.T) { - scenarios := []struct { - descr string - command string - expectedOutput string - }{ - {descr: "command with arg", command: "mycommand \"test name\"", expectedOutput: "Hello, test name"}, - {descr: "command no arg", command: "mycommand", expectedOutput: "Hello, nil value"}, - } - - for _, scenario := range scenarios { - t.Run(scenario.descr, func(t *testing.T) { - srv := newService(t, serviceConfig{ - tableName: "alpha-table", - scriptFS: testScriptFile(t, "test.tm", ` - ext.command("mycommand", func(name = "nil value") { - ui.print(sprintf("Hello, %v", name)) - }) - `), + srv := newService(t, serviceConfig{ + tableName: "alpha-table", + scriptFS: testScriptFile(t, "test.tm", ` + ext.command("mycommand", func(name) { + ui.print("Hello, ", name) }) + `), + }) - invokeCommand(t, srv.scriptController.LoadScript("test.tm")) - invokeCommand(t, srv.commandController.Execute(scenario.command)) + invokeCommand(t, srv.scriptController.LoadScript("test.tm")) + invokeCommand(t, srv.commandController.Execute(`mycommand "test name"`)) - srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second) + srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second) - assert.Len(t, srv.msgSender.msgs, 1) - assert.Equal(t, events.StatusMsg(scenario.expectedOutput), srv.msgSender.msgs[0]) - }) - } + assert.Len(t, srv.msgSender.msgs, 1) + assert.Equal(t, events.StatusMsg("Hello, test name"), srv.msgSender.msgs[0]) }) t.Run("should only allow one script to run at a time", func(t *testing.T) { diff --git a/internal/dynamo-browse/controllers/settings.go b/internal/dynamo-browse/controllers/settings.go index a0bb5a1..ec69dfc 100644 --- a/internal/dynamo-browse/controllers/settings.go +++ b/internal/dynamo-browse/controllers/settings.go @@ -3,7 +3,7 @@ package controllers import ( "fmt" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" + "github.com/lmika/audax/internal/common/ui/events" bus "github.com/lmika/events" "github.com/pkg/errors" "log" diff --git a/internal/dynamo-browse/controllers/settings_test.go b/internal/dynamo-browse/controllers/settings_test.go index f400603..e7c7315 100644 --- a/internal/dynamo-browse/controllers/settings_test.go +++ b/internal/dynamo-browse/controllers/settings_test.go @@ -1,8 +1,8 @@ package controllers_test import ( - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/controllers" "github.com/stretchr/testify/assert" "testing" ) diff --git a/internal/dynamo-browse/controllers/state.go b/internal/dynamo-browse/controllers/state.go index 6a886d2..3aebe13 100644 --- a/internal/dynamo-browse/controllers/state.go +++ b/internal/dynamo-browse/controllers/state.go @@ -1,8 +1,9 @@ package controllers import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" "sync" + + "github.com/lmika/audax/internal/dynamo-browse/models" ) type State struct { diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index 6ef6e18..5170731 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -4,24 +4,22 @@ import ( "bytes" "context" "fmt" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/attrcodec" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" + "github.com/lmika/audax/internal/dynamo-browse/services/inputhistory" + "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" + "github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot" + bus "github.com/lmika/events" + "github.com/pkg/errors" + "golang.design/x/clipboard" "log" "strings" "sync" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrcodec" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/serialisable" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/inputhistory" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/itemrenderer" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/viewsnapshot" - bus "github.com/lmika/events" - "github.com/pkg/errors" ) type resultSetUpdateOp int @@ -35,7 +33,6 @@ const ( resultSetUpdateTouch resultSetUpdateNextPage resultSetUpdateScript - resultSetUpdateResort ) type MarkOp int @@ -60,12 +57,11 @@ type TableReadController struct { eventBus *bus.Bus tableName string loadFromLastView bool - pasteboardProvider services.PasteboardProvider - relatedItemSupplier RelatedItemSupplier // state - mutex *sync.Mutex - state *State + mutex *sync.Mutex + state *State + clipboardInit bool } func NewTableReadController( @@ -76,8 +72,6 @@ func NewTableReadController( jobController *JobsController, inputHistoryService *inputhistory.Service, eventBus *bus.Bus, - pasteboardProvider services.PasteboardProvider, - relatedItemSupplier RelatedItemSupplier, tableName string, ) *TableReadController { return &TableReadController{ @@ -89,8 +83,6 @@ func NewTableReadController( inputHistoryService: inputHistoryService, eventBus: eventBus, tableName: tableName, - pasteboardProvider: pasteboardProvider, - relatedItemSupplier: relatedItemSupplier, mutex: new(sync.Mutex), } } @@ -151,13 +143,6 @@ func (c *TableReadController) ScanTable(name string) tea.Msg { }).OnEither(c.handleResultSetFromJobResult(c.state.Filter(), true, false, resultSetUpdateInit)).Submit() } -func (c *TableReadController) SortResultSet(newCriteria models.SortCriteria) { - c.state.withResultSet(func(rs *models.ResultSet) { - rs.Sort(newCriteria.Append(models.PKSKSortFilter(rs.TableInfo))) - }) - c.eventBus.Fire(newResultSetEvent, c.state.resultSet, resultSetUpdateResort) -} - func (c *TableReadController) PromptForQuery() tea.Msg { return events.PromptForInputMsg{ Prompt: "query: ", @@ -291,35 +276,13 @@ func (c *TableReadController) setResultSetAndFilter(resultSet *models.ResultSet, return c.state.buildNewResultSetMessage("") } -func (c *TableReadController) Mark(op MarkOp, where string) tea.Msg { - var ( - whereExpr *queryexpr.QueryExpr - err error - ) - - if where != "" { - whereExpr, err = queryexpr.Parse(where) - if err != nil { - return events.Error(err) - } - } - - if err := c.state.withResultSetReturningError(func(resultSet *models.ResultSet) error { - for i, item := range resultSet.Items() { +func (c *TableReadController) Mark(op MarkOp) tea.Msg { + c.state.withResultSet(func(resultSet *models.ResultSet) { + for i := range resultSet.Items() { if resultSet.Hidden(i) { continue } - if whereExpr != nil { - res, err := whereExpr.EvalItem(item) - if err != nil { - return errors.Wrapf(err, "item %d", i) - } - if !attrutils.Truthy(res) { - continue - } - } - switch op { case MarkOpMark: resultSet.SetMark(i, true) @@ -329,10 +292,7 @@ func (c *TableReadController) Mark(op MarkOp, where string) tea.Msg { resultSet.SetMark(i, !resultSet.Marked(i)) } } - return nil - }); err != nil { - return events.Error(err) - } + }) return ResultSetUpdated{} } @@ -417,7 +377,7 @@ func (c *TableReadController) NextPage() tea.Msg { resultSet := c.state.ResultSet() if resultSet == nil { return events.StatusMsg("Result-set is nil") - } else if !resultSet.HasNextPage() { + } else if resultSet.LastEvaluatedKey == nil { return events.StatusMsg("No more results") } currentFilter := c.state.filter @@ -486,8 +446,12 @@ func (c *TableReadController) updateViewToSnapshot(viewSnapshot *serialisable.Vi } func (c *TableReadController) CopyItemToClipboard(idx int) tea.Msg { + if err := c.initClipboard(); err != nil { + return events.Error(err) + } + itemCount := 0 - if err := c.state.withResultSetReturningError(func(resultSet *models.ResultSet) error { + c.state.withResultSet(func(resultSet *models.ResultSet) { sb := new(strings.Builder) _ = applyToMarkedItems(resultSet, idx, func(idx int, item models.Item) error { if sb.Len() > 0 { @@ -497,14 +461,23 @@ func (c *TableReadController) CopyItemToClipboard(idx int) tea.Msg { itemCount += 1 return nil }) - - if err := c.pasteboardProvider.WriteText([]byte(sb.String())); err != nil { - return err - } - return nil - }); err != nil { - return events.Error(err) - } + clipboard.Write(clipboard.FmtText, []byte(sb.String())) + }) return events.StatusMsg(applyToN("", itemCount, "item", "items", " copied to clipboard")) } + +func (c *TableReadController) initClipboard() error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.clipboardInit { + return nil + } + + if err := clipboard.Init(); err != nil { + return errors.Wrap(err, "unable to enable clipboard") + } + c.clipboardInit = true + return nil +} diff --git a/internal/dynamo-browse/controllers/tableread_test.go b/internal/dynamo-browse/controllers/tableread_test.go index 18bf075..6a86a85 100644 --- a/internal/dynamo-browse/controllers/tableread_test.go +++ b/internal/dynamo-browse/controllers/tableread_test.go @@ -4,9 +4,9 @@ import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/test/testdynamo" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/test/testdynamo" "github.com/stretchr/testify/assert" "os" "strings" @@ -89,7 +89,7 @@ func TestTableReadController_Query(t *testing.T) { invokeCommand(t, srv.readController.Init()) invokeCommandWithPrompts(t, srv.readController.PromptForQuery(), `pk ^= "abc"`) - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommand(t, srv.exportController.ExportCSV(tempFile)) bts, err := os.ReadFile(tempFile) assert.NoError(t, err) @@ -107,7 +107,7 @@ func TestTableReadController_Query(t *testing.T) { invokeCommand(t, srv.readController.Init()) invokeCommandWithPrompts(t, srv.readController.PromptForQuery(), `alpha = "This is some value"`) - invokeCommand(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommand(t, srv.exportController.ExportCSV(tempFile)) bts, err := os.ReadFile(tempFile) assert.NoError(t, err) @@ -124,7 +124,7 @@ func TestTableReadController_Query(t *testing.T) { tempFile := tempFile(t) invokeCommandExpectingError(t, srv.readController.Init()) - invokeCommandExpectingError(t, srv.exportController.ExportCSV(tempFile, controllers.ExportOptions{})) + invokeCommandExpectingError(t, srv.exportController.ExportCSV(tempFile)) }) } diff --git a/internal/dynamo-browse/controllers/tablewrite.go b/internal/dynamo-browse/controllers/tablewrite.go index 033a0cb..cc1410a 100644 --- a/internal/dynamo-browse/controllers/tablewrite.go +++ b/internal/dynamo-browse/controllers/tablewrite.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/tables" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/services/tables" "github.com/pkg/errors" "log" "strconv" @@ -122,8 +122,6 @@ func (twc *TableWriteController) SetAttributeValue(idx int, itemType models.Item return twc.setBoolValue(idx, path) case models.NullItemType: return twc.setNullValue(idx, path) - case models.ExprValueItemType: - return twc.setToExpressionValue(idx, path) default: return events.Error(errors.New("unsupported attribute type")) } @@ -153,39 +151,6 @@ func (twc *TableWriteController) setStringValue(idx int, attr *queryexpr.QueryEx } } -func (twc *TableWriteController) setToExpressionValue(idx int, attr *queryexpr.QueryExpr) tea.Msg { - return events.PromptForInputMsg{ - Prompt: "expr value: ", - OnDone: func(value string) tea.Msg { - valueExpr, err := queryexpr.Parse(value) - if err != nil { - return events.Error(err) - } - - if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error { - if err := applyToMarkedItems(set, idx, func(idx int, item models.Item) error { - newValue, err := valueExpr.EvalItem(item) - if err != nil { - return err - } - if err := attr.SetEvalItem(item, newValue); err != nil { - return err - } - set.SetDirty(idx, true) - return nil - }); err != nil { - return err - } - set.RefreshColumns() - return nil - }); err != nil { - return events.Error(err) - } - return ResultSetUpdated{} - }, - } -} - func (twc *TableWriteController) setNumberValue(idx int, attr *queryexpr.QueryExpr) tea.Msg { return events.PromptForInputMsg{ Prompt: "number value: ", @@ -274,17 +239,12 @@ func (twc *TableWriteController) DeleteAttribute(idx int, key string) tea.Msg { } if err := twc.state.withResultSetReturningError(func(set *models.ResultSet) error { - if err := applyToMarkedItems(set, idx, func(idx int, item models.Item) error { - if err := path.DeleteAttribute(set.Items()[idx]); err != nil { - return err - } - - set.SetDirty(idx, true) - return nil - }); err != nil { + err := path.DeleteAttribute(set.Items()[idx]) + if err != nil { return err } + set.SetDirty(idx, true) set.RefreshColumns() return nil }); err != nil { @@ -458,44 +418,6 @@ func (twc *TableWriteController) assertReadWrite() error { return nil } -func (twc *TableWriteController) CloneItem(idx int) tea.Msg { - if err := twc.assertReadWrite(); err != nil { - return events.Error(err) - } - - // Work out which keys we need to prompt for - rs := twc.state.ResultSet() - - keyPrompts := &promptSequence{ - prompts: []string{rs.TableInfo.Keys.PartitionKey + ": "}, - } - if rs.TableInfo.Keys.SortKey != "" { - keyPrompts.prompts = append(keyPrompts.prompts, rs.TableInfo.Keys.SortKey+": ") - } - keyPrompts.onAllDone = func(values []string) tea.Msg { - twc.state.withResultSet(func(set *models.ResultSet) { - applyToMarkedItems(set, idx, func(idx int, item models.Item) error { - // TODO: should be a deep clone - clonedItem := item.Clone() - - clonedItem[rs.TableInfo.Keys.PartitionKey] = &types.AttributeValueMemberS{Value: values[0]} - if len(values) == 2 { - clonedItem[rs.TableInfo.Keys.SortKey] = &types.AttributeValueMemberS{Value: values[1]} - } - - set.AddNewItem(clonedItem, models.ItemAttribute{ - New: true, - Dirty: true, - }) - return nil - }) - }) - return twc.state.buildNewResultSetMessage("New item cloned") - } - - return keyPrompts.next() -} - func applyToN(prefix string, n int, singular, plural, suffix string) string { if n == 1 { return fmt.Sprintf("%v%v %v%v", prefix, n, singular, suffix) diff --git a/internal/dynamo-browse/controllers/tablewrite_test.go b/internal/dynamo-browse/controllers/tablewrite_test.go index efd0c14..2dd2269 100644 --- a/internal/dynamo-browse/controllers/tablewrite_test.go +++ b/internal/dynamo-browse/controllers/tablewrite_test.go @@ -4,22 +4,21 @@ import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/commandctrl" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/dynamo" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/inputhistorystore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/pasteboardprovider" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/settingstore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/workspacestore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/inputhistory" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/itemrenderer" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/tables" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/viewsnapshot" - "github.com/lmika/dynamo-browse/test/testdynamo" - "github.com/lmika/dynamo-browse/test/testworkspace" + "github.com/lmika/audax/internal/common/ui/commandctrl" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/inputhistorystore" + "github.com/lmika/audax/internal/dynamo-browse/providers/settingstore" + "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" + "github.com/lmika/audax/internal/dynamo-browse/services/inputhistory" + "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" + "github.com/lmika/audax/internal/dynamo-browse/services/jobs" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/tables" + "github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot" + "github.com/lmika/audax/test/testdynamo" + "github.com/lmika/audax/test/testworkspace" bus "github.com/lmika/events" "github.com/stretchr/testify/assert" "io/fs" @@ -618,23 +617,12 @@ func newService(t *testing.T, cfg serviceConfig) *services { state := controllers.NewState() jobsController := controllers.NewJobsController(jobs.NewService(eventBus), eventBus, true) - readController := controllers.NewTableReadController( - state, - service, - workspaceService, - itemRendererService, - jobsController, - inputHistoryService, - eventBus, - pasteboardprovider.NilProvider{}, - nil, - cfg.tableName, - ) + readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, jobsController, inputHistoryService, eventBus, cfg.tableName) writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore) settingsController := controllers.NewSettingsController(settingStore, eventBus) - columnsController := controllers.NewColumnsController(readController, eventBus) - exportController := controllers.NewExportController(state, service, jobsController, columnsController, pasteboardprovider.NilProvider{}) - scriptController := controllers.NewScriptController(scriptService, readController, jobsController, settingsController, eventBus) + columnsController := controllers.NewColumnsController(eventBus) + exportController := controllers.NewExportController(state, columnsController) + scriptController := controllers.NewScriptController(scriptService, readController, settingsController, eventBus) commandController := commandctrl.NewCommandController(inputHistoryService) commandController.AddCommandLookupExtension(scriptController) diff --git a/internal/dynamo-browse/controllers/utils.go b/internal/dynamo-browse/controllers/utils.go index c8930b5..9a09708 100644 --- a/internal/dynamo-browse/controllers/utils.go +++ b/internal/dynamo-browse/controllers/utils.go @@ -1,6 +1,6 @@ package controllers -import "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" +import "github.com/lmika/audax/internal/dynamo-browse/models" func applyToMarkedItems(rs *models.ResultSet, selectedIndex int, applyFn func(idx int, item models.Item) error) error { if markedItems := rs.MarkedItems(); len(markedItems) > 0 { diff --git a/internal/dynamo-browse/models/attrcodec/codec_test.go b/internal/dynamo-browse/models/attrcodec/codec_test.go index 8ac1a0c..866e42e 100644 --- a/internal/dynamo-browse/models/attrcodec/codec_test.go +++ b/internal/dynamo-browse/models/attrcodec/codec_test.go @@ -3,7 +3,7 @@ package attrcodec_test import ( "bytes" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrcodec" + "github.com/lmika/audax/internal/dynamo-browse/models/attrcodec" "github.com/stretchr/testify/assert" "strings" "testing" diff --git a/internal/dynamo-browse/models/attrutils/truthy.go b/internal/dynamo-browse/models/attrutils/truthy.go deleted file mode 100644 index 91bce48..0000000 --- a/internal/dynamo-browse/models/attrutils/truthy.go +++ /dev/null @@ -1,32 +0,0 @@ -package attrutils - -import ( - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" -) - -func Truthy(x types.AttributeValue) bool { - switch xVal := x.(type) { - case *types.AttributeValueMemberS: - return len(xVal.Value) > 0 - case *types.AttributeValueMemberN: - return len(xVal.Value) > 0 && xVal.Value != "0" - case *types.AttributeValueMemberBOOL: - return xVal.Value - case *types.AttributeValueMemberB: - return len(xVal.Value) > 0 - case *types.AttributeValueMemberNULL: - return !xVal.Value - case *types.AttributeValueMemberL: - return len(xVal.Value) > 0 - case *types.AttributeValueMemberM: - return len(xVal.Value) > 0 - case *types.AttributeValueMemberBS: - return len(xVal.Value) > 0 - case *types.AttributeValueMemberNS: - return len(xVal.Value) > 0 - case *types.AttributeValueMemberSS: - return len(xVal.Value) > 0 - } - - return false -} diff --git a/internal/dynamo-browse/models/columns/columns.go b/internal/dynamo-browse/models/columns/columns.go index 3342a95..c24fe1d 100644 --- a/internal/dynamo-browse/models/columns/columns.go +++ b/internal/dynamo-browse/models/columns/columns.go @@ -1,7 +1,9 @@ package columns import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" ) type Columns struct { @@ -17,7 +19,7 @@ func NewColumnsFromResultSet(rs *models.ResultSet) *Columns { for i, c := range rsCols { cols[i] = Column{ Name: c, - Evaluator: models.SimpleFieldValueEvaluator(c), + Evaluator: SimpleFieldValueEvaluator(c), } } @@ -42,7 +44,7 @@ func (cols *Columns) AddMissingColumns(rs *models.ResultSet) { if _, hasCol := existingColumns[c]; !hasCol { newCols = append(newCols, Column{ Name: c, - Evaluator: models.SimpleFieldValueEvaluator(c), + Evaluator: SimpleFieldValueEvaluator(c), }) } } @@ -54,7 +56,7 @@ func (cols *Columns) AddMissingColumns(rs *models.ResultSet) { } else { newCols[i] = Column{ Name: c, - Evaluator: models.SimpleFieldValueEvaluator(c), + Evaluator: SimpleFieldValueEvaluator(c), } } } @@ -80,6 +82,25 @@ func (cols *Columns) VisibleColumns() []Column { type Column struct { Name string - Evaluator models.FieldValueEvaluator + Evaluator FieldValueEvaluator Hidden bool } + +type FieldValueEvaluator interface { + EvaluateForItem(item models.Item) types.AttributeValue +} + +type SimpleFieldValueEvaluator string + +func (sfve SimpleFieldValueEvaluator) EvaluateForItem(item models.Item) types.AttributeValue { + return item[string(sfve)] +} + +type ExprFieldValueEvaluator struct { + Expr *queryexpr.QueryExpr +} + +func (sfve ExprFieldValueEvaluator) EvaluateForItem(item models.Item) types.AttributeValue { + val, _ := sfve.Expr.EvalItem(item) + return val +} diff --git a/internal/dynamo-browse/models/evaluators.go b/internal/dynamo-browse/models/evaluators.go deleted file mode 100644 index 85ffa26..0000000 --- a/internal/dynamo-browse/models/evaluators.go +++ /dev/null @@ -1,15 +0,0 @@ -package models - -import ( - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" -) - -type FieldValueEvaluator interface { - EvaluateForItem(item Item) types.AttributeValue -} - -type SimpleFieldValueEvaluator string - -func (sfve SimpleFieldValueEvaluator) EvaluateForItem(item Item) types.AttributeValue { - return item[string(sfve)] -} diff --git a/internal/dynamo-browse/models/evaluators/equals.go b/internal/dynamo-browse/models/evaluators/equals.go deleted file mode 100644 index ec1f0dc..0000000 --- a/internal/dynamo-browse/models/evaluators/equals.go +++ /dev/null @@ -1,25 +0,0 @@ -package evaluators - -import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" -) - -func Equals(x, y models.FieldValueEvaluator) bool { - if x == nil { - return y == nil - } - - switch xt := x.(type) { - case models.SimpleFieldValueEvaluator: - if yt, ok := y.(models.SimpleFieldValueEvaluator); ok { - return xt == yt - } - case queryexpr.ExprFieldValueEvaluator: - if yt, ok := y.(queryexpr.ExprFieldValueEvaluator); ok { - return xt.Expr.Equal(yt.Expr) - } - } - - return false -} diff --git a/internal/dynamo-browse/models/items.go b/internal/dynamo-browse/models/items.go index 1319bee..4ed4e1a 100644 --- a/internal/dynamo-browse/models/items.go +++ b/internal/dynamo-browse/models/items.go @@ -2,7 +2,7 @@ package models import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" ) type ItemIndex struct { @@ -16,7 +16,7 @@ type Item map[string]types.AttributeValue func (i Item) Clone() Item { newItem := Item{} - // TODO: should be a deep clone? YES!! + // TODO: should be a deep clone? for k, v := range i { newItem[k] = v } @@ -33,14 +33,6 @@ func (i Item) KeyValue(info *TableInfo) map[string]types.AttributeValue { return itemKey } -func (i Item) PKSK(info *TableInfo) (pk types.AttributeValue, sk types.AttributeValue) { - pk = i[info.Keys.PartitionKey] - if info.Keys.SortKey != "" { - sk = i[info.Keys.SortKey] - } - return pk, sk -} - func (i Item) AttributeValueAsString(key string) (string, bool) { return attrutils.AttributeToString(i[key]) } diff --git a/internal/dynamo-browse/models/itemtype.go b/internal/dynamo-browse/models/itemtype.go index ab271c9..4174005 100644 --- a/internal/dynamo-browse/models/itemtype.go +++ b/internal/dynamo-browse/models/itemtype.go @@ -8,6 +8,4 @@ const ( NumberItemType ItemType = "N" BoolItemType ItemType = "BOOL" NullItemType ItemType = "NULL" - - ExprValueItemType ItemType = "exprvalue" ) diff --git a/internal/dynamo-browse/models/models.go b/internal/dynamo-browse/models/models.go index 03d0421..1e9bf16 100644 --- a/internal/dynamo-browse/models/models.go +++ b/internal/dynamo-browse/models/models.go @@ -3,14 +3,12 @@ package models import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "sort" - "time" ) type ResultSet struct { // Query information TableInfo *TableInfo Query Queryable - Created time.Time ExclusiveStartKey map[string]types.AttributeValue // Result information @@ -18,8 +16,7 @@ type ResultSet struct { items []Item attributes []ItemAttribute - columns []string - sortCriteria SortCriteria + columns []string } type Queryable interface { @@ -49,10 +46,6 @@ func (rs *ResultSet) SetItems(items []Item) { rs.attributes = make([]ItemAttribute, len(items)) } -func (rs *ResultSet) SortCriteria() SortCriteria { - return rs.sortCriteria -} - func (rs *ResultSet) AddNewItem(item Item, attrs ItemAttribute) { rs.items = append(rs.items, item) rs.attributes = append(rs.attributes, attrs) @@ -142,12 +135,3 @@ func (rs *ResultSet) RefreshColumns() { rs.columns = columns } - -func (rs *ResultSet) HasNextPage() bool { - return rs.LastEvaluatedKey != nil -} - -func (rs *ResultSet) Sort(criteria SortCriteria) { - rs.sortCriteria = criteria - Sort(rs.items, criteria) -} diff --git a/internal/dynamo-browse/models/modexpr/astmods.go b/internal/dynamo-browse/models/modexpr/astmods.go index 67ca70c..abc6841 100644 --- a/internal/dynamo-browse/models/modexpr/astmods.go +++ b/internal/dynamo-browse/models/modexpr/astmods.go @@ -1,6 +1,6 @@ package modexpr -import "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" +import "github.com/lmika/audax/internal/dynamo-browse/models" func (a *astExpr) calcPatchMods(item models.Item) ([]patchMod, error) { patchMods := make([]patchMod, 0) diff --git a/internal/dynamo-browse/models/modexpr/expr.go b/internal/dynamo-browse/models/modexpr/expr.go index 856e4f3..6ee5cb4 100644 --- a/internal/dynamo-browse/models/modexpr/expr.go +++ b/internal/dynamo-browse/models/modexpr/expr.go @@ -1,6 +1,6 @@ package modexpr -import "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" +import "github.com/lmika/audax/internal/dynamo-browse/models" type ModExpr struct { ast *astExpr diff --git a/internal/dynamo-browse/models/modexpr/expr_test.go b/internal/dynamo-browse/models/modexpr/expr_test.go index 6a5436e..87c0357 100644 --- a/internal/dynamo-browse/models/modexpr/expr_test.go +++ b/internal/dynamo-browse/models/modexpr/expr_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/modexpr" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/modexpr" "github.com/stretchr/testify/assert" ) diff --git a/internal/dynamo-browse/models/modexpr/mods.go b/internal/dynamo-browse/models/modexpr/mods.go index 4669cd5..0e9ac80 100644 --- a/internal/dynamo-browse/models/modexpr/mods.go +++ b/internal/dynamo-browse/models/modexpr/mods.go @@ -2,7 +2,7 @@ package modexpr import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" ) type patchMod interface { diff --git a/internal/dynamo-browse/models/query.go b/internal/dynamo-browse/models/query.go index dbd2e26..65d1451 100644 --- a/internal/dynamo-browse/models/query.go +++ b/internal/dynamo-browse/models/query.go @@ -3,7 +3,7 @@ package models import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/itemrender" + "github.com/lmika/audax/internal/dynamo-browse/models/itemrender" ) type QueryExecutionPlan struct { diff --git a/internal/dynamo-browse/models/queryexpr/ast.go b/internal/dynamo-browse/models/queryexpr/ast.go index 3455947..7e8cf81 100644 --- a/internal/dynamo-browse/models/queryexpr/ast.go +++ b/internal/dynamo-browse/models/queryexpr/ast.go @@ -4,23 +4,17 @@ import ( "github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2/lexer" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/pkg/errors" - "strconv" ) // Modelled on the expression language here // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html type astExpr struct { - Root *astDisjunction `parser:"@@"` - Options *astOptions `parser:"( 'using' @@ )?"` -} - -type astOptions struct { - Scan bool `parser:"@'scan'"` - Index string `parser:" | 'index' '(' @String ')'"` + Root *astDisjunction `parser:"@@"` } type astDisjunction struct { @@ -44,15 +38,9 @@ type astIn struct { } type astComparisonOp struct { - Ref *astBetweenOp `parser:"@@"` - Op string `parser:"( @('<' | '<=' | '>' | '>=')"` - Value *astBetweenOp `parser:"@@ )?"` -} - -type astBetweenOp struct { - Ref *astEqualityOp `parser:"@@"` - From *astEqualityOp `parser:"( 'between' @@ "` - To *astEqualityOp `parser:" 'and' @@ )?"` + Ref *astEqualityOp `parser:"@@"` + Op string `parser:"( @('<' | '<=' | '>' | '>=')"` + Value *astEqualityOp `parser:"@@ )?"` } type astEqualityOp struct { @@ -70,6 +58,7 @@ type astIsOp struct { type astSubRef struct { Ref *astFunctionCall `parser:"@@"` SubRefs []*astSubRefType `parser:"@@*"` + //Quals []string `parser:"('.' @Ident)*"` } type astSubRefType struct { @@ -132,58 +121,7 @@ func Parse(expr string) (*QueryExpr, error) { return &QueryExpr{ast: ast}, nil } -func (a *astExpr) calcQuery(ctx *evalContext, info *models.TableInfo, preferredIndex string) (*models.QueryExecutionPlan, error) { - plans, err := a.determinePlausibleExecutionPlans(ctx, info) - if err != nil { - return nil, err - } - - scanPlan, _ := sliceutils.FindLast(plans, func(p *models.QueryExecutionPlan) bool { - return !p.CanQuery - }) - queryPlans := sliceutils.Filter(plans, func(p *models.QueryExecutionPlan) bool { - if !p.CanQuery { - return false - } - return true - }) - - if len(queryPlans) == 0 || (a.Options != nil && a.Options.Scan) { - if preferredIndex != "" { - return nil, NoPlausiblePlanWithIndexError{ - PreferredIndex: preferredIndex, - PossibleIndices: sliceutils.Map(queryPlans, func(p *models.QueryExecutionPlan) string { return p.IndexName }), - } - } - return scanPlan, nil - } - - if preferredIndex == "" && (a.Options != nil && a.Options.Index != "") { - preferredIndex, err = strconv.Unquote(a.Options.Index) - if err != nil { - return nil, err - } - } - - if preferredIndex != "" { - queryPlans = sliceutils.Filter(queryPlans, func(p *models.QueryExecutionPlan) bool { return p.IndexName == preferredIndex }) - } - if len(queryPlans) == 1 { - return queryPlans[0], nil - } else if len(queryPlans) == 0 { - return nil, NoPlausiblePlanWithIndexError{ - PreferredIndex: preferredIndex, - } - } - - return nil, MultiplePlansWithIndexError{ - PossibleIndices: sliceutils.Map(queryPlans, func(p *models.QueryExecutionPlan) string { return p.IndexName }), - } -} - -func (a *astExpr) determinePlausibleExecutionPlans(ctx *evalContext, info *models.TableInfo) ([]*models.QueryExecutionPlan, error) { - plans := make([]*models.QueryExecutionPlan, 0) - +func (a *astExpr) calcQuery(ctx *evalContext, info *models.TableInfo) (*models.QueryExecutionPlan, error) { type queryTestAttempt struct { index string keysUnderTest models.KeyAttribute @@ -215,11 +153,11 @@ func (a *astExpr) determinePlausibleExecutionPlans(ctx *evalContext, info *model return nil, err } - plans = append(plans, &models.QueryExecutionPlan{ + return &models.QueryExecutionPlan{ CanQuery: true, IndexName: attempt.index, Expression: expr, - }) + }, nil } } @@ -236,22 +174,21 @@ func (a *astExpr) determinePlausibleExecutionPlans(ctx *evalContext, info *model return nil, err } - plans = append(plans, &models.QueryExecutionPlan{ + return &models.QueryExecutionPlan{ CanQuery: false, Expression: expr, - }) - return plans, nil + }, nil } func (a *astExpr) evalToIR(ctx *evalContext, tableInfo *models.TableInfo) (irAtom, error) { return a.Root.evalToIR(ctx, tableInfo) } -func (a *astExpr) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astExpr) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { return a.Root.evalItem(ctx, item) } -func (a *astExpr) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astExpr) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { return a.Root.setEvalItem(ctx, item, value) } diff --git a/internal/dynamo-browse/models/queryexpr/atom.go b/internal/dynamo-browse/models/queryexpr/atom.go index 029f683..8b4487e 100644 --- a/internal/dynamo-browse/models/queryexpr/atom.go +++ b/internal/dynamo-browse/models/queryexpr/atom.go @@ -1,7 +1,8 @@ package queryexpr import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/pkg/errors" ) @@ -20,6 +21,15 @@ func (a *astAtom) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, er return nil, errors.New("unhandled atom case") } +func (a *astAtom) rightOperandDynamoValue() (types.AttributeValue, error) { + switch { + case a.Literal != nil: + return a.Literal.dynamoValue() + } + + return nil, errors.New("unhandled atom case") +} + func (a *astAtom) unqualifiedName() (string, bool) { switch { case a.Ref != nil: @@ -29,12 +39,12 @@ func (a *astAtom) unqualifiedName() (string, bool) { return "", false } -func (a *astAtom) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astAtom) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { switch { case a.Ref != nil: return a.Ref.evalItem(ctx, item) case a.Literal != nil: - return a.Literal.exprValue() + return a.Literal.dynamoValue() case a.Placeholder != nil: return a.Placeholder.evalItem(ctx, item) case a.Paren != nil: @@ -56,7 +66,7 @@ func (a *astAtom) canModifyItem(ctx *evalContext, item models.Item) bool { return false } -func (a *astAtom) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astAtom) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { switch { case a.Ref != nil: return a.Ref.setEvalItem(ctx, item, value) diff --git a/internal/dynamo-browse/models/queryexpr/between.go b/internal/dynamo-browse/models/queryexpr/between.go deleted file mode 100644 index 3e8efb7..0000000 --- a/internal/dynamo-browse/models/queryexpr/between.go +++ /dev/null @@ -1,155 +0,0 @@ -package queryexpr - -import ( - "fmt" - "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" -) - -func (a *astBetweenOp) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, error) { - leftIR, err := a.Ref.evalToIR(ctx, info) - if err != nil { - return nil, err - } - - if a.From == nil { - return leftIR, nil - } - - nameIR, isNameIR := leftIR.(nameIRAtom) - if !isNameIR { - return nil, OperandNotANameError(a.Ref.String()) - } - - fromIR, err := a.From.evalToIR(ctx, info) - if err != nil { - return nil, err - } - toIR, err := a.To.evalToIR(ctx, info) - if err != nil { - return nil, err - } - - fromOprIR, isFromOprIR := fromIR.(valueIRAtom) - if !isFromOprIR { - return nil, OperandNotAnOperandError{} - } - toOprIR, isToOprIR := toIR.(valueIRAtom) - if !isToOprIR { - return nil, OperandNotAnOperandError{} - } - - return irBetween{name: nameIR, from: fromOprIR, to: toOprIR}, nil -} - -func (a *astBetweenOp) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { - val, err := a.Ref.evalItem(ctx, item) - if a.From == nil { - return val, err - } - - fromIR, err := a.From.evalItem(ctx, item) - if err != nil { - return nil, err - } - - toIR, err := a.To.evalItem(ctx, item) - if err != nil { - return nil, err - } - - switch v := val.(type) { - case stringableExprValue: - fromNumVal, isFromNumVal := fromIR.(stringableExprValue) - if !isFromNumVal { - return nil, ValuesNotComparable{Left: val.asAttributeValue(), Right: fromIR.asAttributeValue()} - } - - toNumVal, isToNumVal := toIR.(stringableExprValue) - if !isToNumVal { - return nil, ValuesNotComparable{Left: val.asAttributeValue(), Right: toNumVal.asAttributeValue()} - } - - return boolExprValue(v.asString() >= fromNumVal.asString() && v.asString() <= toNumVal.asString()), nil - case numberableExprValue: - fromNumVal, isFromNumVal := fromIR.(numberableExprValue) - if !isFromNumVal { - return nil, ValuesNotComparable{Left: val.asAttributeValue(), Right: fromIR.asAttributeValue()} - } - - toNumVal, isToNumVal := toIR.(numberableExprValue) - if !isToNumVal { - return nil, ValuesNotComparable{Left: val.asAttributeValue(), Right: toNumVal.asAttributeValue()} - } - - fromCmp := v.asBigFloat().Cmp(fromNumVal.asBigFloat()) - toCmp := v.asBigFloat().Cmp(toNumVal.asBigFloat()) - - return boolExprValue(fromCmp >= 0 && toCmp <= 0), nil - } - return nil, InvalidTypeForBetweenError{TypeName: val.typeName()} -} - -func (a *astBetweenOp) canModifyItem(ctx *evalContext, item models.Item) bool { - if a.From != nil { - return false - } - return a.Ref.canModifyItem(ctx, item) -} - -func (a *astBetweenOp) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { - if a.From != nil { - return PathNotSettableError{} - } - return a.Ref.setEvalItem(ctx, item, value) -} - -func (a *astBetweenOp) deleteAttribute(ctx *evalContext, item models.Item) error { - if a.From != nil { - return PathNotSettableError{} - } - return a.Ref.deleteAttribute(ctx, item) -} - -func (a *astBetweenOp) String() string { - name := a.Ref.String() - if a.From != nil { - return fmt.Sprintf("%v between %v and %v", name, a.From.String(), a.To.String()) - } - return name -} - -type irBetween struct { - name nameIRAtom - from valueIRAtom - to valueIRAtom -} - -func (i irBetween) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { - nb := i.name.calcName(info) - fb := i.from.calcOperand(info) - tb := i.to.calcOperand(info) - - return nb.Between(fb, tb), nil -} - -func (i irBetween) canBeExecutedAsQuery(qci *queryCalcInfo) bool { - keyName := i.name.keyName() - if keyName == "" { - return false - } - - if keyName == qci.keysUnderTest.SortKey { - return qci.addKey(keyName) - } - - return false -} - -func (i irBetween) calcQueryForQuery() (expression.KeyConditionBuilder, error) { - nb := i.name.keyName() - fb := i.from.exprValue() - tb := i.to.exprValue() - - return expression.Key(nb).Between(buildExpressionFromValue(fb), buildExpressionFromValue(tb)), nil -} diff --git a/internal/dynamo-browse/models/queryexpr/boolnot.go b/internal/dynamo-browse/models/queryexpr/boolnot.go index 8c5f7d2..9c71f79 100644 --- a/internal/dynamo-browse/models/queryexpr/boolnot.go +++ b/internal/dynamo-browse/models/queryexpr/boolnot.go @@ -2,7 +2,8 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" "strings" ) @@ -19,7 +20,7 @@ func (a *astBooleanNot) evalToIR(ctx *evalContext, tableInfo *models.TableInfo) return &irBoolNot{atom: irNode}, nil } -func (a *astBooleanNot) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astBooleanNot) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { val, err := a.Operand.evalItem(ctx, item) if err != nil { return nil, err @@ -29,7 +30,7 @@ func (a *astBooleanNot) evalItem(ctx *evalContext, item models.Item) (exprValue, return val, nil } - return boolExprValue(!isAttributeTrue(val)), nil + return &types.AttributeValueMemberBOOL{Value: !isAttributeTrue(val)}, nil } func (a *astBooleanNot) canModifyItem(ctx *evalContext, item models.Item) bool { @@ -39,7 +40,7 @@ func (a *astBooleanNot) canModifyItem(ctx *evalContext, item models.Item) bool { return a.Operand.canModifyItem(ctx, item) } -func (a *astBooleanNot) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astBooleanNot) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if a.HasNot { return PathNotSettableError{} } diff --git a/internal/dynamo-browse/models/queryexpr/builtins.go b/internal/dynamo-browse/models/queryexpr/builtins.go index d1a072a..a42f0cc 100644 --- a/internal/dynamo-browse/models/queryexpr/builtins.go +++ b/internal/dynamo-browse/models/queryexpr/builtins.go @@ -2,127 +2,38 @@ package queryexpr import ( "context" - + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/pkg/errors" + "strconv" ) -type nativeFunc func(ctx context.Context, args []exprValue) (exprValue, error) +type nativeFunc func(ctx context.Context, args []types.AttributeValue) (types.AttributeValue, error) var nativeFuncs = map[string]nativeFunc{ - "size": func(ctx context.Context, args []exprValue) (exprValue, error) { + "size": func(ctx context.Context, args []types.AttributeValue) (types.AttributeValue, error) { if len(args) != 1 { return nil, InvalidArgumentNumberError{Name: "size", Expected: 1, Actual: len(args)} } var l int switch t := args[0].(type) { - case stringExprValue: - l = len(t) - case mappableExprValue: - l = t.len() - case slicableExprValue: - l = t.len() + case *types.AttributeValueMemberB: + l = len(t.Value) + case *types.AttributeValueMemberS: + l = len(t.Value) + case *types.AttributeValueMemberL: + l = len(t.Value) + case *types.AttributeValueMemberM: + l = len(t.Value) + case *types.AttributeValueMemberSS: + l = len(t.Value) + case *types.AttributeValueMemberNS: + l = len(t.Value) + case *types.AttributeValueMemberBS: + l = len(t.Value) default: return nil, errors.New("cannot take size of arg") } - return int64ExprValue(l), nil - }, - - "range": func(ctx context.Context, args []exprValue) (exprValue, error) { - if len(args) != 2 { - return nil, InvalidArgumentNumberError{Name: "range", Expected: 2, Actual: len(args)} - } - - xVal, isXNum := args[0].(numberableExprValue) - if !isXNum { - return nil, InvalidArgumentTypeError{Name: "range", ArgIndex: 0, Expected: "N"} - } - yVal, isYNum := args[1].(numberableExprValue) - if !isYNum { - return nil, InvalidArgumentTypeError{Name: "range", ArgIndex: 1, Expected: "N"} - } - - xInt, _ := xVal.asBigFloat().Int64() - yInt, _ := yVal.asBigFloat().Int64() - xs := make([]exprValue, 0) - for x := xInt; x <= yInt; x++ { - xs = append(xs, int64ExprValue(x)) - } - return listExprValue(xs), nil - }, - - "marked": func(ctx context.Context, args []exprValue) (exprValue, error) { - if len(args) != 1 { - return nil, InvalidArgumentNumberError{Name: "marked", Expected: 1, Actual: len(args)} - } - - fieldName, ok := args[0].(stringableExprValue) - if !ok { - return nil, InvalidArgumentTypeError{Name: "marked", ArgIndex: 0, Expected: "S"} - } - - rs := currentResultSetFromContext(ctx) - if rs == nil { - return listExprValue{}, nil - } - - var items = []exprValue{} - for i, itm := range rs.Items() { - if !rs.Marked(i) { - continue - } - - attr, hasAttr := itm[fieldName.asString()] - if !hasAttr { - continue - } - - exprAttrValue, err := newExprValueFromAttributeValue(attr) - if err != nil { - return nil, errors.Wrapf(err, "marked(): item %d, attr %v", i, fieldName.asString()) - } - - items = append(items, exprAttrValue) - } - return listExprValue(items), nil - }, - - "_x_now": func(ctx context.Context, args []exprValue) (exprValue, error) { - now := timeSourceFromContext(ctx).now().Unix() - return int64ExprValue(now), nil - }, - - "_x_add": func(ctx context.Context, args []exprValue) (exprValue, error) { - if len(args) != 2 { - return nil, InvalidArgumentNumberError{Name: "_x_add", Expected: 2, Actual: len(args)} - } - - xVal, isXNum := args[0].(numberableExprValue) - if !isXNum { - return nil, InvalidArgumentTypeError{Name: "_x_add", ArgIndex: 0, Expected: "N"} - } - yVal, isYNum := args[1].(numberableExprValue) - if !isYNum { - return nil, InvalidArgumentTypeError{Name: "_x_add", ArgIndex: 1, Expected: "N"} - } - - return bigNumExprValue{num: xVal.asBigFloat().Add(xVal.asBigFloat(), yVal.asBigFloat())}, nil - }, - - "_x_concat": func(ctx context.Context, args []exprValue) (exprValue, error) { - if len(args) != 2 { - return nil, InvalidArgumentNumberError{Name: "_x_concat", Expected: 2, Actual: len(args)} - } - - xVal, isXNum := args[0].(stringableExprValue) - if !isXNum { - return nil, InvalidArgumentTypeError{Name: "_x_concat", ArgIndex: 0, Expected: "S"} - } - yVal, isYNum := args[1].(stringableExprValue) - if !isYNum { - return nil, InvalidArgumentTypeError{Name: "_x_concat", ArgIndex: 1, Expected: "S"} - } - - return stringExprValue(xVal.asString() + yVal.asString()), nil + return &types.AttributeValueMemberN{Value: strconv.Itoa(l)}, nil }, } diff --git a/internal/dynamo-browse/models/queryexpr/comp.go b/internal/dynamo-browse/models/queryexpr/comp.go index 29079c3..31e9e4d 100644 --- a/internal/dynamo-browse/models/queryexpr/comp.go +++ b/internal/dynamo-browse/models/queryexpr/comp.go @@ -2,8 +2,9 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "github.com/pkg/errors" ) @@ -46,7 +47,7 @@ func (a *astComparisonOp) evalToIR(ctx *evalContext, info *models.TableInfo) (ir return irGenericCmp{leftOpr, rightOpr, cmpType}, nil } -func (a *astComparisonOp) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astComparisonOp) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { left, err := a.Ref.evalItem(ctx, item) if err != nil { return nil, err @@ -60,21 +61,20 @@ func (a *astComparisonOp) evalItem(ctx *evalContext, item models.Item) (exprValu return nil, err } - // TODO: use expr value here - cmp, isComparable := attrutils.CompareScalarAttributes(left.asAttributeValue(), right.asAttributeValue()) + cmp, isComparable := attrutils.CompareScalarAttributes(left, right) if !isComparable { - return nil, ValuesNotComparable{Left: left.asAttributeValue(), Right: right.asAttributeValue()} + return nil, ValuesNotComparable{Left: left, Right: right} } switch opToCmdType[a.Op] { case cmpTypeLt: - return boolExprValue(cmp < 0), nil + return &types.AttributeValueMemberBOOL{Value: cmp < 0}, nil case cmpTypeLe: - return boolExprValue(cmp <= 0), nil + return &types.AttributeValueMemberBOOL{Value: cmp <= 0}, nil case cmpTypeGt: - return boolExprValue(cmp > 0), nil + return &types.AttributeValueMemberBOOL{Value: cmp > 0}, nil case cmpTypeGe: - return boolExprValue(cmp >= 0), nil + return &types.AttributeValueMemberBOOL{Value: cmp >= 0}, nil } return nil, errors.Errorf("unrecognised operator: %v", a.Op) } @@ -86,7 +86,7 @@ func (a *astComparisonOp) canModifyItem(ctx *evalContext, item models.Item) bool return a.Ref.canModifyItem(ctx, item) } -func (a *astComparisonOp) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astComparisonOp) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if a.Op != "" { return PathNotSettableError{} } @@ -143,34 +143,34 @@ func (a irKeyFieldCmp) canBeExecutedAsQuery(qci *queryCalcInfo) bool { func (a irKeyFieldCmp) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { nb := a.name.calcName(info) - vb := a.value.exprValue() + vb := a.value.goValue() switch a.cmpType { case cmpTypeLt: - return nb.LessThan(buildExpressionFromValue(vb)), nil + return nb.LessThan(expression.Value(vb)), nil case cmpTypeLe: - return nb.LessThanEqual(buildExpressionFromValue(vb)), nil + return nb.LessThanEqual(expression.Value(vb)), nil case cmpTypeGt: - return nb.GreaterThan(buildExpressionFromValue(vb)), nil + return nb.GreaterThan(expression.Value(vb)), nil case cmpTypeGe: - return nb.GreaterThanEqual(buildExpressionFromValue(vb)), nil + return nb.GreaterThanEqual(expression.Value(vb)), nil } return expression.ConditionBuilder{}, errors.New("unsupported cmp type") } func (a irKeyFieldCmp) calcQueryForQuery() (expression.KeyConditionBuilder, error) { keyName := a.name.keyName() - vb := a.value.exprValue() + vb := a.value.goValue() switch a.cmpType { case cmpTypeLt: - return expression.Key(keyName).LessThan(buildExpressionFromValue(vb)), nil + return expression.Key(keyName).LessThan(expression.Value(vb)), nil case cmpTypeLe: - return expression.Key(keyName).LessThanEqual(buildExpressionFromValue(vb)), nil + return expression.Key(keyName).LessThanEqual(expression.Value(vb)), nil case cmpTypeGt: - return expression.Key(keyName).GreaterThan(buildExpressionFromValue(vb)), nil + return expression.Key(keyName).GreaterThan(expression.Value(vb)), nil case cmpTypeGe: - return expression.Key(keyName).GreaterThanEqual(buildExpressionFromValue(vb)), nil + return expression.Key(keyName).GreaterThanEqual(expression.Value(vb)), nil } return expression.KeyConditionBuilder{}, errors.New("unsupported cmp type") } diff --git a/internal/dynamo-browse/models/queryexpr/conj.go b/internal/dynamo-browse/models/queryexpr/conj.go index 3890a1f..dd7f4a2 100644 --- a/internal/dynamo-browse/models/queryexpr/conj.go +++ b/internal/dynamo-browse/models/queryexpr/conj.go @@ -2,8 +2,8 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "math/big" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" "strings" ) @@ -36,7 +36,7 @@ func (a *astConjunction) evalToIR(ctx *evalContext, tableInfo *models.TableInfo) return &irMultiConjunction{atoms: atoms}, nil } -func (a *astConjunction) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astConjunction) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { val, err := a.Operands[0].evalItem(ctx, item) if err != nil { return nil, err @@ -47,7 +47,7 @@ func (a *astConjunction) evalItem(ctx *evalContext, item models.Item) (exprValue for _, opr := range a.Operands[1:] { if !isAttributeTrue(val) { - return boolExprValue(false), nil + return &types.AttributeValueMemberBOOL{Value: false}, nil } val, err = opr.evalItem(ctx, item) @@ -56,7 +56,7 @@ func (a *astConjunction) evalItem(ctx *evalContext, item models.Item) (exprValue } } - return boolExprValue(isAttributeTrue(val)), nil + return &types.AttributeValueMemberBOOL{Value: isAttributeTrue(val)}, nil } func (a *astConjunction) canModifyItem(ctx *evalContext, item models.Item) bool { @@ -67,7 +67,7 @@ func (a *astConjunction) canModifyItem(ctx *evalContext, item models.Item) bool return false } -func (a *astConjunction) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astConjunction) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if len(a.Operands) == 1 { return a.Operands[0].setEvalItem(ctx, item, value) } @@ -168,16 +168,16 @@ func (d *irMultiConjunction) calcQueryForScan(info *models.TableInfo) (expressio return conjExpr, nil } -func isAttributeTrue(attr exprValue) bool { +func isAttributeTrue(attr types.AttributeValue) bool { switch val := attr.(type) { - case nullExprValue: + case *types.AttributeValueMemberS: + return val.Value != "" + case *types.AttributeValueMemberN: + return val.Value != "0" + case *types.AttributeValueMemberBOOL: + return val.Value + case *types.AttributeValueMemberNULL: return false - case boolExprValue: - return bool(val) - case stringableExprValue: - return val.asString() != "" - case numberableExprValue: - return val.asBigFloat().Cmp(&big.Float{}) != 0 } return true } diff --git a/internal/dynamo-browse/models/queryexpr/context.go b/internal/dynamo-browse/models/queryexpr/context.go deleted file mode 100644 index 08bf1e1..0000000 --- a/internal/dynamo-browse/models/queryexpr/context.go +++ /dev/null @@ -1,40 +0,0 @@ -package queryexpr - -import ( - "context" - "time" - - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" -) - -type timeSource interface { - now() time.Time -} - -type defaultTimeSource struct{} - -func (tds defaultTimeSource) now() time.Time { - return time.Now() -} - -type timeSourceContextKeyType struct{} - -var timeSourceContextKey = timeSourceContextKeyType{} - -func timeSourceFromContext(ctx context.Context) timeSource { - if tts, ok := ctx.Value(timeSourceContextKey).(timeSource); ok { - return tts - } - return defaultTimeSource{} -} - -type currentResultSetContextKeyType struct{} - -var currentResultSetContextKey = currentResultSetContextKeyType{} - -func currentResultSetFromContext(ctx context.Context) *models.ResultSet { - if crs, ok := ctx.Value(currentResultSetContextKey).(*models.ResultSet); ok { - return crs - } - return nil -} diff --git a/internal/dynamo-browse/models/queryexpr/disj.go b/internal/dynamo-browse/models/queryexpr/disj.go index 1a8a5f1..913a503 100644 --- a/internal/dynamo-browse/models/queryexpr/disj.go +++ b/internal/dynamo-browse/models/queryexpr/disj.go @@ -2,7 +2,8 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" "strings" ) @@ -23,7 +24,7 @@ func (a *astDisjunction) evalToIR(ctx *evalContext, tableInfo *models.TableInfo) return &irDisjunction{conj: conj}, nil } -func (a *astDisjunction) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astDisjunction) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { val, err := a.Operands[0].evalItem(ctx, item) if err != nil { return nil, err @@ -34,7 +35,7 @@ func (a *astDisjunction) evalItem(ctx *evalContext, item models.Item) (exprValue for _, opr := range a.Operands[1:] { if isAttributeTrue(val) { - return boolExprValue(true), nil + return &types.AttributeValueMemberBOOL{Value: true}, nil } val, err = opr.evalItem(ctx, item) @@ -43,7 +44,7 @@ func (a *astDisjunction) evalItem(ctx *evalContext, item models.Item) (exprValue } } - return boolExprValue(isAttributeTrue(val)), nil + return &types.AttributeValueMemberBOOL{Value: isAttributeTrue(val)}, nil } func (a *astDisjunction) canModifyItem(ctx *evalContext, item models.Item) bool { @@ -54,7 +55,7 @@ func (a *astDisjunction) canModifyItem(ctx *evalContext, item models.Item) bool return false } -func (a *astDisjunction) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astDisjunction) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if len(a.Operands) == 1 { return a.Operands[0].setEvalItem(ctx, item, value) } diff --git a/internal/dynamo-browse/models/queryexpr/dot.go b/internal/dynamo-browse/models/queryexpr/dot.go index cdb9412..d2c18fc 100644 --- a/internal/dynamo-browse/models/queryexpr/dot.go +++ b/internal/dynamo-browse/models/queryexpr/dot.go @@ -3,7 +3,8 @@ package queryexpr import ( "fmt" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" "strings" ) @@ -15,21 +16,21 @@ func (dt *astRef) unqualifiedName() (string, bool) { return dt.Name, true } -func (dt *astRef) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (dt *astRef) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { res, hasV := item[dt.Name] if !hasV { - return undefinedExprValue{}, nil + return nil, nil } - return newExprValueFromAttributeValue(res) + return res, nil } func (dt *astRef) canModifyItem(ctx *evalContext, item models.Item) bool { return true } -func (dt *astRef) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { - item[dt.Name] = value.asAttributeValue() +func (dt *astRef) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { + item[dt.Name] = value return nil } @@ -70,7 +71,7 @@ func (i irNamePath) calcName(info *models.TableInfo) expression.NameBuilder { switch v := qual.(type) { case string: fullName.WriteString("." + v) - case int64: + case int: fullName.WriteString(fmt.Sprintf("[%v]", qual)) } } diff --git a/internal/dynamo-browse/models/queryexpr/equality.go b/internal/dynamo-browse/models/queryexpr/equality.go index f67803b..702754b 100644 --- a/internal/dynamo-browse/models/queryexpr/equality.go +++ b/internal/dynamo-browse/models/queryexpr/equality.go @@ -2,8 +2,9 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "github.com/pkg/errors" "strings" ) @@ -58,7 +59,7 @@ func (a *astEqualityOp) evalToIR(ctx *evalContext, info *models.TableInfo) (irAt return nil, errors.Errorf("unrecognised operator: %v", a.Op) } -func (a *astEqualityOp) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astEqualityOp) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { left, err := a.Ref.evalItem(ctx, item) if err != nil { return nil, err @@ -75,28 +76,28 @@ func (a *astEqualityOp) evalItem(ctx *evalContext, item models.Item) (exprValue, switch a.Op { case "=": - cmp, isComparable := attrutils.CompareScalarAttributes(left.asAttributeValue(), right.asAttributeValue()) + cmp, isComparable := attrutils.CompareScalarAttributes(left, right) if !isComparable { - return nil, ValuesNotComparable{Left: left.asAttributeValue(), Right: right.asAttributeValue()} + return nil, ValuesNotComparable{Left: left, Right: right} } - return boolExprValue(cmp == 0), nil + return &types.AttributeValueMemberBOOL{Value: cmp == 0}, nil case "!=": - cmp, isComparable := attrutils.CompareScalarAttributes(left.asAttributeValue(), right.asAttributeValue()) + cmp, isComparable := attrutils.CompareScalarAttributes(left, right) if !isComparable { - return nil, ValuesNotComparable{Left: left.asAttributeValue(), Right: right.asAttributeValue()} + return nil, ValuesNotComparable{Left: left, Right: right} } - return boolExprValue(cmp != 0), nil + return &types.AttributeValueMemberBOOL{Value: cmp != 0}, nil case "^=": - strValue, isStrValue := right.(stringableExprValue) + strValue, isStrValue := right.(*types.AttributeValueMemberS) if !isStrValue { return nil, errors.New("operand '^=' must be string") } - leftAsStr, canBeString := left.(stringableExprValue) + leftAsStr, canBeString := attrutils.AttributeToString(left) if !canBeString { - return nil, ValueNotConvertableToString{Val: left.asAttributeValue()} + return nil, ValueNotConvertableToString{Val: left} } - return boolExprValue(strings.HasPrefix(leftAsStr.asString(), strValue.asString())), nil + return &types.AttributeValueMemberBOOL{Value: strings.HasPrefix(leftAsStr, strValue.Value)}, nil } return nil, errors.Errorf("unrecognised operator: %v", a.Op) @@ -109,7 +110,7 @@ func (a *astEqualityOp) canModifyItem(ctx *evalContext, item models.Item) bool { return a.Ref.canModifyItem(ctx, item) } -func (a *astEqualityOp) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astEqualityOp) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if a.Op != "" { return PathNotSettableError{} } @@ -156,8 +157,8 @@ func (a irKeyFieldEq) calcQueryForScan(info *models.TableInfo) (expression.Condi } func (a irKeyFieldEq) calcQueryForQuery() (expression.KeyConditionBuilder, error) { - vb := a.value.exprValue() - return expression.Key(a.name.keyName()).Equal(buildExpressionFromValue(vb)), nil + vb := a.value.goValue() + return expression.Key(a.name.keyName()).Equal(expression.Value(vb)), nil } type irGenericEq struct { @@ -202,21 +203,21 @@ func (a irFieldBeginsWith) canBeExecutedAsQuery(qci *queryCalcInfo) bool { func (a irFieldBeginsWith) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { nb := a.name.calcName(info) - vb := a.value.exprValue() - strValue, isStrValue := vb.(stringableExprValue) + vb := a.value.goValue() + strValue, isStrValue := vb.(string) if !isStrValue { return expression.ConditionBuilder{}, errors.New("operand '^=' must be string") } - return nb.BeginsWith(strValue.asString()), nil + return nb.BeginsWith(strValue), nil } func (a irFieldBeginsWith) calcQueryForQuery() (expression.KeyConditionBuilder, error) { - vb := a.value.exprValue() - strValue, isStrValue := vb.(stringableExprValue) + vb := a.value.goValue() + strValue, isStrValue := vb.(string) if !isStrValue { return expression.KeyConditionBuilder{}, errors.New("operand '^=' must be string") } - return expression.Key(a.name.keyName()).BeginsWith(strValue.asString()), nil + return expression.Key(a.name.keyName()).BeginsWith(strValue), nil } diff --git a/internal/dynamo-browse/models/queryexpr/errors.go b/internal/dynamo-browse/models/queryexpr/errors.go index 852aaef..59f1ddb 100644 --- a/internal/dynamo-browse/models/queryexpr/errors.go +++ b/internal/dynamo-browse/models/queryexpr/errors.go @@ -3,8 +3,8 @@ package queryexpr import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/itemrender" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" + "github.com/lmika/audax/internal/dynamo-browse/models/itemrender" "strings" ) @@ -69,10 +69,6 @@ type ValueNotConvertableToString struct { func (n ValueNotConvertableToString) Error() string { render := itemrender.ToRenderer(n.Val) - if render == nil { - return "nil value is not convertable to string" - } - return fmt.Sprintf("values '%v', type %v, is not convertable to string", render.StringValue(), render.TypeName()) } @@ -102,14 +98,6 @@ func (n InvalidTypeForIsError) Error() string { return "invalid type for 'is': " + n.TypeName } -type InvalidTypeForBetweenError struct { - TypeName string -} - -func (n InvalidTypeForBetweenError) Error() string { - return "invalid type for 'between': " + n.TypeName -} - type InvalidArgumentNumberError struct { Name string Expected int @@ -120,16 +108,6 @@ func (e InvalidArgumentNumberError) Error() string { return fmt.Sprintf("function '%v' expected %v args but received %v", e.Name, e.Expected, e.Actual) } -type InvalidArgumentTypeError struct { - Name string - ArgIndex int - Expected string -} - -func (e InvalidArgumentTypeError) Error() string { - return fmt.Sprintf("function '%v' expected arg %v to be of type %v", e.Name, e.ArgIndex, e.Expected) -} - type UnrecognisedFunctionError struct { Name string } @@ -159,20 +137,3 @@ type ValueNotUsableAsASubref struct { func (e ValueNotUsableAsASubref) Error() string { return "value cannot be used as a subref" } - -type MultiplePlansWithIndexError struct { - PossibleIndices []string -} - -func (e MultiplePlansWithIndexError) Error() string { - return fmt.Sprintf("multiple plans with index found. Specify index or scan with 'using' clause: possible indices are %v", e.PossibleIndices) -} - -type NoPlausiblePlanWithIndexError struct { - PreferredIndex string - PossibleIndices []string -} - -func (e NoPlausiblePlanWithIndexError) Error() string { - return fmt.Sprintf("no plan with index '%v' found: possible indices are %v", e.PreferredIndex, e.PossibleIndices) -} diff --git a/internal/dynamo-browse/models/queryexpr/expr.go b/internal/dynamo-browse/models/queryexpr/expr.go index 5a263cc..aed149f 100644 --- a/internal/dynamo-browse/models/queryexpr/expr.go +++ b/internal/dynamo-browse/models/queryexpr/expr.go @@ -3,32 +3,25 @@ package queryexpr import ( "bytes" "encoding/gob" - "hash/fnv" - "io" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrcodec" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/attrcodec" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "github.com/pkg/errors" "golang.org/x/exp/maps" "golang.org/x/exp/slices" + "hash/fnv" + "io" ) type QueryExpr struct { - ast *astExpr - index string - names map[string]string - values map[string]types.AttributeValue - currentResultSet *models.ResultSet - - // tests fields only - timeSource timeSource + ast *astExpr + names map[string]string + values map[string]types.AttributeValue } type serializedExpr struct { Expr string - Index string Names map[string]string Values []byte } @@ -46,7 +39,6 @@ func DeserializeFrom(r io.Reader) (*QueryExpr, error) { } qe.names = se.Names - qe.index = se.Index if len(se.Values) > 0 { vals, err := attrcodec.NewDecoder(bytes.NewReader(se.Values)).Decode() @@ -64,7 +56,7 @@ func DeserializeFrom(r io.Reader) (*QueryExpr, error) { } func (md *QueryExpr) SerializeTo(w io.Writer) error { - se := serializedExpr{Expr: md.String(), Index: md.index, Names: md.names} + se := serializedExpr{Expr: md.String(), Names: md.names} if md.values != nil { var bts bytes.Buffer if err := attrcodec.NewEncoder(&bts).Encode(&types.AttributeValueMemberM{Value: md.values}); err != nil { @@ -98,7 +90,6 @@ func (md *QueryExpr) Equal(other *QueryExpr) bool { } return md.ast.String() == other.ast.String() && - md.index == other.index && maps.Equal(md.names, other.names) && maps.EqualFunc(md.values, md.values, attrutils.Equals) } @@ -113,7 +104,6 @@ func (md *QueryExpr) HashCode() uint64 { h := fnv.New64a() h.Write([]byte(md.ast.String())) - h.Write([]byte(md.index)) // the names must be in sorted order to maintain consistant key ordering if len(md.names) > 0 { @@ -143,11 +133,9 @@ func (md *QueryExpr) HashCode() uint64 { func (md *QueryExpr) WithNameParams(value map[string]string) *QueryExpr { return &QueryExpr{ - ast: md.ast, - index: md.index, - names: value, - values: md.values, - currentResultSet: md.currentResultSet, + ast: md.ast, + names: value, + values: md.values, } } @@ -169,47 +157,18 @@ func (md *QueryExpr) ValueParamOrNil(name string) types.AttributeValue { func (md *QueryExpr) WithValueParams(value map[string]types.AttributeValue) *QueryExpr { return &QueryExpr{ - ast: md.ast, - index: md.index, - names: md.names, - values: value, - currentResultSet: md.currentResultSet, - } -} - -func (md *QueryExpr) WithIndex(index string) *QueryExpr { - return &QueryExpr{ - ast: md.ast, - index: index, - names: md.names, - values: md.values, - currentResultSet: md.currentResultSet, - } -} - -func (md *QueryExpr) WithCurrentResultSet(currentResultSet *models.ResultSet) *QueryExpr { - return &QueryExpr{ - ast: md.ast, - index: md.index, - names: md.names, - values: md.values, - currentResultSet: currentResultSet, + ast: md.ast, + names: md.names, + values: value, } } func (md *QueryExpr) Plan(tableInfo *models.TableInfo) (*models.QueryExecutionPlan, error) { - return md.ast.calcQuery(md.evalContext(), tableInfo, md.index) + return md.ast.calcQuery(md.evalContext(), tableInfo) } func (md *QueryExpr) EvalItem(item models.Item) (types.AttributeValue, error) { - val, err := md.ast.evalItem(md.evalContext(), item) - if err != nil { - return nil, err - } - if val == nil { - return nil, nil - } - return val.asAttributeValue(), nil + return md.ast.evalItem(md.evalContext(), item) } func (md *QueryExpr) DeleteAttribute(item models.Item) error { @@ -217,11 +176,7 @@ func (md *QueryExpr) DeleteAttribute(item models.Item) error { } func (md *QueryExpr) SetEvalItem(item models.Item, newValue types.AttributeValue) error { - val, err := newExprValueFromAttributeValue(newValue) - if err != nil { - return err - } - return md.ast.setEvalItem(md.evalContext(), item, val) + return md.ast.setEvalItem(md.evalContext(), item, newValue) } func (md *QueryExpr) IsModifiablePath(item models.Item) bool { @@ -232,7 +187,6 @@ func (md *QueryExpr) evalContext() *evalContext { return &evalContext{ namePlaceholders: md.names, valuePlaceholders: md.values, - ctxResultSet: md.currentResultSet, } } @@ -283,8 +237,6 @@ type evalContext struct { nameLookup func(string) (string, bool) valuePlaceholders map[string]types.AttributeValue valueLookup func(string) (types.AttributeValue, bool) - timeSource timeSource - ctxResultSet *models.ResultSet } func (ec *evalContext) lookupName(name string) (string, bool) { @@ -312,10 +264,3 @@ func (ec *evalContext) lookupValue(name string) (types.AttributeValue, bool) { return nil, false } - -func (ec *evalContext) getTimeSource() timeSource { - if ts := ec.timeSource; ts != nil { - return ts - } - return defaultTimeSource{} -} diff --git a/internal/dynamo-browse/models/queryexpr/expr_test.go b/internal/dynamo-browse/models/queryexpr/expr_test.go index 2352329..5575f14 100644 --- a/internal/dynamo-browse/models/queryexpr/expr_test.go +++ b/internal/dynamo-browse/models/queryexpr/expr_test.go @@ -3,14 +3,12 @@ package queryexpr_test import ( "bytes" "fmt" - "testing" - "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" + "testing" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/stretchr/testify/assert" ) @@ -36,13 +34,6 @@ func TestModExpr_Query(t *testing.T) { SortKey: "sk", }, }, - { - Name: "with-apples-and-oranges", - Keys: models.KeyAttribute{ - PartitionKey: "apples", - SortKey: "oranges", - }, - }, }, } @@ -53,11 +44,6 @@ func TestModExpr_Query(t *testing.T) { `#0 = :0`, exprNameIsString(0, 0, "pk", "prefix"), ), - //scanCase("when request pk is fixed (reverse)", - // `prefix="pk"`, - // `#0 = :0`, - // exprNameIsString(0, 0, "pk", "prefix"), - //), scanCase("when request pk is fixed in parens #1", `(pk="prefix")`, `#0 = :0`, @@ -126,13 +112,6 @@ func TestModExpr_Query(t *testing.T) { exprNameIsString(0, 0, "pk", "prefix"), exprNameIsNumber(1, 1, "sk", "100"), ), - scanCase("when request pk is equals and sk is greater or equal to", - `pk="prefix" and sk between 100 and 200`, - `(#0 = :0) AND (#1 BETWEEN :1 AND :2)`, - exprNameIsString(0, 0, "pk", "prefix"), - exprNameIsNumber(1, 1, "sk", "100"), - exprValueIsNumber(2, "200"), - ), scanCase("with placeholders", `:partition=$valuePrefix and :sort=$valueAnother`, @@ -170,13 +149,6 @@ func TestModExpr_Query(t *testing.T) { exprNameIsString(0, 0, "color", "yellow"), exprNameIsString(1, 1, "shade", "dark"), ), - - // Function calls - scanCase("use the value of fn call in query", - `pk = _x_concat("Hello ", "world")`, - `#0 = :0`, - exprNameIsString(0, 0, "pk", "Hello world"), - ), } for _, scenario := range scenarios { @@ -266,13 +238,6 @@ func TestModExpr_Query(t *testing.T) { exprNameIsString(0, 0, "pk", "prefix"), ), - scanCase("with between", `pk between "a" and "z"`, - `#0 BETWEEN :0 AND :1`, - exprName(0, "pk"), - exprValueIsString(0, "a"), - exprValueIsString(1, "z"), - ), - scanCase("with in", `pk in ("alpha", "bravo", "charlie")`, `#0 IN (:0, :1, :2)`, exprName(0, "pk"), @@ -409,53 +374,6 @@ func TestModExpr_Query(t *testing.T) { }) } }) - - t.Run("with index clash", func(t *testing.T) { - t.Run("should return error if attempt to run query with two indices that can be chosen", func(t *testing.T) { - modExpr, err := queryexpr.Parse(`apples="this"`) - assert.NoError(t, err) - - _, err = modExpr.Plan(tableInfo) - assert.Error(t, err) - }) - - t.Run("should run as scan if explicitly forced to", func(t *testing.T) { - modExpr, err := queryexpr.Parse(`apples="this" using scan`) - assert.NoError(t, err) - - plan, err := modExpr.Plan(tableInfo) - assert.NoError(t, err) - assert.False(t, plan.CanQuery) - }) - - t.Run("should run as query with the 'with-apples' index", func(t *testing.T) { - modExpr, err := queryexpr.Parse(`apples="this" using index("with-apples")`) - assert.NoError(t, err) - - plan, err := modExpr.Plan(tableInfo) - assert.NoError(t, err) - assert.True(t, plan.CanQuery) - assert.Equal(t, "with-apples", plan.IndexName) - }) - - t.Run("should run as query with the 'with-apples-and-oranges' index", func(t *testing.T) { - modExpr, err := queryexpr.Parse(`apples="this" using index("with-apples-and-oranges")`) - assert.NoError(t, err) - - plan, err := modExpr.Plan(tableInfo) - assert.NoError(t, err) - assert.True(t, plan.CanQuery) - assert.Equal(t, "with-apples-and-oranges", plan.IndexName) - }) - - t.Run("should return error if the chosen index can't be used", func(t *testing.T) { - modExpr, err := queryexpr.Parse(`apples="this" using index("with-missing")`) - assert.NoError(t, err) - - _, err = modExpr.Plan(tableInfo) - assert.Error(t, err) - }) - }) } func TestQueryExpr_EvalItem(t *testing.T) { @@ -477,9 +395,7 @@ func TestQueryExpr_EvalItem(t *testing.T) { &types.AttributeValueMemberN{Value: "7"}, }, }, - "one": &types.AttributeValueMemberN{Value: "1"}, "three": &types.AttributeValueMemberN{Value: "3"}, - "five": &types.AttributeValueMemberN{Value: "5"}, } ) @@ -517,20 +433,6 @@ func TestQueryExpr_EvalItem(t *testing.T) { {expr: "three < 2", expected: &types.AttributeValueMemberBOOL{Value: false}}, {expr: "three <= 2", expected: &types.AttributeValueMemberBOOL{Value: false}}, - // Between - {expr: "3 between 1 and 5", expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: "three between 1 and 5", expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: "three between one and five", expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: "three between 10 and 15", expected: &types.AttributeValueMemberBOOL{Value: false}}, - {expr: "three between 1 and 2", expected: &types.AttributeValueMemberBOOL{Value: false}}, - {expr: "8 between five and 10", expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: "three between 1 and 3", expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: "three between 3 and 5", expected: &types.AttributeValueMemberBOOL{Value: true}}, - - {expr: `"e" between "a" and "z"`, expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: `"eee" between "aaa" and "zzz"`, expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: `"e" between "between" and "beyond"`, expected: &types.AttributeValueMemberBOOL{Value: false}}, - // In {expr: "three in (2, 3, 4, 5)", expected: &types.AttributeValueMemberBOOL{Value: true}}, {expr: "three in (20, 30, 40)", expected: &types.AttributeValueMemberBOOL{Value: false}}, @@ -607,50 +509,6 @@ func TestQueryExpr_EvalItem(t *testing.T) { } }) - t.Run("functions", func(t *testing.T) { - timeNow := time.Now() - - contextResultSet := models.ResultSet{} - contextResultSet.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "1"}, "num": &types.AttributeValueMemberN{Value: "1"}}, - {"pk": &types.AttributeValueMemberS{Value: "2"}, "num": &types.AttributeValueMemberN{Value: "2"}}, - {"pk": &types.AttributeValueMemberS{Value: "3"}, "num": &types.AttributeValueMemberN{Value: "3"}}, - {"pk": &types.AttributeValueMemberS{Value: "4"}, "num": &types.AttributeValueMemberN{Value: "4"}}, - }) - contextResultSet.SetMark(0, true) - contextResultSet.SetMark(1, true) - - scenarios := []struct { - expr string - expected types.AttributeValue - }{ - // _x_now() -- unreleased version of now - {expr: `_x_now()`, expected: &types.AttributeValueMemberN{Value: fmt.Sprint(timeNow.Unix())}}, - - // Marked - {expr: `marked("num")`, expected: &types.AttributeValueMemberL{Value: []types.AttributeValue{ - &types.AttributeValueMemberN{Value: "1"}, - &types.AttributeValueMemberN{Value: "2"}, - }}}, - {expr: `one in marked("num")`, expected: &types.AttributeValueMemberBOOL{Value: true}}, - {expr: `three in marked("num")`, expected: &types.AttributeValueMemberBOOL{Value: false}}, - } - for _, scenario := range scenarios { - t.Run(scenario.expr, func(t *testing.T) { - modExpr, err := queryexpr.Parse(scenario.expr) - assert.NoError(t, err) - - res, err := modExpr. - WithTestTimeSource(timeNow). - WithCurrentResultSet(&contextResultSet). - EvalItem(item) - assert.NoError(t, err) - - assert.Equal(t, scenario.expected, res) - }) - } - }) - t.Run("unparsed expression", func(t *testing.T) { scenarios := []struct { expr string @@ -674,10 +532,6 @@ func TestQueryExpr_EvalItem(t *testing.T) { }{ {expr: `alpha.bravo`, expectedError: queryexpr.ValueNotAMapError([]string{"alpha", "bravo"})}, {expr: `charlie.tree.bla`, expectedError: queryexpr.ValueNotAMapError([]string{"charlie", "tree", "bla"})}, - - {expr: `missing="no"`, expectedError: queryexpr.ValuesNotComparable{Right: &types.AttributeValueMemberS{Value: "no"}}}, - {expr: `missing!="no"`, expectedError: queryexpr.ValuesNotComparable{Right: &types.AttributeValueMemberS{Value: "no"}}}, - {expr: `missing^="no"`, expectedError: queryexpr.ValueNotConvertableToString{nil}}, } for _, scenario := range scenarios { diff --git a/internal/dynamo-browse/models/queryexpr/fieldevaluator.go b/internal/dynamo-browse/models/queryexpr/fieldevaluator.go deleted file mode 100644 index 616e339..0000000 --- a/internal/dynamo-browse/models/queryexpr/fieldevaluator.go +++ /dev/null @@ -1,15 +0,0 @@ -package queryexpr - -import ( - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" -) - -type ExprFieldValueEvaluator struct { - Expr *QueryExpr -} - -func (sfve ExprFieldValueEvaluator) EvaluateForItem(item models.Item) types.AttributeValue { - val, _ := sfve.Expr.EvalItem(item) - return val -} diff --git a/internal/dynamo-browse/models/queryexpr/fncall.go b/internal/dynamo-browse/models/queryexpr/fncall.go index 5a092af..358071e 100644 --- a/internal/dynamo-browse/models/queryexpr/fncall.go +++ b/internal/dynamo-browse/models/queryexpr/fncall.go @@ -2,12 +2,12 @@ package queryexpr import ( "context" - "strings" - "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/pkg/errors" + "strings" ) func (a *astFunctionCall) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, error) { @@ -29,7 +29,7 @@ func (a *astFunctionCall) evalToIR(ctx *evalContext, info *models.TableInfo) (ir return nil, err } - // Special handling of functions that have IR nodes + // TODO: do this properly switch nameIr.keyName() { case "size": if len(irNodes) != 1 { @@ -40,34 +40,20 @@ func (a *astFunctionCall) evalToIR(ctx *evalContext, info *models.TableInfo) (ir return nil, OperandNotANameError(a.Args[0].String()) } return irSizeFn{name}, nil - } - - builtinFn, hasBuiltin := nativeFuncs[nameIr.keyName()] - if !hasBuiltin { - return nil, UnrecognisedFunctionError{Name: nameIr.keyName()} - } - - // Normal functions which are evaluated to regular values - irValues, err := sliceutils.MapWithError(irNodes, func(a irAtom) (exprValue, error) { - v, isV := a.(valueIRAtom) - if !isV { - return nil, errors.New("cannot use value") + case "range": + if len(irNodes) != 2 { + return nil, InvalidArgumentNumberError{Name: "range", Expected: 2, Actual: len(irNodes)} } - return v.exprValue(), nil - }) - if err != nil { - return nil, err - } - val, err := builtinFn(context.Background(), irValues) - if err != nil { - return nil, err + // TEMP + fromVal := irNodes[0].(valueIRAtom).goValue().(int64) + toVal := irNodes[1].(valueIRAtom).goValue().(int64) + return irRangeFn{fromVal, toVal}, nil } - - return irValue{value: val}, nil + return nil, UnrecognisedFunctionError{Name: nameIr.keyName()} } -func (a *astFunctionCall) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astFunctionCall) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { if !a.IsCall { return a.Caller.evalItem(ctx, item) } @@ -81,16 +67,14 @@ func (a *astFunctionCall) evalItem(ctx *evalContext, item models.Item) (exprValu return nil, UnrecognisedFunctionError{Name: name} } - args, err := sliceutils.MapWithError(a.Args, func(a *astExpr) (exprValue, error) { + args, err := sliceutils.MapWithError(a.Args, func(a *astExpr) (types.AttributeValue, error) { return a.evalItem(ctx, item) }) if err != nil { return nil, err } - cCtx := context.WithValue(context.Background(), timeSourceContextKey, ctx.timeSource) - cCtx = context.WithValue(cCtx, currentResultSetContextKey, ctx.ctxResultSet) - return fn(cCtx, args) + return fn(context.Background(), args) } func (a *astFunctionCall) canModifyItem(ctx *evalContext, item models.Item) bool { @@ -101,7 +85,7 @@ func (a *astFunctionCall) canModifyItem(ctx *evalContext, item models.Item) bool return a.Caller.canModifyItem(ctx, item) } -func (a *astFunctionCall) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astFunctionCall) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { // TODO: Should a function vall return an item? if a.IsCall { return PathNotSettableError{} @@ -163,15 +147,3 @@ func (i irRangeFn) calcGoValues(info *models.TableInfo) ([]any, error) { } return xs, nil } - -type multiValueFnResult struct { - items []any -} - -func (i multiValueFnResult) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { - return expression.ConditionBuilder{}, errors.New("cannot run as scan") -} - -func (i multiValueFnResult) calcGoValues(info *models.TableInfo) ([]any, error) { - return i.items, nil -} diff --git a/internal/dynamo-browse/models/queryexpr/helpers_test.go b/internal/dynamo-browse/models/queryexpr/helpers_test.go deleted file mode 100644 index fe495da..0000000 --- a/internal/dynamo-browse/models/queryexpr/helpers_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package queryexpr - -import ( - "time" -) - -type testTimeSource time.Time - -func (tds testTimeSource) now() time.Time { - return time.Time(tds) -} - -func (a *QueryExpr) WithTestTimeSource(timeNow time.Time) *QueryExpr { - a.timeSource = testTimeSource(timeNow) - return a -} - diff --git a/internal/dynamo-browse/models/queryexpr/in.go b/internal/dynamo-browse/models/queryexpr/in.go index 63c46cb..6a169d3 100644 --- a/internal/dynamo-browse/models/queryexpr/in.go +++ b/internal/dynamo-browse/models/queryexpr/in.go @@ -1,10 +1,13 @@ package queryexpr import ( + "bytes" + "fmt" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "github.com/pkg/errors" "strings" ) @@ -68,13 +71,6 @@ func (a *astIn) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, erro return nil, OperandNotANameError(a.Ref.String()) } ir = irContains{needle: lit, haystack: t} - case valueIRAtom: - nameIR, isNameIR := leftIR.(irNamePath) - if !isNameIR { - return nil, OperandNotANameError(a.Ref.String()) - } - - ir = irLiteralValues{name: nameIR, values: t} case oprIRAtom: nameIR, isNameIR := leftIR.(irNamePath) if !isNameIR { @@ -82,6 +78,13 @@ func (a *astIn) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, erro } ir = irIn{name: nameIR, values: []oprIRAtom{t}} + case multiValueIRAtom: + nameIR, isNameIR := leftIR.(irNamePath) + if !isNameIR { + return nil, OperandNotANameError(a.Ref.String()) + } + + ir = irLiteralValues{name: nameIR, values: t} default: return nil, OperandNotAnOperandError{} } @@ -93,7 +96,7 @@ func (a *astIn) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, erro return ir, nil } -func (a *astIn) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astIn) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { val, err := a.Ref.evalItem(ctx, item) if err != nil { return nil, err @@ -109,15 +112,14 @@ func (a *astIn) evalItem(ctx *evalContext, item models.Item) (exprValue, error) if err != nil { return nil, err } - // TODO: use native types here - cmp, isComparable := attrutils.CompareScalarAttributes(val.asAttributeValue(), evalOp.asAttributeValue()) + cmp, isComparable := attrutils.CompareScalarAttributes(val, evalOp) if !isComparable { continue } else if cmp == 0 { - return boolExprValue(true), nil + return &types.AttributeValueMemberBOOL{Value: true}, nil } } - return boolExprValue(false), nil + return &types.AttributeValueMemberBOOL{Value: false}, nil case a.SingleOperand != nil: evalOp, err := a.SingleOperand.evalItem(ctx, item) if err != nil { @@ -125,38 +127,69 @@ func (a *astIn) evalItem(ctx *evalContext, item models.Item) (exprValue, error) } switch t := evalOp.(type) { - case stringableExprValue: - str, canToStr := val.(stringableExprValue) + case *types.AttributeValueMemberS: + str, canToStr := attrutils.AttributeToString(val) if !canToStr { - return boolExprValue(false), nil + return &types.AttributeValueMemberBOOL{Value: false}, nil } - return boolExprValue(strings.Contains(t.asString(), str.asString())), nil - case slicableExprValue: - for i := 0; i < t.len(); i++ { - va, err := t.valueAt(i) - if err != nil { - return nil, err - } - - // TODO: use expr value types here - cmp, isComparable := attrutils.CompareScalarAttributes(val.asAttributeValue(), va.asAttributeValue()) + return &types.AttributeValueMemberBOOL{Value: strings.Contains(t.Value, str)}, nil + case *types.AttributeValueMemberL: + for _, listItem := range t.Value { + cmp, isComparable := attrutils.CompareScalarAttributes(val, listItem) if !isComparable { continue } else if cmp == 0 { - return boolExprValue(true), nil + return &types.AttributeValueMemberBOOL{Value: true}, nil } } - return boolExprValue(false), nil - case mappableExprValue: - str, canToStr := val.(stringableExprValue) + return &types.AttributeValueMemberBOOL{Value: false}, nil + case *types.AttributeValueMemberSS: + str, canToStr := attrutils.AttributeToString(val) if !canToStr { - return boolExprValue(false), nil + return &types.AttributeValueMemberBOOL{Value: false}, nil } - hasKey := t.hasKey(str.asString()) - return boolExprValue(hasKey), nil + + for _, listItem := range t.Value { + if str != listItem { + return &types.AttributeValueMemberBOOL{Value: false}, nil + } + } + return &types.AttributeValueMemberBOOL{Value: true}, nil + case *types.AttributeValueMemberBS: + b, isB := val.(*types.AttributeValueMemberB) + if !isB { + return &types.AttributeValueMemberBOOL{Value: false}, nil + } + + for _, listItem := range t.Value { + if !bytes.Equal(b.Value, listItem) { + return &types.AttributeValueMemberBOOL{Value: false}, nil + } + } + return &types.AttributeValueMemberBOOL{Value: true}, nil + case *types.AttributeValueMemberNS: + n, isN := val.(*types.AttributeValueMemberN) + if !isN { + return &types.AttributeValueMemberBOOL{Value: false}, nil + } + + for _, listItem := range t.Value { + // TODO: this is not actually right + if n.Value != listItem { + return &types.AttributeValueMemberBOOL{Value: false}, nil + } + } + return &types.AttributeValueMemberBOOL{Value: true}, nil + case *types.AttributeValueMemberM: + str, canToStr := attrutils.AttributeToString(val) + if !canToStr { + return &types.AttributeValueMemberBOOL{Value: false}, nil + } + _, hasItem := t.Value[str] + return &types.AttributeValueMemberBOOL{Value: hasItem}, nil } - return nil, ValuesNotInnableError{Val: evalOp.asAttributeValue()} + return nil, ValuesNotInnableError{Val: evalOp} } return nil, errors.New("internal error: unhandled 'in' case") } @@ -168,7 +201,7 @@ func (a *astIn) canModifyItem(ctx *evalContext, item models.Item) bool { return a.Ref.canModifyItem(ctx, item) } -func (a *astIn) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astIn) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if len(a.Operand) != 0 || a.SingleOperand != nil { return PathNotSettableError{} } @@ -230,38 +263,19 @@ func (i irIn) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuil type irLiteralValues struct { name nameIRAtom - values valueIRAtom + values multiValueIRAtom } -func (iv irLiteralValues) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { - if sliceable, isSliceable := iv.values.exprValue().(slicableExprValue); isSliceable { - if sliceable.len() == 1 { - va, err := sliceable.valueAt(0) - if err != nil { - return expression.ConditionBuilder{}, err - } - - return iv.name.calcName(info).In(buildExpressionFromValue(va)), nil - } else if sliceable.len() == 0 { - // name is not in an empty slice, so this branch always evaluates to false - // TODO: would be better to not even include this branch in some way? - return expression.Equal(expression.Value(false), expression.Value(true)), nil - } - - items := make([]expression.OperandBuilder, sliceable.len()) - for i := 0; i < sliceable.len(); i++ { - va, err := sliceable.valueAt(i) - if err != nil { - return expression.ConditionBuilder{}, err - } - - items[i] = buildExpressionFromValue(va) - } - - return iv.name.calcName(info).In(items[0], items[1:]...), nil +func (i irLiteralValues) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { + vals, err := i.values.calcGoValues(info) + if err != nil { + return expression.ConditionBuilder{}, err } - return iv.name.calcName(info).In(buildExpressionFromValue(iv.values.exprValue())), nil + oprValues := sliceutils.Map(vals, func(t any) expression.OperandBuilder { + return expression.Value(t) + }) + return i.name.calcName(info).In(oprValues[0], oprValues[1:]...), nil } type irContains struct { @@ -270,11 +284,8 @@ type irContains struct { } func (i irContains) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { - strNeedle, isString := i.needle.exprValue().(stringableExprValue) - if !isString { - return expression.ConditionBuilder{}, errors.New("value cannot be converted to string") - } - + needle := i.needle.goValue() haystack := i.haystack.calcName(info) - return haystack.Contains(strNeedle.asString()), nil + + return haystack.Contains(fmt.Sprint(needle)), nil } diff --git a/internal/dynamo-browse/models/queryexpr/ir.go b/internal/dynamo-browse/models/queryexpr/ir.go index ad86d69..db9c218 100644 --- a/internal/dynamo-browse/models/queryexpr/ir.go +++ b/internal/dynamo-browse/models/queryexpr/ir.go @@ -2,7 +2,7 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" ) // TO DELETE = operandFieldName() string @@ -36,7 +36,11 @@ type nameIRAtom interface { type valueIRAtom interface { oprIRAtom - exprValue() exprValue + goValue() any +} + +type multiValueIRAtom interface { + calcGoValues(info *models.TableInfo) ([]any, error) } func canExecuteAsQuery(ir irAtom, qci *queryCalcInfo) bool { diff --git a/internal/dynamo-browse/models/queryexpr/is.go b/internal/dynamo-browse/models/queryexpr/is.go index cc2bc88..50daf6b 100644 --- a/internal/dynamo-browse/models/queryexpr/is.go +++ b/internal/dynamo-browse/models/queryexpr/is.go @@ -2,7 +2,9 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "reflect" "strings" ) @@ -10,50 +12,50 @@ import ( type isTypeInfo struct { isAny bool attributeType expression.DynamoDBAttributeType - goTypes []reflect.Type + goType reflect.Type } var validIsTypeNames = map[string]isTypeInfo{ "ANY": {isAny: true}, "B": { attributeType: expression.Binary, - // TODO + goType: reflect.TypeOf(&types.AttributeValueMemberB{}), }, "BOOL": { attributeType: expression.Boolean, - goTypes: []reflect.Type{reflect.TypeOf(boolExprValue(false))}, + goType: reflect.TypeOf(&types.AttributeValueMemberBOOL{}), }, "S": { attributeType: expression.String, - goTypes: []reflect.Type{reflect.TypeOf(stringExprValue(""))}, + goType: reflect.TypeOf(&types.AttributeValueMemberS{}), }, "N": { attributeType: expression.Number, - goTypes: []reflect.Type{reflect.TypeOf(int64ExprValue(0)), reflect.TypeOf(bigNumExprValue{})}, + goType: reflect.TypeOf(&types.AttributeValueMemberN{}), }, "NULL": { attributeType: expression.Null, - goTypes: []reflect.Type{reflect.TypeOf(nullExprValue{})}, + goType: reflect.TypeOf(&types.AttributeValueMemberNULL{}), }, "L": { attributeType: expression.List, - goTypes: []reflect.Type{reflect.TypeOf(listExprValue{}), reflect.TypeOf(listProxyValue{})}, + goType: reflect.TypeOf(&types.AttributeValueMemberL{}), }, "M": { attributeType: expression.Map, - goTypes: []reflect.Type{reflect.TypeOf(mapExprValue{}), reflect.TypeOf(mapProxyValue{})}, + goType: reflect.TypeOf(&types.AttributeValueMemberM{}), }, "BS": { attributeType: expression.BinarySet, - // TODO + goType: reflect.TypeOf(&types.AttributeValueMemberBS{}), }, "NS": { attributeType: expression.NumberSet, - // TODO + goType: reflect.TypeOf(&types.AttributeValueMemberNS{}), }, "SS": { attributeType: expression.StringSet, - // TODO + goType: reflect.TypeOf(&types.AttributeValueMemberSS{}), }, } @@ -81,14 +83,14 @@ func (a *astIsOp) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, er if !isValueIR { return nil, ValueMustBeLiteralError{} } - strValue, isStringValue := valueIR.exprValue().(stringableExprValue) + strValue, isStringValue := valueIR.goValue().(string) if !isStringValue { return nil, ValueMustBeStringError{} } - typeInfo, isValidType := validIsTypeNames[strings.ToUpper(strValue.asString())] + typeInfo, isValidType := validIsTypeNames[strings.ToUpper(strValue)] if !isValidType { - return nil, InvalidTypeForIsError{TypeName: strValue.asString()} + return nil, InvalidTypeForIsError{TypeName: strValue} } var ir = irIs{name: nameIR, typeInfo: typeInfo} @@ -102,7 +104,7 @@ func (a *astIsOp) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, er return ir, nil } -func (a *astIsOp) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (a *astIsOp) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { ref, err := a.Ref.evalItem(ctx, item) if err != nil { return nil, err @@ -116,32 +118,26 @@ func (a *astIsOp) evalItem(ctx *evalContext, item models.Item) (exprValue, error if err != nil { return nil, err } - str, canToStr := expTypeVal.(stringableExprValue) + str, canToStr := attrutils.AttributeToString(expTypeVal) if !canToStr { return nil, ValueMustBeStringError{} } - typeInfo, hasTypeInfo := validIsTypeNames[strings.ToUpper(str.asString())] + typeInfo, hasTypeInfo := validIsTypeNames[strings.ToUpper(str)] if !hasTypeInfo { - return nil, InvalidTypeForIsError{TypeName: str.asString()} + return nil, InvalidTypeForIsError{TypeName: str} } var resultOfIs bool if typeInfo.isAny { - resultOfIs = ref != undefinedExprValue{} + resultOfIs = ref != nil } else { refType := reflect.TypeOf(ref) - - for _, t := range typeInfo.goTypes { - if t.AssignableTo(refType) { - resultOfIs = true - break - } - } + resultOfIs = typeInfo.goType.AssignableTo(refType) } if a.HasNot { resultOfIs = !resultOfIs } - return boolExprValue(resultOfIs), nil + return &types.AttributeValueMemberBOOL{Value: resultOfIs}, nil } func (a *astIsOp) canModifyItem(ctx *evalContext, item models.Item) bool { @@ -151,7 +147,7 @@ func (a *astIsOp) canModifyItem(ctx *evalContext, item models.Item) bool { return a.Ref.canModifyItem(ctx, item) } -func (a *astIsOp) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (a *astIsOp) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if a.Value != nil { return PathNotSettableError{} } diff --git a/internal/dynamo-browse/models/queryexpr/placeholder.go b/internal/dynamo-browse/models/queryexpr/placeholder.go index eca6dc7..ec94a83 100644 --- a/internal/dynamo-browse/models/queryexpr/placeholder.go +++ b/internal/dynamo-browse/models/queryexpr/placeholder.go @@ -1,7 +1,8 @@ package queryexpr import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/pkg/errors" ) @@ -20,12 +21,7 @@ func (p *astPlaceholder) evalToIR(ctx *evalContext, info *models.TableInfo) (irA return nil, MissingPlaceholderError{Placeholder: p.Placeholder} } - ev, err := newExprValueFromAttributeValue(val) - if err != nil { - return nil, err - } - - return irValue{value: ev}, nil + return irValue{value: val}, nil } else if placeholderType == namePlaceholderPrefix { name, hasName := ctx.lookupName(placeholder) if !hasName { @@ -38,7 +34,7 @@ func (p *astPlaceholder) evalToIR(ctx *evalContext, info *models.TableInfo) (irA return nil, errors.New("unrecognised placeholder") } -func (p *astPlaceholder) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (p *astPlaceholder) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { placeholderType := p.Placeholder[0] placeholder := p.Placeholder[1:] @@ -47,7 +43,7 @@ func (p *astPlaceholder) evalItem(ctx *evalContext, item models.Item) (exprValue if !hasVal { return nil, MissingPlaceholderError{Placeholder: p.Placeholder} } - return newExprValueFromAttributeValue(val) + return val, nil } else if placeholderType == namePlaceholderPrefix { name, hasName := ctx.lookupName(placeholder) if !hasName { @@ -59,7 +55,7 @@ func (p *astPlaceholder) evalItem(ctx *evalContext, item models.Item) (exprValue return nil, nil } - return newExprValueFromAttributeValue(res) + return res, nil } return nil, errors.New("unrecognised placeholder") @@ -70,7 +66,7 @@ func (p *astPlaceholder) canModifyItem(ctx *evalContext, item models.Item) bool return placeholderType == namePlaceholderPrefix } -func (p *astPlaceholder) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (p *astPlaceholder) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { placeholderType := p.Placeholder[0] placeholder := p.Placeholder[1:] @@ -82,7 +78,7 @@ func (p *astPlaceholder) setEvalItem(ctx *evalContext, item models.Item, value e return MissingPlaceholderError{Placeholder: p.Placeholder} } - item[name] = value.asAttributeValue() + item[name] = value return nil } diff --git a/internal/dynamo-browse/models/queryexpr/subref.go b/internal/dynamo-browse/models/queryexpr/subref.go index 0498298..0674fcb 100644 --- a/internal/dynamo-browse/models/queryexpr/subref.go +++ b/internal/dynamo-browse/models/queryexpr/subref.go @@ -1,8 +1,10 @@ package queryexpr import ( - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/models" + "strconv" "strings" ) @@ -32,7 +34,7 @@ func (r *astSubRef) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, return irNamePath{name: namePath.name, quals: quals}, nil } -func (r *astSubRef) evalItem(ctx *evalContext, item models.Item) (exprValue, error) { +func (r *astSubRef) evalItem(ctx *evalContext, item models.Item) (types.AttributeValue, error) { res, err := r.Ref.evalItem(ctx, item) if err != nil { return nil, err @@ -46,7 +48,7 @@ func (r *astSubRef) evalItem(ctx *evalContext, item models.Item) (exprValue, err return res, nil } -func (r *astSubRef) evalSubRefs(ctx *evalContext, item models.Item, res exprValue, subRefs []*astSubRefType) (exprValue, error) { +func (r *astSubRef) evalSubRefs(ctx *evalContext, item models.Item, res types.AttributeValue, subRefs []*astSubRefType) (types.AttributeValue, error) { for i, sr := range subRefs { sv, err := sr.evalToStrOrInt(ctx, nil) if err != nil { @@ -55,30 +57,24 @@ func (r *astSubRef) evalSubRefs(ctx *evalContext, item models.Item, res exprValu switch val := sv.(type) { case string: - mapRes, isMapRes := res.(mappableExprValue) + var hasV bool + mapRes, isMapRes := res.(*types.AttributeValueMemberM) if !isMapRes { return nil, newValueNotAMapError(r, subRefs[:i+1]) } - if mapRes.hasKey(val) { - res, err = mapRes.valueOf(val) - if err != nil { - return nil, err - } - } else { - res = nil + res, hasV = mapRes.Value[val] + if !hasV { + return nil, nil } - case int64: - listRes, isMapRes := res.(slicableExprValue) + case int: + listRes, isMapRes := res.(*types.AttributeValueMemberL) if !isMapRes { return nil, newValueNotAListError(r, subRefs[:i+1]) } - // TODO - deal with index properly (i.e. error handling) - res, err = listRes.valueAt(int(val)) - if err != nil { - return nil, err - } + // TODO - deal with index properly + res = listRes.Value[val] } } return res, nil @@ -88,7 +84,7 @@ func (r *astSubRef) canModifyItem(ctx *evalContext, item models.Item) bool { return r.Ref.canModifyItem(ctx, item) } -func (r *astSubRef) setEvalItem(ctx *evalContext, item models.Item, value exprValue) error { +func (r *astSubRef) setEvalItem(ctx *evalContext, item models.Item, value types.AttributeValue) error { if len(r.SubRefs) == 0 { return r.Ref.setEvalItem(ctx, item, value) } @@ -112,19 +108,20 @@ func (r *astSubRef) setEvalItem(ctx *evalContext, item models.Item, value exprVa switch val := sv.(type) { case string: - mapRes, isMapRes := parentItem.(modifiableMapExprValue) + mapRes, isMapRes := parentItem.(*types.AttributeValueMemberM) if !isMapRes { return newValueNotAMapError(r, r.SubRefs) } - mapRes.setValueOf(val, value) - case int64: - listRes, isMapRes := parentItem.(modifiableSliceExprValue) + mapRes.Value[val] = value + case int: + listRes, isMapRes := parentItem.(*types.AttributeValueMemberL) if !isMapRes { return newValueNotAListError(r, r.SubRefs) } - listRes.setValueAt(int(val), value) + // TODO: handle indexes + listRes.Value[val] = value } return nil } @@ -139,6 +136,20 @@ func (r *astSubRef) deleteAttribute(ctx *evalContext, item models.Item) error { return err } + /* + for i, key := range r.Quals { + mapItem, isMapItem := parentItem.(*types.AttributeValueMemberM) + if !isMapItem { + return PathNotSettableError{} + } + + if isLast := i == len(r.Quals)-1; isLast { + delete(mapItem.Value, key) + } else { + parentItem = mapItem.Value[key] + } + } + */ if len(r.SubRefs) > 1 { parentItem, err = r.evalSubRefs(ctx, item, parentItem, r.SubRefs[0:len(r.SubRefs)-1]) if err != nil { @@ -153,20 +164,23 @@ func (r *astSubRef) deleteAttribute(ctx *evalContext, item models.Item) error { switch val := sv.(type) { case string: - mapRes, isMapRes := parentItem.(modifiableMapExprValue) + mapRes, isMapRes := parentItem.(*types.AttributeValueMemberM) if !isMapRes { return newValueNotAMapError(r, r.SubRefs) } - mapRes.deleteValueOf(val) - case int64: - listRes, isMapRes := parentItem.(modifiableSliceExprValue) + delete(mapRes.Value, val) + case int: + listRes, isMapRes := parentItem.(*types.AttributeValueMemberL) if !isMapRes { return newValueNotAListError(r, r.SubRefs) } // TODO: handle indexes out of bounds - listRes.deleteValueAt(int(val)) + oldList := listRes.Value + newList := append([]types.AttributeValue{}, oldList[:val]...) + newList = append(newList, oldList[val+1:]...) + listRes.Value = newList } return nil } @@ -200,10 +214,18 @@ func (sr *astSubRefType) evalToStrOrInt(ctx *evalContext, item models.Item) (any return nil, err } switch v := subEvalItem.(type) { - case stringableExprValue: - return v.asString(), nil - case numberableExprValue: - return v.asInt(), nil + case *types.AttributeValueMemberS: + return v.Value, nil + case *types.AttributeValueMemberN: + intVal, err := strconv.Atoi(v.Value) + if err == nil { + return intVal, nil + } + flVal, err := strconv.ParseFloat(v.Value, 64) + if err == nil { + return int(flVal), nil + } + return nil, err } return nil, ValueNotUsableAsASubref{} } diff --git a/internal/dynamo-browse/models/queryexpr/types.go b/internal/dynamo-browse/models/queryexpr/types.go index 2e55c7a..7175665 100644 --- a/internal/dynamo-browse/models/queryexpr/types.go +++ b/internal/dynamo-browse/models/queryexpr/types.go @@ -1,414 +1 @@ package queryexpr - -import ( - "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/common/maputils" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/pkg/errors" - "math/big" - "strconv" -) - -type exprValue interface { - typeName() string - asGoValue() any - asAttributeValue() types.AttributeValue -} - -type stringableExprValue interface { - exprValue - asString() string -} - -type numberableExprValue interface { - exprValue - asBigFloat() *big.Float - asInt() int64 -} - -type slicableExprValue interface { - exprValue - len() int - valueAt(idx int) (exprValue, error) -} - -type modifiableSliceExprValue interface { - setValueAt(idx int, value exprValue) - deleteValueAt(idx int) -} - -type mappableExprValue interface { - len() int - hasKey(name string) bool - valueOf(name string) (exprValue, error) -} - -type modifiableMapExprValue interface { - setValueOf(name string, value exprValue) - deleteValueOf(name string) -} - -func buildExpressionFromValue(ev exprValue) expression.ValueBuilder { - return expression.Value(ev) -} - -func newExprValueFromAttributeValue(ev types.AttributeValue) (exprValue, error) { - if ev == nil { - return nil, nil - } - - switch xVal := ev.(type) { - case *types.AttributeValueMemberS: - return stringExprValue(xVal.Value), nil - case *types.AttributeValueMemberN: - xNumVal, _, err := big.ParseFloat(xVal.Value, 10, 63, big.ToNearestEven) - if err != nil { - return nil, err - } - return bigNumExprValue{num: xNumVal}, nil - case *types.AttributeValueMemberBOOL: - return boolExprValue(xVal.Value), nil - case *types.AttributeValueMemberNULL: - return nullExprValue{}, nil - case *types.AttributeValueMemberL: - return listProxyValue{list: xVal}, nil - case *types.AttributeValueMemberM: - return mapProxyValue{mapValue: xVal}, nil - case *types.AttributeValueMemberSS: - return stringSetProxyValue{stringSet: xVal}, nil - case *types.AttributeValueMemberNS: - return numberSetProxyValue{numberSet: xVal}, nil - } - return nil, errors.New("cannot convert to expr value") -} - -type undefinedExprValue struct{} - -func (b undefinedExprValue) asGoValue() any { - return nil -} - -func (b undefinedExprValue) asAttributeValue() types.AttributeValue { - return nil -} - -func (s undefinedExprValue) typeName() string { - return "UNDEFINED" -} - -type stringExprValue string - -func (s stringExprValue) asGoValue() any { - return string(s) -} - -func (s stringExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberS{Value: string(s)} -} - -func (s stringExprValue) asString() string { - return string(s) -} - -func (s stringExprValue) typeName() string { - return "S" -} - -type int64ExprValue int64 - -func (i int64ExprValue) asGoValue() any { - return int(i) -} - -func (i int64ExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberN{Value: strconv.Itoa(int(i))} -} - -func (i int64ExprValue) asInt() int64 { - return int64(i) -} - -func (i int64ExprValue) asBigFloat() *big.Float { - var f big.Float - f.SetInt64(int64(i)) - return &f -} - -func (s int64ExprValue) typeName() string { - return "N" -} - -type bigNumExprValue struct { - num *big.Float -} - -func (i bigNumExprValue) asGoValue() any { - return i.num -} - -func (i bigNumExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberN{Value: i.num.String()} -} - -func (i bigNumExprValue) asInt() int64 { - x, _ := i.num.Int64() - return x -} - -func (i bigNumExprValue) asBigFloat() *big.Float { - return i.num -} - -func (s bigNumExprValue) typeName() string { - return "N" -} - -type boolExprValue bool - -func (b boolExprValue) asGoValue() any { - return bool(b) -} - -func (b boolExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberBOOL{Value: bool(b)} -} - -func (s boolExprValue) typeName() string { - return "BOOL" -} - -type nullExprValue struct{} - -func (b nullExprValue) asGoValue() any { - return nil -} - -func (b nullExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberNULL{Value: true} -} - -func (s nullExprValue) typeName() string { - return "NULL" -} - -type listExprValue []exprValue - -func (bs listExprValue) asGoValue() any { - return sliceutils.Map(bs, func(t exprValue) any { - return t.asGoValue() - }) -} - -func (bs listExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberL{Value: sliceutils.Map(bs, func(t exprValue) types.AttributeValue { - return t.asAttributeValue() - })} -} - -func (bs listExprValue) len() int { - return len(bs) -} - -func (bs listExprValue) valueAt(i int) (exprValue, error) { - return bs[i], nil -} - -func (s listExprValue) typeName() string { - return "L" -} - -type mapExprValue map[string]exprValue - -func (bs mapExprValue) asGoValue() any { - return maputils.MapValues(bs, func(t exprValue) any { - return t.asGoValue() - }) -} - -func (bs mapExprValue) asAttributeValue() types.AttributeValue { - return &types.AttributeValueMemberM{Value: maputils.MapValues(bs, func(t exprValue) types.AttributeValue { - return t.asAttributeValue() - })} -} - -func (bs mapExprValue) len() int { - return len(bs) -} - -func (bs mapExprValue) hasKey(name string) bool { - _, ok := bs[name] - return ok -} - -func (bs mapExprValue) valueOf(name string) (exprValue, error) { - return bs[name], nil -} - -func (s mapExprValue) typeName() string { - return "M" -} - -type listProxyValue struct { - list *types.AttributeValueMemberL -} - -func (bs listProxyValue) asGoValue() any { - resultingList := make([]any, len(bs.list.Value)) - for i, item := range bs.list.Value { - if av, _ := newExprValueFromAttributeValue(item); av != nil { - resultingList[i] = av.asGoValue() - } else { - resultingList[i] = nil - } - } - return resultingList -} - -func (bs listProxyValue) asAttributeValue() types.AttributeValue { - return bs.list -} - -func (bs listProxyValue) len() int { - return len(bs.list.Value) -} - -func (bs listProxyValue) valueAt(i int) (exprValue, error) { - return newExprValueFromAttributeValue(bs.list.Value[i]) -} - -func (bs listProxyValue) setValueAt(i int, newVal exprValue) { - bs.list.Value[i] = newVal.asAttributeValue() -} - -func (bs listProxyValue) deleteValueAt(idx int) { - newList := append([]types.AttributeValue{}, bs.list.Value[:idx]...) - newList = append(newList, bs.list.Value[idx+1:]...) - bs.list = &types.AttributeValueMemberL{Value: newList} -} - -func (s listProxyValue) typeName() string { - return "L" -} - -type mapProxyValue struct { - mapValue *types.AttributeValueMemberM -} - -func (bs mapProxyValue) asGoValue() any { - resultingMap := make(map[string]any) - for k, item := range bs.mapValue.Value { - if av, _ := newExprValueFromAttributeValue(item); av != nil { - resultingMap[k] = av.asGoValue() - } else { - resultingMap[k] = nil - } - } - return resultingMap -} - -func (bs mapProxyValue) asAttributeValue() types.AttributeValue { - return bs.mapValue -} - -func (bs mapProxyValue) len() int { - return len(bs.mapValue.Value) -} - -func (bs mapProxyValue) hasKey(name string) bool { - _, ok := bs.mapValue.Value[name] - return ok -} - -func (bs mapProxyValue) valueOf(name string) (exprValue, error) { - return newExprValueFromAttributeValue(bs.mapValue.Value[name]) -} - -func (bs mapProxyValue) setValueOf(name string, newVal exprValue) { - bs.mapValue.Value[name] = newVal.asAttributeValue() -} - -func (bs mapProxyValue) deleteValueOf(name string) { - delete(bs.mapValue.Value, name) -} - -func (s mapProxyValue) typeName() string { - return "M" -} - -type stringSetProxyValue struct { - stringSet *types.AttributeValueMemberSS -} - -func (bs stringSetProxyValue) asGoValue() any { - return bs.stringSet.Value -} - -func (bs stringSetProxyValue) asAttributeValue() types.AttributeValue { - return bs.stringSet -} - -func (bs stringSetProxyValue) len() int { - return len(bs.stringSet.Value) -} - -func (bs stringSetProxyValue) valueAt(i int) (exprValue, error) { - return stringExprValue(bs.stringSet.Value[i]), nil -} - -func (bs stringSetProxyValue) setValueAt(i int, newVal exprValue) { - if str, isStr := newVal.(stringableExprValue); isStr { - bs.stringSet.Value[i] = str.asString() - } -} - -func (bs stringSetProxyValue) deleteValueAt(idx int) { - newList := append([]string{}, bs.stringSet.Value[:idx]...) - newList = append(newList, bs.stringSet.Value[idx+1:]...) - bs.stringSet = &types.AttributeValueMemberSS{Value: newList} -} - -func (s stringSetProxyValue) typeName() string { - return "SS" -} - -type numberSetProxyValue struct { - numberSet *types.AttributeValueMemberNS -} - -func (bs numberSetProxyValue) asGoValue() any { - return bs.numberSet.Value -} - -func (bs numberSetProxyValue) asAttributeValue() types.AttributeValue { - return bs.numberSet -} - -func (bs numberSetProxyValue) len() int { - return len(bs.numberSet.Value) -} - -func (bs numberSetProxyValue) valueAt(i int) (exprValue, error) { - fs, _, err := big.ParseFloat(bs.numberSet.Value[i], 10, 63, big.ToNearestEven) - if err != nil { - return nil, err - } - - return bigNumExprValue{fs}, nil -} - -func (bs numberSetProxyValue) setValueAt(i int, newVal exprValue) { - if str, isStr := newVal.(numberableExprValue); isStr { - bs.numberSet.Value[i] = str.asBigFloat().String() - } -} - -func (bs numberSetProxyValue) deleteValueAt(idx int) { - newList := append([]string{}, bs.numberSet.Value[:idx]...) - newList = append(newList, bs.numberSet.Value[idx+1:]...) - bs.numberSet = &types.AttributeValueMemberNS{Value: newList} -} - -func (s numberSetProxyValue) typeName() string { - return "NS" -} diff --git a/internal/dynamo-browse/models/queryexpr/values.go b/internal/dynamo-browse/models/queryexpr/values.go index b58a567..4a81c45 100644 --- a/internal/dynamo-browse/models/queryexpr/values.go +++ b/internal/dynamo-browse/models/queryexpr/values.go @@ -2,34 +2,59 @@ package queryexpr import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" "strconv" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/pkg/errors" ) func (a *astLiteralValue) evalToIR(ctx *evalContext, info *models.TableInfo) (irAtom, error) { - v, err := a.exprValue() + v, err := a.goValue() if err != nil { return nil, err } return irValue{value: v}, nil } -func (a *astLiteralValue) exprValue() (exprValue, error) { +func (a *astLiteralValue) dynamoValue() (types.AttributeValue, error) { + if a == nil { + return nil, nil + } + + goValue, err := a.goValue() + if err != nil { + return nil, err + } + + switch v := goValue.(type) { + case string: + return &types.AttributeValueMemberS{Value: v}, nil + case int64: + return &types.AttributeValueMemberN{Value: strconv.FormatInt(v, 10)}, nil + } + + return nil, errors.New("unrecognised type") +} + +func (a *astLiteralValue) goValue() (any, error) { + if a == nil { + return nil, nil + } + switch { case a.StringVal != nil: s, err := strconv.Unquote(*a.StringVal) if err != nil { return nil, errors.Wrap(err, "cannot unquote string") } - return stringExprValue(s), nil + return s, nil case a.IntVal != nil: - return int64ExprValue(*a.IntVal), nil + return *a.IntVal, nil case a.TrueBoolValue: - return boolExprValue(true), nil + return true, nil case a.FalseBoolValue: - return boolExprValue(false), nil + return false, nil } return nil, errors.New("unrecognised type") } @@ -53,17 +78,17 @@ func (a *astLiteralValue) String() string { } type irValue struct { - value exprValue + value any } func (i irValue) calcQueryForScan(info *models.TableInfo) (expression.ConditionBuilder, error) { return expression.ConditionBuilder{}, NodeCannotBeConvertedToQueryError{} } -func (i irValue) exprValue() exprValue { +func (i irValue) goValue() any { return i.value } func (a irValue) calcOperand(info *models.TableInfo) expression.OperandBuilder { - return expression.Value(a.value.asGoValue()) + return expression.Value(a.goValue()) } diff --git a/internal/dynamo-browse/models/relitems/relitem.go b/internal/dynamo-browse/models/relitems/relitem.go deleted file mode 100644 index 3a46733..0000000 --- a/internal/dynamo-browse/models/relitems/relitem.go +++ /dev/null @@ -1,12 +0,0 @@ -package relitems - -import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" -) - -type RelatedItem struct { - Name string - Table string - Query *queryexpr.QueryExpr - OnSelect func() error -} diff --git a/internal/dynamo-browse/models/serialisable/viewsnapshot.go b/internal/dynamo-browse/models/serialisable/viewsnapshot.go index 541ed8b..610e695 100644 --- a/internal/dynamo-browse/models/serialisable/viewsnapshot.go +++ b/internal/dynamo-browse/models/serialisable/viewsnapshot.go @@ -2,7 +2,7 @@ package serialisable import ( "bytes" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" "time" ) diff --git a/internal/dynamo-browse/models/sorted.go b/internal/dynamo-browse/models/sorted.go index 29c5e49..60e5d49 100644 --- a/internal/dynamo-browse/models/sorted.go +++ b/internal/dynamo-browse/models/sorted.go @@ -1,67 +1,20 @@ package models import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" + "github.com/lmika/audax/internal/dynamo-browse/models/attrutils" "sort" ) // sortedItems is a collection of items that is sorted. // Items are sorted based on the PK, and SK in ascending order type sortedItems struct { - criteria SortCriteria - items []Item -} - -type SortField struct { - Field FieldValueEvaluator - Asc bool -} - -type SortCriteria struct { - Fields []SortField -} - -func (sc SortCriteria) FirstField() SortField { - if len(sc.Fields) == 0 { - return SortField{} - } - return sc.Fields[0] -} - -func (sc SortCriteria) Equals(osc SortCriteria) bool { - if len(sc.Fields) != len(osc.Fields) { - return false - } - - for i := range osc.Fields { - if sc.Fields[i].Field != osc.Fields[i].Field || - sc.Fields[i].Asc != osc.Fields[i].Asc { - return false - } - } - - return true -} - -func (sc SortCriteria) Append(osc SortCriteria) SortCriteria { - newItems := make([]SortField, 0, len(osc.Fields)) - newItems = append(newItems, sc.Fields...) - newItems = append(newItems, osc.Fields...) - return SortCriteria{Fields: newItems} -} - -func PKSKSortFilter(ti *TableInfo) SortCriteria { - return SortCriteria{ - Fields: []SortField{ - {Field: SimpleFieldValueEvaluator(ti.Keys.PartitionKey), Asc: true}, - {Field: SimpleFieldValueEvaluator(ti.Keys.SortKey), Asc: true}, - }, - } + tableInfo *TableInfo + items []Item } // Sort sorts the items in place -func Sort(items []Item, criteria SortCriteria) { - si := sortedItems{items: items, criteria: criteria} +func Sort(items []Item, tableInfo *TableInfo) { + si := sortedItems{items: items, tableInfo: tableInfo} sort.Sort(&si) } @@ -70,21 +23,30 @@ func (si *sortedItems) Len() int { } func (si *sortedItems) Less(i, j int) bool { - for _, field := range si.criteria.Fields { - // Compare primary keys - pv1, pv2 := field.Field.EvaluateForItem(si.items[i]), field.Field.EvaluateForItem(si.items[j]) - pc, ok := attrutils.CompareScalarAttributes(pv1, pv2) + // Compare primary keys + pv1, pv2 := si.items[i][si.tableInfo.Keys.PartitionKey], si.items[j][si.tableInfo.Keys.PartitionKey] + pc, ok := attrutils.CompareScalarAttributes(pv1, pv2) + if !ok { + return i < j + } + + if pc < 0 { + return true + } else if pc > 0 { + return false + } + + // Partition keys are equal, compare sort key + if sortKey := si.tableInfo.Keys.SortKey; sortKey != "" { + sv1, sv2 := si.items[i][sortKey], si.items[j][sortKey] + sc, ok := attrutils.CompareScalarAttributes(sv1, sv2) if !ok { return i < j } - if !field.Asc { - pc = -pc - } - - if pc < 0 { + if sc < 0 { return true - } else if pc > 0 { + } else if sc > 0 { return false } } diff --git a/internal/dynamo-browse/models/sorted_test.go b/internal/dynamo-browse/models/sorted_test.go index 5b39d6b..b23b71c 100644 --- a/internal/dynamo-browse/models/sorted_test.go +++ b/internal/dynamo-browse/models/sorted_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/stretchr/testify/assert" ) @@ -15,7 +15,7 @@ func TestSort(t *testing.T) { items := make([]models.Item, len(testStringData)) copy(items, testStringData) - models.Sort(items, models.PKSKSortFilter(tableInfo)) + models.Sort(items, tableInfo) assert.Equal(t, items[0], testStringData[1]) assert.Equal(t, items[1], testStringData[2]) @@ -28,7 +28,7 @@ func TestSort(t *testing.T) { items := make([]models.Item, len(testNumberData)) copy(items, testNumberData) - models.Sort(items, models.PKSKSortFilter(tableInfo)) + models.Sort(items, tableInfo) assert.Equal(t, items[0], testNumberData[2]) assert.Equal(t, items[1], testNumberData[1]) @@ -41,7 +41,7 @@ func TestSort(t *testing.T) { items := make([]models.Item, len(testBoolData)) copy(items, testBoolData) - models.Sort(items, models.PKSKSortFilter(tableInfo)) + models.Sort(items, tableInfo) assert.Equal(t, items[0], testBoolData[2]) assert.Equal(t, items[1], testBoolData[1]) diff --git a/internal/dynamo-browse/providers/dynamo/provider.go b/internal/dynamo-browse/providers/dynamo/provider.go index 6953931..66e8d1c 100644 --- a/internal/dynamo-browse/providers/dynamo/provider.go +++ b/internal/dynamo-browse/providers/dynamo/provider.go @@ -7,9 +7,9 @@ import ( "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/services/jobs" "github.com/pkg/errors" "time" ) diff --git a/internal/dynamo-browse/providers/dynamo/provider_test.go b/internal/dynamo-browse/providers/dynamo/provider_test.go index ce86a04..625895d 100644 --- a/internal/dynamo-browse/providers/dynamo/provider_test.go +++ b/internal/dynamo-browse/providers/dynamo/provider_test.go @@ -3,12 +3,12 @@ package dynamo_test import ( "context" "fmt" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" "testing" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/dynamo" - "github.com/lmika/dynamo-browse/test/testdynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/test/testdynamo" "github.com/stretchr/testify/assert" ) diff --git a/internal/dynamo-browse/providers/inputhistorystore/store.go b/internal/dynamo-browse/providers/inputhistorystore/store.go index 57d6bc4..14878d0 100644 --- a/internal/dynamo-browse/providers/inputhistorystore/store.go +++ b/internal/dynamo-browse/providers/inputhistorystore/store.go @@ -3,8 +3,8 @@ package inputhistorystore import ( "context" "github.com/asdine/storm" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/common/workspaces" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/common/workspaces" "github.com/pkg/errors" "sort" "time" diff --git a/internal/dynamo-browse/providers/pasteboardprovider/nilprovider.go b/internal/dynamo-browse/providers/pasteboardprovider/nilprovider.go deleted file mode 100644 index 7285aff..0000000 --- a/internal/dynamo-browse/providers/pasteboardprovider/nilprovider.go +++ /dev/null @@ -1,11 +0,0 @@ -package pasteboardprovider - -type NilProvider struct{} - -func (NilProvider) ReadText() (string, bool) { - return "", false -} - -func (n NilProvider) WriteText(bts []byte) error { - return nil -} diff --git a/internal/dynamo-browse/providers/pasteboardprovider/providers.go b/internal/dynamo-browse/providers/pasteboardprovider/providers.go deleted file mode 100644 index fb53ac2..0000000 --- a/internal/dynamo-browse/providers/pasteboardprovider/providers.go +++ /dev/null @@ -1,55 +0,0 @@ -package pasteboardprovider - -import ( - "github.com/pkg/errors" - "golang.design/x/clipboard" - "sync" -) - -type Provider struct { - mutex *sync.Mutex - clipboardInit bool -} - -func New() *Provider { - return &Provider{ - mutex: new(sync.Mutex), - } -} - -func (c *Provider) initClipboard() error { - c.mutex.Lock() - defer c.mutex.Unlock() - - if c.clipboardInit { - return nil - } - - if err := clipboard.Init(); err != nil { - return errors.Wrap(err, "unable to enable clipboard") - } - c.clipboardInit = true - return nil -} - -func (c *Provider) WriteText(bts []byte) error { - if err := c.initClipboard(); err != nil { - return err - } - - clipboard.Write(clipboard.FmtText, bts) - return nil -} - -func (c *Provider) ReadText() (string, bool) { - if err := c.initClipboard(); err != nil { - return "", false - } - - content := clipboard.Read(clipboard.FmtText) - if content == nil { - return "", false - } - - return string(content), true -} diff --git a/internal/dynamo-browse/providers/settingstore/settingstore.go b/internal/dynamo-browse/providers/settingstore/settingstore.go index b22b596..30db68e 100644 --- a/internal/dynamo-browse/providers/settingstore/settingstore.go +++ b/internal/dynamo-browse/providers/settingstore/settingstore.go @@ -2,7 +2,7 @@ package settingstore import ( "github.com/asdine/storm" - "github.com/lmika/dynamo-browse/internal/common/workspaces" + "github.com/lmika/audax/internal/common/workspaces" "github.com/pkg/errors" "io/fs" "log" @@ -113,7 +113,7 @@ func (c *SettingStore) SetDefaultLimit(limit int) error { func (c *SettingStore) getStringValue(key string, def string) (string, error) { var val string - if err := c.ws.Get(settingBucket, key, &val); err != nil { + if err := c.ws.Get(settingBucket, keyTableReadOnly, &val); err != nil { if errors.Is(err, storm.ErrNotFound) { return def, nil } diff --git a/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go b/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go index 65ea0ad..6639e17 100644 --- a/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go +++ b/internal/dynamo-browse/providers/workspacestore/resultsetsnapshot.go @@ -2,8 +2,8 @@ package workspacestore import ( "github.com/asdine/storm" - "github.com/lmika/dynamo-browse/internal/common/workspaces" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/serialisable" + "github.com/lmika/audax/internal/common/workspaces" + "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" "github.com/pkg/errors" "log" ) diff --git a/internal/dynamo-browse/services/inputhistory/iter.go b/internal/dynamo-browse/services/inputhistory/iter.go index ea9417c..3cb64cc 100644 --- a/internal/dynamo-browse/services/inputhistory/iter.go +++ b/internal/dynamo-browse/services/inputhistory/iter.go @@ -2,7 +2,7 @@ package inputhistory import ( "context" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" + "github.com/lmika/audax/internal/dynamo-browse/services" "log" "strings" ) diff --git a/internal/dynamo-browse/services/itemrenderer/service.go b/internal/dynamo-browse/services/itemrenderer/service.go index 4f543cd..374bbd1 100644 --- a/internal/dynamo-browse/services/itemrenderer/service.go +++ b/internal/dynamo-browse/services/itemrenderer/service.go @@ -2,8 +2,8 @@ package itemrenderer import ( "fmt" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/itemrender" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/itemrender" "io" "text/tabwriter" ) diff --git a/internal/dynamo-browse/services/pasteboardprovider.go b/internal/dynamo-browse/services/pasteboardprovider.go deleted file mode 100644 index 18b3549..0000000 --- a/internal/dynamo-browse/services/pasteboardprovider.go +++ /dev/null @@ -1,6 +0,0 @@ -package services - -type PasteboardProvider interface { - ReadText() (string, bool) - WriteText(bts []byte) error -} diff --git a/internal/dynamo-browse/services/scriptmanager/builtins.go b/internal/dynamo-browse/services/scriptmanager/builtins.go index 93c6e78..9674c99 100644 --- a/internal/dynamo-browse/services/scriptmanager/builtins.go +++ b/internal/dynamo-browse/services/scriptmanager/builtins.go @@ -7,11 +7,8 @@ package scriptmanager import ( "context" - "fmt" + "github.com/cloudcmds/tamarin/object" "log" - - "github.com/pkg/errors" - "github.com/risor-io/risor/object" ) func printBuiltin(ctx context.Context, args ...object.Object) object.Object { @@ -56,47 +53,3 @@ func printfBuiltin(ctx context.Context, args ...object.Object) object.Object { log.Printf("%s "+format, values...) return object.Nil } - -// This is taken from the args package -func require(funcName string, count int, args []object.Object) *object.Error { - nArgs := len(args) - if nArgs != count { - if count == 1 { - return object.Errorf( - fmt.Sprintf("type error: %s() takes exactly 1 argument (%d given)", - funcName, nArgs)) - } - return object.Errorf( - fmt.Sprintf("type error: %s() takes exactly %d arguments (%d given)", - funcName, count, nArgs)) - } - return nil -} - -func bindArgs(funcName string, args []object.Object, bindArgs ...any) *object.Error { - if err := require(funcName, len(bindArgs), args); err != nil { - return err - } - - for i, bindArg := range bindArgs { - switch t := bindArg.(type) { - case *string: - str, err := object.AsString(args[i]) - if err != nil { - return err - } - - *t = str - case **object.Function: - fnRes, isFnRes := args[i].(*object.Function) - if !isFnRes { - return object.NewError(errors.Errorf("expected arg %v to be a function, was %T", i, bindArg)) - } - - *t = fnRes - default: - return object.NewError(errors.Errorf("unhandled arg type %v", i)) - } - } - return nil -} diff --git a/internal/dynamo-browse/services/scriptmanager/iface.go b/internal/dynamo-browse/services/scriptmanager/iface.go index 39b4d9e..a717057 100644 --- a/internal/dynamo-browse/services/scriptmanager/iface.go +++ b/internal/dynamo-browse/services/scriptmanager/iface.go @@ -3,7 +3,7 @@ package scriptmanager import ( "context" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" ) //go:generate mockery --with-expecter --name UIService @@ -32,7 +32,6 @@ type SessionService interface { type QueryOptions struct { TableName string - IndexName string NamePlaceholders map[string]string ValuePlaceholders map[string]types.AttributeValue } diff --git a/internal/dynamo-browse/services/scriptmanager/mocks/SessionService.go b/internal/dynamo-browse/services/scriptmanager/mocks/SessionService.go index bdfa6b1..7ef842b 100644 --- a/internal/dynamo-browse/services/scriptmanager/mocks/SessionService.go +++ b/internal/dynamo-browse/services/scriptmanager/mocks/SessionService.go @@ -5,10 +5,10 @@ package mocks import ( context "context" - models "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + models "github.com/lmika/audax/internal/dynamo-browse/models" mock "github.com/stretchr/testify/mock" - scriptmanager "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" + scriptmanager "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" ) // SessionService is an autogenerated mock type for the SessionService type diff --git a/internal/dynamo-browse/services/scriptmanager/modext.go b/internal/dynamo-browse/services/scriptmanager/modext.go index 4ad97d4..c581d17 100644 --- a/internal/dynamo-browse/services/scriptmanager/modext.go +++ b/internal/dynamo-browse/services/scriptmanager/modext.go @@ -3,13 +3,11 @@ package scriptmanager import ( "context" "fmt" - "regexp" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" + "github.com/cloudcmds/tamarin/arg" + "github.com/cloudcmds/tamarin/object" + "github.com/cloudcmds/tamarin/scope" "github.com/pkg/errors" - "github.com/risor-io/risor/object" + "regexp" ) var ( @@ -20,18 +18,22 @@ type extModule struct { scriptPlugin *ScriptPlugin } -func (m *extModule) register() *object.Module { - return object.NewBuiltinsModule("ext", map[string]object.Object{ - "command": object.NewBuiltin("command", m.command), - "key_binding": object.NewBuiltin("key_binding", m.keyBinding), - "related_items": object.NewBuiltin("related_items", m.relatedItem), +func (m *extModule) register(scp *scope.Scope) { + modScope := scope.New(scope.Opts{}) + mod := object.NewModule("ext", modScope) + + modScope.AddBuiltins([]*object.Builtin{ + object.NewBuiltin("command", m.command, mod), + object.NewBuiltin("key_binding", m.keyBinding, mod), }) + + scp.Declare("ext", mod, true) } func (m *extModule) command(ctx context.Context, args ...object.Object) object.Object { thisEnv := scriptEnvFromCtx(ctx) - if err := require("ext.command", 2, args); err != nil { + if err := arg.Require("ext.command", 2, args); err != nil { return err } @@ -57,12 +59,11 @@ func (m *extModule) command(ctx context.Context, args ...object.Object) object.O } newEnv := thisEnv + newEnv.options = m.scriptPlugin.scriptService.options ctx = ctxWithScriptEnv(ctx, newEnv) - res, err := callFn(ctx, fnRes, objArgs) - if err != nil { - return errors.Errorf("command error '%v':%v - %v", m.scriptPlugin.name, cmdName, err) - } else if object.IsError(res) { + res := callFn(ctx, fnRes.Scope(), fnRes, objArgs) + if object.IsError(res) { errObj := res.(*object.Error) return errors.Errorf("command error '%v':%v - %v", m.scriptPlugin.name, cmdName, errObj.Inspect()) } @@ -79,7 +80,7 @@ func (m *extModule) command(ctx context.Context, args ...object.Object) object.O func (m *extModule) keyBinding(ctx context.Context, args ...object.Object) object.Object { thisEnv := scriptEnvFromCtx(ctx) - if err := require("ext.key_binding", 3, args); err != nil { + if err := arg.Require("ext.key_binding", 3, args); err != nil { return err } @@ -118,12 +119,11 @@ func (m *extModule) keyBinding(ctx context.Context, args ...object.Object) objec } newEnv := thisEnv + newEnv.options = m.scriptPlugin.scriptService.options ctx = ctxWithScriptEnv(ctx, newEnv) - res, err := callFn(ctx, fnRes, objArgs) - if err != nil { - return errors.Errorf("command error '%v':%v - %v", m.scriptPlugin.name, bindingName, err) - } else if object.IsError(res) { + res := callFn(ctx, fnRes.Scope(), fnRes, objArgs) + if object.IsError(res) { errObj := res.(*object.Error) return errors.Errorf("command error '%v':%v - %v", m.scriptPlugin.name, bindingName, errObj.Inspect()) } @@ -141,130 +141,3 @@ func (m *extModule) keyBinding(ctx context.Context, args ...object.Object) objec m.scriptPlugin.keyToKeyBinding[defaultKey] = fullBindingName return nil } - -func (m *extModule) relatedItem(ctx context.Context, args ...object.Object) object.Object { - thisEnv := scriptEnvFromCtx(ctx) - - var ( - tableName string - callbackFn *object.Function - ) - if err := bindArgs("ext.related_items", args, &tableName, &callbackFn); err != nil { - return err - } - - callFn, hasCallFn := object.GetCallFunc(ctx) - if !hasCallFn { - return object.NewError(errors.New("no callFn found in context")) - } - - newHandler := func(ctx context.Context, rs *models.ResultSet, index int) ([]relatedItem, error) { - newEnv := thisEnv - ctx = ctxWithScriptEnv(ctx, newEnv) - - res, err := callFn(ctx, callbackFn, []object.Object{ - newItemProxy(newResultSetProxy(rs), index), - }) - - if err != nil { - return nil, errors.Errorf("script error '%v':related_item - %v", m.scriptPlugin.name, err) - } else if object.IsError(res) { - errObj := res.(*object.Error) - return nil, errors.Errorf("script error '%v':related_item - %v", m.scriptPlugin.name, errObj.Inspect()) - } - - itr, objErr := object.AsIterator(res) - if err != nil { - return nil, objErr.Value() - } - - var relItems []relatedItem - for next, hasNext := itr.Next(ctx); hasNext; next, hasNext = itr.Next(ctx) { - var newRelItem relatedItem - - itemMap, objErr := object.AsMap(next) - if err != nil { - return nil, objErr.Value() - } - - labelName, objErr := object.AsString(itemMap.Get("label")) - if objErr != nil { - continue - } - newRelItem.label = labelName - - var tableStr = "" - if itemMap.Get("table") != object.Nil { - tableStr, objErr = object.AsString(itemMap.Get("table")) - if objErr != nil { - continue - } - } - newRelItem.table = tableStr - - if selectFn, ok := itemMap.Get("on_select").(*object.Function); ok { - newRelItem.onSelect = func() error { - thisNewEnv := thisEnv - ctx = ctxWithScriptEnv(ctx, thisNewEnv) - - res, err := callFn(ctx, selectFn, []object.Object{}) - if err != nil { - return errors.Errorf("rel error '%v' - %v", m.scriptPlugin.name, err) - } else if object.IsError(res) { - errObj := res.(*object.Error) - return errors.Errorf("rel error '%v' - %v", m.scriptPlugin.name, errObj.Inspect()) - } - return nil - } - } else { - queryExprStr, objErr := object.AsString(itemMap.Get("query")) - if objErr != nil { - continue - } - - query, err := queryexpr.Parse(queryExprStr) - if err != nil { - continue - } - - // Placeholders - if argsVal, isArgsValMap := object.AsMap(itemMap.Get("args")); isArgsValMap == nil { - namePlaceholders := make(map[string]string) - valuePlaceholders := make(map[string]types.AttributeValue) - - for k, val := range argsVal.Value() { - switch v := val.(type) { - case *object.String: - namePlaceholders[k] = v.Value() - valuePlaceholders[k] = &types.AttributeValueMemberS{Value: v.Value()} - case *object.Int: - valuePlaceholders[k] = &types.AttributeValueMemberN{Value: fmt.Sprint(v.Value())} - case *object.Float: - valuePlaceholders[k] = &types.AttributeValueMemberN{Value: fmt.Sprint(v.Value())} - case *object.Bool: - valuePlaceholders[k] = &types.AttributeValueMemberBOOL{Value: v.Value()} - case *object.NilType: - valuePlaceholders[k] = &types.AttributeValueMemberNULL{Value: true} - default: - continue - } - } - - query = query.WithNameParams(namePlaceholders).WithValueParams(valuePlaceholders) - } - newRelItem.query = query - } - - relItems = append(relItems, newRelItem) - } - - return relItems, nil - } - - m.scriptPlugin.relatedItems = append(m.scriptPlugin.relatedItems, &relatedItemBuilder{ - table: tableName, - itemProduction: newHandler, - }) - - return nil -} diff --git a/internal/dynamo-browse/services/scriptmanager/modext_test.go b/internal/dynamo-browse/services/scriptmanager/modext_test.go deleted file mode 100644 index b3413e6..0000000 --- a/internal/dynamo-browse/services/scriptmanager/modext_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package scriptmanager_test - -import ( - "context" - "testing" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/stretchr/testify/assert" -) - -func TestExtModule_RelatedItems(t *testing.T) { - t.Run("should register a function which will return related items for an item", func(t *testing.T) { - scenarios := []struct { - desc string - code string - }{ - { - desc: "single function, table name match", - code: ` - ext.related_items("test-table", func(item) { - print("Hello") - return [ - {"label": "Customer", "query": "pk=$foo", "args": {"foo": "foo"}}, - {"label": "Payment", "query": "fla=$daa", "args": {"daa": "Hello"}}, - ] - }) - `, - }, - { - desc: "single function, table prefix match", - code: ` - ext.related_items("test-*", func(item) { - print("Hello") - return [ - {"label": "Customer", "query": "pk=$foo", "args": {"foo": "foo"}}, - {"label": "Payment", "query": "fla=$daa", "args": {"daa": "Hello"}}, - ] - }) - `, - }, - { - desc: "multi function, table name match", - code: ` - ext.related_items("test-table", func(item) { - print("Hello") - return [ - {"label": "Customer", "query": "pk=$foo", "args": {"foo": "foo"}}, - ] - }) - - ext.related_items("test-table", func(item) { - return [ - {"label": "Payment", "query": "fla=$daa", "args": {"daa": "Hello"}}, - ] - }) - `, - }, - { - desc: "multi function, table name prefix", - code: ` - ext.related_items("test-*", func(item) { - print("Hello") - return [ - {"label": "Customer", "query": "pk=$foo", "args": {"foo": "foo"}}, - ] - }) - - ext.related_items("test-*", func(item) { - return [ - {"label": "Payment", "query": "fla=$daa", "args": {"daa": "Hello"}}, - ] - }) - `, - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.desc, func(t *testing.T) { - // Load the script - srv := scriptmanager.New(scriptmanager.WithFS(testScriptFile(t, "test.tm", scenario.code))) - - ctx := context.Background() - plugin, err := srv.LoadScript(ctx, "test.tm") - assert.NoError(t, err) - assert.NotNil(t, plugin) - - // Get related items of result set - rs := &models.ResultSet{ - TableInfo: &models.TableInfo{ - Name: "test-table", - }, - } - rs.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "abc"}}, - {"pk": &types.AttributeValueMemberS{Value: "1232"}}, - }) - - relItems, err := srv.RelatedItemOfItem(context.Background(), rs, 0) - assert.NoError(t, err) - assert.Len(t, relItems, 2) - - assert.Equal(t, "Customer", relItems[0].Name) - assert.Equal(t, "pk=$foo", relItems[0].Query.String()) - assert.Equal(t, "foo", relItems[0].Query.ValueParamOrNil("foo").(*types.AttributeValueMemberS).Value) - - assert.Equal(t, "Payment", relItems[1].Name) - assert.Equal(t, "fla=$daa", relItems[1].Query.String()) - assert.Equal(t, "Hello", relItems[1].Query.ValueParamOrNil("daa").(*types.AttributeValueMemberS).Value) - }) - } - }) - - t.Run("should support rel_items with on select", func(t *testing.T) { - // Load the script - srv := scriptmanager.New(scriptmanager.WithFS(testScriptFile(t, "test.tm", ` - ext.related_items("test-table", func(item) { - print("Hello") - return [ - {"label": "Customer", "on_select": func() { - print("Selected") - }}, - ] - }) - `))) - - ctx := context.Background() - plugin, err := srv.LoadScript(ctx, "test.tm") - assert.NoError(t, err) - assert.NotNil(t, plugin) - - // Get related items of result set - rs := &models.ResultSet{ - TableInfo: &models.TableInfo{ - Name: "test-table", - }, - } - rs.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "abc"}}, - {"pk": &types.AttributeValueMemberS{Value: "1232"}}, - }) - - relItems, err := srv.RelatedItemOfItem(context.Background(), rs, 0) - assert.NoError(t, err) - assert.Len(t, relItems, 1) - - assert.Equal(t, "Customer", relItems[0].Name) - assert.NoError(t, relItems[0].OnSelect()) - }) -} diff --git a/internal/dynamo-browse/services/scriptmanager/modos.go b/internal/dynamo-browse/services/scriptmanager/modos.go new file mode 100644 index 0000000..b050fc4 --- /dev/null +++ b/internal/dynamo-browse/services/scriptmanager/modos.go @@ -0,0 +1,71 @@ +package scriptmanager + +import ( + "context" + "github.com/cloudcmds/tamarin/arg" + "github.com/cloudcmds/tamarin/object" + "github.com/cloudcmds/tamarin/scope" + "os" + "os/exec" +) + +type osModule struct { +} + +func (om *osModule) exec(ctx context.Context, args ...object.Object) object.Object { + if err := arg.Require("os.exec", 1, args); err != nil { + return err + } + + cmdExec, objErr := object.AsString(args[0]) + if objErr != nil { + return objErr + } + + opts := scriptEnvFromCtx(ctx).options + if !opts.Permissions.AllowShellCommands { + return object.NewErrResult(object.Errorf("permission error: no permission to shell out")) + } + + cmd := exec.Command(opts.OSExecShell, "-c", cmdExec) + out, err := cmd.Output() + if err != nil { + return object.NewErrResult(object.NewError(err)) + } + + return object.NewOkResult(object.NewString(string(out))) +} + +func (om *osModule) env(ctx context.Context, args ...object.Object) object.Object { + if err := arg.Require("os.env", 1, args); err != nil { + return err + } + + cmdEnvName, objErr := object.AsString(args[0]) + if objErr != nil { + return objErr + } + + opts := scriptEnvFromCtx(ctx).options + if !opts.Permissions.AllowEnv { + return object.Nil + } + + envVal, hasVal := os.LookupEnv(cmdEnvName) + if !hasVal { + return object.Nil + } + return object.NewString(envVal) +} + +func (om *osModule) register(scp *scope.Scope) { + modScope := scope.New(scope.Opts{}) + mod := object.NewModule("os", modScope) + + modScope.AddBuiltins([]*object.Builtin{ + object.NewBuiltin("exec", om.exec, mod), + object.NewBuiltin("env", om.env, mod), + }) + + scp.Declare("os", mod, true) +} diff --git a/internal/dynamo-browse/services/scriptmanager/modos_test.go b/internal/dynamo-browse/services/scriptmanager/modos_test.go index 455125b..d9823e9 100644 --- a/internal/dynamo-browse/services/scriptmanager/modos_test.go +++ b/internal/dynamo-browse/services/scriptmanager/modos_test.go @@ -2,8 +2,8 @@ package scriptmanager_test import ( "context" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager/mocks" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "testing" @@ -15,16 +15,49 @@ func TestOSModule_Env(t *testing.T) { t.Setenv("EMPTY_VALUE", "") testFS := testScriptFile(t, "test.tm", ` - assert(os.getenv("FULL_VALUE") == "this is a value") - assert(os.getenv("EMPTY_VALUE") == "") - assert(os.getenv("MISSING_VALUE") == "") + assert(os.env("FULL_VALUE") == "this is a value") + assert(os.env("EMPTY_VALUE") == "") + assert(os.env("MISSING_VALUE") == nil) - assert(bool(os.getenv("FULL_VALUE")) == true) - assert(bool(os.getenv("EMPTY_VALUE")) == false) - assert(bool(os.getenv("MISSING_VALUE")) == false) + assert(bool(os.env("FULL_VALUE")) == true) + assert(bool(os.env("EMPTY_VALUE")) == false) + assert(bool(os.env("MISSING_VALUE")) == false) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowEnv: true, + }, + }) + + ctx := context.Background() + err := <-srv.RunAdHocScript(ctx, "test.tm") + assert.NoError(t, err) + }) + + t.Run("should return nil when no access to environment variables", func(t *testing.T) { + t.Setenv("FULL_VALUE", "this is a value") + t.Setenv("EMPTY_VALUE", "") + + testFS := testScriptFile(t, "test.tm", ` + assert(os.env("FULL_VALUE") == nil) + assert(os.env("EMPTY_VALUE") == nil) + assert(os.env("MISSING_VALUE") == nil) + + assert(bool(os.env("FULL_VALUE")) == false) + assert(bool(os.env("EMPTY_VALUE")) == false) + assert(bool(os.env("MISSING_VALUE")) == false) + `) + + srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowEnv: false, + }, + }) ctx := context.Background() err := <-srv.RunAdHocScript(ctx, "test.tm") @@ -35,14 +68,22 @@ func TestOSModule_Env(t *testing.T) { func TestOSModule_Exec(t *testing.T) { t.Run("should run command and return stdout", func(t *testing.T) { mockedUIService := mocks.NewUIService(t) + mockedUIService.EXPECT().PrintMessage(mock.Anything, "false") mockedUIService.EXPECT().PrintMessage(mock.Anything, "hello world\n") testFS := testScriptFile(t, "test.tm", ` - res := exec('echo', ["hello world"]).stdout - ui.print(res) + res := os.exec('echo "hello world"') + ui.print(res.is_err()) + ui.print(res.unwrap()) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowShellCommands: true, + }, + }) srv.SetIFaces(scriptmanager.Ifaces{ UI: mockedUIService, }) @@ -53,4 +94,73 @@ func TestOSModule_Exec(t *testing.T) { mockedUIService.AssertExpectations(t) }) + + t.Run("should refuse to execute command if do not have permissions", func(t *testing.T) { + mockedUIService := mocks.NewUIService(t) + mockedUIService.EXPECT().PrintMessage(mock.Anything, "true") + + testFS := testScriptFile(t, "test.tm", ` + res := os.exec('echo "hello world"') + ui.print(res.is_err()) + `) + + srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowShellCommands: false, + }, + }) + srv.SetIFaces(scriptmanager.Ifaces{ + UI: mockedUIService, + }) + + ctx := context.Background() + err := <-srv.RunAdHocScript(ctx, "test.tm") + assert.NoError(t, err) + + mockedUIService.AssertExpectations(t) + }) + + t.Run("should be able to change permissions which will affect plugins", func(t *testing.T) { + mockedUIService := mocks.NewUIService(t) + mockedUIService.EXPECT().PrintMessage(mock.Anything, "Loaded the plugin\n") + mockedUIService.EXPECT().PrintMessage(mock.Anything, "true") + + testFS := testScriptFile(t, "test.tm", ` + ext.command("mycommand", func() { + ui.print(os.exec('echo "this cannot run"').is_err()) + }) + + ui.print(os.exec('echo "Loaded the plugin"').unwrap()) + `) + + srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowShellCommands: true, + }, + }) + srv.SetIFaces(scriptmanager.Ifaces{ + UI: mockedUIService, + }) + + ctx := context.Background() + _, err := srv.LoadScript(ctx, "test.tm") + assert.NoError(t, err) + + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowShellCommands: false, + }, + }) + + errChan := make(chan error) + assert.NoError(t, srv.LookupCommand("mycommand").Invoke(ctx, []string{}, errChan)) + assert.NoError(t, waitForErr(t, errChan)) + + mockedUIService.AssertExpectations(t) + }) } diff --git a/internal/dynamo-browse/services/scriptmanager/modsession.go b/internal/dynamo-browse/services/scriptmanager/modsession.go index 95c16c7..034b73f 100644 --- a/internal/dynamo-browse/services/scriptmanager/modsession.go +++ b/internal/dynamo-browse/services/scriptmanager/modsession.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/cloudcmds/tamarin/arg" + "github.com/cloudcmds/tamarin/object" + "github.com/cloudcmds/tamarin/scope" "github.com/pkg/errors" - "github.com/risor-io/risor/object" ) type sessionModule struct { @@ -42,11 +44,6 @@ func (um *sessionModule) query(ctx context.Context, args ...object.Object) objec } } - // Index name - if val, isStr := objMap.Get("index").(*object.String); isStr { - options.IndexName = val.Value() - } - // Placeholders if argsVal, isArgsValMap := objMap.Get("args").(*object.Map); isArgsValMap { options.NamePlaceholders = make(map[string]string) @@ -75,13 +72,13 @@ func (um *sessionModule) query(ctx context.Context, args ...object.Object) objec resp, err := um.sessionService.Query(ctx, expr, options) if err != nil { - return object.NewError(err) + return object.NewErrResult(object.NewError(err)) } - return &resultSetProxy{resultSet: resp} + return object.NewOkResult(&resultSetProxy{resultSet: resp}) } func (um *sessionModule) resultSet(ctx context.Context, args ...object.Object) object.Object { - if err := require("session.result_set", 0, args); err != nil { + if err := arg.Require("session.result_set", 0, args); err != nil { return err } @@ -93,7 +90,7 @@ func (um *sessionModule) resultSet(ctx context.Context, args ...object.Object) o } func (um *sessionModule) selectedItem(ctx context.Context, args ...object.Object) object.Object { - if err := require("session.result_set", 0, args); err != nil { + if err := arg.Require("session.result_set", 0, args); err != nil { return err } @@ -108,7 +105,7 @@ func (um *sessionModule) selectedItem(ctx context.Context, args ...object.Object } func (um *sessionModule) setResultSet(ctx context.Context, args ...object.Object) object.Object { - if err := require("session.set_result_set", 1, args); err != nil { + if err := arg.Require("session.set_result_set", 1, args); err != nil { return err } @@ -122,7 +119,7 @@ func (um *sessionModule) setResultSet(ctx context.Context, args ...object.Object } func (um *sessionModule) currentTable(ctx context.Context, args ...object.Object) object.Object { - if err := require("session.current_table", 0, args); err != nil { + if err := arg.Require("session.current_table", 0, args); err != nil { return err } @@ -134,12 +131,17 @@ func (um *sessionModule) currentTable(ctx context.Context, args ...object.Object return &tableProxy{table: rs.TableInfo} } -func (um *sessionModule) register() *object.Module { - return object.NewBuiltinsModule("session", map[string]object.Object{ - "query": object.NewBuiltin("query", um.query), - "current_table": object.NewBuiltin("current_table", um.currentTable), - "result_set": object.NewBuiltin("result_set", um.resultSet), - "selected_item": object.NewBuiltin("selected_item", um.selectedItem), - "set_result_set": object.NewBuiltin("set_result_set", um.setResultSet), +func (um *sessionModule) register(scp *scope.Scope) { + modScope := scope.New(scope.Opts{}) + mod := object.NewModule("session", modScope) + + modScope.AddBuiltins([]*object.Builtin{ + object.NewBuiltin("query", um.query, mod), + object.NewBuiltin("current_table", um.currentTable, mod), + object.NewBuiltin("result_set", um.resultSet, mod), + object.NewBuiltin("selected_item", um.selectedItem, mod), + object.NewBuiltin("set_result_set", um.setResultSet, mod), }) + + scp.Declare("session", mod, true) } diff --git a/internal/dynamo-browse/services/scriptmanager/modsession_test.go b/internal/dynamo-browse/services/scriptmanager/modsession_test.go index 8712cf5..c28bade 100644 --- a/internal/dynamo-browse/services/scriptmanager/modsession_test.go +++ b/internal/dynamo-browse/services/scriptmanager/modsession_test.go @@ -3,9 +3,9 @@ package scriptmanager_test import ( "context" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager/mocks" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager/mocks" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -102,7 +102,7 @@ func TestModSession_Query(t *testing.T) { mockedUIService.EXPECT().PrintMessage(mock.Anything, "res[1].attr('size(pk)') = 4") testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") + res := session.query("some expr").unwrap() ui.print(res.length) ui.print("res[0]['pk'].S = ", res[0].attr("pk")) ui.print("res[1]['pk'].S = ", res[1].attr("pk")) @@ -128,9 +128,13 @@ func TestModSession_Query(t *testing.T) { mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(nil, errors.New("bang")) mockedUIService := mocks.NewUIService(t) + mockedUIService.EXPECT().PrintMessage(mock.Anything, "true") + mockedUIService.EXPECT().PrintMessage(mock.Anything, "err(\"bang\")") testFS := testScriptFile(t, "test.tm", ` res := session.query("some expr") + ui.print(res.is_err()) + ui.print(res) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) @@ -141,7 +145,7 @@ func TestModSession_Query(t *testing.T) { ctx := context.Background() err := <-srv.RunAdHocScript(ctx, "test.tm") - assert.Error(t, err) + assert.NoError(t, err) mockedUIService.AssertExpectations(t) mockedSessionService.AssertExpectations(t) @@ -161,7 +165,7 @@ func TestModSession_Query(t *testing.T) { res := session.query("some expr", { table: "some-table", }) - assert(res) + assert(!res.is_err()) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) @@ -197,7 +201,7 @@ func TestModSession_Query(t *testing.T) { res := session.query("some expr", { table: session.result_set().table, }) - assert(res) + assert(!res.is_err()) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) @@ -238,7 +242,7 @@ func TestModSession_Query(t *testing.T) { value: "world", }, }) - assert(res) + assert(!res.is_err()) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) @@ -284,7 +288,7 @@ func TestModSession_Query(t *testing.T) { "nil": nil, }, }) - assert(res) + assert(!res.is_err()) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) @@ -311,6 +315,7 @@ func TestModSession_Query(t *testing.T) { "bad": func() { }, }, }) + assert(res.is_err()) `) srv := scriptmanager.New(scriptmanager.WithFS(testFS)) @@ -406,7 +411,7 @@ func TestModSession_SetResultSet(t *testing.T) { mockedUIService := mocks.NewUIService(t) testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") + res := session.query("some expr").unwrap() session.set_result_set(res) `) diff --git a/internal/dynamo-browse/services/scriptmanager/modui.go b/internal/dynamo-browse/services/scriptmanager/modui.go index d53b2e4..081eaa5 100644 --- a/internal/dynamo-browse/services/scriptmanager/modui.go +++ b/internal/dynamo-browse/services/scriptmanager/modui.go @@ -2,9 +2,10 @@ package scriptmanager import ( "context" + "github.com/cloudcmds/tamarin/arg" + "github.com/cloudcmds/tamarin/object" + "github.com/cloudcmds/tamarin/scope" "strings" - - "github.com/risor-io/risor/object" ) type uiModule struct { @@ -14,10 +15,6 @@ type uiModule struct { func (um *uiModule) print(ctx context.Context, args ...object.Object) object.Object { var msg strings.Builder for _, arg := range args { - if arg == nil { - continue - } - switch a := arg.(type) { case *object.String: msg.WriteString(a.Value()) @@ -31,7 +28,7 @@ func (um *uiModule) print(ctx context.Context, args ...object.Object) object.Obj } func (um *uiModule) prompt(ctx context.Context, args ...object.Object) object.Object { - if err := require("ui.prompt", 1, args); err != nil { + if err := arg.Require("ui.prompt", 1, args); err != nil { return err } @@ -50,9 +47,14 @@ func (um *uiModule) prompt(ctx context.Context, args ...object.Object) object.Ob } } -func (um *uiModule) register() *object.Module { - return object.NewBuiltinsModule("ui", map[string]object.Object{ - "print": object.NewBuiltin("print", um.print), - "prompt": object.NewBuiltin("prompt", um.prompt), +func (um *uiModule) register(scp *scope.Scope) { + modScope := scope.New(scope.Opts{}) + mod := object.NewModule("ui", modScope) + + modScope.AddBuiltins([]*object.Builtin{ + object.NewBuiltin("print", um.print, mod), + object.NewBuiltin("prompt", um.prompt, mod), }) + + scp.Declare("ui", mod, true) } diff --git a/internal/dynamo-browse/services/scriptmanager/modui_test.go b/internal/dynamo-browse/services/scriptmanager/modui_test.go index 3a8b96d..7fa264e 100644 --- a/internal/dynamo-browse/services/scriptmanager/modui_test.go +++ b/internal/dynamo-browse/services/scriptmanager/modui_test.go @@ -2,8 +2,8 @@ package scriptmanager_test import ( "context" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager/mocks" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "testing" diff --git a/internal/dynamo-browse/services/scriptmanager/opts.go b/internal/dynamo-browse/services/scriptmanager/opts.go index 39d961f..9d8e489 100644 --- a/internal/dynamo-browse/services/scriptmanager/opts.go +++ b/internal/dynamo-browse/services/scriptmanager/opts.go @@ -2,12 +2,42 @@ package scriptmanager import ( "context" - "github.com/risor-io/risor/limits" + "os" ) +type Options struct { + // OSExecShell is the shell to use for calls to 'os.exec'. If not defined, + // it will use the value of the SHELL environment variable, otherwise it will + // default to '/bin/bash' + OSExecShell string + + // Permissions are the permissions the script can execute in + Permissions Permissions +} + +func (opts Options) configuredShell() string { + if opts.OSExecShell != "" { + return opts.OSExecShell + } + if shell, hasShell := os.LookupEnv("SHELL"); hasShell { + return shell + } + return "/bin/bash" +} + +// Permissions control the set of permissions of a script +type Permissions struct { + // AllowShellCommands determines whether or not a script can execute shell commands. + AllowShellCommands bool + + // AllowEnv determines whether or not a script can access environment variables + AllowEnv bool +} + // scriptEnv is the runtime environment for a particular script execution type scriptEnv struct { filename string + options Options } type scriptEnvKeyType struct{} @@ -20,7 +50,5 @@ func scriptEnvFromCtx(ctx context.Context) scriptEnv { } func ctxWithScriptEnv(ctx context.Context, perms scriptEnv) context.Context { - newCtx := context.WithValue(ctx, scriptEnvKey, perms) - newCtx = limits.WithLimits(newCtx, limits.New()) - return newCtx + return context.WithValue(ctx, scriptEnvKey, perms) } diff --git a/internal/dynamo-browse/services/scriptmanager/relitem.go b/internal/dynamo-browse/services/scriptmanager/relitem.go deleted file mode 100644 index 63d7629..0000000 --- a/internal/dynamo-browse/services/scriptmanager/relitem.go +++ /dev/null @@ -1,57 +0,0 @@ -package scriptmanager - -import ( - "context" - "log" - "path" - - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/relitems" -) - -type relatedItem struct { - label string - table string - query *queryexpr.QueryExpr - onSelect func() error -} - -type relatedItemBuilder struct { - table string - itemProduction func(ctx context.Context, rs *models.ResultSet, index int) ([]relatedItem, error) -} - -func (s *Service) RelatedItemOfItem(ctx context.Context, rs *models.ResultSet, index int) ([]relitems.RelatedItem, error) { - riModels := []relitems.RelatedItem{} - - for _, plugin := range s.plugins { - for _, rb := range plugin.relatedItems { - // TODO: should support matching - match, _ := tableMatchesGlob(rb.table, rs.TableInfo.Name) - log.Printf("RelatedItemOfItem: table = '%v', pattern = '%v', match = '%v'", rb.table, rs.TableInfo.Name, match) - if match { - relatedItems, err := rb.itemProduction(ctx, rs, index) - if err != nil { - // TODO: should probably return error if no rel items were found and an error was raised - return nil, err - } - - // TODO: make this nicer - for _, ri := range relatedItems { - riModels = append(riModels, relitems.RelatedItem{ - Name: ri.label, - Query: ri.query, - Table: ri.table, - OnSelect: ri.onSelect, - }) - } - } - } - } - return riModels, nil -} - -func tableMatchesGlob(tableName, pattern string) (bool, error) { - return path.Match(tableName, pattern) -} diff --git a/internal/dynamo-browse/services/scriptmanager/resultsetproxy.go b/internal/dynamo-browse/services/scriptmanager/resultsetproxy.go index 953a3e4..ed169ff 100644 --- a/internal/dynamo-browse/services/scriptmanager/resultsetproxy.go +++ b/internal/dynamo-browse/services/scriptmanager/resultsetproxy.go @@ -2,37 +2,17 @@ package scriptmanager import ( "context" - "time" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/attrutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" + "github.com/cloudcmds/tamarin/arg" + "github.com/cloudcmds/tamarin/object" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" "github.com/pkg/errors" - "github.com/risor-io/risor/object" - "github.com/risor-io/risor/op" ) type resultSetProxy struct { resultSet *models.ResultSet } -func newResultSetProxy(rs *models.ResultSet) *resultSetProxy { - return &resultSetProxy{resultSet: rs} -} - -func (r *resultSetProxy) SetAttr(name string, value object.Object) error { - return errors.Errorf("attribute error: %v", name) -} - -func (r *resultSetProxy) RunOperation(opType op.BinaryOpType, right object.Object) object.Object { - return object.Errorf("op error: unsupported %v", opType) -} - -func (r *resultSetProxy) Cost() int { - return len(r.resultSet.Items()) -} - func (r *resultSetProxy) Interface() interface{} { return r.resultSet } @@ -115,105 +95,17 @@ func (r *resultSetProxy) GetAttr(name string) (object.Object, bool) { return &tableProxy{table: r.resultSet.TableInfo}, true case "length": return object.NewInt(int64(len(r.resultSet.Items()))), true - case "find": - return object.NewBuiltin("find", r.find), true - case "merge": - return object.NewBuiltin("merge", r.merge), true } return nil, false } -func (i *resultSetProxy) find(ctx context.Context, args ...object.Object) object.Object { - if objErr := require("resultset.find", 1, args); objErr != nil { - return objErr - } - - str, objErr := object.AsString(args[0]) - if objErr != nil { - return objErr - } - - modExpr, err := queryexpr.Parse(str) - if err != nil { - return object.Errorf("arg error: invalid path expression: %v", err) - } - - for idx, item := range i.resultSet.Items() { - rs, err := modExpr.EvalItem(item) - if err != nil { - continue - } - - if attrutils.Truthy(rs) { - return newItemProxy(i, idx) - } - } - - return object.Nil -} - -func (i *resultSetProxy) merge(ctx context.Context, args ...object.Object) object.Object { - type pksk struct { - pk types.AttributeValue - sk types.AttributeValue - } - - if objErr := require("resultset.merge", 1, args); objErr != nil { - return objErr - } - - otherRS, isRS := args[0].(*resultSetProxy) - if !isRS { - return object.NewError(errors.Errorf("type error: expected a resultset (got %v)", args[0].Type())) - } - - if !i.resultSet.TableInfo.Equal(otherRS.resultSet.TableInfo) { - return object.Nil - } - - itemsInI := make(map[pksk]models.Item) - newItems := make([]models.Item, 0, len(i.resultSet.Items())+len(otherRS.resultSet.Items())) - for _, item := range i.resultSet.Items() { - pk, sk := item.PKSK(i.resultSet.TableInfo) - itemsInI[pksk{pk, sk}] = item - newItems = append(newItems, item) - } - - for _, item := range otherRS.resultSet.Items() { - pk, sk := item.PKSK(i.resultSet.TableInfo) - if _, hasItem := itemsInI[pksk{pk, sk}]; !hasItem { - newItems = append(newItems, item) - } - } - - newResultSet := &models.ResultSet{ - Created: time.Now(), - TableInfo: i.resultSet.TableInfo, - } - newResultSet.SetItems(newItems) - - return &resultSetProxy{resultSet: newResultSet} -} - type itemProxy struct { resultSetProxy *resultSetProxy itemIndex int item models.Item } -func (i *itemProxy) SetAttr(name string, value object.Object) error { - return errors.Errorf("attribute error: %v", name) -} - -func (i *itemProxy) RunOperation(opType op.BinaryOpType, right object.Object) object.Object { - return object.Errorf("op error: unsupported %v", opType) -} - -func (i *itemProxy) Cost() int { - return len(i.item) -} - func newItemProxy(rs *resultSetProxy, itemIndex int) *itemProxy { return &itemProxy{ resultSetProxy: rs, @@ -262,7 +154,7 @@ func (i *itemProxy) GetAttr(name string) (object.Object, bool) { } func (i *itemProxy) value(ctx context.Context, args ...object.Object) object.Object { - if objErr := require("item.attr", 1, args); objErr != nil { + if objErr := arg.Require("item.attr", 1, args); objErr != nil { return objErr } @@ -288,7 +180,7 @@ func (i *itemProxy) value(ctx context.Context, args ...object.Object) object.Obj } func (i *itemProxy) setValue(ctx context.Context, args ...object.Object) object.Object { - if objErr := require("item.set_attr", 2, args); objErr != nil { + if objErr := arg.Require("item.set_attr", 2, args); objErr != nil { return objErr } @@ -315,7 +207,7 @@ func (i *itemProxy) setValue(ctx context.Context, args ...object.Object) object. } func (i *itemProxy) deleteAttr(ctx context.Context, args ...object.Object) object.Object { - if objErr := require("item.delete_attr", 1, args); objErr != nil { + if objErr := arg.Require("item.delete_attr", 1, args); objErr != nil { return objErr } diff --git a/internal/dynamo-browse/services/scriptmanager/resultsetproxy_test.go b/internal/dynamo-browse/services/scriptmanager/resultsetproxy_test.go index 3b7f354..9b5b87d 100644 --- a/internal/dynamo-browse/services/scriptmanager/resultsetproxy_test.go +++ b/internal/dynamo-browse/services/scriptmanager/resultsetproxy_test.go @@ -2,14 +2,13 @@ package scriptmanager_test import ( "context" - "testing" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager/mocks" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "testing" ) func TestResultSetProxy(t *testing.T) { @@ -30,7 +29,7 @@ func TestResultSetProxy(t *testing.T) { mockedUIService := mocks.NewUIService(t) testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") + res := session.query("some expr").unwrap() // Test properties of the result set assert(res.table.name, "hello") @@ -61,123 +60,6 @@ func TestResultSetProxy(t *testing.T) { }) } -func TestResultSetProxy_Find(t *testing.T) { - t.Run("should return the first item that matches the given expression", func(t *testing.T) { - rs := &models.ResultSet{} - rs.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "abc"}}, - {"pk": &types.AttributeValueMemberS{Value: "abc"}, "sk": &types.AttributeValueMemberS{Value: "abc"}, "primary": &types.AttributeValueMemberS{Value: "yes"}}, - {"pk": &types.AttributeValueMemberS{Value: "1232"}, "findMe": &types.AttributeValueMemberS{Value: "yes"}}, - {"pk": &types.AttributeValueMemberS{Value: "2345"}, "findMe": &types.AttributeValueMemberS{Value: "second"}}, - }) - - mockedSessionService := mocks.NewSessionService(t) - mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil) - - testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") - - assert(res.find('findMe is "any"').attr("pk") == "1232") - assert(res.find('findMe = "second"').attr("pk") == "2345") - assert(res.find('pk = sk').attr("primary") == "yes") - - assert(res.find('findMe = "missing"') == nil) - `) - - srv := scriptmanager.New(scriptmanager.WithFS(testFS)) - srv.SetIFaces(scriptmanager.Ifaces{ - Session: mockedSessionService, - }) - - ctx := context.Background() - err := <-srv.RunAdHocScript(ctx, "test.tm") - assert.NoError(t, err) - - mockedSessionService.AssertExpectations(t) - }) -} - -func TestResultSetProxy_Merge(t *testing.T) { - t.Run("should return a result set with items from both if both are from the same table", func(t *testing.T) { - td := &models.TableInfo{Name: "test", Keys: models.KeyAttribute{PartitionKey: "pk", SortKey: "sk"}} - - rs1 := &models.ResultSet{TableInfo: td} - rs1.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "abc"}, "sk": &types.AttributeValueMemberS{Value: "123"}}, - }) - - rs2 := &models.ResultSet{TableInfo: td} - rs2.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "bcd"}, "sk": &types.AttributeValueMemberS{Value: "234"}}, - }) - - mockedSessionService := mocks.NewSessionService(t) - mockedSessionService.EXPECT().Query(mock.Anything, "rs1", scriptmanager.QueryOptions{}).Return(rs1, nil) - mockedSessionService.EXPECT().Query(mock.Anything, "rs2", scriptmanager.QueryOptions{}).Return(rs2, nil) - - testFS := testScriptFile(t, "test.tm", ` - r1 := session.query("rs1") - r2 := session.query("rs2") - - res := r1.merge(r2) - - assert(res[0].attr("pk") == "abc") - assert(res[0].attr("sk") == "123") - assert(res[1].attr("pk") == "bcd") - assert(res[1].attr("sk") == "234") - `) - - srv := scriptmanager.New(scriptmanager.WithFS(testFS)) - srv.SetIFaces(scriptmanager.Ifaces{ - Session: mockedSessionService, - }) - - ctx := context.Background() - err := <-srv.RunAdHocScript(ctx, "test.tm") - assert.NoError(t, err) - - mockedSessionService.AssertExpectations(t) - }) - - t.Run("should return nil if result-sets are from different tables", func(t *testing.T) { - td1 := &models.TableInfo{Name: "test", Keys: models.KeyAttribute{PartitionKey: "pk", SortKey: "sk"}} - rs1 := &models.ResultSet{TableInfo: td1} - rs1.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "abc"}, "sk": &types.AttributeValueMemberS{Value: "123"}}, - }) - - td2 := &models.TableInfo{Name: "test2", Keys: models.KeyAttribute{PartitionKey: "pk2", SortKey: "sk"}} - rs2 := &models.ResultSet{TableInfo: td2} - rs2.SetItems([]models.Item{ - {"pk": &types.AttributeValueMemberS{Value: "bcd"}, "sk": &types.AttributeValueMemberS{Value: "234"}}, - }) - - mockedSessionService := mocks.NewSessionService(t) - mockedSessionService.EXPECT().Query(mock.Anything, "rs1", scriptmanager.QueryOptions{}).Return(rs1, nil) - mockedSessionService.EXPECT().Query(mock.Anything, "rs2", scriptmanager.QueryOptions{}).Return(rs2, nil) - - testFS := testScriptFile(t, "test.tm", ` - r1 := session.query("rs1") - r2 := session.query("rs2") - - res := r1.merge(r2) - - assert(res == nil) - `) - - srv := scriptmanager.New(scriptmanager.WithFS(testFS)) - srv.SetIFaces(scriptmanager.Ifaces{ - Session: mockedSessionService, - }) - - ctx := context.Background() - err := <-srv.RunAdHocScript(ctx, "test.tm") - assert.NoError(t, err) - - mockedSessionService.AssertExpectations(t) - }) -} - func TestResultSetProxy_GetAttr(t *testing.T) { t.Run("should return the value of items within a result set", func(t *testing.T) { rs := &models.ResultSet{} @@ -205,7 +87,7 @@ func TestResultSetProxy_GetAttr(t *testing.T) { mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil) testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") + res := session.query("some expr").unwrap() assert(res[0].attr("pk") == "abc", "str attr") assert(res[0].attr("sk") == 123, "num attr") @@ -282,7 +164,7 @@ func TestResultSetProxy_SetAttr(t *testing.T) { mockedUIService := mocks.NewUIService(t) testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") + res := session.query("some expr").unwrap() res[0].set_attr("pk", "bla-di-bla") res[0].set_attr("num", 123) @@ -333,7 +215,7 @@ func TestResultSetProxy_DeleteAttr(t *testing.T) { mockedUIService := mocks.NewUIService(t) testFS := testScriptFile(t, "test.tm", ` - res := session.query("some expr") + res := session.query("some expr").unwrap() res[0].delete_attr("deleteMe") session.set_result_set(res) `) diff --git a/internal/dynamo-browse/services/scriptmanager/scrsched.go b/internal/dynamo-browse/services/scriptmanager/scrsched.go index 3846676..e04ebdf 100644 --- a/internal/dynamo-browse/services/scriptmanager/scrsched.go +++ b/internal/dynamo-browse/services/scriptmanager/scrsched.go @@ -3,7 +3,6 @@ package scriptmanager import ( "context" "github.com/pkg/errors" - "time" ) type scriptScheduler struct { @@ -42,7 +41,7 @@ func (ss *scriptScheduler) runNow(ctx context.Context, job func(ctx context.Cont select { case ss.jobChan <- scriptJob{ctx: ctx, job: job}: return nil - case <-time.After(500 * time.Millisecond): + default: return errors.New("a script is already running") } } diff --git a/internal/dynamo-browse/services/scriptmanager/service.go b/internal/dynamo-browse/services/scriptmanager/service.go index eac6638..30d4f7a 100644 --- a/internal/dynamo-browse/services/scriptmanager/service.go +++ b/internal/dynamo-browse/services/scriptmanager/service.go @@ -2,25 +2,22 @@ package scriptmanager import ( "context" + "github.com/cloudcmds/tamarin/exec" + "github.com/cloudcmds/tamarin/object" + "github.com/cloudcmds/tamarin/scope" + "github.com/lmika/audax/internal/dynamo-browse/services/keybindings" + "github.com/pkg/errors" "io/fs" "log" "os" "path/filepath" "strings" - - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/keybindings" - "github.com/pkg/errors" - "github.com/risor-io/risor" - "github.com/risor-io/risor/object" -) - -var ( - relPrefix = "." + string(filepath.Separator) ) type Service struct { lookupPaths []fs.FS ifaces Ifaces + options Options sched *scriptScheduler plugins []*ScriptPlugin } @@ -40,6 +37,10 @@ func (s *Service) SetLookupPaths(fs []fs.FS) { s.lookupPaths = fs } +func (s *Service) SetDefaultOptions(options Options) { + s.options = options +} + func (s *Service) SetIFaces(ifaces Ifaces) { s.ifaces = ifaces } @@ -93,11 +94,19 @@ func (s *Service) startAdHocScript(ctx context.Context, filename string, errChan return } - ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename)}) + scp := scope.New(scope.Opts{Parent: s.parentScope()}) - if _, err := risor.Eval(ctx, code, - risor.WithGlobals(s.builtins()), - ); err != nil { + ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename), options: s.options}) + + if _, err = exec.Execute(ctx, exec.Opts{ + Input: string(code), + File: filename, + Scope: scp, + Builtins: []*object.Builtin{ + object.NewBuiltin("print", printBuiltin), + object.NewBuiltin("printf", printfBuiltin), + }, + }); err != nil { errChan <- errors.Wrapf(err, "script %v", filename) return } @@ -122,14 +131,17 @@ func (s *Service) loadScript(ctx context.Context, filename string, resChan chan scriptService: s, } - ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename)}) + scp := scope.New(scope.Opts{Parent: s.parentScope()}) - if _, err := risor.Eval(ctx, code, - risor.WithGlobals(s.builtins()), - risor.WithGlobals(map[string]any{ - "ext": (&extModule{scriptPlugin: newPlugin}).register(), - }), - ); err != nil { + (&extModule{scriptPlugin: newPlugin}).register(scp) + + ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename), options: s.options}) + + if _, err = exec.Execute(ctx, exec.Opts{ + Input: string(code), + File: filename, + Scope: scp, + }); err != nil { resChan <- loadedScriptResult{err: errors.Wrapf(err, "script %v", filename)} return } @@ -137,7 +149,7 @@ func (s *Service) loadScript(ctx context.Context, filename string, resChan chan resChan <- loadedScriptResult{scriptPlugin: newPlugin} } -func (s *Service) readScript(filename string, allowCwd bool) (string, error) { +func (s *Service) readScript(filename string, allowCwd bool) ([]byte, error) { if allowCwd { if cwd, err := os.Getwd(); err == nil { fullScriptPath := filepath.Join(cwd, filename) @@ -145,21 +157,21 @@ func (s *Service) readScript(filename string, allowCwd bool) (string, error) { if stat, err := os.Stat(fullScriptPath); err == nil && !stat.IsDir() { code, err := os.ReadFile(filename) if err != nil { - return "", err + return nil, err } - return string(code), nil + return code, nil } } else { log.Printf("warn: cannot get cwd for reading script %v: %v", filename, err) } } - if strings.HasPrefix(filename, string(filepath.Separator)) || strings.HasPrefix(filename, relPrefix) { + if strings.HasPrefix(filename, string(filepath.Separator)) { code, err := os.ReadFile(filename) if err != nil { - return "", err + return nil, err } - return string(code), nil + return code, nil } for _, currFS := range s.lookupPaths { @@ -169,7 +181,7 @@ func (s *Service) readScript(filename string, allowCwd bool) (string, error) { if errors.Is(err, os.ErrNotExist) { continue } else { - return "", err + return nil, err } } else if stat.IsDir() { continue @@ -177,13 +189,13 @@ func (s *Service) readScript(filename string, allowCwd bool) (string, error) { code, err := fs.ReadFile(currFS, filename) if err == nil { - return string(code), nil + return code, nil } else { - return "", err + return nil, err } } - return "", os.ErrNotExist + return nil, os.ErrNotExist } // LookupCommand looks up a command defined by a script. @@ -240,11 +252,10 @@ func (s *Service) RebindKeyBinding(keyBinding string, newKey string) error { return keybindings.InvalidBindingError(keyBinding) } -func (s *Service) builtins() map[string]any { - return map[string]any{ - "ui": (&uiModule{uiService: s.ifaces.UI}).register(), - "session": (&sessionModule{sessionService: s.ifaces.Session}).register(), - "print": object.NewBuiltin("print", printBuiltin), - "printf": object.NewBuiltin("printf", printfBuiltin), - } +func (s *Service) parentScope() *scope.Scope { + scp := scope.New(scope.Opts{}) + (&uiModule{uiService: s.ifaces.UI}).register(scp) + (&sessionModule{sessionService: s.ifaces.Session}).register(scp) + (&osModule{}).register(scp) + return scp } diff --git a/internal/dynamo-browse/services/scriptmanager/service_test.go b/internal/dynamo-browse/services/scriptmanager/service_test.go index 745124d..1526434 100644 --- a/internal/dynamo-browse/services/scriptmanager/service_test.go +++ b/internal/dynamo-browse/services/scriptmanager/service_test.go @@ -2,8 +2,8 @@ package scriptmanager_test import ( "context" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager/mocks" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" + "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "io/fs" diff --git a/internal/dynamo-browse/services/scriptmanager/tableproxy.go b/internal/dynamo-browse/services/scriptmanager/tableproxy.go index 252348f..1ef6204 100644 --- a/internal/dynamo-browse/services/scriptmanager/tableproxy.go +++ b/internal/dynamo-browse/services/scriptmanager/tableproxy.go @@ -1,11 +1,9 @@ package scriptmanager import ( - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/pkg/errors" - "github.com/risor-io/risor/object" - "github.com/risor-io/risor/op" + "github.com/cloudcmds/tamarin/object" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/models" "reflect" ) @@ -18,18 +16,6 @@ type tableProxy struct { table *models.TableInfo } -func (t *tableProxy) SetAttr(name string, value object.Object) error { - return errors.Errorf("attribute error: %v", name) -} - -func (t *tableProxy) RunOperation(opType op.BinaryOpType, right object.Object) object.Object { - return object.Errorf("op error: unsupported %v", opType) -} - -func (t *tableProxy) Cost() int { - return 0 -} - func (t *tableProxy) Type() object.Type { return "table" } @@ -82,18 +68,6 @@ type tableIndexProxy struct { gsi models.TableGSI } -func (t tableIndexProxy) SetAttr(name string, value object.Object) error { - return errors.Errorf("attribute error: %v", name) -} - -func (t tableIndexProxy) RunOperation(opType op.BinaryOpType, right object.Object) object.Object { - return object.Errorf("op error: unsupported %v", opType) -} - -func (t tableIndexProxy) Cost() int { - return 0 -} - func newTableIndexProxy(gsi models.TableGSI) object.Object { return tableIndexProxy{gsi: gsi} } diff --git a/internal/dynamo-browse/services/scriptmanager/typemapping.go b/internal/dynamo-browse/services/scriptmanager/typemapping.go index d7b8a47..6cf1583 100644 --- a/internal/dynamo-browse/services/scriptmanager/typemapping.go +++ b/internal/dynamo-browse/services/scriptmanager/typemapping.go @@ -3,10 +3,10 @@ package scriptmanager import ( "fmt" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/common/maputils" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" + "github.com/cloudcmds/tamarin/object" + "github.com/lmika/audax/internal/common/maputils" + "github.com/lmika/audax/internal/common/sliceutils" "github.com/pkg/errors" - "github.com/risor-io/risor/object" "regexp" "strconv" ) diff --git a/internal/dynamo-browse/services/scriptmanager/types.go b/internal/dynamo-browse/services/scriptmanager/types.go index 442f03b..b0bf1a0 100644 --- a/internal/dynamo-browse/services/scriptmanager/types.go +++ b/internal/dynamo-browse/services/scriptmanager/types.go @@ -1,8 +1,6 @@ package scriptmanager -import ( - "context" -) +import "context" type ScriptPlugin struct { scriptService *Service @@ -10,7 +8,6 @@ type ScriptPlugin struct { definedCommands map[string]*Command definedKeyBindings map[string]*Command keyToKeyBinding map[string]string - relatedItems []*relatedItemBuilder } func (sp *ScriptPlugin) Name() string { @@ -29,7 +26,3 @@ func (c *Command) Invoke(ctx context.Context, args []string, errChan chan error) errChan <- c.cmdFn(ctx, args) }) } - -//func (c *Command) LookupRelevantItems(ctx context.Context, table *models.TableInfo, item *models.Item) error { -// -//} diff --git a/internal/dynamo-browse/services/tables/iface.go b/internal/dynamo-browse/services/tables/iface.go index 8ebc335..75aca24 100644 --- a/internal/dynamo-browse/services/tables/iface.go +++ b/internal/dynamo-browse/services/tables/iface.go @@ -4,7 +4,7 @@ import ( "context" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" ) type TableProvider interface { diff --git a/internal/dynamo-browse/services/tables/service.go b/internal/dynamo-browse/services/tables/service.go index 52f97bb..fb2bad3 100644 --- a/internal/dynamo-browse/services/tables/service.go +++ b/internal/dynamo-browse/services/tables/service.go @@ -5,13 +5,13 @@ import ( "fmt" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/jobs" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/dynamo-browse/services/jobs" "log" "strings" "time" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/pkg/errors" ) @@ -79,23 +79,22 @@ func (s *Service) doScan( if err != nil && len(results) == 0 { return &models.ResultSet{ TableInfo: tableInfo, - Created: time.Now(), Query: expr, ExclusiveStartKey: exclusiveStartKey, LastEvaluatedKey: lastEvalKey, }, errors.Wrapf(err, "unable to scan table %v", tableInfo.Name) } + models.Sort(results, tableInfo) + resultSet := &models.ResultSet{ TableInfo: tableInfo, - Created: time.Now(), Query: expr, ExclusiveStartKey: exclusiveStartKey, LastEvaluatedKey: lastEvalKey, } resultSet.SetItems(results) resultSet.RefreshColumns() - resultSet.Sort(models.PKSKSortFilter(tableInfo)) return resultSet, err } diff --git a/internal/dynamo-browse/services/tables/service_test.go b/internal/dynamo-browse/services/tables/service_test.go index 3ed9b9f..3135b5d 100644 --- a/internal/dynamo-browse/services/tables/service_test.go +++ b/internal/dynamo-browse/services/tables/service_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/dynamo" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/tables" - "github.com/lmika/dynamo-browse/test/testdynamo" + "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/services/tables" + "github.com/lmika/audax/test/testdynamo" "github.com/stretchr/testify/assert" ) diff --git a/internal/dynamo-browse/services/viewsnapshot/iface.go b/internal/dynamo-browse/services/viewsnapshot/iface.go index c6ac3a4..69ec99c 100644 --- a/internal/dynamo-browse/services/viewsnapshot/iface.go +++ b/internal/dynamo-browse/services/viewsnapshot/iface.go @@ -1,6 +1,6 @@ package viewsnapshot -import "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/serialisable" +import "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" type ViewSnapshotStore interface { Save(rs *serialisable.ViewSnapshot) error diff --git a/internal/dynamo-browse/services/viewsnapshot/service.go b/internal/dynamo-browse/services/viewsnapshot/service.go index 410f43d..791ee90 100644 --- a/internal/dynamo-browse/services/viewsnapshot/service.go +++ b/internal/dynamo-browse/services/viewsnapshot/service.go @@ -1,7 +1,7 @@ package viewsnapshot import ( - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/serialisable" + "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" "github.com/pkg/errors" "time" ) diff --git a/internal/dynamo-browse/services/viewsnapshot/service_test.go b/internal/dynamo-browse/services/viewsnapshot/service_test.go index 5805cce..845be56 100644 --- a/internal/dynamo-browse/services/viewsnapshot/service_test.go +++ b/internal/dynamo-browse/services/viewsnapshot/service_test.go @@ -3,11 +3,11 @@ package viewsnapshot_test import ( "bytes" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/queryexpr" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/serialisable" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/workspacestore" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/viewsnapshot" - "github.com/lmika/dynamo-browse/test/testworkspace" + "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" + "github.com/lmika/audax/internal/dynamo-browse/models/serialisable" + "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" + "github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot" + "github.com/lmika/audax/test/testworkspace" "github.com/stretchr/testify/assert" "testing" ) diff --git a/internal/dynamo-browse/ui/keybindings/defaults.go b/internal/dynamo-browse/ui/keybindings/defaults.go index 818919a..639a369 100644 --- a/internal/dynamo-browse/ui/keybindings/defaults.go +++ b/internal/dynamo-browse/ui/keybindings/defaults.go @@ -12,7 +12,6 @@ func Default() *KeyBindings { ResetColumns: key.NewBinding(key.WithKeys("R", "reset columns")), AddColumn: key.NewBinding(key.WithKeys("a", "add new column")), DeleteColumn: key.NewBinding(key.WithKeys("d", "delete column")), - SortByColumn: key.NewBinding(key.WithKeys("s", "sort by column")), }, TableView: &TableKeyBinding{ MoveUp: key.NewBinding(key.WithKeys("i", "up")), @@ -26,9 +25,7 @@ func Default() *KeyBindings { }, View: &ViewKeyBindings{ Mark: key.NewBinding(key.WithKeys("m"), key.WithHelp("m", "mark")), - ToggleMarkedItems: key.NewBinding(key.WithKeys("M"), key.WithHelp("M", "toggle marged items")), CopyItemToClipboard: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy item to clipboard")), - CopyTableToClipboard: key.NewBinding(key.WithKeys("C"), key.WithHelp("C", "copy table to clipboard")), Rescan: key.NewBinding(key.WithKeys("R"), key.WithHelp("R", "rescan")), PromptForQuery: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "prompt for query")), PromptForFilter: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "filter")), @@ -39,7 +36,6 @@ func Default() *KeyBindings { CycleLayoutBackwards: key.NewBinding(key.WithKeys("W"), key.WithHelp("W", "cycle layout backward")), PromptForCommand: key.NewBinding(key.WithKeys(":"), key.WithHelp(":", "prompt for command")), ShowColumnOverlay: key.NewBinding(key.WithKeys("f"), key.WithHelp("f", "show column overlay")), - ShowRelItemsOverlay: key.NewBinding(key.WithKeys("O"), key.WithHelp("O", "show related items overlay")), CancelRunningJob: key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "cancel running job or quit")), Quit: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "quit")), }, diff --git a/internal/dynamo-browse/ui/keybindings/keybindings.go b/internal/dynamo-browse/ui/keybindings/keybindings.go index 96c24b3..26178fd 100644 --- a/internal/dynamo-browse/ui/keybindings/keybindings.go +++ b/internal/dynamo-browse/ui/keybindings/keybindings.go @@ -16,7 +16,6 @@ type FieldsPopupBinding struct { ResetColumns key.Binding `keymap:"reset-columns"` AddColumn key.Binding `keymap:"add-column"` DeleteColumn key.Binding `keymap:"delete-column"` - SortByColumn key.Binding `keymap:"sort-by-column"` } type TableKeyBinding struct { @@ -32,9 +31,7 @@ type TableKeyBinding struct { type ViewKeyBindings struct { Mark key.Binding `keymap:"mark"` - ToggleMarkedItems key.Binding `keymap:"toggle-marked-items"` CopyItemToClipboard key.Binding `keymap:"copy-item-to-clipboard"` - CopyTableToClipboard key.Binding `keymap:"copy-table-to-clipboard"` Rescan key.Binding `keymap:"rescan"` PromptForQuery key.Binding `keymap:"prompt-for-query"` PromptForFilter key.Binding `keymap:"prompt-for-filter"` @@ -46,7 +43,6 @@ type ViewKeyBindings struct { CycleLayoutBackwards key.Binding `keymap:"cycle-layout-backwards"` PromptForCommand key.Binding `keymap:"prompt-for-command"` ShowColumnOverlay key.Binding `keymap:"show-fields-popup"` - ShowRelItemsOverlay key.Binding `keymap:"show-rel-items-popup"` CancelRunningJob key.Binding `keymap:"cancel-running-job"` Quit key.Binding `keymap:"quit"` } diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index cb07519..ef5d5da 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -1,32 +1,29 @@ package ui import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/audax/internal/common/ui/commandctrl" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" + "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/colselector" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dialogprompt" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamoitemedit" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamoitemview" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamotableview" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/statusandprompt" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/tableselect" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" + bus "github.com/lmika/events" + "github.com/pkg/errors" "log" "os" "strings" - - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/commandctrl" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/itemrenderer" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/keybindings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/colselector" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/dialogprompt" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamoitemedit" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamoitemview" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamotableview" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/relselector" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/statusandprompt" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/tableselect" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" - bus "github.com/lmika/events" - "github.com/pkg/errors" ) const ( @@ -50,7 +47,6 @@ type Model struct { scriptController *controllers.ScriptController jobController *controllers.JobsController colSelector *colselector.Model - relSelector *relselector.Model itemEdit *dynamoitemedit.Model statusAndPrompt *statusandprompt.StatusAndPrompt tableSelect *tableselect.Model @@ -78,7 +74,6 @@ func NewModel( scriptController *controllers.ScriptController, eventBus *bus.Bus, keyBindingController *controllers.KeyBindingController, - pasteboardProvider services.PasteboardProvider, defaultKeyMap *keybindings.KeyBindings, ) Model { uiStyles := styles.DefaultStyles @@ -88,9 +83,8 @@ func NewModel( mainView := layout.NewVBox(layout.LastChildFixedAt(14), dtv, div) colSelector := colselector.New(mainView, defaultKeyMap, columnsController) - relSelector := relselector.New(colSelector) - itemEdit := dynamoitemedit.NewModel(relSelector) - statusAndPrompt := statusandprompt.New(itemEdit, pasteboardProvider, "", uiStyles.StatusAndPrompt) + itemEdit := dynamoitemedit.NewModel(colSelector) + statusAndPrompt := statusandprompt.New(itemEdit, "", uiStyles.StatusAndPrompt) dialogPrompt := dialogprompt.New(statusAndPrompt) tableSelect := tableselect.New(dialogPrompt, uiStyles) @@ -108,14 +102,7 @@ func NewModel( if len(args) == 0 { return events.Error(errors.New("expected filename")) } - - opts := controllers.ExportOptions{} - if len(args) == 2 && args[0] == "-all" { - opts.AllResults = true - args = args[1:] - } - - return exportController.ExportCSV(args[0], opts) + return exportController.ExportCSV(args[0]) }, "mark": func(ctx commandctrl.ExecContext, args []string) tea.Msg { var markOp = controllers.MarkOpMark @@ -132,12 +119,7 @@ func NewModel( } } - var whereExpr = "" - if len(args) == 3 && args[1] == "-where" { - whereExpr = args[2] - } - - return rc.Mark(markOp, whereExpr) + return rc.Mark(markOp) }, "next-page": func(ctx commandctrl.ExecContext, args []string) tea.Msg { return rc.NextPage() @@ -146,9 +128,6 @@ func NewModel( // TEMP "new-item": commandctrl.NoArgCommand(wc.NewItem), - "clone": func(ctx commandctrl.ExecContext, args []string) tea.Msg { - return wc.CloneItem(dtv.SelectedItemIndex()) - }, "set-attr": func(ctx commandctrl.ExecContext, args []string) tea.Msg { if len(args) == 0 { return events.Error(errors.New("expected field")) @@ -165,8 +144,6 @@ func NewModel( itemType = models.BoolItemType case "-NULL": itemType = models.NullItemType - case "-TO": - itemType = models.ExprValueItemType default: return events.Error(errors.New("unrecognised item type")) } @@ -248,7 +225,6 @@ func NewModel( jobController: jobController, itemEdit: itemEdit, colSelector: colSelector, - relSelector: relSelector, statusAndPrompt: statusAndPrompt, tableSelect: tableSelect, root: root, @@ -272,20 +248,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ) case tea.KeyMsg: // TODO: use modes here - if !m.statusAndPrompt.InPrompt() && !m.tableSelect.Visible() && !m.colSelector.ColSelectorVisible() && !m.relSelector.SelectorVisible() { + if !m.statusAndPrompt.InPrompt() && !m.tableSelect.Visible() && !m.colSelector.ColSelectorVisible() { switch { case key.Matches(msg, m.keyMap.Mark): if idx := m.tableView.SelectedItemIndex(); idx >= 0 { return m, events.SetTeaMessage(m.tableWriteController.ToggleMark(idx)) } - case key.Matches(msg, m.keyMap.ToggleMarkedItems): - return m, events.SetTeaMessage(m.tableReadController.Mark(controllers.MarkOpToggle, "")) case key.Matches(msg, m.keyMap.CopyItemToClipboard): if idx := m.tableView.SelectedItemIndex(); idx >= 0 { return m, events.SetTeaMessage(m.tableReadController.CopyItemToClipboard(idx)) } - case key.Matches(msg, m.keyMap.CopyTableToClipboard): - return m, events.SetTeaMessage(m.exportController.ExportCSVToClipboard()) case key.Matches(msg, m.keyMap.Rescan): return m, m.tableReadController.Rescan case key.Matches(msg, m.keyMap.PromptForQuery): @@ -307,10 +279,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // return m, nil case key.Matches(msg, m.keyMap.ShowColumnOverlay): return m, events.SetTeaMessage(controllers.ShowColumnOverlay{}) - case key.Matches(msg, m.keyMap.ShowRelItemsOverlay): - if idx := m.tableView.SelectedItemIndex(); idx >= 0 { - return m, events.SetTeaMessage(m.scriptController.LookupRelatedItems(idx)) - } case key.Matches(msg, m.keyMap.PromptForCommand): return m, m.commandController.Prompt case key.Matches(msg, m.keyMap.PromptForTable): diff --git a/internal/dynamo-browse/ui/teamodels/colselector/colmodel.go b/internal/dynamo-browse/ui/teamodels/colselector/colmodel.go index 514c8ac..7a6f2ae 100644 --- a/internal/dynamo-browse/ui/teamodels/colselector/colmodel.go +++ b/internal/dynamo-browse/ui/teamodels/colselector/colmodel.go @@ -4,13 +4,13 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/columns" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/keybindings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/models/columns" + "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" table "github.com/lmika/go-bubble-table" + "log" "strings" ) @@ -26,9 +26,8 @@ type colListModel struct { keyBinding *keybindings.KeyBindings colController *controllers.ColumnsController - rows []table.Row - table table.Model - sortCriteria models.SortCriteria + rows []table.Row + table table.Model } func newColListModel(keyBinding *keybindings.KeyBindings, colController *controllers.ColumnsController) *colListModel { @@ -50,6 +49,7 @@ func (m *colListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case controllers.SetSelectedColumnInColSelector: // HACK: this needs to work for all cases + log.Printf("%d == %d?", int(msg), m.table.Cursor()+1) if int(msg) == m.table.Cursor()+1 { m.table.GoDown() } @@ -70,8 +70,6 @@ func (m *colListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, events.SetTeaMessage(m.colController.AddColumn(m.table.Cursor())) case key.Matches(msg, m.keyBinding.ColumnPopup.DeleteColumn): return m, events.SetTeaMessage(m.colController.DeleteColumn(m.table.Cursor())) - case key.Matches(msg, m.keyBinding.ColumnPopup.SortByColumn): - return m, events.SetTeaMessage(m.colController.SortByColumn(m.table.Cursor())) // Main table nav case key.Matches(msg, m.keyBinding.TableView.ColLeft): @@ -126,7 +124,6 @@ func (c *colListModel) Resize(w, h int) layout.ResizingModel { func (c *colListModel) refreshTable() { colsFromController := c.colController.Columns() - c.sortCriteria = c.colController.SortCriteria() if len(c.rows) != len(colsFromController.Columns) { c.setColumnsFromModel(colsFromController) } diff --git a/internal/dynamo-browse/ui/teamodels/colselector/model.go b/internal/dynamo-browse/ui/teamodels/colselector/model.go index 8a6aabb..8d9d805 100644 --- a/internal/dynamo-browse/ui/teamodels/colselector/model.go +++ b/internal/dynamo-browse/ui/teamodels/colselector/model.go @@ -2,10 +2,10 @@ package colselector import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/keybindings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) const ( @@ -42,7 +42,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cc utils.CmdCollector switch msg := msg.(type) { case controllers.ShowColumnOverlay: - m.colListModel.sortCriteria = m.columnsController.SortCriteria() m.colListModel.setColumnsFromModel(m.columnsController.Columns()) m.compositor.SetOverlay(m.colListModel, m.w/2-overlayWidth/2, m.h/2-overlayHeight/2, overlayWidth, overlayHeight) case controllers.HideColumnOverlay: diff --git a/internal/dynamo-browse/ui/teamodels/colselector/tblmodel.go b/internal/dynamo-browse/ui/teamodels/colselector/tblmodel.go index 6d4b3b4..505d2e7 100644 --- a/internal/dynamo-browse/ui/teamodels/colselector/tblmodel.go +++ b/internal/dynamo-browse/ui/teamodels/colselector/tblmodel.go @@ -3,7 +3,6 @@ package colselector import ( "fmt" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/evaluators" table "github.com/lmika/go-bubble-table" "io" ) @@ -24,17 +23,9 @@ func (clr colListRowModel) Render(w io.Writer, model table.Model, index int) { } col := clr.m.colController.Columns().Columns[index] - ff := clr.m.sortCriteria.FirstField() - switch { - case col.Hidden: - fmt.Fprintln(w, style.Render(fmt.Sprintf("✕\t%v", col.Name))) - case evaluators.Equals(ff.Field, col.Evaluator): - if ff.Asc { - fmt.Fprintln(w, style.Render(fmt.Sprintf("v\t%v", col.Name))) - } else { - fmt.Fprintln(w, style.Render(fmt.Sprintf("^\t%v", col.Name))) - } - default: + if !col.Hidden { fmt.Fprintln(w, style.Render(fmt.Sprintf("⋅\t%v", col.Name))) + } else { + fmt.Fprintln(w, style.Render(fmt.Sprintf("✕\t%v", col.Name))) } } diff --git a/internal/dynamo-browse/ui/teamodels/dialogprompt/dialogmodel.go b/internal/dynamo-browse/ui/teamodels/dialogprompt/dialogmodel.go index da74c27..467eca6 100644 --- a/internal/dynamo-browse/ui/teamodels/dialogprompt/dialogmodel.go +++ b/internal/dynamo-browse/ui/teamodels/dialogprompt/dialogmodel.go @@ -3,7 +3,7 @@ package dialogprompt import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" ) var style = lipgloss.NewStyle(). diff --git a/internal/dynamo-browse/ui/teamodels/dialogprompt/model.go b/internal/dynamo-browse/ui/teamodels/dialogprompt/model.go index 6711293..e5629b0 100644 --- a/internal/dynamo-browse/ui/teamodels/dialogprompt/model.go +++ b/internal/dynamo-browse/ui/teamodels/dialogprompt/model.go @@ -2,7 +2,7 @@ package dialogprompt import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" ) type Model struct { diff --git a/internal/dynamo-browse/ui/teamodels/dynamoitemedit/model.go b/internal/dynamo-browse/ui/teamodels/dynamoitemedit/model.go index 48d8362..a827dd8 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamoitemedit/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamoitemedit/model.go @@ -4,8 +4,8 @@ import ( table "github.com/calyptia/go-bubble-table" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) type Model struct { diff --git a/internal/dynamo-browse/ui/teamodels/dynamoitemview/events.go b/internal/dynamo-browse/ui/teamodels/dynamoitemview/events.go index 030be9e..ec393f0 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamoitemview/events.go +++ b/internal/dynamo-browse/ui/teamodels/dynamoitemview/events.go @@ -1,6 +1,6 @@ package dynamoitemview -import "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" +import "github.com/lmika/audax/internal/dynamo-browse/models" type NewItemSelected struct { ResultSet *models.ResultSet diff --git a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go index 1d8adae..1703a30 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamoitemview/model.go @@ -4,11 +4,11 @@ import ( "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/itemrenderer" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/frame" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/frame" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles" "strings" ) diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go index cf61d72..a7b3c57 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go @@ -4,14 +4,14 @@ import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/columns" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/keybindings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/dynamoitemview" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/frame" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models/columns" + "github.com/lmika/audax/internal/dynamo-browse/ui/keybindings" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/dynamoitemview" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/frame" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles" bus "github.com/lmika/events" table "github.com/lmika/go-bubble-table" "strings" diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go index a38aea8..feeb5fd 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go +++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/tblmodel.go @@ -3,11 +3,11 @@ package dynamotableview import ( "fmt" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/itemrender" + "github.com/lmika/audax/internal/dynamo-browse/models/itemrender" "io" "strings" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" table "github.com/lmika/go-bubble-table" ) diff --git a/internal/dynamo-browse/ui/teamodels/frame/frame.go b/internal/dynamo-browse/ui/teamodels/frame/frame.go index e2aea1c..032ce1a 100644 --- a/internal/dynamo-browse/ui/teamodels/frame/frame.go +++ b/internal/dynamo-browse/ui/teamodels/frame/frame.go @@ -4,7 +4,7 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) var ( diff --git a/internal/dynamo-browse/ui/teamodels/itemdisplay/model.go b/internal/dynamo-browse/ui/teamodels/itemdisplay/model.go index 138b771..550f8ee 100644 --- a/internal/dynamo-browse/ui/teamodels/itemdisplay/model.go +++ b/internal/dynamo-browse/ui/teamodels/itemdisplay/model.go @@ -2,8 +2,8 @@ package itemdisplay import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) type Model struct { diff --git a/internal/dynamo-browse/ui/teamodels/layout/model.go b/internal/dynamo-browse/ui/teamodels/layout/model.go index 13a5791..9ffe92c 100644 --- a/internal/dynamo-browse/ui/teamodels/layout/model.go +++ b/internal/dynamo-browse/ui/teamodels/layout/model.go @@ -3,7 +3,7 @@ package layout import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" "strconv" "strings" ) diff --git a/internal/dynamo-browse/ui/teamodels/layout/vbox.go b/internal/dynamo-browse/ui/teamodels/layout/vbox.go index e52a917..3f4be6e 100644 --- a/internal/dynamo-browse/ui/teamodels/layout/vbox.go +++ b/internal/dynamo-browse/ui/teamodels/layout/vbox.go @@ -4,7 +4,7 @@ import ( "strings" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) // VBox is a model which will display its children vertically. diff --git a/internal/dynamo-browse/ui/teamodels/layout/zstack.go b/internal/dynamo-browse/ui/teamodels/layout/zstack.go index 2d271c8..63aa687 100644 --- a/internal/dynamo-browse/ui/teamodels/layout/zstack.go +++ b/internal/dynamo-browse/ui/teamodels/layout/zstack.go @@ -2,7 +2,7 @@ package layout import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) type ZStack struct { diff --git a/internal/dynamo-browse/ui/teamodels/modal/model.go b/internal/dynamo-browse/ui/teamodels/modal/model.go index 6d8bec8..a9e9767 100644 --- a/internal/dynamo-browse/ui/teamodels/modal/model.go +++ b/internal/dynamo-browse/ui/teamodels/modal/model.go @@ -2,8 +2,8 @@ package modal import ( tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" "log" ) diff --git a/internal/dynamo-browse/ui/teamodels/relselector/itemmdl.go b/internal/dynamo-browse/ui/teamodels/relselector/itemmdl.go deleted file mode 100644 index 79372c7..0000000 --- a/internal/dynamo-browse/ui/teamodels/relselector/itemmdl.go +++ /dev/null @@ -1,17 +0,0 @@ -package relselector - -type relItemModel struct { - name string -} - -func (ti relItemModel) FilterValue() string { - return ti.name -} - -func (ti relItemModel) Title() string { - return ti.name -} - -func (ti relItemModel) Description() string { - return ti.name -} diff --git a/internal/dynamo-browse/ui/teamodels/relselector/listmdl.go b/internal/dynamo-browse/ui/teamodels/relselector/listmdl.go deleted file mode 100644 index dc91665..0000000 --- a/internal/dynamo-browse/ui/teamodels/relselector/listmdl.go +++ /dev/null @@ -1,132 +0,0 @@ -package relselector - -import ( - "strings" - - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models/relitems" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" -) - -var ( - frameColor = lipgloss.Color("63") - - frameStyle = lipgloss.NewStyle(). - Foreground(frameColor) - style = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(frameColor) - - keyEsc = key.NewBinding(key.WithKeys(tea.KeyEsc.String())) - keyEnter = key.NewBinding(key.WithKeys(tea.KeyEnter.String())) -) - -type listModel struct { - event controllers.ShowRelatedItemsOverlay - list list.Model - height int -} - -func newListModel() *listModel { - items := []list.Item{} - - delegate := list.NewDefaultDelegate() - delegate.ShowDescription = false - delegate.Styles.SelectedTitle = lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(lipgloss.Color("#2c5fb7")). - Foreground(lipgloss.Color("#2c5fb7")). - Padding(0, 0, 0, 1) - delegate.Styles.SelectedDesc = lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(lipgloss.Color("#2c5fb7")). - Foreground(lipgloss.Color("#5277b7")). - Padding(0, 0, 0, 1) - - list := list.New(items, delegate, overlayWidth, overlayHeight-4) - list.KeyMap.CursorUp = key.NewBinding( - key.WithKeys("up", "i"), - key.WithHelp("↑/i", "up"), - ) - list.KeyMap.CursorDown = key.NewBinding( - key.WithKeys("down", "k"), - key.WithHelp("↓/k", "down"), - ) - list.KeyMap.PrevPage = key.NewBinding( - key.WithKeys("left", "j", "pgup", "b", "u"), - key.WithHelp("←/j/pgup", "prev page"), - ) - list.KeyMap.NextPage = key.NewBinding( - key.WithKeys("right", "l", "pgdown", "f", "d"), - key.WithHelp("→/l/pgdn", "next page"), - ) - list.SetShowTitle(false) - list.SetShowHelp(false) - //list.DisableQuitKeybindings() - - return &listModel{ - list: list, - } -} - -func (m *listModel) Init() tea.Cmd { - return nil -} - -func (m *listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cc utils.CmdCollector - - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, keyEnter): - if onSel := m.event.OnSelected; onSel != nil { - cc.Add(events.SetTeaMessage(onSel(m.event.Items[m.list.Index()]))) - } - return m, events.SetTeaMessage(controllers.HideRelatedItemsOverlay{}) - case key.Matches(msg, keyEsc): - return m, events.SetTeaMessage(controllers.HideRelatedItemsOverlay{}) - default: - m.list = cc.Collect(m.list.Update(msg)).(list.Model) - } - default: - m.list = cc.Collect(m.list.Update(msg)).(list.Model) - } - return m, cc.Cmd() -} - -func (m *listModel) View() string { - innerView := lipgloss.JoinVertical( - lipgloss.Top, - lipgloss.PlaceHorizontal(overlayWidth-2, lipgloss.Center, "Related Items"), - frameStyle.Render(strings.Repeat(lipgloss.NormalBorder().Top, overlayWidth-2)), - m.list.View(), - ) - - view := style.Width(overlayWidth - 2).Height(m.height - 2).Render(innerView) - - return view -} - -func (m *listModel) Resize(w, h int) layout.ResizingModel { - return m -} - -func (m *listModel) setItems(event controllers.ShowRelatedItemsOverlay, newHeight int) { - listItems := sliceutils.Map(event.Items, func(item relitems.RelatedItem) list.Item { - return relItemModel{name: item.Name} - }) - m.event = event - m.list.SetItems(listItems) - m.list.Select(0) - m.list.SetHeight(newHeight - 4) - - m.height = newHeight -} diff --git a/internal/dynamo-browse/ui/teamodels/relselector/model.go b/internal/dynamo-browse/ui/teamodels/relselector/model.go deleted file mode 100644 index 6721763..0000000 --- a/internal/dynamo-browse/ui/teamodels/relselector/model.go +++ /dev/null @@ -1,73 +0,0 @@ -package relselector - -import ( - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" -) - -const ( - overlayWidth = 50 - - overlayHeight = 8 - overlayHeightExtra2 = 2 - maxItems = 8 -) - -type Model struct { - subModel tea.Model - compositor *layout.Compositor - listModel *listModel - w, h int -} - -func New(subModel tea.Model) *Model { - compositor := layout.NewCompositor(subModel) - listModel := newListModel() - - return &Model{ - subModel: subModel, - listModel: listModel, - compositor: compositor, - } -} - -func (m *Model) Init() tea.Cmd { - return m.compositor.Init() -} - -func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cc utils.CmdCollector - switch msg := msg.(type) { - case controllers.ShowRelatedItemsOverlay: - newHeight := overlayHeight + utils.Min(len(msg.Items), maxItems)*overlayHeightExtra2 - - m.listModel.setItems(msg, newHeight) - m.compositor.SetOverlay(m.listModel, m.w/2-overlayWidth/2, m.h/2-newHeight/2, overlayWidth, newHeight) - case controllers.HideRelatedItemsOverlay: - m.compositor.ClearOverlay() - case tea.KeyMsg: - m.compositor = cc.Collect(m.compositor.Update(msg)).(*layout.Compositor) - default: - m.subModel = cc.Collect(m.subModel.Update(msg)).(tea.Model) - } - return m, cc.Cmd() -} - -func (m *Model) View() string { - return m.compositor.View() -} - -func (m *Model) Resize(w, h int) layout.ResizingModel { - m.w, m.h = w, h - m.compositor.MoveOverlay(m.w/2-overlayWidth/2, m.h/2-overlayHeight/2) - m.listModel.Resize(w, h) - m.subModel = layout.Resize(m.subModel, w, h) - m.listModel = layout.Resize(m.listModel, w, h).(*listModel) - return m -} - -func (m *Model) SelectorVisible() bool { - return m.compositor.HasOverlay() -} diff --git a/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go b/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go index cb4b838..62be7b0 100644 --- a/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go +++ b/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go @@ -5,21 +5,18 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/common/sliceutils" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" - "strings" + "github.com/lmika/audax/internal/common/sliceutils" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) // StatusAndPrompt is a resizing model which displays a submodel and a status bar. When the start prompt // event is received, focus will be torn away and the user will be given a prompt the enter text. type StatusAndPrompt struct { model layout.ResizingModel - pasteboardProvider PasteboardProvider style Style modeLine string - rightModeLine string statusMessage string spinner spinner.Model spinnerVisible bool @@ -33,17 +30,15 @@ type Style struct { ModeLine lipgloss.Style } -func New(model layout.ResizingModel, pasteboardProvider PasteboardProvider, initialMsg string, style Style) *StatusAndPrompt { +func New(model layout.ResizingModel, initialMsg string, style Style) *StatusAndPrompt { textInput := textinput.New() return &StatusAndPrompt{ - model: model, - pasteboardProvider: pasteboardProvider, - style: style, - statusMessage: initialMsg, - modeLine: "", - rightModeLine: "", - spinner: spinner.New(spinner.WithSpinner(spinner.Line)), - textInput: textInput, + model: model, + style: style, + statusMessage: initialMsg, + modeLine: "", + spinner: spinner.New(spinner.WithSpinner(spinner.Line)), + textInput: textInput, } } @@ -78,11 +73,6 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if hasModeMessage, ok := msg.(events.MessageWithMode); ok { s.modeLine = hasModeMessage.ModeMessage() } - if rightModeMessage, ok := msg.(events.MessageWithRightMode); ok { - s.rightModeLine = rightModeMessage.RightModeMessage() - } else { - s.rightModeLine = "" - } s.statusMessage = msg.StatusMessage() case events.PromptForInputMsg: if s.pendingInput != nil { @@ -106,24 +96,6 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) { }) } s.pendingInput = nil - case tea.KeyCtrlV: - if content, ok := s.pasteboardProvider.ReadText(); ok { - pasteContent := strings.TrimSpace(content) - - cursorPos := s.textInput.Cursor() - beforeValue := s.textInput.Value()[:cursorPos] + pasteContent - newValue := beforeValue + s.textInput.Value()[cursorPos:] - - s.textInput.SetValue(newValue) - s.textInput.SetCursor(len(beforeValue)) - } - case tea.KeyTab: - if tabCompletion := s.pendingInput.originalMsg.OnTabComplete; tabCompletion != nil { - if completion, ok := tabCompletion(s.textInput.Value()); ok { - s.textInput.SetValue(completion) - s.textInput.SetCursor(len(s.textInput.Value())) - } - } case tea.KeyEnter: pendingInput := s.pendingInput s.pendingInput = nil @@ -204,10 +176,7 @@ func (s *StatusAndPrompt) Resize(w, h int) layout.ResizingModel { } func (s *StatusAndPrompt) viewStatus() string { - rightModeLine := s.style.ModeLine.Render(s.rightModeLine) - modeLine := s.style.ModeLine.Render( - lipgloss.PlaceHorizontal(s.width-lipgloss.Width(rightModeLine), lipgloss.Left, s.modeLine, lipgloss.WithWhitespaceChars(" ")), - ) + rightModeLine + modeLine := s.style.ModeLine.Render(lipgloss.PlaceHorizontal(s.width, lipgloss.Left, s.modeLine, lipgloss.WithWhitespaceChars(" "))) var statusLine string if s.pendingInput != nil { diff --git a/internal/dynamo-browse/ui/teamodels/statusandprompt/types.go b/internal/dynamo-browse/ui/teamodels/statusandprompt/types.go index 654b303..10c766c 100644 --- a/internal/dynamo-browse/ui/teamodels/statusandprompt/types.go +++ b/internal/dynamo-browse/ui/teamodels/statusandprompt/types.go @@ -1,6 +1,6 @@ package statusandprompt -import "github.com/lmika/dynamo-browse/internal/common/ui/events" +import "github.com/lmika/audax/internal/common/ui/events" type pendingInputState struct { originalMsg events.PromptForInputMsg @@ -10,7 +10,3 @@ type pendingInputState struct { func newPendingInputState(msg events.PromptForInputMsg) *pendingInputState { return &pendingInputState{originalMsg: msg, historyIdx: -1} } - -type PasteboardProvider interface { - ReadText() (string, bool) -} diff --git a/internal/dynamo-browse/ui/teamodels/styles/styles.go b/internal/dynamo-browse/ui/teamodels/styles/styles.go index 9d4fc0e..29c25de 100644 --- a/internal/dynamo-browse/ui/teamodels/styles/styles.go +++ b/internal/dynamo-browse/ui/teamodels/styles/styles.go @@ -2,8 +2,8 @@ package styles import ( "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/frame" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/statusandprompt" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/frame" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/statusandprompt" ) type Styles struct { diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/list.go b/internal/dynamo-browse/ui/teamodels/tableselect/list.go index 21fa76e..629a178 100644 --- a/internal/dynamo-browse/ui/teamodels/tableselect/list.go +++ b/internal/dynamo-browse/ui/teamodels/tableselect/list.go @@ -5,7 +5,7 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" ) var ( diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/model.go b/internal/dynamo-browse/ui/teamodels/tableselect/model.go index de18bab..95c03a1 100644 --- a/internal/dynamo-browse/ui/teamodels/tableselect/model.go +++ b/internal/dynamo-browse/ui/teamodels/tableselect/model.go @@ -5,12 +5,12 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/lmika/dynamo-browse/internal/common/ui/events" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/frame" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/layout" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/ui/teamodels/utils" + "github.com/lmika/audax/internal/common/ui/events" + "github.com/lmika/audax/internal/dynamo-browse/controllers" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/frame" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/layout" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/styles" + "github.com/lmika/audax/internal/dynamo-browse/ui/teamodels/utils" ) var ( diff --git a/internal/dynamo-browse/ui/teamodels/utils/minmax.go b/internal/dynamo-browse/ui/teamodels/utils/minmax.go index 17ffd5e..2fbb42f 100644 --- a/internal/dynamo-browse/ui/teamodels/utils/minmax.go +++ b/internal/dynamo-browse/ui/teamodels/utils/minmax.go @@ -1,12 +1,5 @@ package utils -func Min(x, y int) int { - if x < y { - return x - } - return y -} - func Max(x, y int) int { if x > y { return x diff --git a/macos.goreleaser.yml b/macos.goreleaser.yml index e124d05..cf3bd2e 100644 --- a/macos.goreleaser.yml +++ b/macos.goreleaser.yml @@ -17,7 +17,7 @@ archives: format: tar.gz brews: - name: audax - repository: + tap: owner: lmika name: homebrew-audax token: "{{ .Env.HOMEBREW_GITHUB_TOKEN }}" diff --git a/test.tm b/test.tm deleted file mode 100644 index 1fa251d..0000000 --- a/test.tm +++ /dev/null @@ -1,8 +0,0 @@ -ext.related_items("business-addresses", func(item) { - print("Hello") - return [ - {"label": "Customer", "query": `city="Austin"`, "args": {"foo": "foo"}}, - {"label": "Payment", "query": `officeOpened=false`, "args": {"daa": "Hello"}}, - {"label": "Thing", "query": `colors.door^="P"`, "args": {"daa": "Hello"}}, - ] -}) \ No newline at end of file diff --git a/test/cmd/load-test-table/main.go b/test/cmd/load-test-table/main.go index 154d99e..3b730a4 100644 --- a/test/cmd/load-test-table/main.go +++ b/test/cmd/load-test-table/main.go @@ -9,9 +9,9 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/brianvoe/gofakeit/v6" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/providers/dynamo" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/tables" + "github.com/lmika/audax/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" + "github.com/lmika/audax/internal/dynamo-browse/services/tables" "github.com/lmika/gopkgs/cli" "github.com/pkg/errors" "log" @@ -91,14 +91,10 @@ func main() { } } - var key = gofakeit.UUID() + key := gofakeit.UUID() for i := 0; i < totalItems; i++ { - if i%50 == 0 { - key = gofakeit.UUID() - } if err := tableService.Put(ctx, inventoryTableInfo, models.Item{ "pk": &types.AttributeValueMemberS{Value: key}, - "sk": &types.AttributeValueMemberN{Value: fmt.Sprint(i % 50)}, "uuid": &types.AttributeValueMemberS{Value: gofakeit.UUID()}, }); err != nil { log.Fatalln(err) diff --git a/test/testdynamo/helpers.go b/test/testdynamo/helpers.go index 37daeea..0a834ab 100644 --- a/test/testdynamo/helpers.go +++ b/test/testdynamo/helpers.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" - "github.com/lmika/dynamo-browse/internal/dynamo-browse/models" + "github.com/lmika/audax/internal/dynamo-browse/models" "github.com/stretchr/testify/assert" ) diff --git a/test/testuictx/testuictx.go b/test/testuictx/testuictx.go index e75ff28..6668ca2 100644 --- a/test/testuictx/testuictx.go +++ b/test/testuictx/testuictx.go @@ -4,8 +4,8 @@ import ( "context" tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/dynamo-browse/internal/common/ui/dispatcher" - "github.com/lmika/dynamo-browse/internal/common/ui/uimodels" + "github.com/lmika/audax/internal/common/ui/dispatcher" + "github.com/lmika/audax/internal/common/ui/uimodels" ) func New(ctx context.Context) (context.Context, *TestUIContext) { diff --git a/test/testworkspace/workspace.go b/test/testworkspace/workspace.go index 9715d0e..d3f6f34 100644 --- a/test/testworkspace/workspace.go +++ b/test/testworkspace/workspace.go @@ -1,7 +1,7 @@ package testworkspace import ( - "github.com/lmika/dynamo-browse/internal/common/workspaces" + "github.com/lmika/audax/internal/common/workspaces" "github.com/stretchr/testify/assert" "os" "testing"