diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0caab8e..f78a296 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,7 +24,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - name: Configure run: | git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index e19cca8..291e372 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -4,8 +4,6 @@ import ( "context" "flag" "fmt" - "os" - "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/dynamodb" tea "github.com/charmbracelet/bubbletea" @@ -17,6 +15,8 @@ import ( "github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/lmika/awstools/internal/dynamo-browse/ui" "github.com/lmika/gopkgs/cli" + "log" + "os" ) func main() { @@ -58,6 +58,18 @@ func main() { p := tea.NewProgram(uiModel, tea.WithAltScreen()) loopback.program = p + // TEMP -- profiling + //cf, err := os.Create("trace.out") + //if err != nil { + // log.Fatal("could not create CPU profile: ", err) + //} + //defer cf.Close() // error handling omitted for example + //if err := trace.Start(cf); err != nil { + // log.Fatal("could not start CPU profile: ", err) + //} + //defer trace.Stop() + // END TEMP + f, err := tea.LogToFile("debug.log", "debug") if err != nil { fmt.Println("fatal:", err) @@ -65,6 +77,7 @@ func main() { } defer f.Close() + log.Println("launching") if err := p.Start(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) os.Exit(1) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ff5b618 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3' +services: + dynamo: + image: amazon/dynamodb-local:latest + ports: + - 8000:8000 \ No newline at end of file diff --git a/go.mod b/go.mod index c06ea6e..e259caa 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,30 @@ module github.com/lmika/awstools -go 1.17 +go 1.18 require ( + github.com/alecthomas/participle/v2 v2.0.0-alpha7 + github.com/asdine/storm v2.1.2+incompatible github.com/aws/aws-sdk-go-v2 v1.15.0 github.com/aws/aws-sdk-go-v2/config v1.13.1 github.com/aws/aws-sdk-go-v2/credentials v1.8.0 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.8.0 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.15.0 github.com/aws/aws-sdk-go-v2/service/sqs v1.16.0 + github.com/brianvoe/gofakeit/v6 v6.15.0 github.com/calyptia/go-bubble-table v0.1.0 github.com/charmbracelet/bubbles v0.10.3 github.com/charmbracelet/bubbletea v0.20.0 github.com/charmbracelet/lipgloss v0.5.0 + github.com/google/uuid v1.3.0 github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e github.com/lmika/gopkgs v0.0.0-20211210041137-0dc91e939890 + github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.7.1 ) require ( - github.com/alecthomas/participle/v2 v2.0.0-alpha7 // indirect - github.com/asdine/storm v2.1.2+incompatible // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 // indirect @@ -34,13 +37,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 // indirect github.com/aws/smithy-go v1.11.1 // indirect - github.com/brianvoe/gofakeit/v6 v6.15.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/juju/ansiterm v0.0.0-20210929141451-8b71cc96ebdc // indirect - github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe // 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.14 // indirect @@ -50,6 +50,7 @@ require ( github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect diff --git a/go.sum b/go.sum index 386946f..23fc8c7 100644 --- a/go.sum +++ b/go.sum @@ -116,6 +116,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index 00b994c..b9f4eee 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -34,10 +34,12 @@ type uiModel struct { table table.Model viewport viewport.Model + // TEMP + tableSelect tea.Model + tableWidth, tableHeight int - ready bool - //resultSet *models.ResultSet + ready bool state controllers.State message string @@ -62,6 +64,11 @@ func NewModel(dispatcher *dispatcher.Dispatcher, commandController *commandctrl. message: "Press s to scan", textInput: textInput, + // TEMP + tableSelect: newSizeWaitModel(func(w, h int) tea.Model { + return newTableSelectModel(w, h) + }), + dispatcher: dispatcher, commandController: commandController, tableReadController: tableReadController, @@ -72,7 +79,7 @@ func NewModel(dispatcher *dispatcher.Dispatcher, commandController *commandctrl. } func (m uiModel) Init() tea.Cmd { - m.invokeOperation(context.Background(), m.tableReadController.Scan()) + //m.invokeOperation(context.Background(), m.tableReadController.Scan()) return nil } @@ -215,11 +222,13 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { updatedTable, tableMsgs := m.table.Update(msg) updatedViewport, viewportMsgs := m.viewport.Update(msg) + updatedTableSelectModel, tableSelectMsgs := m.tableSelect.Update(msg) m.table = updatedTable m.viewport = updatedViewport + m.tableSelect = updatedTableSelectModel - return m, tea.Batch(textInputCommands, tableMsgs, viewportMsgs) + return m, tea.Batch(textInputCommands, tableMsgs, viewportMsgs, tableSelectMsgs) } func (m uiModel) invokeOperation(ctx context.Context, op uimodels.Operation) { @@ -233,27 +242,32 @@ func (m uiModel) invokeOperation(ctx context.Context, op uimodels.Operation) { } func (m uiModel) View() string { - if !m.ready { - return "Initializing" - } + // TEMP + return m.tableSelect.View() + + /* + if !m.ready { + return "Initializing" + } + + if m.pendingInput != nil { + return lipgloss.JoinVertical(lipgloss.Top, + m.headerView(), + m.table.View(), + m.splitterView(), + m.viewport.View(), + m.textInput.View(), + ) + } - if m.pendingInput != nil { return lipgloss.JoinVertical(lipgloss.Top, m.headerView(), m.table.View(), m.splitterView(), m.viewport.View(), - m.textInput.View(), + m.footerView(), ) - } - - return lipgloss.JoinVertical(lipgloss.Top, - m.headerView(), - m.table.View(), - m.splitterView(), - m.viewport.View(), - m.footerView(), - ) + */ } func (m uiModel) headerView() string { diff --git a/internal/dynamo-browse/ui/sizewaitmodel.go b/internal/dynamo-browse/ui/sizewaitmodel.go new file mode 100644 index 0000000..db57486 --- /dev/null +++ b/internal/dynamo-browse/ui/sizewaitmodel.go @@ -0,0 +1,48 @@ +package ui + +import ( + tea "github.com/charmbracelet/bubbletea" + "log" +) + +// sizeWaitModel is a model which waits until the first screen size message comes through. It then creates the +// submodel and delegates calls to that model +type sizeWaitModel struct { + constr func(width, height int) tea.Model + model tea.Model +} + +func newSizeWaitModel(constr func(width, height int) tea.Model) tea.Model { + return sizeWaitModel{constr: constr} +} + +func (s sizeWaitModel) Init() tea.Cmd { + return nil +} + +func (s sizeWaitModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch m := msg.(type) { + case tea.WindowSizeMsg: + log.Println("got window size message") + if s.model == nil { + log.Println("creating model") + s.model = s.constr(m.Width, m.Height) + s.model.Init() + } + } + + var submodelCmds tea.Cmd + if s.model != nil { + log.Println("starting update") + s.model, submodelCmds = s.model.Update(msg) + log.Println("ending update") + } + return s, submodelCmds +} + +func (s sizeWaitModel) View() string { + if s.model == nil { + return "" + } + return s.model.View() +} diff --git a/internal/dynamo-browse/ui/tableselectmodel.go b/internal/dynamo-browse/ui/tableselectmodel.go new file mode 100644 index 0000000..c9d2c6b --- /dev/null +++ b/internal/dynamo-browse/ui/tableselectmodel.go @@ -0,0 +1,95 @@ +package ui + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +var ( + titleStyle = lipgloss.NewStyle().MarginLeft(2) + itemStyle = lipgloss.NewStyle().PaddingLeft(4) + selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) + helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) + quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) +) + +type tableSelectModel struct { + list list.Model +} + +func (t tableSelectModel) Init() tea.Cmd { + return nil +} + +func (t tableSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + t.list.SetHeight(msg.Height) + t.list.SetWidth(msg.Width) + return t, nil + + case tea.KeyMsg: + switch keypress := msg.String(); keypress { + case "ctrl+c": + return t, tea.Quit + + case "enter": + //i, ok := m.list.SelectedItem().(item) + //if ok { + // m.choice = string(i) + //} + return t, tea.Quit + } + } + + var cmd tea.Cmd + t.list, cmd = t.list.Update(msg) + return t, cmd +} + +func (t tableSelectModel) View() string { + return t.list.View() +} + +func newTableSelectModel(w, h int) tableSelectModel { + tableItems := []tableItem{ + {name: "alpha"}, + {name: "beta"}, + {name: "gamma"}, + } + + items := toListItems(tableItems) + + delegate := list.NewDefaultDelegate() + delegate.ShowDescription = false + + return tableSelectModel{ + list: list.New(items, delegate, w, h), + } +} + +type tableItem struct { + name string +} + +func (ti tableItem) FilterValue() string { + return "" +} + +func (ti tableItem) Title() string { + return ti.name +} + +func (ti tableItem) Description() string { + return "abc" +} + +func toListItems[T list.Item](xs []T) []list.Item { + ls := make([]list.Item, len(xs)) + for i, x := range xs { + ls[i] = x + } + return ls +}