This commit is contained in:
parent
76dce52a86
commit
291d1439f4
|
@ -28,7 +28,6 @@ import (
|
|||
"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"
|
||||
|
@ -107,7 +106,6 @@ func main() {
|
|||
tableService := tables.NewService(dynamoProvider, settingStore)
|
||||
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
|
||||
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
|
||||
scriptManagerService := scriptmanager.New()
|
||||
jobsService := jobs.NewService(eventBus)
|
||||
inputHistoryService := inputhistory.New(inputHistoryStore)
|
||||
|
||||
|
@ -122,7 +120,6 @@ func main() {
|
|||
inputHistoryService,
|
||||
eventBus,
|
||||
pasteboardProvider,
|
||||
scriptManagerService,
|
||||
*flagTable,
|
||||
)
|
||||
tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore)
|
||||
|
@ -130,7 +127,6 @@ func main() {
|
|||
exportController := controllers.NewExportController(state, tableService, jobsController, columnsController, pasteboardProvider)
|
||||
settingsController := controllers.NewSettingsController(settingStore, eventBus)
|
||||
keyBindings := keybindings.Default()
|
||||
//scriptController := controllers.NewScriptController(scriptManagerService, tableReadController, jobsController, settingsController, eventBus)
|
||||
|
||||
if *flagQuery != "" {
|
||||
if *flagTable == "" {
|
||||
|
@ -177,7 +173,6 @@ func main() {
|
|||
if err != nil {
|
||||
cli.Fatalf("cannot setup command controller: %v", err)
|
||||
}
|
||||
//commandController.AddCommandLookupExtension(scriptController)
|
||||
commandController.SetCommandCompletionProvider(columnsController)
|
||||
|
||||
model := ui.NewModel(
|
||||
|
@ -189,7 +184,6 @@ func main() {
|
|||
jobsController,
|
||||
itemRendererService,
|
||||
commandController,
|
||||
//scriptController,
|
||||
eventBus,
|
||||
keyBindingController,
|
||||
pasteboardProvider,
|
||||
|
@ -203,8 +197,6 @@ func main() {
|
|||
p := tea.NewProgram(model, tea.WithAltScreen())
|
||||
|
||||
jobsController.SetMessageSender(p.Send)
|
||||
//scriptController.Init()
|
||||
//scriptController.SetMessageSender(p.Send)
|
||||
|
||||
if err := commandController.LoadExtensions(context.Background(), strings.Split(*flagExtDir, string(os.PathListSeparator))); err != nil {
|
||||
fmt.Printf("Unable to load extensions: %v", err)
|
||||
|
|
49
go.mod
49
go.mod
|
@ -13,14 +13,11 @@ require (
|
|||
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/ssm v1.24.0
|
||||
github.com/brianvoe/gofakeit/v6 v6.15.0
|
||||
github.com/calyptia/go-bubble-table v0.2.1
|
||||
github.com/charmbracelet/bubbles v0.14.0
|
||||
github.com/charmbracelet/bubbletea v0.22.1
|
||||
github.com/charmbracelet/lipgloss v0.6.0
|
||||
github.com/cloudcmds/tamarin v1.0.0
|
||||
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e
|
||||
github.com/lmika/go-bubble-table v0.2.2-0.20220616114432-6bbb2995e538
|
||||
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f
|
||||
|
@ -32,65 +29,33 @@ require (
|
|||
github.com/stretchr/testify v1.9.0
|
||||
golang.design/x/clipboard v0.6.2
|
||||
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
|
||||
ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e
|
||||
)
|
||||
|
||||
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/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/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/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/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect
|
||||
github.com/kr/pretty v0.3.1 // 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
|
||||
|
@ -98,24 +63,18 @@ require (
|
|||
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/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tidwall/gjson v1.14.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
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/exp/shiny v0.0.0-20230213192124-5e25df0256eb // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554 // indirect
|
||||
golang.org/x/net v0.10.0 // 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
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e // indirect
|
||||
)
|
||||
|
|
239
go.sum
239
go.sum
|
@ -1,150 +1,57 @@
|
|||
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=
|
||||
github.com/Sereal/Sereal v0.0.0-20220220040404-e0d1e550e879 h1:M5ptEKnqKqpFTKbe+p5zEf3ro1deJ6opUz5j3g3/ErQ=
|
||||
github.com/Sereal/Sereal v0.0.0-20220220040404-e0d1e550e879/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||
github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
|
||||
github.com/alecthomas/participle v0.7.1 h1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=
|
||||
github.com/alecthomas/participle v0.7.1/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY=
|
||||
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/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
|
||||
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
|
||||
github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
|
||||
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/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
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=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.13.0/go.mod h1:L6+ZpqHaLbAaxsqV0L4cvxZY7QupWJB4fhkf8LXvC7w=
|
||||
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=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
|
||||
|
@ -162,51 +69,23 @@ github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJ
|
|||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
|
||||
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
|
||||
github.com/cloudcmds/tamarin v1.0.0 h1:PhrJ74FCUJo24/nIPXnQe9E3WVEIYo4aG58pICOMDBE=
|
||||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
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/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
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=
|
||||
|
@ -214,19 +93,18 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC
|
|||
github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc h1:ZQrgZFsLzkw7o3CoDzsfBhx0bf/1rVBXrLy8dXKRe8o=
|
||||
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/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
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=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e h1:0QkUe2ejnT/i+xbgGylMU1b+XnZponQKiPVNi+C/xgA=
|
||||
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e/go.mod h1:qtkBmNC9OfD0STtOR9sF55pQchjIfNlC3gzm4n8CrqM=
|
||||
github.com/lmika/go-bubble-table v0.2.2-0.20220616114432-6bbb2995e538 h1:dtMPRNoDqDnnP3HgOvYhswcJVSqdISkYlCtGOjTqg6Q=
|
||||
github.com/lmika/go-bubble-table v0.2.2-0.20220616114432-6bbb2995e538/go.mod h1:0RT1upgKZ6qZ6B1SqseE3wWsPjSQRv/G/HjpYK8jNsg=
|
||||
github.com/lmika/gopkgs v0.0.0-20211210041137-0dc91e939890 h1:mwl/exYV/WkBMeShqK7q+B2w2r+b0vP1TSA7clBn9kI=
|
||||
github.com/lmika/gopkgs v0.0.0-20211210041137-0dc91e939890/go.mod h1:FH6OJSvYcJ9xY8CGs9yGgR89kMCK1UimuUQ6kE5YuJQ=
|
||||
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f h1:tz68Lhc1oR15HVz69IGbtdukdH0x70kBDEvvj5pTXyE=
|
||||
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f/go.mod h1:zHQvhjGXRro/Xp2C9dbC+ZUpE0gL4GYW75x1lk7hwzI=
|
||||
github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe h1:1UXS/6OFkbi6JrihPykmYO1VtsABB02QQ+YmYYzTY18=
|
||||
|
@ -235,11 +113,9 @@ 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=
|
||||
|
@ -250,8 +126,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=
|
||||
|
@ -265,88 +139,42 @@ 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
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=
|
||||
|
@ -362,13 +190,12 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||
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/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
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=
|
||||
|
@ -385,14 +212,10 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
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=
|
||||
|
@ -400,8 +223,6 @@ 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=
|
||||
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=
|
||||
|
@ -422,43 +243,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
ucl.lmika.dev v0.0.0-20240427010304-6315afc54287 h1:llPHrjca54duvQx9PgMTFDhOW2VQiVvqV1CEHpO4AnY=
|
||||
ucl.lmika.dev v0.0.0-20240427010304-6315afc54287/go.mod h1:T6V4jIUxlWvMTgn4J752VDHNA8iyVrEX6v98EvDj8G4=
|
||||
ucl.lmika.dev v0.0.0-20240501110514-25594c80d273 h1:+JpKw02VTAcOjJw7Q6juun/9hk9ypNSdTRlf+E4M5Nw=
|
||||
ucl.lmika.dev v0.0.0-20240501110514-25594c80d273/go.mod h1:T6V4jIUxlWvMTgn4J752VDHNA8iyVrEX6v98EvDj8G4=
|
||||
ucl.lmika.dev v0.0.0-20240504001444-cf3a12bf0d4d h1:OqGmR0Y+OG6aFIOlXy2QwEHtuUNasYCh/6cxHokYQj4=
|
||||
ucl.lmika.dev v0.0.0-20240504001444-cf3a12bf0d4d/go.mod h1:T6V4jIUxlWvMTgn4J752VDHNA8iyVrEX6v98EvDj8G4=
|
||||
ucl.lmika.dev v0.0.0-20240504013531-0dc9fd3c3281 h1:/M7phiv/0XVp3wKkOxEnGQysf8+RS6NOaBQZyUEoSsA=
|
||||
ucl.lmika.dev v0.0.0-20240504013531-0dc9fd3c3281/go.mod h1:T6V4jIUxlWvMTgn4J752VDHNA8iyVrEX6v98EvDj8G4=
|
||||
ucl.lmika.dev v0.0.0-20250306030053-ad6d002a22e8 h1:vWttdW8GJWcTUQeJFbQHqCHJDLFWQ9nccUTx/lW2v8s=
|
||||
ucl.lmika.dev v0.0.0-20250306030053-ad6d002a22e8/go.mod h1:FMP2ncSu4UxfvB0iA2zlebwL+1UPCARdyYNOrmi86A4=
|
||||
ucl.lmika.dev v0.0.0-20250515115457-27b6cc0b92e2 h1:cvguOoQ0HVgLKbHH17ZHvAUFht6HXApLi0o8JOdaaNU=
|
||||
ucl.lmika.dev v0.0.0-20250515115457-27b6cc0b92e2/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250517003439-109be33d1495 h1:r46r+7T59Drm+in7TEWKCZfFYIM0ZyZ26QjHAbj8Lto=
|
||||
ucl.lmika.dev v0.0.0-20250517003439-109be33d1495/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e h1:CQ+qPqI5lYiiEM0tNAr4jS0iMz16bFqOui5mU3AHsCU=
|
||||
ucl.lmika.dev v0.0.0-20250517115116-0f1ceba0902e/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250517212052-51e35aa9a675 h1:kGKh3zj6lMzOrGAquFW7ROgx9/6nwJ8DXiSLtceRiak=
|
||||
ucl.lmika.dev v0.0.0-20250517212052-51e35aa9a675/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250517212757-33d04ba18db4 h1:rnietWu2B+NXLqKfo7jgf6r+srMwxFa5eizywkq4LFk=
|
||||
ucl.lmika.dev v0.0.0-20250517212757-33d04ba18db4/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250517213937-94aad417121d h1:CMcA8aQV6iiPK75EbHvoIVZhZmSggfrWNhK9BFm2aIg=
|
||||
ucl.lmika.dev v0.0.0-20250517213937-94aad417121d/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250518024533-f4be44fcbc94 h1:x3IRtT1jbedblimi2hesKGBihg243+wNOSvagCPR0KU=
|
||||
ucl.lmika.dev v0.0.0-20250518024533-f4be44fcbc94/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78 h1:lbOZUb6whYMLI4win5QL+eLSgqc3N9TtTgT8hTipNl8=
|
||||
ucl.lmika.dev v0.0.0-20250518033831-f79e91e26d78/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250519111943-1173d163f5e3 h1:ZMQ1rkcAWa///c3bVvlXbtuqjfAWxDm01abQl3g/YVw=
|
||||
ucl.lmika.dev v0.0.0-20250519111943-1173d163f5e3/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250519114239-7ca821016e9a h1:dzBBFCY50+MQcJaQ90swdDyjzag5oIhwdfqbmZkvX3Q=
|
||||
ucl.lmika.dev v0.0.0-20250519114239-7ca821016e9a/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250519120409-53b05b5ba6f8 h1:h32JQi0d1MI86RaAMaEU7kvti4uSLX5XYe/nk2abApg=
|
||||
ucl.lmika.dev v0.0.0-20250519120409-53b05b5ba6f8/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e h1:N+HzQUunDUvdjAzbSDtHQZVZ1k+XHbVgbNwmc+EKmlQ=
|
||||
ucl.lmika.dev v0.0.0-20250525023717-3076897eb73e/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
|
||||
|
|
|
@ -417,6 +417,13 @@ func (sc StandardCommands) ConfigureUCL(ucl *ucl.Inst) {
|
|||
ucl.SetBuiltin("noisy-touch", sc.cmdNoisyTouch)
|
||||
ucl.SetBuiltin("rebind", sc.cmdRebind)
|
||||
|
||||
// Aliases
|
||||
ucl.SetBuiltin("sa", sc.cmdSetAttr)
|
||||
ucl.SetBuiltin("da", sc.cmdDelAttr)
|
||||
ucl.SetBuiltin("np", sc.cmdNextPage)
|
||||
ucl.SetBuiltin("w", sc.cmdPut)
|
||||
ucl.SetBuiltin("q", sc.cmdQuit)
|
||||
|
||||
ucl.SetPseudoVar("resultset", resultSetPVar{sc.State, sc.ReadController})
|
||||
ucl.SetPseudoVar("table", tablePVar{sc.State})
|
||||
ucl.SetPseudoVar("item", itemPVar{sc.State})
|
||||
|
|
|
@ -126,7 +126,6 @@ func newService(t *testing.T, opts ...serviceOpt) *services {
|
|||
inputHistoryService,
|
||||
eventBus,
|
||||
pasteboardprovider.NilProvider{},
|
||||
nil,
|
||||
s.table,
|
||||
)
|
||||
writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore)
|
||||
|
|
|
@ -1,290 +0,0 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"ucl.lmika.dev/ucl"
|
||||
|
||||
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"
|
||||
bus "github.com/lmika/events"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ScriptController struct {
|
||||
scriptManager *scriptmanager.Service
|
||||
tableReadController *TableReadController
|
||||
jobController *JobsController
|
||||
settingsController *SettingsController
|
||||
eventBus *bus.Bus
|
||||
sendMsg func(msg tea.Msg)
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
sessionImpl := &sessionImpl{sc: sc, lastSelectedItemIndex: -1}
|
||||
scriptManager.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: &uiImpl{sc: sc},
|
||||
Session: sessionImpl,
|
||||
})
|
||||
|
||||
sessionImpl.subscribeToEvents(eventBus)
|
||||
|
||||
// Setup event handling when settings have changed
|
||||
eventBus.On(BusEventSettingsUpdated, func(name, value string) {
|
||||
if !strings.HasPrefix(name, "script.") {
|
||||
return
|
||||
}
|
||||
sc.Init()
|
||||
})
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *ScriptController) Init() {
|
||||
if lookupPaths, err := sc.settingsController.settings.ScriptLookupFS(); err == nil {
|
||||
sc.scriptManager.SetLookupPaths(lookupPaths)
|
||||
} else {
|
||||
log.Printf("warn: script lookup paths are invalid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *ScriptController) SetMessageSender(sendMsg func(msg tea.Msg)) {
|
||||
sc.sendMsg = sendMsg
|
||||
}
|
||||
|
||||
func (sc *ScriptController) LoadScript(filename string) tea.Msg {
|
||||
ctx := context.Background()
|
||||
plugin, err := sc.scriptManager.LoadScript(ctx, filename)
|
||||
if err != nil {
|
||||
return events.Error(err)
|
||||
}
|
||||
|
||||
return events.StatusMsg(fmt.Sprintf("Script '%v' loaded", plugin.Name()))
|
||||
}
|
||||
|
||||
func (sc *ScriptController) RunScript(filename string) tea.Msg {
|
||||
ctx := context.Background()
|
||||
if err := sc.scriptManager.StartAdHocScript(ctx, filename, sc.waitAndPrintScriptError()); err != nil {
|
||||
return events.Error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sc *ScriptController) waitAndPrintScriptError() chan error {
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
if err := <-errChan; err != nil {
|
||||
sc.sendMsg(events.Error(err))
|
||||
}
|
||||
}()
|
||||
return errChan
|
||||
}
|
||||
|
||||
func (sc *ScriptController) LookupCommand(name string) commandctrl.Command {
|
||||
cmd := sc.scriptManager.LookupCommand(name)
|
||||
if cmd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(execCtx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
errChan := sc.waitAndPrintScriptError()
|
||||
ctx := context.Background()
|
||||
|
||||
invokeArgs := make([]string, 0)
|
||||
for args.NArgs() > 0 {
|
||||
var s string
|
||||
if err := args.Bind(&s); err == nil {
|
||||
invokeArgs = append(invokeArgs, s)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.Invoke(ctx, invokeArgs, errChan); err != nil {
|
||||
return events.Error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type uiImpl struct {
|
||||
sc *ScriptController
|
||||
}
|
||||
|
||||
func (u uiImpl) PrintMessage(ctx context.Context, msg string) {
|
||||
u.sc.sendMsg(events.StatusMsg(msg))
|
||||
}
|
||||
|
||||
func (u uiImpl) Prompt(ctx context.Context, msg string) chan string {
|
||||
resultChan := make(chan string)
|
||||
u.sc.sendMsg(events.PromptForInputMsg{
|
||||
Prompt: msg,
|
||||
OnDone: func(value string) tea.Msg {
|
||||
resultChan <- value
|
||||
return nil
|
||||
},
|
||||
OnCancel: func() tea.Msg {
|
||||
close(resultChan)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return resultChan
|
||||
}
|
||||
|
||||
type sessionImpl struct {
|
||||
sc *ScriptController
|
||||
lastSelectedItemIndex int
|
||||
}
|
||||
|
||||
func (s *sessionImpl) subscribeToEvents(bus *bus.Bus) {
|
||||
bus.On("ui.new-item-selected", func(rs *models.ResultSet, itemIndex int) {
|
||||
s.lastSelectedItemIndex = itemIndex
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sessionImpl) SelectedItemIndex(ctx context.Context) int {
|
||||
return s.lastSelectedItemIndex
|
||||
}
|
||||
|
||||
func (s *sessionImpl) ResultSet(ctx context.Context) *models.ResultSet {
|
||||
return s.sc.tableReadController.state.ResultSet()
|
||||
}
|
||||
|
||||
func (s *sessionImpl) SetResultSet(ctx context.Context, newResultSet *models.ResultSet) {
|
||||
state := s.sc.tableReadController.state
|
||||
msg := s.sc.tableReadController.setResultSetAndFilter(newResultSet, state.filter, true, resultSetUpdateScript)
|
||||
s.sc.sendMsg(msg)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.NamePlaceholders != nil {
|
||||
expr = expr.WithNameParams(opts.NamePlaceholders)
|
||||
}
|
||||
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
|
||||
)
|
||||
|
||||
tableName := opts.TableName
|
||||
currentResultSet := s.tableReadController.state.ResultSet()
|
||||
|
||||
if tableName != "" {
|
||||
// Table specified. If it's the same as the current table, then use the existing table info
|
||||
if currentResultSet != nil && currentResultSet.TableInfo.Name == tableName {
|
||||
tableInfo = currentResultSet.TableInfo
|
||||
}
|
||||
|
||||
// Otherwise, describe the table
|
||||
tableInfo, err = s.tableReadController.tableService.Describe(ctx, tableName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot describe table '%v'", tableName)
|
||||
}
|
||||
} else {
|
||||
// Table not specified. Use the existing table, if any
|
||||
if currentResultSet == nil {
|
||||
return nil, errors.New("no table currently selected")
|
||||
}
|
||||
tableInfo = currentResultSet.TableInfo
|
||||
}
|
||||
|
||||
newResultSet, err := s.tableReadController.tableService.ScanOrQuery(ctx, tableInfo, expr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newResultSet, nil
|
||||
}
|
||||
|
||||
func (sc *ScriptController) CustomKeyCommand(key string) tea.Cmd {
|
||||
_, cmd := sc.scriptManager.LookupKeyBinding(key)
|
||||
if cmd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func() tea.Msg {
|
||||
errChan := sc.waitAndPrintScriptError()
|
||||
ctx := context.Background()
|
||||
|
||||
if err := cmd.Invoke(ctx, nil, errChan); err != nil {
|
||||
return events.Error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *ScriptController) Rebind(bindingName string, newKey string) error {
|
||||
return sc.scriptManager.RebindKeyBinding(bindingName, newKey)
|
||||
}
|
||||
|
||||
func (sc *ScriptController) LookupBinding(theKey string) string {
|
||||
bindingName, _ := sc.scriptManager.LookupKeyBinding(theKey)
|
||||
return bindingName
|
||||
}
|
||||
|
||||
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()
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
package controllers_test
|
||||
|
||||
import (
|
||||
"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) {
|
||||
t.Run("should execute scripts successfully", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
ui.print("Hello world")
|
||||
`),
|
||||
})
|
||||
|
||||
msg := srv.scriptController.RunScript("test.tm")
|
||||
assert.Nil(t, msg)
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.Equal(t, events.StatusMsg("Hello world"), srv.msgSender.msgs[0])
|
||||
})
|
||||
|
||||
t.Run("session.result_set", func(t *testing.T) {
|
||||
t.Run("should return current result set if not-nil", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
tableName: "alpha-table",
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
rs := session.result_set()
|
||||
ui.print(rs.length)
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
|
||||
msg := srv.scriptController.RunScript("test.tm")
|
||||
assert.Nil(t, msg)
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.Equal(t, events.StatusMsg("3"), srv.msgSender.msgs[0])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("session.query", func(t *testing.T) {
|
||||
t.Run("should run query against current table", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
tableName: "alpha-table",
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
rs := session.query('pk="abc"')
|
||||
ui.print(rs.length)
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
msg := srv.scriptController.RunScript("test.tm")
|
||||
assert.Nil(t, msg)
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.Equal(t, events.StatusMsg("2"), srv.msgSender.msgs[0])
|
||||
})
|
||||
|
||||
t.Run("should run query against another table", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
tableName: "alpha-table",
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
rs := session.query('pk!="abc"', { table: "count-to-30" })
|
||||
ui.print(rs.length)
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
msg := srv.scriptController.RunScript("test.tm")
|
||||
assert.Nil(t, msg)
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.Equal(t, events.StatusMsg("30"), srv.msgSender.msgs[0])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("session.set_result_set", func(t *testing.T) {
|
||||
t.Run("should set the result set from the result of a query", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
tableName: "alpha-table",
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
rs := session.query('pk="abc"')
|
||||
session.set_result_set(rs)
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
msg := srv.scriptController.RunScript("test.tm")
|
||||
assert.Nil(t, msg)
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.IsType(t, controllers.NewResultSet{}, srv.msgSender.msgs[0])
|
||||
})
|
||||
|
||||
t.Run("changed attributes of the result set should show up as modified", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
tableName: "alpha-table",
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
rs := session.query('pk="abc"')
|
||||
rs[0].set_attr("pk", "131")
|
||||
session.set_result_set(rs)
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.readController.Init())
|
||||
msg := srv.scriptController.RunScript("test.tm")
|
||||
assert.Nil(t, msg)
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.IsType(t, controllers.NewResultSet{}, srv.msgSender.msgs[0])
|
||||
|
||||
assert.Equal(t, "131", srv.state.ResultSet().Items()[0]["pk"].(*types.AttributeValueMemberS).Value)
|
||||
assert.True(t, srv.state.ResultSet().IsDirty(0))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
})
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.scriptController.LoadScript("test.tm"))
|
||||
invokeCommand(t, srv.commandController.Execute(scenario.command))
|
||||
|
||||
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])
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should only allow one script to run at a time", func(t *testing.T) {
|
||||
srv := newService(t, serviceConfig{
|
||||
tableName: "alpha-table",
|
||||
scriptFS: testScriptFile(t, "test.tm", `
|
||||
ext.command("mycommand", func() {
|
||||
time.sleep(1.5)
|
||||
ui.print("Done my thing")
|
||||
})
|
||||
`),
|
||||
})
|
||||
|
||||
invokeCommand(t, srv.scriptController.LoadScript("test.tm"))
|
||||
|
||||
invokeCommand(t, srv.commandController.Execute(`mycommand`))
|
||||
invokeCommandExpectingError(t, srv.commandController.Execute(`mycommand`))
|
||||
|
||||
srv.msgSender.waitForAtLeastOneMessages(t, 5*time.Second)
|
||||
|
||||
assert.Len(t, srv.msgSender.msgs, 1)
|
||||
assert.Equal(t, events.StatusMsg("Done my thing"), srv.msgSender.msgs[0])
|
||||
})
|
||||
|
||||
}
|
|
@ -61,7 +61,6 @@ type TableReadController struct {
|
|||
tableName string
|
||||
loadFromLastView bool
|
||||
pasteboardProvider services.PasteboardProvider
|
||||
relatedItemSupplier RelatedItemSupplier
|
||||
|
||||
// state
|
||||
mutex *sync.Mutex
|
||||
|
@ -77,7 +76,6 @@ func NewTableReadController(
|
|||
inputHistoryService *inputhistory.Service,
|
||||
eventBus *bus.Bus,
|
||||
pasteboardProvider services.PasteboardProvider,
|
||||
relatedItemSupplier RelatedItemSupplier,
|
||||
tableName string,
|
||||
) *TableReadController {
|
||||
return &TableReadController{
|
||||
|
@ -90,7 +88,6 @@ func NewTableReadController(
|
|||
eventBus: eventBus,
|
||||
tableName: tableName,
|
||||
pasteboardProvider: pasteboardProvider,
|
||||
relatedItemSupplier: relatedItemSupplier,
|
||||
mutex: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"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"
|
||||
|
@ -587,7 +586,6 @@ type services struct {
|
|||
settingsController *controllers.SettingsController
|
||||
columnsController *controllers.ColumnsController
|
||||
exportController *controllers.ExportController
|
||||
scriptController *controllers.ScriptController
|
||||
commandController *commandctrl.CommandController
|
||||
}
|
||||
|
||||
|
@ -607,7 +605,6 @@ func newService(t *testing.T, cfg serviceConfig) *services {
|
|||
|
||||
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
|
||||
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
|
||||
scriptService := scriptmanager.New()
|
||||
inputHistoryService := inputhistory.New(inputHistoryStore)
|
||||
|
||||
client := testdynamo.SetupTestTable(t, testData)
|
||||
|
@ -627,17 +624,14 @@ func newService(t *testing.T, cfg serviceConfig) *services {
|
|||
inputHistoryService,
|
||||
eventBus,
|
||||
pasteboardprovider.NilProvider{},
|
||||
nil,
|
||||
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)
|
||||
|
||||
commandController, _ := commandctrl.NewCommandController(inputHistoryService)
|
||||
commandController.AddCommandLookupExtension(scriptController)
|
||||
|
||||
if cfg.isReadOnly {
|
||||
if err := settingStore.SetReadOnly(cfg.isReadOnly); err != nil {
|
||||
|
@ -651,12 +645,7 @@ func newService(t *testing.T, cfg serviceConfig) *services {
|
|||
}
|
||||
|
||||
msgSender := &msgSender{}
|
||||
scriptController.Init()
|
||||
jobsController.SetMessageSender(msgSender.send)
|
||||
scriptController.SetMessageSender(msgSender.send)
|
||||
|
||||
// Initting will setup the default script lookup paths, so revert them to the test ones
|
||||
scriptService.SetLookupPaths([]fs.FS{cfg.scriptFS})
|
||||
|
||||
return &services{
|
||||
state: state,
|
||||
|
@ -666,7 +655,6 @@ func newService(t *testing.T, cfg serviceConfig) *services {
|
|||
settingsController: settingsController,
|
||||
columnsController: columnsController,
|
||||
exportController: exportController,
|
||||
scriptController: scriptController,
|
||||
commandController: commandController,
|
||||
msgSender: msgSender,
|
||||
}
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* Builtins adopted and modified from Taramin
|
||||
* Copyright (c) 2022 Curtis Myzie
|
||||
*/
|
||||
|
||||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/risor-io/risor/object"
|
||||
)
|
||||
|
||||
func printBuiltin(ctx context.Context, args ...object.Object) object.Object {
|
||||
env := scriptEnvFromCtx(ctx)
|
||||
prefix := "script " + env.filename + ":"
|
||||
|
||||
values := make([]interface{}, len(args)+1)
|
||||
values[0] = prefix
|
||||
for i, arg := range args {
|
||||
switch arg := arg.(type) {
|
||||
case *object.String:
|
||||
values[i+1] = arg.Value()
|
||||
default:
|
||||
values[i+1] = arg.Inspect()
|
||||
}
|
||||
}
|
||||
log.Println(values...)
|
||||
return object.Nil
|
||||
}
|
||||
|
||||
func printfBuiltin(ctx context.Context, args ...object.Object) object.Object {
|
||||
env := scriptEnvFromCtx(ctx)
|
||||
prefix := "script " + env.filename + ":"
|
||||
|
||||
numArgs := len(args)
|
||||
if numArgs < 1 {
|
||||
return object.Errorf("type error: printf() takes 1 or more arguments (%d given)", len(args))
|
||||
}
|
||||
format, err := object.AsString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var values = []interface{}{prefix}
|
||||
for _, arg := range args[1:] {
|
||||
switch arg := arg.(type) {
|
||||
case *object.String:
|
||||
values = append(values, arg.Value())
|
||||
default:
|
||||
values = append(values, arg.Interface())
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
"github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
||||
)
|
||||
|
||||
//go:generate mockery --with-expecter --name UIService
|
||||
//go:generate mockery --with-expecter --name SessionService
|
||||
|
||||
type Ifaces struct {
|
||||
UI UIService
|
||||
Session SessionService
|
||||
}
|
||||
|
||||
type UIService interface {
|
||||
PrintMessage(ctx context.Context, msg string)
|
||||
|
||||
// Prompt should return a channel which will provide the input from the user. If the user
|
||||
// provides no input, prompt should close the channel without providing anything.
|
||||
Prompt(ctx context.Context, msg string) chan string
|
||||
}
|
||||
|
||||
type SessionService interface {
|
||||
Query(ctx context.Context, expr string, queryOptions QueryOptions) (*models.ResultSet, error)
|
||||
|
||||
ResultSet(ctx context.Context) *models.ResultSet
|
||||
SelectedItemIndex(ctx context.Context) int
|
||||
SetResultSet(ctx context.Context, newResultSet *models.ResultSet)
|
||||
}
|
||||
|
||||
type QueryOptions struct {
|
||||
TableName string
|
||||
IndexName string
|
||||
NamePlaceholders map[string]string
|
||||
ValuePlaceholders map[string]types.AttributeValue
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
// Code generated by mockery v2.20.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
models "github.com/lmika/dynamo-browse/internal/dynamo-browse/models"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
scriptmanager "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager"
|
||||
)
|
||||
|
||||
// SessionService is an autogenerated mock type for the SessionService type
|
||||
type SessionService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type SessionService_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *SessionService) EXPECT() *SessionService_Expecter {
|
||||
return &SessionService_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Query provides a mock function with given fields: ctx, expr, queryOptions
|
||||
func (_m *SessionService) Query(ctx context.Context, expr string, queryOptions scriptmanager.QueryOptions) (*models.ResultSet, error) {
|
||||
ret := _m.Called(ctx, expr, queryOptions)
|
||||
|
||||
var r0 *models.ResultSet
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, scriptmanager.QueryOptions) (*models.ResultSet, error)); ok {
|
||||
return rf(ctx, expr, queryOptions)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, scriptmanager.QueryOptions) *models.ResultSet); ok {
|
||||
r0 = rf(ctx, expr, queryOptions)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.ResultSet)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, scriptmanager.QueryOptions) error); ok {
|
||||
r1 = rf(ctx, expr, queryOptions)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SessionService_Query_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Query'
|
||||
type SessionService_Query_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Query is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - expr string
|
||||
// - queryOptions scriptmanager.QueryOptions
|
||||
func (_e *SessionService_Expecter) Query(ctx interface{}, expr interface{}, queryOptions interface{}) *SessionService_Query_Call {
|
||||
return &SessionService_Query_Call{Call: _e.mock.On("Query", ctx, expr, queryOptions)}
|
||||
}
|
||||
|
||||
func (_c *SessionService_Query_Call) Run(run func(ctx context.Context, expr string, queryOptions scriptmanager.QueryOptions)) *SessionService_Query_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(scriptmanager.QueryOptions))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_Query_Call) Return(_a0 *models.ResultSet, _a1 error) *SessionService_Query_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_Query_Call) RunAndReturn(run func(context.Context, string, scriptmanager.QueryOptions) (*models.ResultSet, error)) *SessionService_Query_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ResultSet provides a mock function with given fields: ctx
|
||||
func (_m *SessionService) ResultSet(ctx context.Context) *models.ResultSet {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 *models.ResultSet
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *models.ResultSet); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*models.ResultSet)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SessionService_ResultSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResultSet'
|
||||
type SessionService_ResultSet_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ResultSet is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *SessionService_Expecter) ResultSet(ctx interface{}) *SessionService_ResultSet_Call {
|
||||
return &SessionService_ResultSet_Call{Call: _e.mock.On("ResultSet", ctx)}
|
||||
}
|
||||
|
||||
func (_c *SessionService_ResultSet_Call) Run(run func(ctx context.Context)) *SessionService_ResultSet_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_ResultSet_Call) Return(_a0 *models.ResultSet) *SessionService_ResultSet_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_ResultSet_Call) RunAndReturn(run func(context.Context) *models.ResultSet) *SessionService_ResultSet_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SelectedItemIndex provides a mock function with given fields: ctx
|
||||
func (_m *SessionService) SelectedItemIndex(ctx context.Context) int {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func(context.Context) int); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SessionService_SelectedItemIndex_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectedItemIndex'
|
||||
type SessionService_SelectedItemIndex_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SelectedItemIndex is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *SessionService_Expecter) SelectedItemIndex(ctx interface{}) *SessionService_SelectedItemIndex_Call {
|
||||
return &SessionService_SelectedItemIndex_Call{Call: _e.mock.On("SelectedItemIndex", ctx)}
|
||||
}
|
||||
|
||||
func (_c *SessionService_SelectedItemIndex_Call) Run(run func(ctx context.Context)) *SessionService_SelectedItemIndex_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_SelectedItemIndex_Call) Return(_a0 int) *SessionService_SelectedItemIndex_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_SelectedItemIndex_Call) RunAndReturn(run func(context.Context) int) *SessionService_SelectedItemIndex_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetResultSet provides a mock function with given fields: ctx, newResultSet
|
||||
func (_m *SessionService) SetResultSet(ctx context.Context, newResultSet *models.ResultSet) {
|
||||
_m.Called(ctx, newResultSet)
|
||||
}
|
||||
|
||||
// SessionService_SetResultSet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetResultSet'
|
||||
type SessionService_SetResultSet_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetResultSet is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - newResultSet *models.ResultSet
|
||||
func (_e *SessionService_Expecter) SetResultSet(ctx interface{}, newResultSet interface{}) *SessionService_SetResultSet_Call {
|
||||
return &SessionService_SetResultSet_Call{Call: _e.mock.On("SetResultSet", ctx, newResultSet)}
|
||||
}
|
||||
|
||||
func (_c *SessionService_SetResultSet_Call) Run(run func(ctx context.Context, newResultSet *models.ResultSet)) *SessionService_SetResultSet_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*models.ResultSet))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_SetResultSet_Call) Return() *SessionService_SetResultSet_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *SessionService_SetResultSet_Call) RunAndReturn(run func(context.Context, *models.ResultSet)) *SessionService_SetResultSet_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewSessionService interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewSessionService creates a new instance of SessionService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewSessionService(t mockConstructorTestingTNewSessionService) *SessionService {
|
||||
mock := &SessionService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
// Code generated by mockery v2.20.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// UIService is an autogenerated mock type for the UIService type
|
||||
type UIService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type UIService_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *UIService) EXPECT() *UIService_Expecter {
|
||||
return &UIService_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// PrintMessage provides a mock function with given fields: ctx, msg
|
||||
func (_m *UIService) PrintMessage(ctx context.Context, msg string) {
|
||||
_m.Called(ctx, msg)
|
||||
}
|
||||
|
||||
// UIService_PrintMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PrintMessage'
|
||||
type UIService_PrintMessage_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PrintMessage is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - msg string
|
||||
func (_e *UIService_Expecter) PrintMessage(ctx interface{}, msg interface{}) *UIService_PrintMessage_Call {
|
||||
return &UIService_PrintMessage_Call{Call: _e.mock.On("PrintMessage", ctx, msg)}
|
||||
}
|
||||
|
||||
func (_c *UIService_PrintMessage_Call) Run(run func(ctx context.Context, msg string)) *UIService_PrintMessage_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UIService_PrintMessage_Call) Return() *UIService_PrintMessage_Call {
|
||||
_c.Call.Return()
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UIService_PrintMessage_Call) RunAndReturn(run func(context.Context, string)) *UIService_PrintMessage_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Prompt provides a mock function with given fields: ctx, msg
|
||||
func (_m *UIService) Prompt(ctx context.Context, msg string) chan string {
|
||||
ret := _m.Called(ctx, msg)
|
||||
|
||||
var r0 chan string
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) chan string); ok {
|
||||
r0 = rf(ctx, msg)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(chan string)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UIService_Prompt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Prompt'
|
||||
type UIService_Prompt_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Prompt is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - msg string
|
||||
func (_e *UIService_Expecter) Prompt(ctx interface{}, msg interface{}) *UIService_Prompt_Call {
|
||||
return &UIService_Prompt_Call{Call: _e.mock.On("Prompt", ctx, msg)}
|
||||
}
|
||||
|
||||
func (_c *UIService_Prompt_Call) Run(run func(ctx context.Context, msg string)) *UIService_Prompt_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UIService_Prompt_Call) Return(_a0 chan string) *UIService_Prompt_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *UIService_Prompt_Call) RunAndReturn(run func(context.Context, string) chan string) *UIService_Prompt_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
type mockConstructorTestingTNewUIService interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}
|
||||
|
||||
// NewUIService creates a new instance of UIService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewUIService(t mockConstructorTestingTNewUIService) *UIService {
|
||||
mock := &UIService{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
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/pkg/errors"
|
||||
"github.com/risor-io/risor/object"
|
||||
)
|
||||
|
||||
var (
|
||||
validKeyBindingNames = regexp.MustCompile(`^[-a-zA-Z0-9_]+$`)
|
||||
)
|
||||
|
||||
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) command(ctx context.Context, args ...object.Object) object.Object {
|
||||
thisEnv := scriptEnvFromCtx(ctx)
|
||||
|
||||
if err := require("ext.command", 2, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmdName, err := object.AsString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fnRes, isFnRes := args[1].(*object.Function)
|
||||
if !isFnRes {
|
||||
return object.NewError(errors.New("expected second arg to be a function"))
|
||||
}
|
||||
|
||||
callFn, hasCallFn := object.GetCallFunc(ctx)
|
||||
if !hasCallFn {
|
||||
return object.NewError(errors.New("no callFn found in context"))
|
||||
}
|
||||
|
||||
// This command function will be executed by the script scheduler
|
||||
newCommand := func(ctx context.Context, args []string) error {
|
||||
objArgs := make([]object.Object, len(args))
|
||||
for i, a := range args {
|
||||
objArgs[i] = object.NewString(a)
|
||||
}
|
||||
|
||||
newEnv := thisEnv
|
||||
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) {
|
||||
errObj := res.(*object.Error)
|
||||
return errors.Errorf("command error '%v':%v - %v", m.scriptPlugin.name, cmdName, errObj.Inspect())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.scriptPlugin.definedCommands == nil {
|
||||
m.scriptPlugin.definedCommands = make(map[string]*Command)
|
||||
}
|
||||
m.scriptPlugin.definedCommands[cmdName] = &Command{plugin: m.scriptPlugin, cmdFn: newCommand}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
bindingName, err := object.AsString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !validKeyBindingNames.MatchString(bindingName) {
|
||||
return object.NewError(errors.New("value error: binding name must match regexp [-a-zA-Z0-9_]+"))
|
||||
}
|
||||
|
||||
options, err := object.AsMap(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var defaultKey string
|
||||
if strVal, isStrVal := options.Get("default").(*object.String); isStrVal {
|
||||
defaultKey = strVal.Value()
|
||||
}
|
||||
|
||||
fnRes, isFnRes := args[2].(*object.Function)
|
||||
if !isFnRes {
|
||||
return object.NewError(errors.New("expected second arg to be a function"))
|
||||
}
|
||||
|
||||
callFn, hasCallFn := object.GetCallFunc(ctx)
|
||||
if !hasCallFn {
|
||||
return object.NewError(errors.New("no callFn found in context"))
|
||||
}
|
||||
|
||||
// This command function will be executed by the script scheduler
|
||||
newCommand := func(ctx context.Context, args []string) error {
|
||||
objArgs := make([]object.Object, len(args))
|
||||
for i, a := range args {
|
||||
objArgs[i] = object.NewString(a)
|
||||
}
|
||||
|
||||
newEnv := thisEnv
|
||||
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) {
|
||||
errObj := res.(*object.Error)
|
||||
return errors.Errorf("command error '%v':%v - %v", m.scriptPlugin.name, bindingName, errObj.Inspect())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fullBindingName := fmt.Sprintf("ext.%v.%v", m.scriptPlugin.name, bindingName)
|
||||
|
||||
if m.scriptPlugin.definedKeyBindings == nil {
|
||||
m.scriptPlugin.definedKeyBindings = make(map[string]*Command)
|
||||
m.scriptPlugin.keyToKeyBinding = make(map[string]string)
|
||||
}
|
||||
|
||||
m.scriptPlugin.definedKeyBindings[fullBindingName] = &Command{plugin: m.scriptPlugin, cmdFn: newCommand}
|
||||
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
|
||||
}
|
|
@ -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())
|
||||
})
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
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/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOSModule_Env(t *testing.T) {
|
||||
t.Run("should return value of 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.getenv("FULL_VALUE") == "this is a value")
|
||||
assert(os.getenv("EMPTY_VALUE") == "")
|
||||
assert(os.getenv("MISSING_VALUE") == "")
|
||||
|
||||
assert(bool(os.getenv("FULL_VALUE")) == true)
|
||||
assert(bool(os.getenv("EMPTY_VALUE")) == false)
|
||||
assert(bool(os.getenv("MISSING_VALUE")) == false)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
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, "hello world\n")
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := exec('echo', ["hello world"]).stdout
|
||||
ui.print(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
})
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/risor-io/risor/object"
|
||||
)
|
||||
|
||||
type sessionModule struct {
|
||||
sessionService SessionService
|
||||
}
|
||||
|
||||
func (um *sessionModule) query(ctx context.Context, args ...object.Object) object.Object {
|
||||
if len(args) == 0 || len(args) > 2 {
|
||||
return object.Errorf("type error: session.query takes either 1 or 2 arguments (%d given)", len(args))
|
||||
}
|
||||
|
||||
var options QueryOptions
|
||||
|
||||
expr, objErr := object.AsString(args[0])
|
||||
if objErr != nil {
|
||||
return objErr
|
||||
}
|
||||
|
||||
if len(args) == 2 {
|
||||
objMap, objErr := object.AsMap(args[1])
|
||||
if objErr != nil {
|
||||
return objErr
|
||||
}
|
||||
|
||||
// Table name
|
||||
if val := objMap.Get("table"); val != object.Nil && val.IsTruthy() {
|
||||
switch tv := val.(type) {
|
||||
case *object.String:
|
||||
options.TableName = tv.Value()
|
||||
case *tableProxy:
|
||||
options.TableName = tv.table.Name
|
||||
default:
|
||||
return object.Errorf("type error: query option 'table' must be either a string or table")
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
options.ValuePlaceholders = make(map[string]types.AttributeValue)
|
||||
|
||||
for k, val := range argsVal.Value() {
|
||||
switch v := val.(type) {
|
||||
case *object.String:
|
||||
options.NamePlaceholders[k] = v.Value()
|
||||
options.ValuePlaceholders[k] = &types.AttributeValueMemberS{Value: v.Value()}
|
||||
case *object.Int:
|
||||
options.ValuePlaceholders[k] = &types.AttributeValueMemberN{Value: fmt.Sprint(v.Value())}
|
||||
case *object.Float:
|
||||
options.ValuePlaceholders[k] = &types.AttributeValueMemberN{Value: fmt.Sprint(v.Value())}
|
||||
case *object.Bool:
|
||||
options.ValuePlaceholders[k] = &types.AttributeValueMemberBOOL{Value: v.Value()}
|
||||
case *object.NilType:
|
||||
options.ValuePlaceholders[k] = &types.AttributeValueMemberNULL{Value: true}
|
||||
default:
|
||||
return object.Errorf("type error: arg '%v' of type '%v' is not supported", k, val.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := um.sessionService.Query(ctx, expr, options)
|
||||
|
||||
if err != nil {
|
||||
return object.NewError(err)
|
||||
}
|
||||
return &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 {
|
||||
return err
|
||||
}
|
||||
|
||||
rs := um.sessionService.ResultSet(ctx)
|
||||
if rs == nil {
|
||||
return object.Nil
|
||||
}
|
||||
return &resultSetProxy{resultSet: rs}
|
||||
}
|
||||
|
||||
func (um *sessionModule) selectedItem(ctx context.Context, args ...object.Object) object.Object {
|
||||
if err := require("session.result_set", 0, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs := um.sessionService.ResultSet(ctx)
|
||||
idx := um.sessionService.SelectedItemIndex(ctx)
|
||||
if rs == nil || idx < 0 {
|
||||
return object.Nil
|
||||
}
|
||||
|
||||
rsProxy := &resultSetProxy{resultSet: rs}
|
||||
return newItemProxy(rsProxy, idx)
|
||||
}
|
||||
|
||||
func (um *sessionModule) setResultSet(ctx context.Context, args ...object.Object) object.Object {
|
||||
if err := require("session.set_result_set", 1, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resultSetProxy, isResultSetProxy := args[0].(*resultSetProxy)
|
||||
if !isResultSetProxy {
|
||||
return object.NewError(errors.Errorf("type error: expected a resultsset (got %v)", args[0]))
|
||||
}
|
||||
|
||||
um.sessionService.SetResultSet(ctx, resultSetProxy.resultSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (um *sessionModule) currentTable(ctx context.Context, args ...object.Object) object.Object {
|
||||
if err := require("session.current_table", 0, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs := um.sessionService.ResultSet(ctx)
|
||||
if rs == nil {
|
||||
return object.Nil
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
|
@ -1,426 +0,0 @@
|
|||
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/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModSession_Table(t *testing.T) {
|
||||
t.Run("should return details of the current table", func(t *testing.T) {
|
||||
tableDef := models.TableInfo{
|
||||
Name: "test_table",
|
||||
Keys: models.KeyAttribute{
|
||||
PartitionKey: "pk",
|
||||
SortKey: "sk",
|
||||
},
|
||||
GSIs: []models.TableGSI{
|
||||
{
|
||||
Name: "index-1",
|
||||
Keys: models.KeyAttribute{
|
||||
PartitionKey: "ipk",
|
||||
SortKey: "isk",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rs := models.ResultSet{TableInfo: &tableDef}
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().ResultSet(mock.Anything).Return(&rs)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
table := session.current_table()
|
||||
|
||||
assert(table.name == "test_table")
|
||||
assert(table.keys["hash"] == "pk")
|
||||
assert(table.keys["range"] == "sk")
|
||||
assert(len(table.gsis) == 1)
|
||||
assert(table.gsis[0].name == "index-1")
|
||||
assert(table.gsis[0].keys["hash"] == "ipk")
|
||||
assert(table.gsis[0].keys["range"] == "isk")
|
||||
|
||||
assert(table == session.result_set().table)
|
||||
`)
|
||||
|
||||
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 no current result set", func(t *testing.T) {
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().ResultSet(mock.Anything).Return(nil)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
table := session.current_table()
|
||||
|
||||
assert(table == 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 TestModSession_Query(t *testing.T) {
|
||||
t.Run("should successfully return query result", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "2")
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "res[0]['pk'].S = abc")
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "res[1]['pk'].S = 1232")
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "res[1].attr('size(pk)') = 4")
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr")
|
||||
ui.print(res.length)
|
||||
ui.print("res[0]['pk'].S = ", res[0].attr("pk"))
|
||||
ui.print("res[1]['pk'].S = ", res[1].attr("pk"))
|
||||
ui.print("res[1].attr('size(pk)') = ", res[1].attr("size(pk)"))
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should return error if query returns error", func(t *testing.T) {
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(nil, errors.New("bang"))
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr")
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.Error(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should successfully specify table name", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{
|
||||
TableName: "some-table",
|
||||
}).Return(rs, nil)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr", {
|
||||
table: "some-table",
|
||||
})
|
||||
assert(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should successfully specify table proxy", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().ResultSet(mock.Anything).Return(&models.ResultSet{
|
||||
TableInfo: &models.TableInfo{
|
||||
Name: "some-resultset-table",
|
||||
},
|
||||
})
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{
|
||||
TableName: "some-resultset-table",
|
||||
}).Return(rs, nil)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr", {
|
||||
table: session.result_set().table,
|
||||
})
|
||||
assert(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should set placeholder values", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, ":name = $value", scriptmanager.QueryOptions{
|
||||
NamePlaceholders: map[string]string{
|
||||
"name": "hello",
|
||||
"value": "world",
|
||||
},
|
||||
ValuePlaceholders: map[string]types.AttributeValue{
|
||||
"name": &types.AttributeValueMemberS{Value: "hello"},
|
||||
"value": &types.AttributeValueMemberS{Value: "world"},
|
||||
},
|
||||
}).Return(rs, nil)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query(":name = $value", {
|
||||
args: {
|
||||
name: "hello",
|
||||
value: "world",
|
||||
},
|
||||
})
|
||||
assert(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should support various placeholder value type", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, ":name = $value", scriptmanager.QueryOptions{
|
||||
NamePlaceholders: map[string]string{
|
||||
"str": "hello",
|
||||
},
|
||||
ValuePlaceholders: map[string]types.AttributeValue{
|
||||
"str": &types.AttributeValueMemberS{Value: "hello"},
|
||||
"int": &types.AttributeValueMemberN{Value: "123"},
|
||||
"float": &types.AttributeValueMemberN{Value: "3.14"},
|
||||
"bool": &types.AttributeValueMemberBOOL{Value: true},
|
||||
"nil": &types.AttributeValueMemberNULL{Value: true},
|
||||
},
|
||||
}).Return(rs, nil)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query(":name = $value", {
|
||||
args: {
|
||||
"str": "hello",
|
||||
"int": 123,
|
||||
"float": 3.14,
|
||||
"bool": true,
|
||||
"nil": nil,
|
||||
},
|
||||
})
|
||||
assert(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should return error when placeholder value type is unsupported", func(t *testing.T) {
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query(":name = $value", {
|
||||
args: {
|
||||
"bad": func() { },
|
||||
},
|
||||
})
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.Error(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModSession_SelectedItem(t *testing.T) {
|
||||
t.Run("should return selected item from service implementation", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().ResultSet(mock.Anything).Return(rs)
|
||||
mockedSessionService.EXPECT().SelectedItemIndex(mock.Anything).Return(1)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
selItem := session.selected_item()
|
||||
|
||||
assert(selItem != nil, "selItem != nil")
|
||||
assert(selItem.index == 1, "selItem.index")
|
||||
assert(selItem.result_set == session.result_set(), "selItem.result_set")
|
||||
assert(selItem.attr('pk') == '1232', "selItem.attr('pk')")
|
||||
`)
|
||||
|
||||
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 selected item returns -1", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().ResultSet(mock.Anything).Return(rs)
|
||||
mockedSessionService.EXPECT().SelectedItemIndex(mock.Anything).Return(-1)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
selItem := session.selected_item()
|
||||
|
||||
assert(selItem == nil, "selItem != 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 TestModSession_SetResultSet(t *testing.T) {
|
||||
t.Run("should set the result set on the session", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil)
|
||||
mockedSessionService.EXPECT().SetResultSet(mock.Anything, rs)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr")
|
||||
session.set_result_set(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/risor-io/risor/object"
|
||||
)
|
||||
|
||||
type uiModule struct {
|
||||
uiService UIService
|
||||
}
|
||||
|
||||
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())
|
||||
default:
|
||||
msg.WriteString(a.Inspect())
|
||||
}
|
||||
}
|
||||
|
||||
um.uiService.PrintMessage(ctx, msg.String())
|
||||
return object.Nil
|
||||
}
|
||||
|
||||
func (um *uiModule) prompt(ctx context.Context, args ...object.Object) object.Object {
|
||||
if err := require("ui.prompt", 1, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, _ := object.AsString(args[0])
|
||||
respChan := um.uiService.Prompt(ctx, msg)
|
||||
|
||||
select {
|
||||
case resp, hasResp := <-respChan:
|
||||
if hasResp {
|
||||
return object.NewString(resp)
|
||||
} else {
|
||||
return object.Nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return object.NewError(ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
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/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModUI_Prompt(t *testing.T) {
|
||||
t.Run("should successfully return prompt value", func(t *testing.T) {
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
ui.print("Hello, world")
|
||||
var name = ui.prompt("What is your name? ")
|
||||
ui.print("Hello, " + name)
|
||||
`)
|
||||
|
||||
promptChan := make(chan string)
|
||||
go func() {
|
||||
promptChan <- "T. Test"
|
||||
}()
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, world")
|
||||
mockedUIService.EXPECT().Prompt(mock.Anything, "What is your name? ").Return(promptChan)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, T. Test")
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
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 return nil if prompt was cancelled", func(t *testing.T) {
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
ui.print("Hello, world")
|
||||
var name = ui.prompt("What is your name? ")
|
||||
ui.print("After")
|
||||
ui.print(nil)
|
||||
`)
|
||||
|
||||
promptChan := make(chan string)
|
||||
close(promptChan)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, world")
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "After")
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "nil")
|
||||
mockedUIService.EXPECT().Prompt(mock.Anything, "What is your name? ").Return(promptChan)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
})
|
||||
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should return error if context was cancelled", func(t *testing.T) {
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
ui.print("Hello, world")
|
||||
var name = ui.prompt("What is your name? ")
|
||||
ui.print("After")
|
||||
`)
|
||||
|
||||
promptChan := make(chan string)
|
||||
ctx, cancelFn := context.WithCancel(context.Background())
|
||||
defer cancelFn()
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, world")
|
||||
mockedUIService.EXPECT().Prompt(mock.Anything, "What is your name? ").Run(func(ctx context.Context, msg string) {
|
||||
cancelFn()
|
||||
}).Return(promptChan)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
})
|
||||
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.Error(t, err)
|
||||
|
||||
mockedUIService.AssertNotCalled(t, "Prompt", "after")
|
||||
mockedUIService.AssertExpectations(t)
|
||||
})
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/risor-io/risor/limits"
|
||||
)
|
||||
|
||||
// scriptEnv is the runtime environment for a particular script execution
|
||||
type scriptEnv struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
type scriptEnvKeyType struct{}
|
||||
|
||||
var scriptEnvKey = scriptEnvKeyType{}
|
||||
|
||||
func scriptEnvFromCtx(ctx context.Context) scriptEnv {
|
||||
perms, _ := ctx.Value(scriptEnvKey).(scriptEnv)
|
||||
return perms
|
||||
}
|
||||
|
||||
func ctxWithScriptEnv(ctx context.Context, perms scriptEnv) context.Context {
|
||||
newCtx := context.WithValue(ctx, scriptEnvKey, perms)
|
||||
newCtx = limits.WithLimits(newCtx, limits.New())
|
||||
return newCtx
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,337 +0,0 @@
|
|||
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/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
|
||||
}
|
||||
|
||||
func (r *resultSetProxy) IsTruthy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *resultSetProxy) Type() object.Type {
|
||||
return "resultset"
|
||||
}
|
||||
|
||||
func (r *resultSetProxy) Inspect() string {
|
||||
return "resultset"
|
||||
}
|
||||
|
||||
func (r *resultSetProxy) Equals(other object.Object) object.Object {
|
||||
otherRS, isOtherRS := other.(*resultSetProxy)
|
||||
if !isOtherRS {
|
||||
return object.False
|
||||
}
|
||||
|
||||
return object.NewBool(r.resultSet == otherRS.resultSet)
|
||||
}
|
||||
|
||||
// GetItem implements the [key] operator for a container type.
|
||||
func (r *resultSetProxy) GetItem(key object.Object) (object.Object, *object.Error) {
|
||||
idx, err := object.AsInt(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
realIdx := int(idx)
|
||||
if realIdx < 0 {
|
||||
realIdx = len(r.resultSet.Items()) + realIdx
|
||||
}
|
||||
|
||||
if realIdx < 0 || realIdx >= len(r.resultSet.Items()) {
|
||||
return nil, object.NewError(errors.Errorf("index error: index out of range: %v", idx))
|
||||
}
|
||||
|
||||
return newItemProxy(r, realIdx), nil
|
||||
}
|
||||
|
||||
// GetSlice implements the [start:stop] operator for a container type.
|
||||
func (r *resultSetProxy) GetSlice(s object.Slice) (object.Object, *object.Error) {
|
||||
return nil, object.NewError(errors.New("TODO"))
|
||||
}
|
||||
|
||||
// SetItem implements the [key] = value operator for a container type.
|
||||
func (r *resultSetProxy) SetItem(key, value object.Object) *object.Error {
|
||||
return object.NewError(errors.New("TODO"))
|
||||
}
|
||||
|
||||
// DelItem implements the del [key] operator for a container type.
|
||||
func (r *resultSetProxy) DelItem(key object.Object) *object.Error {
|
||||
return object.NewError(errors.New("TODO"))
|
||||
}
|
||||
|
||||
// Contains returns true if the given item is found in this container.
|
||||
func (r *resultSetProxy) Contains(item object.Object) *object.Bool {
|
||||
// TODO
|
||||
return object.False
|
||||
}
|
||||
|
||||
// Len returns the number of items in this container.
|
||||
func (r *resultSetProxy) Len() *object.Int {
|
||||
return object.NewInt(int64(len(r.resultSet.Items())))
|
||||
}
|
||||
|
||||
// Iter returns an iterator for this container.
|
||||
func (r *resultSetProxy) Iter() object.Iterator {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *resultSetProxy) GetAttr(name string) (object.Object, bool) {
|
||||
switch name {
|
||||
case "table":
|
||||
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,
|
||||
itemIndex: itemIndex,
|
||||
item: rs.resultSet.Items()[itemIndex],
|
||||
}
|
||||
}
|
||||
|
||||
func (i *itemProxy) Interface() interface{} {
|
||||
return i.item
|
||||
}
|
||||
|
||||
func (i *itemProxy) IsTruthy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *itemProxy) Type() object.Type {
|
||||
return "item"
|
||||
}
|
||||
|
||||
func (i *itemProxy) Inspect() string {
|
||||
return "item"
|
||||
}
|
||||
|
||||
func (i *itemProxy) Equals(other object.Object) object.Object {
|
||||
// TODO
|
||||
return object.False
|
||||
}
|
||||
|
||||
func (i *itemProxy) GetAttr(name string) (object.Object, bool) {
|
||||
// TODO: this should implement the container interface
|
||||
switch name {
|
||||
case "result_set":
|
||||
return i.resultSetProxy, true
|
||||
case "index":
|
||||
return object.NewInt(int64(i.itemIndex)), true
|
||||
case "attr":
|
||||
return object.NewBuiltin("attr", i.value), true
|
||||
case "set_attr":
|
||||
return object.NewBuiltin("set_attr", i.setValue), true
|
||||
case "delete_attr":
|
||||
return object.NewBuiltin("delete_attr", i.deleteAttr), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (i *itemProxy) value(ctx context.Context, args ...object.Object) object.Object {
|
||||
if objErr := require("item.attr", 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)
|
||||
}
|
||||
av, err := modExpr.EvalItem(i.item)
|
||||
if err != nil {
|
||||
return object.NewError(errors.Errorf("arg error: path expression evaluate error: %v", err))
|
||||
}
|
||||
|
||||
tVal, err := attributeValueToTamarin(av)
|
||||
if err != nil {
|
||||
return object.NewError(err)
|
||||
}
|
||||
return tVal
|
||||
}
|
||||
|
||||
func (i *itemProxy) setValue(ctx context.Context, args ...object.Object) object.Object {
|
||||
if objErr := require("item.set_attr", 2, args); objErr != nil {
|
||||
return objErr
|
||||
}
|
||||
|
||||
pathExpr, objErr := object.AsString(args[0])
|
||||
if objErr != nil {
|
||||
return objErr
|
||||
}
|
||||
|
||||
path, err := queryexpr.Parse(pathExpr)
|
||||
if err != nil {
|
||||
return object.Errorf("arg error: invalid path expression: %v", err)
|
||||
}
|
||||
|
||||
newValue, err := tamarinValueToAttributeValue(args[1])
|
||||
if err != nil {
|
||||
return object.NewError(err)
|
||||
}
|
||||
if err := path.SetEvalItem(i.item, newValue); err != nil {
|
||||
return object.NewError(err)
|
||||
}
|
||||
|
||||
i.resultSetProxy.resultSet.SetDirty(i.itemIndex, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *itemProxy) deleteAttr(ctx context.Context, args ...object.Object) object.Object {
|
||||
if objErr := require("item.delete_attr", 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)
|
||||
}
|
||||
if err := modExpr.DeleteAttribute(i.item); err != nil {
|
||||
return object.NewError(errors.Errorf("arg error: path expression evaluate error: %v", err))
|
||||
}
|
||||
|
||||
i.resultSetProxy.resultSet.SetDirty(i.itemIndex, true)
|
||||
return nil
|
||||
}
|
|
@ -1,355 +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/lmika/dynamo-browse/internal/dynamo-browse/services/scriptmanager/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestResultSetProxy(t *testing.T) {
|
||||
t.Run("should property return properties of a resultset and item", func(t *testing.T) {
|
||||
rs := &models.ResultSet{
|
||||
TableInfo: &models.TableInfo{
|
||||
Name: "test-table",
|
||||
},
|
||||
}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr")
|
||||
|
||||
// Test properties of the result set
|
||||
assert(res.table.name, "hello")
|
||||
|
||||
assert(res == res, "result_set.equals")
|
||||
assert(res.length == 2, "result_set.length")
|
||||
|
||||
// Test properties of items
|
||||
assert(res[0].index == 0, "res[0].index")
|
||||
assert(res[0].result_set == res, "res[0].result_set")
|
||||
assert(res[0].attr('pk') == 'abc', "res[0].attr('pk')")
|
||||
|
||||
assert(res[1].attr('pk') == '1232', "res[1].attr('pk')")
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(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{}
|
||||
rs.SetItems([]models.Item{
|
||||
{
|
||||
"pk": &types.AttributeValueMemberS{Value: "abc"},
|
||||
"sk": &types.AttributeValueMemberN{Value: "123"},
|
||||
"bool": &types.AttributeValueMemberBOOL{Value: true},
|
||||
"null": &types.AttributeValueMemberNULL{Value: true},
|
||||
"list": &types.AttributeValueMemberL{Value: []types.AttributeValue{
|
||||
&types.AttributeValueMemberS{Value: "apple"},
|
||||
&types.AttributeValueMemberS{Value: "banana"},
|
||||
&types.AttributeValueMemberS{Value: "cherry"},
|
||||
}},
|
||||
"map": &types.AttributeValueMemberM{Value: map[string]types.AttributeValue{
|
||||
"this": &types.AttributeValueMemberS{Value: "that"},
|
||||
"another": &types.AttributeValueMemberS{Value: "thing"},
|
||||
}},
|
||||
"strSet": &types.AttributeValueMemberSS{Value: []string{"apple", "banana", "cherry"}},
|
||||
"numSet": &types.AttributeValueMemberNS{Value: []string{"123", "45.67", "8.911", "-321"}},
|
||||
},
|
||||
})
|
||||
|
||||
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[0].attr("pk") == "abc", "str attr")
|
||||
assert(res[0].attr("sk") == 123, "num attr")
|
||||
assert(res[0].attr("bool") == true, "bool attr")
|
||||
assert(res[0].attr("null") == nil, "null attr")
|
||||
assert(res[0].attr("list") == ["apple","banana","cherry"], "list attr")
|
||||
assert(res[0].attr("map") == {"this":"that", "another":"thing"}, "map attr")
|
||||
assert(res[0].attr("strSet") == {"apple","banana","cherry"}, "string set")
|
||||
assert(res[0].attr("numSet") == {123, 45.67, 8.911, -321}, "number set")
|
||||
`)
|
||||
|
||||
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_SetAttr(t *testing.T) {
|
||||
t.Run("should set the value of the item within a result set", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil)
|
||||
mockedSessionService.EXPECT().SetResultSet(mock.Anything, mock.MatchedBy(func(rs *models.ResultSet) bool {
|
||||
assert.Equal(t, "bla-di-bla", rs.Items()[0]["pk"].(*types.AttributeValueMemberS).Value)
|
||||
assert.Equal(t, "123", rs.Items()[0]["num"].(*types.AttributeValueMemberN).Value)
|
||||
assert.Equal(t, "123.45", rs.Items()[0]["numFloat"].(*types.AttributeValueMemberN).Value)
|
||||
assert.Equal(t, true, rs.Items()[0]["bool"].(*types.AttributeValueMemberBOOL).Value)
|
||||
assert.Equal(t, true, rs.Items()[0]["nil"].(*types.AttributeValueMemberNULL).Value)
|
||||
|
||||
list := rs.Items()[0]["lists"].(*types.AttributeValueMemberL).Value
|
||||
assert.Equal(t, "abc", list[0].(*types.AttributeValueMemberS).Value)
|
||||
assert.Equal(t, "123", list[1].(*types.AttributeValueMemberN).Value)
|
||||
assert.Equal(t, true, list[2].(*types.AttributeValueMemberBOOL).Value)
|
||||
|
||||
nestedLists := rs.Items()[0]["nestedLists"].(*types.AttributeValueMemberL).Value
|
||||
assert.Equal(t, "1", nestedLists[0].(*types.AttributeValueMemberL).Value[0].(*types.AttributeValueMemberN).Value)
|
||||
assert.Equal(t, "2", nestedLists[0].(*types.AttributeValueMemberL).Value[1].(*types.AttributeValueMemberN).Value)
|
||||
assert.Equal(t, "3", nestedLists[1].(*types.AttributeValueMemberL).Value[0].(*types.AttributeValueMemberN).Value)
|
||||
assert.Equal(t, "4", nestedLists[1].(*types.AttributeValueMemberL).Value[1].(*types.AttributeValueMemberN).Value)
|
||||
|
||||
mapValue := rs.Items()[0]["map"].(*types.AttributeValueMemberM).Value
|
||||
assert.Equal(t, "world", mapValue["hello"].(*types.AttributeValueMemberS).Value)
|
||||
assert.Equal(t, "213", mapValue["nums"].(*types.AttributeValueMemberN).Value)
|
||||
|
||||
numSet := rs.Items()[0]["numSet"].(*types.AttributeValueMemberNS).Value
|
||||
assert.Len(t, numSet, 4)
|
||||
assert.Contains(t, numSet, "1")
|
||||
assert.Contains(t, numSet, "2")
|
||||
assert.Contains(t, numSet, "3")
|
||||
assert.Contains(t, numSet, "4.5")
|
||||
|
||||
strSet := rs.Items()[0]["strSet"].(*types.AttributeValueMemberSS).Value
|
||||
assert.Len(t, strSet, 3)
|
||||
assert.Contains(t, strSet, "a")
|
||||
assert.Contains(t, strSet, "b")
|
||||
assert.Contains(t, strSet, "c")
|
||||
|
||||
assert.True(t, rs.IsDirty(0))
|
||||
return true
|
||||
}))
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr")
|
||||
|
||||
res[0].set_attr("pk", "bla-di-bla")
|
||||
res[0].set_attr("num", 123)
|
||||
res[0].set_attr("numFloat", 123.45)
|
||||
res[0].set_attr("bool", true)
|
||||
res[0].set_attr("nil", nil)
|
||||
res[0].set_attr("lists", ['abc', 123, true])
|
||||
res[0].set_attr("nestedLists", [[1,2], [3,4]])
|
||||
res[0].set_attr("map", {"hello": "world", "nums": 213})
|
||||
res[0].set_attr("numSet", {1,2,3,4.5})
|
||||
res[0].set_attr("strSet", {"a","b","c"})
|
||||
|
||||
session.set_result_set(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResultSetProxy_DeleteAttr(t *testing.T) {
|
||||
t.Run("should delete the value of the item within a result set", func(t *testing.T) {
|
||||
rs := &models.ResultSet{}
|
||||
rs.SetItems([]models.Item{
|
||||
{"pk": &types.AttributeValueMemberS{Value: "abc"}, "deleteMe": &types.AttributeValueMemberBOOL{Value: true}},
|
||||
{"pk": &types.AttributeValueMemberS{Value: "1232"}},
|
||||
})
|
||||
|
||||
mockedSessionService := mocks.NewSessionService(t)
|
||||
mockedSessionService.EXPECT().Query(mock.Anything, "some expr", scriptmanager.QueryOptions{}).Return(rs, nil)
|
||||
mockedSessionService.EXPECT().SetResultSet(mock.Anything, mock.MatchedBy(func(rs *models.ResultSet) bool {
|
||||
assert.Equal(t, "abc", rs.Items()[0]["pk"].(*types.AttributeValueMemberS).Value)
|
||||
assert.Nil(t, rs.Items()[0]["deleteMe"])
|
||||
assert.True(t, rs.IsDirty(0))
|
||||
return true
|
||||
}))
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
res := session.query("some expr")
|
||||
res[0].delete_attr("deleteMe")
|
||||
session.set_result_set(res)
|
||||
`)
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
Session: mockedSessionService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
mockedSessionService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type scriptScheduler struct {
|
||||
jobChan chan scriptJob
|
||||
}
|
||||
|
||||
func newScriptScheduler() *scriptScheduler {
|
||||
ss := &scriptScheduler{}
|
||||
ss.start()
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss *scriptScheduler) start() {
|
||||
ss.jobChan = make(chan scriptJob)
|
||||
go func() {
|
||||
for job := range ss.jobChan {
|
||||
job.job(job.ctx)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// startJobOnceFree will submit a script execution job. The function will wait until the scheduler is free.
|
||||
// The job will then run on the script goroutine and the function will return.
|
||||
func (ss *scriptScheduler) startJobOnceFree(ctx context.Context, job func(ctx context.Context)) error {
|
||||
select {
|
||||
case ss.jobChan <- scriptJob{ctx: ctx, job: job}:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// runNow will submit a job for immediate execution. The job will run as long as the scheduler is free.
|
||||
// If the scheduler is not free, an error will be returned and the job will not run.
|
||||
func (ss *scriptScheduler) runNow(ctx context.Context, job func(ctx context.Context)) error {
|
||||
select {
|
||||
case ss.jobChan <- scriptJob{ctx: ctx, job: job}:
|
||||
return nil
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
return errors.New("a script is already running")
|
||||
}
|
||||
}
|
||||
|
||||
type scriptJob struct {
|
||||
ctx context.Context
|
||||
job func(ctx context.Context)
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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
|
||||
sched *scriptScheduler
|
||||
plugins []*ScriptPlugin
|
||||
}
|
||||
|
||||
func New(opts ...ServiceOption) *Service {
|
||||
srv := &Service{
|
||||
lookupPaths: nil,
|
||||
sched: newScriptScheduler(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(srv)
|
||||
}
|
||||
return srv
|
||||
}
|
||||
|
||||
func (s *Service) SetLookupPaths(fs []fs.FS) {
|
||||
s.lookupPaths = fs
|
||||
}
|
||||
|
||||
func (s *Service) SetIFaces(ifaces Ifaces) {
|
||||
s.ifaces = ifaces
|
||||
}
|
||||
|
||||
func (s *Service) LoadScript(ctx context.Context, filename string) (*ScriptPlugin, error) {
|
||||
resChan := make(chan loadedScriptResult)
|
||||
|
||||
if err := s.sched.startJobOnceFree(ctx, func(ctx context.Context) {
|
||||
s.loadScript(ctx, filename, resChan)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := <-resChan
|
||||
if res.err != nil {
|
||||
return nil, res.err
|
||||
}
|
||||
|
||||
// Look for the previous version. If one is there, replace it, otherwise add it
|
||||
// TODO: this should probably be protected by a mutex
|
||||
newPlugin := res.scriptPlugin
|
||||
for i, p := range s.plugins {
|
||||
if p.name == newPlugin.name {
|
||||
s.plugins[i] = newPlugin
|
||||
return newPlugin, nil
|
||||
}
|
||||
}
|
||||
|
||||
s.plugins = append(s.plugins, newPlugin)
|
||||
return newPlugin, nil
|
||||
}
|
||||
|
||||
func (s *Service) RunAdHocScript(ctx context.Context, filename string) chan error {
|
||||
errChan := make(chan error)
|
||||
go s.startAdHocScript(ctx, filename, errChan)
|
||||
return errChan
|
||||
}
|
||||
|
||||
func (s *Service) StartAdHocScript(ctx context.Context, filename string, errChan chan error) error {
|
||||
return s.sched.startJobOnceFree(ctx, func(ctx context.Context) {
|
||||
s.startAdHocScript(ctx, filename, errChan)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) startAdHocScript(ctx context.Context, filename string, errChan chan error) {
|
||||
defer close(errChan)
|
||||
|
||||
code, err := s.readScript(filename, true)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrapf(err, "cannot load script file %v", filename)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename)})
|
||||
|
||||
if _, err := risor.Eval(ctx, code,
|
||||
risor.WithGlobals(s.builtins()),
|
||||
); err != nil {
|
||||
errChan <- errors.Wrapf(err, "script %v", filename)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type loadedScriptResult struct {
|
||||
scriptPlugin *ScriptPlugin
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *Service) loadScript(ctx context.Context, filename string, resChan chan loadedScriptResult) {
|
||||
defer close(resChan)
|
||||
|
||||
code, err := s.readScript(filename, false)
|
||||
if err != nil {
|
||||
resChan <- loadedScriptResult{err: errors.Wrapf(err, "cannot load script file %v", filename)}
|
||||
return
|
||||
}
|
||||
|
||||
newPlugin := &ScriptPlugin{
|
||||
name: strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename)),
|
||||
scriptService: s,
|
||||
}
|
||||
|
||||
ctx = ctxWithScriptEnv(ctx, scriptEnv{filename: filepath.Base(filename)})
|
||||
|
||||
if _, err := risor.Eval(ctx, code,
|
||||
risor.WithGlobals(s.builtins()),
|
||||
risor.WithGlobals(map[string]any{
|
||||
"ext": (&extModule{scriptPlugin: newPlugin}).register(),
|
||||
}),
|
||||
); err != nil {
|
||||
resChan <- loadedScriptResult{err: errors.Wrapf(err, "script %v", filename)}
|
||||
return
|
||||
}
|
||||
|
||||
resChan <- loadedScriptResult{scriptPlugin: newPlugin}
|
||||
}
|
||||
|
||||
func (s *Service) readScript(filename string, allowCwd bool) (string, error) {
|
||||
if allowCwd {
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
fullScriptPath := filepath.Join(cwd, filename)
|
||||
log.Printf("checking %v", fullScriptPath)
|
||||
if stat, err := os.Stat(fullScriptPath); err == nil && !stat.IsDir() {
|
||||
code, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(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) {
|
||||
code, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(code), nil
|
||||
}
|
||||
|
||||
for _, currFS := range s.lookupPaths {
|
||||
log.Printf("checking %v/%v", currFS, filename)
|
||||
stat, err := fs.Stat(currFS, filename)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
} else if stat.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
code, err := fs.ReadFile(currFS, filename)
|
||||
if err == nil {
|
||||
return string(code), nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
|
||||
// LookupCommand looks up a command defined by a script.
|
||||
// TODO: Command should probably accept/return a chan error to indicate that this will run in a separate goroutine
|
||||
func (s *Service) LookupCommand(name string) *Command {
|
||||
for _, p := range s.plugins {
|
||||
if cmd, hasCmd := p.definedCommands[name]; hasCmd {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) LookupKeyBinding(key string) (string, *Command) {
|
||||
for _, p := range s.plugins {
|
||||
if bindingName, hasBinding := p.keyToKeyBinding[key]; hasBinding {
|
||||
if cmd, hasCmd := p.definedKeyBindings[bindingName]; hasCmd {
|
||||
return bindingName, cmd
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *Service) UnbindKey(key string) {
|
||||
for _, p := range s.plugins {
|
||||
if _, hasBinding := p.keyToKeyBinding[key]; hasBinding {
|
||||
delete(p.keyToKeyBinding, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) RebindKeyBinding(keyBinding string, newKey string) error {
|
||||
if newKey == "" {
|
||||
for _, p := range s.plugins {
|
||||
for k, b := range p.keyToKeyBinding {
|
||||
if b == keyBinding {
|
||||
delete(p.keyToKeyBinding, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, p := range s.plugins {
|
||||
if _, hasCmd := p.definedKeyBindings[keyBinding]; hasCmd {
|
||||
if newKey != "" {
|
||||
p.keyToKeyBinding[newKey] = keyBinding
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
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/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"io/fs"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestService_RunAdHocScript(t *testing.T) {
|
||||
t.Run("successfully loads and executes a script", func(t *testing.T) {
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
ui.print("Hello, world")
|
||||
`)
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, world")
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
err := <-srv.RunAdHocScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_LoadScript(t *testing.T) {
|
||||
t.Run("successfully loads a script and exposes it as a plugin", func(t *testing.T) {
|
||||
testFS := testScriptFile(t, "test.tm", `
|
||||
ext.command("somewhere", func(a) {
|
||||
ui.print("Hello, " + a)
|
||||
})
|
||||
`)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, someone")
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
})
|
||||
|
||||
plugin, err := srv.LoadScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, plugin)
|
||||
assert.Equal(t, "test", plugin.Name())
|
||||
|
||||
cmd := srv.LookupCommand("somewhere")
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
errChan := make(chan error)
|
||||
err = cmd.Invoke(ctx, []string{"someone"}, errChan)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, waitForErr(t, errChan))
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("reloading a script with the same name should remove the old one", func(t *testing.T) {
|
||||
testFS := fstest.MapFS{
|
||||
"test.tm": &fstest.MapFile{
|
||||
Data: []byte(`
|
||||
ext.command("somewhere", func(a) {
|
||||
ui.print("Hello, " + a)
|
||||
})
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
mockedUIService := mocks.NewUIService(t)
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Hello, someone").Once()
|
||||
mockedUIService.EXPECT().PrintMessage(mock.Anything, "Goodbye, someone").Once()
|
||||
|
||||
srv := scriptmanager.New(scriptmanager.WithFS(testFS))
|
||||
srv.SetIFaces(scriptmanager.Ifaces{
|
||||
UI: mockedUIService,
|
||||
})
|
||||
|
||||
// Execute the old script
|
||||
_, err := srv.LoadScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
cmd := srv.LookupCommand("somewhere")
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
errChan := make(chan error)
|
||||
err = cmd.Invoke(ctx, []string{"someone"}, errChan)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, waitForErr(t, errChan))
|
||||
|
||||
// Change the script and reload
|
||||
testFS["test.tm"] = &fstest.MapFile{
|
||||
Data: []byte(`
|
||||
ext.command("somewhere", func(a) {
|
||||
ui.print("Goodbye, " + a)
|
||||
})
|
||||
`),
|
||||
}
|
||||
|
||||
_, err = srv.LoadScript(ctx, "test.tm")
|
||||
assert.NoError(t, err)
|
||||
|
||||
cmd = srv.LookupCommand("somewhere")
|
||||
assert.NotNil(t, cmd)
|
||||
|
||||
errChan = make(chan error)
|
||||
err = cmd.Invoke(ctx, []string{"someone"}, errChan)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, waitForErr(t, errChan))
|
||||
|
||||
mockedUIService.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func testScriptFile(t *testing.T, filename, code string) fs.FS {
|
||||
t.Helper()
|
||||
|
||||
testFs := fstest.MapFS{
|
||||
filename: &fstest.MapFile{
|
||||
Data: []byte(code),
|
||||
},
|
||||
}
|
||||
return testFs
|
||||
}
|
||||
|
||||
func waitForErr(t *testing.T, errChan chan error) error {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return err
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("timed-out waiting for an error")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import "io/fs"
|
||||
|
||||
type ServiceOption func(srv *Service)
|
||||
|
||||
func WithFS(fs ...fs.FS) ServiceOption {
|
||||
return func(srv *Service) {
|
||||
srv.lookupPaths = fs
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
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"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
tableProxyPartitionKey = "hash"
|
||||
tableProxySortKey = "range"
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
func (t *tableProxy) Inspect() string {
|
||||
return "table(" + t.table.Name + ")"
|
||||
}
|
||||
|
||||
func (t *tableProxy) Interface() interface{} {
|
||||
return t.table
|
||||
}
|
||||
|
||||
func (t *tableProxy) Equals(other object.Object) object.Object {
|
||||
otherT, isOtherRS := other.(*tableProxy)
|
||||
if !isOtherRS {
|
||||
return object.False
|
||||
}
|
||||
|
||||
return object.NewBool(reflect.DeepEqual(t.table, otherT.table))
|
||||
}
|
||||
|
||||
func (t *tableProxy) GetAttr(name string) (object.Object, bool) {
|
||||
switch name {
|
||||
case "name":
|
||||
return object.NewString(t.table.Name), true
|
||||
case "keys":
|
||||
if t.table.Keys.SortKey == "" {
|
||||
return object.NewMap(map[string]object.Object{
|
||||
tableProxyPartitionKey: object.NewString(t.table.Keys.PartitionKey),
|
||||
tableProxySortKey: object.Nil,
|
||||
}), true
|
||||
}
|
||||
|
||||
return object.NewMap(map[string]object.Object{
|
||||
tableProxyPartitionKey: object.NewString(t.table.Keys.PartitionKey),
|
||||
tableProxySortKey: object.NewString(t.table.Keys.SortKey),
|
||||
}), true
|
||||
case "gsis":
|
||||
return object.NewList(sliceutils.Map(t.table.GSIs, newTableIndexProxy)), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (t *tableProxy) IsTruthy() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
func (t tableIndexProxy) Type() object.Type {
|
||||
return "table_index"
|
||||
}
|
||||
|
||||
func (t tableIndexProxy) Inspect() string {
|
||||
return "table_index(gsi," + t.gsi.Name + ")"
|
||||
}
|
||||
|
||||
func (t tableIndexProxy) Interface() interface{} {
|
||||
return t.gsi
|
||||
}
|
||||
|
||||
func (t tableIndexProxy) Equals(other object.Object) object.Object {
|
||||
otherIP, isOtherIP := other.(tableIndexProxy)
|
||||
if !isOtherIP {
|
||||
return object.False
|
||||
}
|
||||
|
||||
return object.NewBool(reflect.DeepEqual(t.gsi, otherIP.gsi))
|
||||
}
|
||||
|
||||
func (t tableIndexProxy) GetAttr(name string) (object.Object, bool) {
|
||||
switch name {
|
||||
case "name":
|
||||
return object.NewString(t.gsi.Name), true
|
||||
case "keys":
|
||||
return object.NewMap(map[string]object.Object{
|
||||
tableProxyPartitionKey: object.NewString(t.gsi.Keys.PartitionKey),
|
||||
tableProxySortKey: object.NewString(t.gsi.Keys.SortKey),
|
||||
}), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (t tableIndexProxy) IsTruthy() bool {
|
||||
return true
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
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/pkg/errors"
|
||||
"github.com/risor-io/risor/object"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func tamarinValueToAttributeValue(val object.Object) (types.AttributeValue, error) {
|
||||
switch v := val.(type) {
|
||||
case *object.String:
|
||||
return &types.AttributeValueMemberS{Value: v.Value()}, nil
|
||||
case *object.Int:
|
||||
return &types.AttributeValueMemberN{Value: strconv.FormatInt(v.Value(), 10)}, nil
|
||||
case *object.Float:
|
||||
return &types.AttributeValueMemberN{Value: strconv.FormatFloat(v.Value(), 'f', -1, 64)}, nil
|
||||
case *object.Bool:
|
||||
return &types.AttributeValueMemberBOOL{Value: v.Value()}, nil
|
||||
case *object.NilType:
|
||||
return &types.AttributeValueMemberNULL{Value: true}, nil
|
||||
case *object.List:
|
||||
attrValue, err := sliceutils.MapWithError(v.Value(), tamarinValueToAttributeValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.AttributeValueMemberL{Value: attrValue}, nil
|
||||
case *object.Map:
|
||||
attrValue, err := maputils.MapValuesWithError(v.Value(), tamarinValueToAttributeValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.AttributeValueMemberM{Value: attrValue}, nil
|
||||
case *object.Set:
|
||||
values := maputils.Values(v.Value())
|
||||
canBeNumSet := sliceutils.All(values, func(t object.Object) bool {
|
||||
_, isInt := t.(*object.Int)
|
||||
_, isFloat := t.(*object.Float)
|
||||
return isInt || isFloat
|
||||
})
|
||||
|
||||
if canBeNumSet {
|
||||
return &types.AttributeValueMemberNS{
|
||||
Value: sliceutils.Map(values, func(t object.Object) string {
|
||||
switch v := t.(type) {
|
||||
case *object.Int:
|
||||
return strconv.FormatInt(v.Value(), 10)
|
||||
case *object.Float:
|
||||
return strconv.FormatFloat(v.Value(), 'f', -1, 64)
|
||||
}
|
||||
panic(fmt.Sprintf("unhandled object type: %v", t.Type()))
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
return &types.AttributeValueMemberSS{
|
||||
Value: sliceutils.Map(values, func(t object.Object) string {
|
||||
v, _ := object.AsString(t)
|
||||
return v
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.Errorf("type error: unsupported value type (got %v)", val.Type())
|
||||
}
|
||||
|
||||
func attributeValueToTamarin(val types.AttributeValue) (object.Object, error) {
|
||||
if val == nil {
|
||||
return object.Nil, nil
|
||||
}
|
||||
|
||||
switch v := val.(type) {
|
||||
case *types.AttributeValueMemberS:
|
||||
return object.NewString(v.Value), nil
|
||||
case *types.AttributeValueMemberN:
|
||||
f, err := convertNumAttributeToTamarinValue(v.Value)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("value error: invalid N value: %v", v.Value)
|
||||
}
|
||||
return f, nil
|
||||
case *types.AttributeValueMemberBOOL:
|
||||
if v.Value {
|
||||
return object.True, nil
|
||||
}
|
||||
return object.False, nil
|
||||
case *types.AttributeValueMemberNULL:
|
||||
return object.Nil, nil
|
||||
case *types.AttributeValueMemberL:
|
||||
list, err := sliceutils.MapWithError(v.Value, attributeValueToTamarin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object.NewList(list), nil
|
||||
case *types.AttributeValueMemberM:
|
||||
objMap, err := maputils.MapValuesWithError(v.Value, attributeValueToTamarin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object.NewMap(objMap), nil
|
||||
case *types.AttributeValueMemberSS:
|
||||
return object.NewSet(sliceutils.Map(v.Value, func(s string) object.Object {
|
||||
return object.NewString(s)
|
||||
})), nil
|
||||
case *types.AttributeValueMemberNS:
|
||||
nums, err := sliceutils.MapWithError(v.Value, func(s string) (object.Object, error) {
|
||||
return convertNumAttributeToTamarinValue(s)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object.NewSet(nums), nil
|
||||
}
|
||||
return nil, errors.Errorf("value error: cannot convert type %T to tamarin object", val)
|
||||
}
|
||||
|
||||
var intNumberPattern = regexp.MustCompile(`^[-]?[0-9]+$`)
|
||||
|
||||
// XXX - this is pretty crappy in that it does not support large values
|
||||
func convertNumAttributeToTamarinValue(n string) (object.Object, error) {
|
||||
if intNumberPattern.MatchString(n) {
|
||||
parsedInt, err := strconv.ParseInt(n, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object.NewInt(parsedInt), nil
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(n, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object.NewFloat(f), nil
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package scriptmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ScriptPlugin struct {
|
||||
scriptService *Service
|
||||
name string
|
||||
definedCommands map[string]*Command
|
||||
definedKeyBindings map[string]*Command
|
||||
keyToKeyBinding map[string]string
|
||||
relatedItems []*relatedItemBuilder
|
||||
}
|
||||
|
||||
func (sp *ScriptPlugin) Name() string {
|
||||
return sp.name
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
plugin *ScriptPlugin
|
||||
cmdFn func(ctx context.Context, args []string) error
|
||||
}
|
||||
|
||||
// Invoke will schedule the command for invocation. If the script scheduler is free, it will be started immediately.
|
||||
// Otherwise an error will be returned.
|
||||
func (c *Command) Invoke(ctx context.Context, args []string, errChan chan error) error {
|
||||
return c.plugin.scriptService.sched.runNow(ctx, func(ctx context.Context) {
|
||||
errChan <- c.cmdFn(ctx, args)
|
||||
})
|
||||
}
|
||||
|
||||
//func (c *Command) LookupRelevantItems(ctx context.Context, table *models.TableInfo, item *models.Item) error {
|
||||
//
|
||||
//}
|
|
@ -85,172 +85,12 @@ func NewModel(
|
|||
dialogPrompt := dialogprompt.New(statusAndPrompt)
|
||||
tableSelect := tableselect.New(dialogPrompt, uiStyles)
|
||||
|
||||
/*
|
||||
cc.AddCommands(&commandctrl.CommandList{
|
||||
Commands: map[string]commandctrl.Command{
|
||||
"quit": commandctrl.NoArgCommand(tea.Quit),
|
||||
"table": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var tableName string
|
||||
if err := args.Bind(&tableName); err == nil {
|
||||
return rc.ScanTable(tableName)
|
||||
}
|
||||
|
||||
return rc.ListTables(false)
|
||||
},
|
||||
"export": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var filename string
|
||||
if err := args.Bind(&filename); err != nil {
|
||||
return events.Error(errors.New("expected filename"))
|
||||
}
|
||||
|
||||
opts := controllers.ExportOptions{
|
||||
AllResults: args.HasSwitch("all"),
|
||||
}
|
||||
|
||||
return exportController.ExportCSV(filename, opts)
|
||||
},
|
||||
"mark": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var markOp = controllers.MarkOpMark
|
||||
|
||||
var markOpStr string
|
||||
if err := args.Bind(&markOpStr); err == nil {
|
||||
switch markOpStr {
|
||||
case "all":
|
||||
markOp = controllers.MarkOpMark
|
||||
case "none":
|
||||
markOp = controllers.MarkOpUnmark
|
||||
case "toggle":
|
||||
markOp = controllers.MarkOpToggle
|
||||
default:
|
||||
return events.Error(errors.New("unrecognised mark operation"))
|
||||
}
|
||||
}
|
||||
|
||||
var whereExpr = ""
|
||||
_ = args.BindSwitch("where", &whereExpr)
|
||||
|
||||
return rc.Mark(markOp, whereExpr)
|
||||
},
|
||||
"unmark": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
return rc.Mark(controllers.MarkOpUnmark, "")
|
||||
},
|
||||
"next-page": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
return rc.NextPage()
|
||||
},
|
||||
"delete": commandctrl.NoArgCommand(wc.DeleteMarked),
|
||||
|
||||
// TEMP
|
||||
"new-item": commandctrl.NoArgCommand(wc.NewItem),
|
||||
"clone": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
return wc.CloneItem(dtv.SelectedItemIndex())
|
||||
},
|
||||
"set-attr": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var fieldName string
|
||||
if err := args.Bind(&fieldName); err != nil {
|
||||
return events.Error(errors.New("expected field"))
|
||||
}
|
||||
|
||||
var itemType = models.UnsetItemType
|
||||
switch {
|
||||
case args.HasSwitch("S"):
|
||||
itemType = models.StringItemType
|
||||
case args.HasSwitch("N"):
|
||||
itemType = models.NumberItemType
|
||||
case args.HasSwitch("BOOL"):
|
||||
itemType = models.BoolItemType
|
||||
case args.HasSwitch("NULL"):
|
||||
itemType = models.NullItemType
|
||||
case args.HasSwitch("TO"):
|
||||
itemType = models.ExprValueItemType
|
||||
}
|
||||
|
||||
return wc.SetAttributeValue(dtv.SelectedItemIndex(), itemType, fieldName)
|
||||
},
|
||||
"del-attr": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var fieldName string
|
||||
// TODO: support rest args
|
||||
if err := args.Bind(&fieldName); err != nil {
|
||||
return events.Error(errors.New("expected field"))
|
||||
}
|
||||
|
||||
return wc.DeleteAttribute(dtv.SelectedItemIndex(), fieldName)
|
||||
},
|
||||
|
||||
"put": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
return wc.PutItems()
|
||||
},
|
||||
"touch": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
return wc.TouchItem(dtv.SelectedItemIndex())
|
||||
},
|
||||
"noisy-touch": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
return wc.NoisyTouchItem(dtv.SelectedItemIndex())
|
||||
},
|
||||
|
||||
|
||||
"echo": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
s := new(strings.Builder)
|
||||
for _, arg := range args {
|
||||
s.WriteString(arg)
|
||||
}
|
||||
return events.StatusMsg(s.String())
|
||||
},
|
||||
"set-opt": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var name string
|
||||
if err := args.Bind(&name); err != nil {
|
||||
return events.Error(errors.New("expected settingName"))
|
||||
}
|
||||
|
||||
var value string
|
||||
if err := args.Bind(&value); err == nil {
|
||||
return settingsController.SetSetting(name, value)
|
||||
}
|
||||
|
||||
return settingsController.SetSetting(name, "")
|
||||
},
|
||||
"rebind": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var bindingName, newKey string
|
||||
if err := args.Bind(&bindingName, &newKey); err != nil {
|
||||
return events.Error(errors.New("expected: bindingName newKey"))
|
||||
}
|
||||
|
||||
return keyBindingController.Rebind(bindingName, newKey, ctx.FromFile)
|
||||
},
|
||||
|
||||
"run-script": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var name string
|
||||
if err := args.Bind(&name); err != nil {
|
||||
return events.Error(errors.New("expected: script name"))
|
||||
}
|
||||
|
||||
return scriptController.RunScript(name)
|
||||
},
|
||||
"load-script": func(ctx commandctrl.ExecContext, args ucl.CallArgs) tea.Msg {
|
||||
var name string
|
||||
if err := args.Bind(&name); err != nil {
|
||||
return events.Error(errors.New("expected: script name"))
|
||||
}
|
||||
|
||||
return scriptController.LoadScript(name)
|
||||
},
|
||||
|
||||
// Aliases
|
||||
"sa": cc.Alias("set-attr"),
|
||||
"da": cc.Alias("del-attr"),
|
||||
"np": cc.Alias("next-page"),
|
||||
"w": cc.Alias("put"),
|
||||
"q": cc.Alias("quit"),
|
||||
},
|
||||
})
|
||||
|
||||
*/
|
||||
|
||||
root := layout.FullScreen(tableSelect)
|
||||
|
||||
return Model{
|
||||
tableReadController: rc,
|
||||
tableWriteController: wc,
|
||||
commandController: cc,
|
||||
//scriptController: scriptController,
|
||||
exportController: exportController,
|
||||
jobController: jobController,
|
||||
itemEdit: itemEdit,
|
||||
|
|
Loading…
Reference in a new issue