Fixed MacOS release #3

Merged
lmika merged 26 commits from feature/release-testing into main 2025-10-26 00:41:25 +00:00
58 changed files with 1930 additions and 102 deletions

View file

@ -4,12 +4,10 @@ on:
push: push:
branches: branches:
- main - main
pull_request: - feature/*
branches:
- main
jobs: jobs:
build: Build:
runs-on: docker runs-on: docker
services: services:
localstack: localstack:
@ -24,7 +22,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: 1.24 go-version: 1.25
- name: Configure - name: Configure
run: | run: |
git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika"
@ -35,4 +33,4 @@ jobs:
go test -p 1 ./... go test -p 1 ./...
env: env:
TEST_DYNAMO_URL: "http://localstack:4566" TEST_DYNAMO_URL: "http://localstack:4566"
GOPRIVATE: "github:com/lmika/*" GOPRIVATE: "github:com/lmika/*"

View file

@ -1,11 +1,12 @@
name: release name: Release
on: on:
push: push:
tags: tags:
- 'v*' - 'v*'
jobs: jobs:
build: Build:
runs-on: docker runs-on: docker
services: services:
localstack: localstack:
@ -20,7 +21,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: 1.24 go-version: 1.25
- name: Configure - name: Configure
run: | run: |
git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika"
@ -30,53 +31,86 @@ jobs:
go get ./... go get ./...
go test -p 1 ./... go test -p 1 ./...
env: env:
GOPRIVATE: "github:com/lmika/*"
TEST_DYNAMO_URL: "http://localstack:4566" TEST_DYNAMO_URL: "http://localstack:4566"
GOPRIVATE: "github:com/lmika/*"
release-macos: Site:
needs: build needs: Build
runs-on: macos-12 runs-on: docker
env:
NETLIFY_SITE_ID: 987651c8-4ffd-48d8-af67-4dbd49c48887
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: 1.22 go-version: 1.24
- uses: actions/setup-node@v4
with:
node-version: 21.1
- name: Install Hugo
run: |
curl -LO https://github.com/gohugoio/hugo/releases/download/v0.146.0/hugo_extended_0.146.0_linux-amd64.deb
apt install -y ./hugo_extended_0.146.0_linux-amd64.deb
- name: Install Netlify CLI
run: |
npm install netlify-cli@15.0.1 -g
- name: Build Site
run: |
cd _site
mkdir -p themes
git clone https://github.com/alex-shpak/hugo-book.git themes/hugo-book
npm install
hugo --minify
- name: Publish Site
run: |
cd _site
netlify deploy --dir docs --prod
'Release MacOS':
needs: Build
runs-on: macos
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.25
- name: Configure - name: Configure
run: | run: |
git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika"
- name: Setup Goreleaser - name: Setup Goreleaser
run: | run: |
brew install goreleaser/tap/goreleaser go install github.com/goreleaser/goreleaser/v2@v2.12.7
brew install goreleaser
- name: Release - name: Release
if: startsWith(github.ref, 'refs/tags/')
run: | run: |
goreleaser release -f macos.goreleaser.yml --skip=validate --clean goreleaser publish -f macos.goreleaser.yml --skip=validate --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} HOMEBREW_TAP_PRIVATE_KEY: ${{ secrets.HOMEBREW_TAP_PRIVATE_KEY }}
release-linux: # release-linux:
needs: build # needs: build
runs-on: ubuntu-latest # runs-on: ubuntu-latest
steps: # steps:
- name: Checkout # - name: Checkout
uses: actions/checkout@v2 # uses: actions/checkout@v2
- name: Setup Go # - name: Setup Go
uses: actions/setup-go@v3 # uses: actions/setup-go@v3
with: # with:
go-version: 1.22 # go-version: 1.22
- name: Configure # - name: Configure
run: | # run: |
git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika" # git config --global url."https://${{ secrets.GO_MODULES_TOKEN }}:x-oauth-basic@github.com/lmika".insteadOf "https://github.com/lmika"
- name: Release # - name: Release
uses: goreleaser/goreleaser-action@v1 # uses: goreleaser/goreleaser-action@v1
if: startsWith(github.ref, 'refs/tags/') # if: startsWith(github.ref, 'refs/tags/')
with: # with:
version: latest # version: latest
args: release -f linux.goreleaser.yml --skip=validate --clean # args: release -f linux.goreleaser.yml --skip=validate --clean
env: # env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} # HOMEBREW_GITHUB_TOKEN: ${{ secrets.HOMEBREW_GITHUB_TOKEN }}

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
debug.log debug.log
.DS_store .DS_store
.idea .idea
# Local Netlify folder
.netlify

View file

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

55
_site/assets/_custom.scss Normal file
View file

@ -0,0 +1,55 @@
:root {
--accent: #6591ff;
}
figure.screenshot {
text-align: center;
}
div.site-header {
text-align: center;
margin-bottom: 48px;
display: flex;
flex-direction: row;
align-content: center;
justify-content: center;
align-items: center;
& img {
width: 64px;
height: 64px;
}
& h1 {
font-size: 2.2em;
font-variant: small-caps;
vertical-align: middle;
margin: 0;
margin-left: 16px;
}
}
kbd {
background: var(--body-font-color);
color: var(--body-background);
border-radius: 4px;
font-weight: bold;
padding: 2px 3px;
font-size: 1.0em;
}
input {
-webkit-appearance: auto;
appearance: auto;
}
/**
* Keybinding settings.
*/
table.key-bindings.show-binding-names .kb-key-binding {
display: none;
}
table.key-bindings:not(.show-binding-names) .kb-binding-name {
display: none;
}

View file

@ -0,0 +1,17 @@
import { Controller } from "@hotwired/stimulus"
export class KeybindingsController extends Controller {
static targets = [
"showBindingNames",
"keyBindingTable"
];
bindingNamesChanged() {
let showBindingNames = this.showBindingNamesTarget;
if (showBindingNames.checked) {
this.keyBindingTableTarget.classList.add("show-binding-names");
} else {
this.keyBindingTableTarget.classList.remove("show-binding-names");
}
}
}

5
_site/assets/js/index.js Normal file
View file

@ -0,0 +1,5 @@
import { Application } from "@hotwired/stimulus";
import { KeybindingsController } from "./controllers/keybindings_controller";
const application = Application.start();
application.register("keybindings", KeybindingsController);

View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"*"
]
}
}
}

19
_site/bin/process-images.py Executable file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env python3
import os.path
import subprocess
images = [
'main-item-view.png',
'table-selection.png',
'filter-items.png',
'query-items.png',
'modified-items.png',
'fields-popup.png'
]
for image in images:
src_file = os.path.join('images', image)
target_file = os.path.join('static/images/dynamo-browse', image)
subprocess.run(['magick', src_file, '-shave', '24x16', target_file])

34
_site/config.toml Normal file
View file

@ -0,0 +1,34 @@
baseURL = "https://dynamobrowse.app/"
languageCode = "en-us"
title = "Dynamo-Browse"
theme = "hugo-book"
publishDir = "docs"
[markup.goldmark.renderer]
unsafe = true
[params]
BookTheme = "auto"
BookMenuBundle = "/menu"
[params.Logo]
logoText = "Dynamo-Browse"
[menu]
[[menu.main]]
identifier = "download"
name = "Download"
url = "/download"
weight = 20
[[menu.main]]
identifier = "docs"
name = "Documentation"
url = "/docs"
weight = 40
[[menu.main]]
identifier = "updates"
name = "Updates"
url = "/updates"
weight = 50

31
_site/content/_index.md Normal file
View file

@ -0,0 +1,31 @@
+++
title = "Dynamo-Browse"
bookToc = false
+++
<div class="site-header">
<img src="/images/dynamo-browse/dynamo-browse-logo.png">
<h1>Dynamo-Browse</h1>
</div>
<figure class="screenshot">
<img src="/images/dynamo-browse/main-item-view.png" alt="dynamo-browse">
</figure>
Dynamo-Browse is a terminal-based UI (TUI) app for working with DynamoDB tables.
With it, you can quickly connect to and browse the contents of a DynamoDB table
in your AWS account or local machine. There are some basic facilities for
editing as well.
## Getting Started
Instructions for installing Dynamo-Browse can be found on the [Downloads page](/download).
This video gives a brief introduction of how to use Dynamo-Browse to view the items of a DynamoDB table:
{{< youtube cQnTIg1_tfg >}}
More information about the tool can be found within the [Dynamo-Browse user manual](/docs).

View file

@ -0,0 +1,35 @@
# User Guide
## Table Of Contents
- [Launching and Quitting](/docs/launching)
- [Selecting a Table](/docs/launching#selecting-a-table)
- [Selecting a Workspace](/docs/launching#selecting-a-workspace)
- [Quitting](/docs/launching#quitting)
- [Getting Around](/docs/getting-around)
- [The Back-stack](/docs/getting-around#the-back-stack)
- [Adjusting The Layout](/docs/getting-around#adjusting-the-layout)
- [Adjusting The Displayed Columns](/docs/getting-around#adjusting-the-displayed-columns)
- [Entering Commands](/docs/getting-around#entering-commands)
- [Filtering and Querying](/docs/filtering-querying)
- [Filtering](/docs/filtering-querying#filtering)
- [Querying](/docs/filtering-querying#querying)
- [Editing Items](/docs/editing-items)
- [Marking Items](/docs/editing-items#marking-items)
- [Modifying Attributes](/docs/editing-items#modifying-attributes)
- [Deleting Attributes](/docs/editing-items#deleting-attributes)
- [Adding Items](/docs/editing-items#adding-items)
- [Deleting Items](/docs/editing-items#deleting-items)
- [Committing Changes](/docs/editing-items#committing-changes)
- [Backing Out of Changes](/docs/editing-items#backing-out-of-changes)
- [Customising Dynamo-Browse](/docs/customising)
- [The RC File](/docs/customising#rc-file)
- [Rebinding Keys](/docs/customising#rebinding-keys)
References
- [Key Bindings](/docs/reference/key-bindings)
- [Commands](/docs/reference/commands)
- [Query Expressions](/docs/reference/query-expressions)
- [Launch Flags](/docs/reference/launch-flags)
- [Settings](/docs/reference/settings)

View file

@ -0,0 +1,41 @@
# Customising Dynamo-Browse
Some commands can be used to customise Dynamo-Browse, such as modify key bindings.
The effect of these commands will only be applied for the duration of the session: they are currently not
tracked within the workspace file. So in order to keep customisations across relaunches, these commands
can be added to an RC file.
## The RC File
The RC file is a text file containing commands that will be executed by Dynamo-Browse upon launch.
By default, the RC file is located at the following path:
```
$HOME/.config/audax/dynamo-browse/init.rc
```
This file is primarily intended for commands that customise Dynamo-Browse in a particular way, but any
command can be entered here. If this file is found, Dynamo-Browse will invoke each command before loading
or prompting the table.
## Rebinding Keys
The default key bindings of Dynamo-Browse can be changed using the [rebind](/docs/reference/commands#rebind) command. This takes
a binding name corresponding to the particular action to invoke, and the key
to which it should be mapped to.
Putting these commands in the RC file will effectively change the default bindings of Dynamo-Browse.
```
# Rebind T to prompt for a table
rebind "view.prompt-for-table" "T"
# Rebind escape to prompt for a command
rebind "view.prompt-for-command" "esc"
```
At the moment each binding name can only be mapped to a single key. It's also currently not possible
to setup bindings for commands. These may be supported in the future.
A list of available binding names can be found the the [reference](/docs/reference/key-bindings)
(check the "Show binding names" checkbox). Note that some bindings may not have default key bindings.

View file

@ -0,0 +1,91 @@
# Editing Items
Dynamo-Browse offers some basic facilities for editing items — such as creating items, deleting items,
and modifying their attribute values.
<figure class="screenshot">
<img src="/images/dynamo-browse/modified-items.png" alt="Item indicators">
</figure>
## Marking Items
Most modifications are applied to items that are marked. A marked item is indicated by a grey
background and a bullet indicator (`•`) on the left side of the table.
To mark or unmark the selected item, press <kbd>m</kbd>.
The command `unmark` can be used to clear all marked items.
## Modifying Attributes
Item attributes can be added or modified by using the command `set-attr` or the alias `sa`.
This command can be used to modify the value and type of an attribute of the currently selected items, or
from any marked items.
The format of the command is as follows:
```
:set-attr [<type>] <attributeName>
```
Where type is one of the following (case insensitive):
- `-S`: string
- `-N`: number
- `-BOOL`: boolean
- `-NULL`: null
If the type is not specified, and the attribute exists, then the attribute type will not change.
The type must be specified if this is a new attribute or multiple items have been marked.
After executing the command, Dynamo-Browse will prompt for the value of the new attribute if one is
required.
Modified attributes will only be tracked in memory: they will not be written
to the actual table until it is "putted" (see [Committing Changes](#committing-changes)).
An item that has been modified will be displayed in red and a modified indicator (`M`) will appear
on the left-most column.
## Deleting Attributes
An attribute can be deleted by using the command `del-attr` or the alias `da`. The format of the command
is as follows:
```
:del-attr <attributeName>
```
When executed, the attribute with the name _attributeName_ will be deleted from the selected item, or
from any marked items.
Deleted attributes will only be tracked in memory: they will not be removed from
the actual table until it is "putted" (see [Committing Changes](#committing-changes)).
An item that has been modified will be displayed in red and a modified indicator (`M`) will appear
on the left-most column.
## Adding Items
A new item can be created by typing in the command `new-item`.
When entered, Dynamo-Browse will prompt for the partition and sort key. Once these are entered,
the item will appear in the top pane in green with an asterisk indicator (`*`) on the left left-most column.
Any additional attributes can be set by using `set-attr`.
A new item will only appear in memory: it will not be written
to the actual table until it is "putted" (see [Committing Changes](#committing-changes)).
## Deleting Items
Items can be deleted by marking them and then typing in the command `delete`.
Unlike most of the other modified commands, running `delete` WILL make changes to the table
immediately.
## Committing Changes
New or modified items (but not deleted items) will be kept in memory until they are committed
or "putted" to the table. To put the changes, use the `put` command or `w` alias.
## Backing Out of Changes
Any modified items can be reverted back to what they are in the actual table by rerunning the
current query. This can be done by pressing <kbd>&#8679;R</kbd>.

View file

@ -0,0 +1,39 @@
# Querying And Viewing Results
## Querying
<figure class="screenshot">
<img src="/images/dynamo-browse/query-items.png" alt="Items with query applied">
</figure>
A query or scan over the table can be performed by entering a _Query Expression_.
Query expressions are a built-in expression language which translates to either a DynamoDB query
or scan, depending on the expression. Details about the Query Expression language can be found in the
[Query Expressions references](/docs/reference/query-expressions/).
To run a query, press <kbd>?</kbd>, and enter the query expression.
To clear a query, press <kbd>?</kbd>, and press <kbd>Enter</kbd> without entering any value.
While the query is running, a spinner indicating activity will be shown in the status bar. A running
query can be cancelled while this spinner is visible by pressing <kbd>^C</kbd>. You have the option
to view any partial results that have been retrieved at the time.
## Filtering
<figure class="screenshot">
<img src="/images/dynamo-browse/filter-items.png" alt="Items with filter applied">
</figure>
The displayed items of the current result-set can be filtered down to those that contain a specific substring.
To set the filter, press <kbd>/</kbd>, and enter the substring you wish to filter on.
To clear the filter, press <kbd>/</kbd>, and press <kbd>Enter</kbd> without entering any value.
When a filter is set, any item that does not have a top-level attribute containing the substring will be hidden.
Filtering will only consist the items that are in the current result-set. It will not result in a call to the actual
table itself.
Note that filtering is case sensitive.

View file

@ -0,0 +1,153 @@
# Getting Around
After selecting a table, Dynamo-Browse will perform a scan and present the results in the default view mode.
<figure class="screenshot">
<img src="/images/dynamo-browse/main-item-view.png" alt="Main item view">
</figure>
This mode consists of three panes:
- The top pane displays the result-set of the last scan or query. The table name is at the top-left.
- The middle pane displays the attributes of the currently selected item, along with their type.
- The bottom pane displays the current query or filter, plus any messages. Prompts for input will
also appear at the bottom.
The result-set is sorted in ascending order based on the value and type of the partition and sort key.
Up to 1,000 rows will be displayed for the current result-set.
Since DynamoDB does not require all items to have the same attribute (unless they are pre-defined), any
attribute not set for a column is indicated with a grey tilde character `~`.
Use the following keys to change the currently selected row, which is highlighted in purple:
- <kbd>&uarr;</kbd>/<kbd>i</kbd>: Move selection up
- <kbd>&darr;</kbd>/<kbd>k</kbd>: Move selection down
- <kbd>PgUp</kbd>/<kbd>&#8679;I</kbd>: Page up
- <kbd>PgDn</kbd>/<kbd>&#8679;K</kbd>: Page down
- <kbd>Home</kbd>/<kbd>0</kbd>: First row
- <kbd>End</kbd>/<kbd>$</kbd>: Last row
The columns of the table
consist of the top-level attributes of the result-set. The partition key, sort key, plus any explicitly defined
attributes will always be displayed from the left margin onwards. The other attributes are determined
from the results of the last scan or query, and may change depending on the result.
The display columns of the table can be scrolled across by using the following keys:
- <kbd>&larr;</kbd>/<kbd>j</kbd>: Scroll to the left
- <kbd>&rarr;</kbd>/<kbd>l</kbd>: Scroll to the right
The attributes of the currently selected item will appear in the middle pane. Both the type and the value of each
attribute will be displayed. Any nested attributes will be indented, and will below their parent item. A value
displayed in grey does not represent the actual value of the item, but indicates some meta-information about the item,
such as the length.
## The Back-stack
Changes to the view of Dynamo-Browse will be maintained in back-stack, similar to how a
web-browse keeps track of the webpages you've visited. This stack will record the
currently viewed table, filter, or query, allowing you to "go back" to a previous view
by pressing <kbd>Backspace</kbd>. Pressing <kbd>\\</kbd> will allow you to go forward through the stack.
The back-stack is preserved in the workspace file, and can be restored by launching Dynamo-Browse with the `-w`
switch. Launching Dynamo-Browse with a workspace that has a non-empty stack will restore the last viewed
table, filter, or query from the session that was previously using the workspace.
{{<hint info>}}
**Note:** the back-stack does not preserve the actual items in the workspace. Going backwards or forwards
through the back-stack will execute any queries or filters against the actual table itself.
{{</hint>}}
## Adjusting The Layout
The horizontal size of the item table and currently selected item pane can be changed to one of the
following layout configurations:
- Item view taking up 14 rows on the bottom with the table pane taking up the rest of the vertical space (the default)
- Item view and table view taking up half of the available space
- Table view taking up 7 rows on the top with the item view taking up the rest of the vertical space
- Table view hidden
- Item view hidden
Pressing <kbd>w</kbd> will cycle forward though these layouts. For example, while in the
default layout, pressing <kbd>w</kbd> will switch to the second layout, where both the table view take up half the
screen. Pressing <kbd>&#8679;W</kbd> will cycle through the layouts in the reverse order.
## Adjusting The Displayed Columns
The columns of the result-set can be adjusted by opening up the _Fields Popup_. This popup can be opened by pressing <kbd>f</kbd>.
<figure class="screenshot">
<img src="/images/dynamo-browse/fields-popup.png" alt="dynamo-browse">
</figure>
While this popup is opened, the following changes can be applied to the displayed columns of the main table:
- Columns can be hidden
- The order columns appear in the main table can be rearranged
- New columns can be added
The popup will display the list of columns of the main result-set table. Pressing <kbd>&uarr;</kbd>/<kbd>i</kbd>
or <kbd>&darr;</kbd>/<kbd>k</kbd> will move the selection indicator to the column to apply the operation. Pressing
<kbd>&larr;</kbd>/<kbd>j</kbd> or <kbd>&rarr;</kbd>/<kbd>l</kbd> will scroll the main table left or right so that any
operations can be previewed.
To reset the columns to the top-level fields of the current result set, press <kbd>&#8679;R</kbd>.
To close the popup, press <kbd>Escape</kbd>.
### Showing And Hiding Columns
In the Fields Popup, each row has a symbol indicating whether the row is currently visible (`.`) or hidden (`✕`). Pressing
<kbd>Space</kbd> will toggle whether the currently selected column is shown or hidden.
### Re-arranging The Order Of Columns
The currently selected row can be moved up or down the table. This will move the corresponding column in the main table either
left or right.
Press <kbd>&#8679;I</kbd> to the selected row up, which will move the corresponding column left.
Press <kbd>&#8679;K</kbd> to the selected row down, which will move the corresponding column right.
### Adding And Removing Columns
New columns can be added in the table. The value of these columns will be determined by the result of a query expression,
and can be used to expose fields that are not at the top level.
Any nested fields of maps or lists will not be included as a column by default. Consider, for example, a table of books
with authors structured as so:
```
{
"book": {"S": "The Lord Of The Rings"},
"author": {"M": {
"firstName": {"S": "John"},
"middleName": {"S": "Ronald Reuel"},
"lastName": {"S": "Tolkien"},
}}
}
```
If you wanted to show the the author's first and last name in the main table, rather than just see the description `(3 items)`, you
can add a new column with an expression selecting the fields of the author map. The expressions that can be used here
are as follows:
- First name: `author.firstName`
- Last name: `author.lastName`
This can be extended to expressions that perform comparisons or operations. For example, the expression `author.firstName ^= "J"` can be
use in a new column to display `True` for any first name that begins with a J.
To add a new column, press <kbd>a</kbd> while the Fields Popup is visible. You'll be prompted to enter a query expression,
which will be evaluated over each row within the result-set when displaying the table.
Any column, that was either retrieved from the result-set or added by the user, can be deleted by selecting the column
within the Fields Popup and pressing <kbd>d</kbd>.
## Entering Commands
Commands can be entered by pressing <kbd>:</kbd> and entering the command, with any arguments, at the prompt.
The list of available commands can be found within the [reference section](/docs/reference/#commands).

View file

@ -0,0 +1,62 @@
# Launching And Quitting
To launch Dynamo-Browse, run the following command at the terminal:
```
dynamo-browse
```
This will use your current AWS configuration and region, which can be changed by setting
the relevant `AWS_` environment variables.
To connect to a local instance of DynamoDB, such as one
running in a Docker container, use the `--local` flag. This takes as the argument the hostname
and endpoint of the local DynamoDB server. The hostname can be omitted, and will default to `localhost`:
```
dynamo-browse --local :8080
```
## Selecting a Table
Upon launch, Dynamo-Browse will present a list of all the tables within the region:
<figure class="screenshot">
<img src="/images/dynamo-browse/table-selection.png" alt="Table selection">
</figure>
Select the table to view by pressing <kbd>Enter</kbd>. Use the following keys to navigate
the items within the list:
- <kbd>&uarr;</kbd>/<kbd>i</kbd>: Move selection up
- <kbd>&darr;</kbd>/<kbd>k</kbd>: Move selection down
Once the table is selected, the table will be scanned and Dynamo-Browse will be presented in
[View Mode](#view-mode). Another table can be selected from within view mode using the `:table` command.
Dynamo-Browse can also be launched directly in view mode by specifying a table using the `-t` flag:
```
dynamo-browse -t user-accounts
```
## Selecting a Workspace
Dynamo-Browse tracks session state, such as the back-stack, in a workspace file. By default the workspace
file will be a new file created within the temporary directory, but a specific workspace filename can be
specified by using the `-w` flag:
```
dynamo-browse -w my-workspace.ws
```
If the workspace filename references an existing file, Dynamo-Browse will restore the workspace and use it for the duration of
the session. If the workspace filename references a non-existing file, Dynamo-Browse will initialise a new workspace
using the specified filename.
Only one running instance of Dynamo-Browse can use a single workspace file at any one time.
## Quitting
To quit dynamodb-browse, enter the command `q` by pressing <kbd>:</kbd>, then typing <kbd>q</kbd> <kbd>Enter</kbd>.
The keystroke <kbd>Ctrl+C</kbd> can also be used to quit.

View file

@ -0,0 +1,150 @@
# Commands
## clone
```
:clone
```
Copies the currently selected item to a new item, which will appear at the bottom of the table.
Cloning an item will prompt for a new partition key and sort key but will not check for duplicates.
## del-attr
```
:del-attr <attribute>
```
Alias: `da`
Deletes _attribute_ from the currently selected item; or if there are any marked items, the marked items.
## delete
```
:delete
```
Deletes the marked items. Unlike the other commands that modify items, this command will be executed on
the table straight away.
## echo
```
:echo [message ...]
```
Displays _message_ in the status bar. Mainly used for debugging.
## export
```
:export [-all] <filename>
```
Writes the currently loaded items as a CSV file to _filename_.
Only string, numerical, and boolean values will be written to the export; all other value types will be
black. Exporting will honour the columns currently visible in the table. Filtered items will also be included
in the exported file.
When called with the `-all` flag, any subsequent pages will be included in the export. If invoked after running
a query, all items returned from that query will be exported to file.
## mark
```
:mark [all | none | toggle] [-where <expr>]
```
Mark the rows in the following way:
- `all`: will mark all rows. This is the default when invoked without an argument.
- `none`: will unmark all rows.
- `toggle`: will toggle all marked and unmarked rows.
Adding the `-where` option would only select rows that match the given query expression.
## new-item
```
:new-item
```
Creates a new item. When executed, the value for the partition key and sort key will be prompted.
The new item will not be written to the table until it is committed with the `put` command.
## put
```
:put
```
Alias: `w`
Commits all new and modified items to the table.
## quit
```
:quit
```
Alias: `q`
Quits Dynamo-Browse.
## rebind
```
:rebind <bindingName> <key>
```
Rebinds the action with _bindingName_ to _key_. This will replace any existing binding for that action.
See [Key Bindings](#key-bindings) with "Show binding names" checked to see available binding names.
## set
```
:set <name> [value]
```
Set the value of a setting. Flag setting types can be enabled without any value. See [Settings](#settings) for possible setting values.
## set-attr
```
:set-attr [type] <attributeName>
```
Alias: `sa`
Modifies the value of _attribute_ of the currently selected item; or if there are any marked items, the marked items.
The value of _type_ can be use to specify the type of the attribute. It can be one of the following (case insensitive):
- `-S`: string value
- `-N`: number value
- `-BOOL`: boolean value
- `-NULL`: null value
- `-TO`: value of an expression
If unset, the attribute type will not be changed. _type_ must be set if multiple items have been marked.
## table
```
:table
```
Select the table to display.
## unmark
```
:unmark
```
Unmark all marked items. This is essentially an alias for `mark none`.

View file

@ -0,0 +1,189 @@
# Key Bindings
<div data-controller="keybindings">
<label>
<input type="checkbox" id="show-kb-binding-names"
data-keybindings-target="showBindingNames" data-action="keybindings#bindingNamesChanged"> Show binding names
</label>
<table class="key-bindings" data-keybindings-target="keyBindingTable">
<thead>
<tr>
<th class="kb-key-binding" style="text-align:left">Key</th>
<th class="kb-binding-name" style="text-align:left">Binding Name</th>
<th style="text-align:left">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2">Main View Mode</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&uarr;</kbd>/<kbd>i</kbd></td>
<td class="kb-binding-name">table.move-up</td>
<td>Move selection up</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&darr;</kbd>/<kbd>k</kbd></td>
<td class="kb-binding-name">table.move-down</td>
<td>Move selection down</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>PgUp</kbd>/<kbd>&#8679;I</kbd></td>
<td class="kb-binding-name">table.page-up</td>
<td>Page up</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>PgDn</kbd>/<kbd>&#8679;K</kbd></td>
<td class="kb-binding-name">table.page-down</td>
<td>Page down</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>Home</kbd>/<kbd>0</kbd></td>
<td class="kb-binding-name">table.goto-top</td>
<td>Move selection to first item</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>End</kbd>/<kbd>$</kbd></td>
<td class="kb-binding-name">table.goto-bottom</td>
<td>Move selection to last item</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&larr;</kbd>/<kbd>j</kbd></td>
<td class="kb-binding-name">table.move-left</td>
<td>Scroll displayed columns left</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&rarr;</kbd>/<kbd>l</kbd></td>
<td class="kb-binding-name">table.move-right</td>
<td>Scroll displayed columns right</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>Backspace</kbd></td>
<td class="kb-binding-name">view.view-back</td>
<td>Go back</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>\</kbd></td>
<td class="kb-binding-name">view.view-forward</td>
<td>Go forward</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>w</kbd></td>
<td class="kb-binding-name">view.cycle-layout-forward</td>
<td>Cycle forward through layout</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&#8679;W</kbd></td>
<td class="kb-binding-name">view.cycle-layout-backwards</td>
<td>Cycle backwards through layout</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>m</kbd></td>
<td class="kb-binding-name">view.mark</td>
<td>Mark/unmark currently selected item</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>M</kbd></td>
<td class="kb-binding-name">view.toggle-marked-items</td>
<td>Toggle marked/unmarked items</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>c</kbd></td>
<td class="kb-binding-name">view.copy-item-to-clipboard</td>
<td>Copy displayed item to pasteboard</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>C</kbd></td>
<td class="kb-binding-name">view.copy-table-to-clipboard</td>
<td>Copy displayed table to pasteboard as a CSV</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>/</kbd></td>
<td class="kb-binding-name">view.prompt-for-filter</td>
<td>Filter</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>?</kbd></td>
<td class="kb-binding-name">view.prompt-for-query</td>
<td>Run scan/query</td>
</tr>
<tr class="kb-binding-name">
<td class="kb-key-binding"></td>
<td class="kb-binding-name">view.prompt-for-table</td>
<td>Select table</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>R</kbd></td>
<td class="kb-binding-name">view.rescan</td>
<td>Rerun last scan/query</td>
</tr>
<tr class="kb-binding-name">
<td class="kb-key-binding"><kbd>&gt;</kbd></td>
<td class="kb-binding-name">view.fetch-next-page</td>
<td>Fetch the next page of results</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>:</kbd></td>
<td class="kb-binding-name">view.prompt-for-command</td>
<td>Enter command</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>f</td>
<td class="kb-binding-name">view.show-fields-popup</td>
<td>Show fields popup</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>^C</kbd></td>
<td class="kb-binding-name">view.cancel-running-job</td>
<td>Cancel running operation</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>^C</kbd>/<kbd>Esc</kbd></td>
<td class="kb-binding-name">view.quit</td>
<td>Quit</td>
</tr>
<tr>
<td colspan="2">Field Popup Mode</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&#8679;I</kbd></td>
<td class="kb-binding-name">fields-popup.shift-column-left</td>
<td>Shift selected column left</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&#8679;K</kbd></td>
<td class="kb-binding-name">fields-popup.shift-column-right</td>
<td>Shift selected column right</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>Space</kbd></td>
<td class="kb-binding-name">fields-popup.toggle-column-visible</td>
<td>Toggle selected column visible</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>a</kbd></td>
<td class="kb-binding-name">fields-popup.add-column</td>
<td>Add new column</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>d</kbd></td>
<td class="kb-binding-name">fields-popup.delete-column</td>
<td>Delete selected column</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>&#8679;R</kbd></td>
<td class="kb-binding-name">fields-popup.reset-columns</td>
<td>Reset columns to that of the result-set</td>
</tr>
<tr>
<td class="kb-key-binding"><kbd>^C</kbd>/<kbd>Esc</kbd></td>
<td class="kb-binding-name">fields-popup.close</td>
<td>Close field popup</td>
</tr>
</tbody>
</table>
</div>

View file

@ -0,0 +1,49 @@
# Launch Flags
## -debug
```
-debug <filename>
```
Enable debug logs, which will be written to _filename_.
## -default-limit
```
-default-limit <int>
```
Sets the default limit of queries or scans. The default is 1,000 items.
## -local
```
-local [host]:<port>
```
Connect to a local DynamoDB service listening on _host_:_port_. The default _host_ is `localhost`.
## -ro
```
-ro
```
Enable read-only mode.
## -t
```
-t <tableName>
```
Open the table _tableName_, instead of prompting for a table.
## -w
```
-w <workspaceFile>
```
Use _workspaceFile_ as the workspace file. If unset, a temporary file will be used for the workspace.

View file

@ -0,0 +1,251 @@
# Query Expression
Query expressions are used to select rows of a table. When executed as a query (i.e. by pressing <kbd>?</kbd>),
they will be translated into query or table scans that will run over the DynamoDB table in AWS.
They work similar to the "where" clause in PartiQL except that they only require Query and Scan permission
on the AWS table and do not require "select" clauses.
Such expressions can also be used in other areas of Dynamo-Browse, such as populating the value of new columns.
## Names And Values
A query expressions support the following literals:
- Strings: `"Hello"`
- Integers: `123`
- Boolean: `true` or `false`
Field names are represented as regular identifiers, such as `pk` or `address`.
## Equality
To select rows with a field that equals a given value, use the `=` operator:
```
pk = "something"
```
Either operand will can be an identifier, placeholder, or value that resolves to any type.
The result will be true if both the LHS and RHS equal the same type and value. If the types differ or
the values differ, the result will be false. The field types can be different, but will always produce false.
The compliment is the `!=` operator:
```
pk != "not this"
```
## Numerical Comparison
The operands `<`, `<=`, `>`, `>=` can be used to compare numerical fields and values:
```
three < 5 // true
three <= 3 // true
three > 12 // false
three >= 1 // true
```
To verify that a number exists within a range, use the `between` operand:
```
three between 1 and 5 // true
```
## Prefix Operator
To select rows with a field that starts with a given substring, use the `^=` operator:
```
pk ^= "some"
```
This is equivalent to using the [begins_with](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) function in AWS query expressions.
## Logical Operators
The logical operators `and`, `or` and `not` can be used to express conjunctions, disjunctions and logical negation
between multiple expressions:
```
pk = "this" and sk = "that"
pk != "that" and count > 123
not count = 21
```
The order of precedence of these operators, from lowest to highest, is `or`, `and`, then `not`. This differs
from AWS, in which all operators have the same precedence. For example, the query:
```
pk="this" or pk="that" and sk="foo"
```
is equivalent to:
```
pk="this" or (pk="that" and sk="foo")
```
The order can be overridden using brackets:
```
(pk="this" or pk="that") and sk="foo"
```
{{< hint info >}}
If a query expression is of the form `pk = <val>` or `pk = <val> and sk <op> <val>`,
where:
- _pk_ and _sk_ are the partition and sort keys of the base table or a GSI,
- _val_ resolves to a constant value, and,
- _op_ is either `=`, `^=`, `<`, `<=`, `>`, `>=`, or `between`
the expression will be executed as a Query call. Unlike expressions on the AWS Query API method itself,
the order of the `pk` and `sk` subexpressions can be swapped.
Other expressions are supported but they will be executed as a table Scan.
{{< /hint >}}
## The `in` Operator
The `in` operator can be used to determine if a value exists in a collection:
```
three in (1, 2, 3, 4, 5)
name in ("Tom", "Dick", "Harry")
```
The result will be a boolean, which will be true if the value of the LHS equals any of the items within the RHS.
The collection can be one or more fixed set of values within parenthesis separated by commas. A single
value present within parenthesis is equivalent to the equality test:
```
three in (3) // equivalent to: three = 3
```
The right hand side can also be a subexpression without parenthesis that will resolve to either a string,
list or map. The operand will behave differently based on the RHS type:
- If the RHS is a string, the result will be true if the LHS is a substring of the RHS (equivalent to the `contains` AWS conditional expressions function)
- If the RHS is a list, the result will be true if the LHS equals any of the items of the list
- If the RHS is a map, the result will be true if the LHS appears as a key of the map
The compliment operand is `not in`:
```
three not in (6, 7, 8, 9)
```
## The `is` Operator
The `is` operator can be used to assert the value type. The RHS operand is a string which is to represent an AWS
DynamoDB item attribute type, for example `S` for strings, `N` for numbers, etc.
```
"hello" is "S" // true
123 is "N" // true
"hello" is "N" // false
```
This is equivalent to the `attribute_type` AWS conditional expressions function.
The special value `any` can be used to check that a field is set, regardless of type:
```
pk is "any" // true
```
This is equivalent to the `attribute_exists` AWS conditional expressions function.
The compliment operand is `not is`. Using it with the "any" special value (`not is "any"`) is equivalent to the
`attribute_not_exists` AWS conditional expressions function.
## The `using` Options
A query that is to be executed on the actual table in AWS will go though a short planning phase to determine
whether it's possible to invoke the expression as a `Query` call. If the attributes map to partition and sort keys
of either the main table, or exactly one GSI associated with the table, the expression will be executed as a Query
over the table or the GSI found with those attributes.
In cases where multiple GSI candidates exist on the base table, the query will fail with the following error:
```
multiple plans with index found. Specify index or scan with 'using' clause
```
In these cases, the index will need to be specified with the `using` keyword with the `index` option:
```
address="something" using index("specific-gsi-name")
```
The `using` keyword can also be used to force the expression to run as a table scan,
even if the query can be invoked using a Query call over the base table or GSI:
```
address="something" using scan
```
## Builtin Functions
Query expressions support a number of builtin functions.
### The `marked` function
```
marked(fieldname)
```
The `marked` function will return a list of field values of all marked rows of the current result set. The
items will appear in the list as they appear in the result set. The _fieldname_ currently only supports top-level fields.
If no fields are marked, the empty list is returned.
```
marked("city")
```
### The `range` function
```
range(from, to)
```
The `range` function will return a list of integers between _from_ and _to_ inclusive. Non integers will be truncated
to integers, and the step is always be 1.
```
range(2, 5) // [2, 3, 4, 5]
three in range(2, 5) // true
```
### The `size` function
```
size(v)
```
The `size` function will return the number of items of a list or map, or the length of a string.
{{< hint info >}}
The `size` function is equivalent to the `size` AWS conditional expressions function, and as such is the
only function that is included as is in the generated Query or Scan expression. All other functions are evaluated
prior to making the Query or Scan AWS call.
{{</hint>}}
## Placeholders
In some circumstances, such as the [session.query](/docs/reference/script-api/#session-query) method, it's possible to use a placeholder as a field or value. To expand a placeholder to an identifier, use the `:` prefix. To expanded the placeholder as a value, use the `$` prefix. For example, the expression `:key = $value` in the following script:
```
out := session.query(":key = $value", {
table: "some-table",
args: {
key: "pk",
value: "value"
}
}
```
Is equivalent to the query `pk = "hello"`, as the placeholder `:key` is expanded to an identifier and `$value` is expanded
to a value, in this case a string.

View file

@ -0,0 +1,5 @@
---
title: "Script API"
type: script-api
---
# Script API

View file

@ -0,0 +1,21 @@
# Settings
## default-limit
- Type: int
- Default: `1000`
The maximum number of rows returned from a query or scan.
## ro
- Type: flag
Enable read-only mode. When enabled, all modification operations are disabled, and will fail with a `Read-only mode` error.
The `rw` setting will disable read-only mode.
## rw
- Type: flag
Disable read-only mode. The `ro` setting will enable read-only mode.

View file

@ -0,0 +1,103 @@
---
title: "Scripting"
---
# Scripting
Scripts can be used to automate certain tasks with Dynamo-Browse. They can also be used to define
new commands or key bindings.
## Scripting Basics
Dynamo-Browse scripts are written using the [Tamarin](https://cloudcmds.github.io/tamarin/) scripting language,
which looks a lot like [Go](https://go.dev). All features of the language are available in Dynamo-Browse.
The typical "hello world" script for Dynamo-Browse is below:
```
ui.print("Hello, world")
```
This uses the [ui](/docs/reference/script-api/#module-ui) package, which is the package used to interact with
the Dynamo-Browse user interface.
A full list of supported packages can be found in the [Script API](/docs/reference/script-api/) reference, along
with the builtins and packages supported by Tamarin itself.
{{<hint info>}}
**Note:** the [ext](/docs/reference/script-api/#module-ext) package is only available to Extension Scripts.
{{</hint>}}
To execute this script, use the `run-script` command:
```
run-script /path/to/script/hello.tm
```
You'll see that the message "Hello, world" will appear in the status bar of Dynamo-Browse.
<!-- TODO: Screenshot -->
Any `print` or `printf` messages will be written to the debug log with the prefix `script <filename>`. The
debug log is turned off by default, but it can be enabled using the [-debug](/docs/reference/launch-flags/#-debug) flag on launch.
Scripts loaded using the `run-script` command are for ad-hoc automation tasks that are not necessarily designed for
repeated use. These ad-hoc scripts are executed, then immediately unloaded, and are not generally allowed to extend
Dynamo-Browse. In order to do so, you will need to write an Extension Script.
## Extension Scripts
Extension scripts are scripts designed to extend Dynamo-Browse in some way, such as with new commands or key bindings.
They are traditionally loaded on startup and exist in the predefined "script" directory. They are usually designed for
repeated operations, including those that can be bound to command name or keys.
The following is an example script which will define a "goto" command. When invoked, the script will prompt the
user for the value of the partition key. It will then perform a query over the currently viewed table for any rows with
that partition key. If no error occurred, the results of the query will be shown to the user.
```
// Define a new "goto" command, which can be invoked when the user presses ':' and types in 'goto'
ext.command("goto", func() {
// Use the information of the current table to get the name of the partition key.
pkName := session.current_table().keys["partition"]
// Prompt the user for the value to go to. The user can press Esc, which will cancel
// the input and return 'nil'.
keyVal := ui.prompt(pkName + "? ")
if keyVal == nil {
return nil
}
// Run a query over the DynamoDB table for any rows with the partition key. Notice
// the use of the 'args' option, and the presence of both the name prefix (':key')
// and value prefix ('$val').
res := session.query(":key = $val", {
args: {
key: pkName,
val: keyVal,
},
})
// The query method will return either an error or a result. If it's an error, print
// a notice and exist.
if res.is_err() {
ui.print("Can't goto: " + res.err_msg())
return nil
}
// If no error, unwrap the result object to get the result-set returned from the query.
// Then change the current result-set to this one. This will change the result-set the
// user is currently seeing.
session.set_result_set(res.unwrap())
})
```
To load an extension script, use the `load-script` command:
```
load-script script.tm
```
The script must exist in the "script" directory, which by default is:
```
$HOME/.config/audax/dynamo-browse/scripts
```

41
_site/content/download.md Normal file
View file

@ -0,0 +1,41 @@
+++
layout = "single"
+++
# Download
Binary packages can be [download from GitHub](https://github.com/lmika/audax/releases/latest).
## MacOS Using Homebrew
If you have Homebrew, you can install using the following command:
```
brew tap lmika/audax
brew install audax
```
## Linux
To install the Debian package, download the `.deb` file, and install the package by running:
```
sudo apt install ./audax_0.4.0_linux_amd64.deb
```
To install the RPM package, download the `.rpm` file, and install the package by running:
```
sudo yum install ./audax_0.4.0_linux_amd64.rpm
```
## Install Using Go
If you have Go 1.22, you can install using the following command:
```
go install github.com/lmika/audax/cmd/dynamo-browse@v0.4.0
```
The source code can be [found on GitHub](https://github.com/lmika/audax).

View file

@ -0,0 +1,25 @@
+++
headless = true
+++
- [Download]({{< relref "/download" >}})
- [Releases](https://github.com/lmika/audax/releases)
- [Github](https://github.com/lmika/audax)
<br>
[**User Guide**]({{< relref "/docs" >}})
- [Launching]({{< relref "/docs/launching" >}})
- [Getting Around]({{< relref "/docs/getting-around" >}})
- [Filtering And Querying]({{< relref "/docs/filtering-querying" >}})
- [Editing Items]({{< relref "/docs/editing-items" >}})
- [Customising]({{< relref "/docs/customising" >}})
- [Scripting]({{< relref "/docs/scripting" >}})
<br>
**References**
- [Key Bindings]({{< relref "/docs/reference/key-bindings" >}})
- [Commands]({{< relref "/docs/reference/commands" >}})
- [Query Expressions]({{< relref "/docs/reference/query-expressions" >}})
- [Launch Flags]({{< relref "/docs/reference/launch-flags" >}})
- [Settings]({{< relref "/docs/reference/settings" >}})
- [Script API]({{< relref "/docs/reference/script-api" >}})
<br>

View file

@ -0,0 +1,75 @@
module: ext
docs: |
Provides access to the extension points scripts can used to extend the functionality of Dynamo-Browse.
This module is only available for scripts loaded using the [load-script]() command.
symbols:
- name: command
syntax: ext.command(name, fn)
docs: |
Defines a new command, which can be invoked by entering _name_ within the main view mode.
The parameter _fn_ must be a function, which will be executed when the _name_ command is entered
while in view mode.
The command can accept arguments, which will be passed in to the parameters of _fn_. The number
of command arguments must match the number of parameters, except for any function arguments with
a default value.
example: |
ext.command("add", func(x, y) {
sum := x + y
ui.print("x + y = ", sum)
})
- name: related_items
syntax: ext.related_items(table, fn)
docs: |
Defines a "related item" for a table. These act as quick jumps between tables.
When the user presses Shift+O, all the related item functions that match the given
table will be evaluated. Each one is to return zero or more related queries, which are presented
to the user as a list. When the user selects one, the query will be evaluated and the result set will
be shown.
The _table_ parameter is the name of the table of the related items managed by this function.
If the last character of the table is `*`, then _table_ will be treated as a name prefix.
The _fn_ will produce a list of queries that are related to a given item. The function takes the currently
selected item as the argument, and is expected to produce a list of maps, with each map having the following
fields:
- `label`: The label to use for the picker option
- `query`: The query expression that will run when the option is chosen
- `table`: The table to run the query over. If not set, the current table will be used
- `args`: A map of query placeholder values
- `on_select`: An optional function that will run in place of a predefined query. If set, the `query` field will
be ignored.
example: |
ext.related_items("user-account", func(item) {
return [
{
"label": "Customer",
"table": "billing",
"query": "email=$email",
"args": {"email": item.attr("email")},
},
]
})
- name: key_binding
syntax: ext.key_binding(name, options, fn)
docs: |
Defines a new key binding, which can be invoked while viewing the table.
The _name_ parameter defines the binding name. The binding names will be prefixed with
`ext.<script_basename>`. This name can be used with the [rebind]() command.
The _option_ parameter defines a map of options. The only valid option is
`default`, which is the default key to use for this binding. If unset, the binding will
have no key binding and can only be bound using the [rebind]() command.
The _fn_ parameter is the function that will be invoked when the key is pressed.
It must accept no parameters.
example: |
// Script name: sayhello.tm
//
// This binding can be rebound with the command "rebind ext.sayhello.hello <key>"
ext.key_binding("hello", {"default": "H"}, func() {
ui.print("Hello")
})

View file

@ -0,0 +1,44 @@
module: item
type: type
docs: |
A single record from a DynamoDB table.
Item values are converted to tamarin types using the following:
| Attribute Type | Tamarin Type |
|:---------------|:-------------|
| S | string |
| N | int, float \[1\] |
| BOOL | bool |
| NULL | nil |
| L | list |
| M | map |
| SS | set, with string values |
| NS | set, with number values |
Notes:
- \[1\]: int will be used if the value can be parsed as an integer, otherwise it will be returned as a float.
- Byte array (B or BS) values are currently not supported.
symbols:
- name: resultset
syntax: item.resultset
docs: |
Returns the result-set this item is a member of.
- name: index
syntax: item.index
docs: |
Returns the index of this item within the result set.
- name: attr
syntax: item.attr(expression)
docs: |
Returns the attribute value from the query expression.
- name: set_attr
syntax: item.set_attr(expression, value)
docs: |
Sets the value of the attribute.
- name: delete_attr
syntax: item.delete_attr(expression)
docs: |
Delete the attribute.

View file

@ -0,0 +1,20 @@
module: resultset
type: type
docs: |
Holds a collection of items returned from a query, or presented to a user.
A specific item of a result-set can be retrived using the subscript option. For example, `result[21]` will
return the 21st item of the result-set from the first item. A negative index can be used to retrieve an
item from the last item.
There is no guarantee to the ordering of items within the result-set, although items are usually
ordered based on the partition and sort key.
symbols:
- name: length
syntax: resultset.length
docs: |
Returns the number of items within the result set.
- name: table
syntax: resultset.table
docs: |
Returns information about the table this result set belongs to.

View file

@ -0,0 +1,52 @@
module: session
docs: |
Provides access to the currently viewed table and result-set.
symbols:
- name: query
syntax: session.query(expression, [options])
docs: |
Executes a query against a DynamoDB table. This returns a resultset if the query was successful.
A query with no results will be an empty result-set.
The _expression_ is the query expression to execute. This is similar to the type of expressions entered
after pression <kbd>?</kbd>.
The _options_ map can contain the following key/value pairs:
- `table`: the DynamoDB table to execute the query against. Default is the currently displayed table.
- `args`: A map containing names and values that can be used as placeholders in the query expression.
example: |
out := session.query("pk = $key", {
table: "some-table",
args: {
key: "my partition key"
}
}
session.set_result_set(out.unwrap())
- name: current_table
syntax: session.current_table()
docs: |
Returns information about the currently displayed table. This will be returned as a `table` object. If no
table is displayed, this function will return `nil`.
- name: resultset
syntax: session.resultset
docs: |
Returns the currently displayed result set. This is the set of items that are shown to the user in the items
table. This will be returned as a `resultset` object.
Note that this only contains the items of the current result set that exists in memory. As such, it will be
capped to the configured query limit.
- name: selected_item
syntax: session.selected_item()
docs: |
Returns the item currently highlighted in the items table. This will be returned as an `item` object. If no
item is highlighted, it will return `nil`.
- name: set_result_set
syntax: session.set_result_set(new_result_set)
docs: |
Replaces the currently displayed result-set with a new one. This can be used alongside the `query` function
to display the results of a query.
Changing the displayed result-set will trigger a redraw of the viewport and will push a new history record to
the backstack. Therefore, it's not recommended to call this method too often during a script execution session.
At most once with the final result-set you'd like to show the user is considered best practice.

View file

@ -0,0 +1,20 @@
module: table
type: type
docs: |
Provides information about a DynamoDB table.
symbols:
- name: name
syntax: table.name
docs: |
Returns the name of the table.
- name: keys
syntax: table.keys
docs: |
Returns the keys of the table. This will be returned as a map with the following names:
- `hash`: the attribute name of the partition (hash) key
- `range`: the attribute name of the sort (range) key, or `nil` if one is not defined.
- name: gsis
syntax: table.gsis
docs: |
Returns a list of the GSIs used by this table. The elements of the list will have the type table_index

View file

@ -0,0 +1,16 @@
module: table_index
type: type
docs: |
Provides information about an DynamoDB index.
symbols:
- name: name
syntax: table_index.name
docs: |
Returns the name of the index.
- name: keys
syntax: table_index.keys
docs: |
Returns the keys of the index. This will be returned as a map with the following names:
- `hash`: the attribute name of the partition (hash) key
- `range`: the attribute name of the sort (range) key, or `nil` if one is not defined.

View file

@ -0,0 +1,18 @@
module: ui
docs: |
Provides control over the user interface.
symbols:
- name: print
syntax: ui.print(args...)
docs: |
Displays a message in the status bar.
- name: prompt
syntax: ui.prompt(message)
docs: |
Request a line of input from the user, using _message_ as the prompt.
This function will return the user's input as a string, or `nil` if the user cancels
the prompt by pressing <kbd>Esc</kbd>
example: |
line := ui.prompt("What is your name? ")
ui.print("Hello, ", line)

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View file

@ -0,0 +1,3 @@
{{- $options := dict "targetPath" "js/bundle.js" -}}
{{- $jsBundle := resources.Get "js/index.js" | js.Build $options | resources.Minify | fingerprint -}}
<script src="{{ $jsBundle.RelPermalink | absURL }}" defer></script>

View file

@ -0,0 +1 @@
<script src="https://tinylytics.app/embed/vgYK9BZh7G14oSKuW2wR.js" defer></script>

View file

@ -0,0 +1,62 @@
{{ define "main" }}
<article class="markdown">
{{ partial "docs/post-meta" . }}
{{- .Content -}}
<!-- API Data -->
{{ range sort $.Site.Data.scriptmods }}
<section>
{{ if eq .type "type" }}
<h2 id="type-{{.module}}">
Type: {{ .module }}
<a class="anchor" href="#type-{{.module}}">#</a>
</h2>
{{ else }}
<h2 id="module-{{.module}}">
Module: {{ .module }}
<a class="anchor" href="#module-{{.module}}">#</a>
</h2>
{{ end }}
{{ $.RenderString .docs }}
{{ $moduleName := .module }}
{{ range sort .symbols "name" }}
<h3 id="{{$moduleName}}-{{.name}}">
{{$moduleName}}.{{ .name }}
<a class="anchor" href="#{{$moduleName}}-{{.name}}">#</a>
</h3>
<pre><code>{{ .syntax }}</code></pre>
{{ $.RenderString .docs }}
{{if .example}}
<h4>Example</h4>
<pre><code>{{ .example }}</code></pre>
{{end}}
{{ end }}
</section>
{{end}}
</article>
{{ end }}
{{ define "toc" }}
<nav id="TableOfContents">
<ul>
{{ range sort $.Site.Data.scriptmods }}
{{ if eq .type "type" }}
<li><a href="#type-{{.module}}">{{ .module }}</a></li>
{{ else }}
<li><a href="#module-{{.module}}">{{ .module }}</a></li>
{{ end }}
{{/*
<ul>
{{ $moduleName := .module }}
{{ range sort .symbols "name" }}
<li><a href="#{{$moduleName}}-{{.name}}">{{ .name }}</a></li>
{{ end }}
</ul>
*/}}
{{ end }}
</ul>
</nav>
{{ end }}

28
_site/package-lock.json generated Normal file
View file

@ -0,0 +1,28 @@
{
"name": "awstools-web",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "awstools-web",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@hotwired/stimulus": "^3.1.0"
}
},
"node_modules/@hotwired/stimulus": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.1.0.tgz",
"integrity": "sha512-iDMHUhiEJ1xFeicyHcZQQgBzhtk5mPR0QZO3L6wtqzMsJEk2TKECuCQTGKjm+KJTHVY0dKq1dOOAWvODjpd2Mg=="
}
},
"dependencies": {
"@hotwired/stimulus": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hotwired/stimulus/-/stimulus-3.1.0.tgz",
"integrity": "sha512-iDMHUhiEJ1xFeicyHcZQQgBzhtk5mPR0QZO3L6wtqzMsJEk2TKECuCQTGKjm+KJTHVY0dKq1dOOAWvODjpd2Mg=="
}
}
}

25
_site/package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "awstools-web",
"version": "1.0.0",
"description": "Website for audax",
"main": "index.js",
"directories": {
"doc": "docs"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/lmika/audax-web.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/lmika/audax-web/issues"
},
"homepage": "https://github.com/lmika/audax-web#readme",
"dependencies": {
"@hotwired/stimulus": "^3.1.0"
}
}

BIN
_site/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -4,15 +4,17 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl/cmdpacks"
"log" "log"
"net" "net"
"os" "os"
"strings" "strings"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl/cmdpacks"
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
bus "github.com/lmika/events"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl" "lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/logging" "lmika.dev/cmd/dynamo-browse/internal/common/ui/logging"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/osstyle" "lmika.dev/cmd/dynamo-browse/internal/common/ui/osstyle"
@ -33,8 +35,6 @@ import (
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/keybindings"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/ui/teamodels/styles"
bus "github.com/lmika/events"
"github.com/lmika/gopkgs/cli"
) )
func main() { func main() {
@ -52,7 +52,8 @@ func main() {
cfg, err := config.LoadDefaultConfig(ctx) cfg, err := config.LoadDefaultConfig(ctx)
if err != nil { if err != nil {
cli.Fatalf("cannot load AWS config: %v", err) fmt.Fprintf(os.Stderr, "cannot load AWS config: %v", err)
os.Exit(1)
} }
closeFn := logging.EnableLogging(*flagDebug) closeFn := logging.EnableLogging(*flagDebug)
@ -61,7 +62,8 @@ func main() {
wsManager := workspaces.New(workspaces.MetaInfo{Command: "dynamo-browse"}) wsManager := workspaces.New(workspaces.MetaInfo{Command: "dynamo-browse"})
ws, err := wsManager.OpenOrCreate(*flagWorkspace) ws, err := wsManager.OpenOrCreate(*flagWorkspace)
if err != nil { if err != nil {
cli.Fatalf("cannot create workspace: %v", ws) fmt.Fprintf(os.Stderr, "cannot create workspace: %v", ws)
os.Exit(1)
} }
defer ws.Close() defer ws.Close()
@ -69,7 +71,8 @@ func main() {
if *flagLocal != "" { if *flagLocal != "" {
host, port, err := net.SplitHostPort(*flagLocal) host, port, err := net.SplitHostPort(*flagLocal)
if err != nil { if err != nil {
cli.Fatalf("invalid address '%v': %v", *flagLocal, err) fmt.Fprintf(os.Stderr, "invalid address '%v': %v", *flagLocal, err)
os.Exit(1)
} }
if host == "" { if host == "" {
host = "localhost" host = "localhost"
@ -94,12 +97,14 @@ func main() {
if *flagRO { if *flagRO {
if err := settingStore.SetReadOnly(*flagRO); err != nil { if err := settingStore.SetReadOnly(*flagRO); err != nil {
cli.Fatalf("unable to set read-only mode: %v", err) fmt.Fprintf(os.Stderr, "unable to set read-only mode: %v", err)
os.Exit(1)
} }
} }
if *flagDefaultLimit > 0 { if *flagDefaultLimit > 0 {
if err := settingStore.SetDefaultLimit(*flagDefaultLimit); err != nil { if err := settingStore.SetDefaultLimit(*flagDefaultLimit); err != nil {
cli.Fatalf("unable to set default limit: %v", err) fmt.Fprintf(os.Stderr, "unable to set default limit: %v", err)
os.Exit(1)
} }
} }
@ -130,27 +135,32 @@ func main() {
if *flagQuery != "" { if *flagQuery != "" {
if *flagTable == "" { if *flagTable == "" {
cli.Fatalf("-t will need to be set for -q") fmt.Fprintf(os.Stderr, "-t will need to be set for -q")
os.Exit(1)
} }
ctx := context.Background() ctx := context.Background()
query, err := queryexpr.Parse(*flagQuery) query, err := queryexpr.Parse(*flagQuery)
if err != nil { if err != nil {
cli.Fatalf("query: %v", err) fmt.Fprintf(os.Stderr, "query: %v", err)
os.Exit(1)
} }
ti, err := tableService.Describe(ctx, *flagTable) ti, err := tableService.Describe(ctx, *flagTable)
if err != nil { if err != nil {
cli.Fatalf("cannot describe table: %v", err) fmt.Fprintf(os.Stderr, "cannot describe table: %v", err)
os.Exit(1)
} }
rs, err := tableService.ScanOrQuery(ctx, ti, query, nil) rs, err := tableService.ScanOrQuery(ctx, ti, query, nil)
if err != nil { if err != nil {
cli.Fatalf("cannot execute query: %v", err) fmt.Fprintf(os.Stderr, "cannot execute query: %v", err)
os.Exit(1)
} }
if err := exportController.ExportToWriter(os.Stdout, rs); err != nil { if err := exportController.ExportToWriter(os.Stdout, rs); err != nil {
cli.Fatalf("cannot export results of query: %v", err) fmt.Fprintf(os.Stderr, "cannot export results of query: %v", err)
os.Exit(1)
} }
return return
} }
@ -171,7 +181,8 @@ func main() {
commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands) commandController, err := commandctrl.NewCommandController(inputHistoryService, stdCommands)
if err != nil { if err != nil {
cli.Fatalf("cannot setup command controller: %v", err) fmt.Fprintf(os.Stderr, "cannot setup command controller: %v", err)
os.Exit(1)
} }
commandController.SetCommandCompletionProvider(columnsController) commandController.SetCommandCompletionProvider(columnsController)

9
go.mod
View file

@ -1,8 +1,8 @@
module lmika.dev/cmd/dynamo-browse module lmika.dev/cmd/dynamo-browse
go 1.24 go 1.25
toolchain go1.24.0 toolchain go1.25.0
require ( require (
github.com/alecthomas/participle/v2 v2.1.1 github.com/alecthomas/participle/v2 v2.1.1
@ -20,7 +20,6 @@ require (
github.com/charmbracelet/lipgloss v0.6.0 github.com/charmbracelet/lipgloss v0.6.0
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e
github.com/lmika/go-bubble-table v0.2.2-0.20220616114432-6bbb2995e538 github.com/lmika/go-bubble-table v0.2.2-0.20220616114432-6bbb2995e538
github.com/lmika/gopkgs v0.0.0-20240408110817-a02f6fc67d1f
github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe
github.com/mattn/go-runewidth v0.0.14 github.com/mattn/go-runewidth v0.0.14
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70
@ -29,7 +28,7 @@ require (
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
golang.design/x/clipboard v0.6.2 golang.design/x/clipboard v0.6.2
golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a
ucl.lmika.dev v0.1.0 ucl.lmika.dev v0.1.1
) )
require ( require (
@ -77,5 +76,5 @@ require (
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.9.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b // indirect lmika.dev/pkg/modash v0.1.0 // indirect
) )

26
go.sum
View file

@ -105,8 +105,6 @@ github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e h1:0QkUe2ejnT/i+xbgGy
github.com/lmika/events v0.0.0-20200906102219-a2269cd4394e/go.mod h1:qtkBmNC9OfD0STtOR9sF55pQchjIfNlC3gzm4n8CrqM= 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 h1:dtMPRNoDqDnnP3HgOvYhswcJVSqdISkYlCtGOjTqg6Q=
github.com/lmika/go-bubble-table v0.2.2-0.20220616114432-6bbb2995e538/go.mod h1:0RT1upgKZ6qZ6B1SqseE3wWsPjSQRv/G/HjpYK8jNsg= 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-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= github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe h1:1UXS/6OFkbi6JrihPykmYO1VtsABB02QQ+YmYYzTY18=
github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe/go.mod h1:qpdOkLougV5Yry4Px9f1w1pNMavcr6Z67VW5Ro+vW5I= github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe/go.mod h1:qpdOkLougV5Yry4Px9f1w1pNMavcr6Z67VW5Ro+vW5I=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@ -154,8 +152,6 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
@ -249,21 +245,7 @@ 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.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d h1:x5aMBOkCr4cjJyFmq+qJVUsByfffD9k56HYDx1yZSR4= lmika.dev/pkg/modash v0.1.0 h1:fltroSvP0nKj9K0E6G+S9LULvB9Qhj47+SZ2b9v/v/c=
lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI= lmika.dev/pkg/modash v0.1.0/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI=
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b h1:Oymcj66pgyJ2CtGk9lPh06P4FOekllE1iPehDwaL0vw= ucl.lmika.dev v0.1.1 h1:P8nEqJPKS+wmXZiSjEmJkOUeWQF9YxWSymDkLXt9mvg=
lmika.dev/pkg/modash v0.0.0-20250619112300-0be0b6b35b1b/go.mod h1:8NDl/yR1eCCEhip9FJlVuMNXIeaztQ0Ks/tizExFcTI= ucl.lmika.dev v0.1.1/go.mod h1:f5RzeCTyBO+4k6LYFuDkwGRujnj4/4ONM60AEtQj02k=
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=
ucl.lmika.dev v0.0.0-20250527110948-e869e6c9bd4d h1:SlmmY92u7nvPW6xa66n2ZPfCOx90uNp1KkJZ1IDF6K0=
ucl.lmika.dev v0.0.0-20250527110948-e869e6c9bd4d/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
ucl.lmika.dev v0.0.0-20250527112110-03e6878524a1 h1:e++1/TfwVKdWi1TmO+kfCdO2+lCTKCrh1m4ps0p7UUM=
ucl.lmika.dev v0.0.0-20250527112110-03e6878524a1/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382 h1:rDJtNrcKVmEqLep1l2YrodPjCfq+/yl7p8EZUrKW7Aw=
ucl.lmika.dev v0.0.0-20250527114213-41b4fdb00382/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
ucl.lmika.dev v0.0.0-20250528113931-3a88c0c777d8 h1:kC312X0SvM9YHtuS1r6Js+CgmSS+kSAMLj8cYFuI0+4=
ucl.lmika.dev v0.0.0-20250528113931-3a88c0c777d8/go.mod h1:/MMZKm6mOMtnY4I8TYEot4Pc8dKEy+/IAQo1VdpA5EY=
ucl.lmika.dev v0.0.0-20250718121358-7c76e61b08e4 h1:4HF6Av2/cOXBmRfHBthHn2iHJhk9GvHAFg6Tu6LVUTA=
ucl.lmika.dev v0.0.0-20250718121358-7c76e61b08e4/go.mod h1:+HB5VAi0cI28mr3LbclJvv5lb/HclJ3R60x6cbjgt4c=
ucl.lmika.dev v0.1.0 h1:gIZvLjruY1buIH25cm1hcIOvZ/+BvsZ+f84xrhcS6pY=
ucl.lmika.dev v0.1.0/go.mod h1:+HB5VAi0cI28mr3LbclJvv5lb/HclJ3R60x6cbjgt4c=

View file

@ -23,9 +23,9 @@ nfpms:
builds: builds:
- dynamo-browse - dynamo-browse
vendor: lmika vendor: lmika
homepage: https://audax.tools/ homepage: https://dynamo-browse.lmika.dev/
maintainer: Leon Mika <lmika@lmika.org> maintainer: Leon Mika <lmika@lmika.org>
description: TUI tools for AWS administration description: TUI tools for working with DynamoDB
license: MIT license: MIT
formats: formats:
- deb - deb

View file

@ -1,3 +1,5 @@
version: 2
builds: builds:
- id: dynamo-browse - id: dynamo-browse
targets: targets:
@ -7,25 +9,28 @@ builds:
- CGO_ENABLED=1 - CGO_ENABLED=1
main: ./cmd/dynamo-browse/. main: ./cmd/dynamo-browse/.
binary: dynamo-browse binary: dynamo-browse
archives: archives:
- id: zip - id: zip
builds:
- dynamo-browse
wrap_in_directory: true wrap_in_directory: true
format_overrides: formats:
- goos: macos - tar.gz
format: tar.gz
brews: homebrew_casks:
- name: audax - name: dynamo-browse
repository: repository:
owner: lmika owner: casks
name: homebrew-audax name: dynamo-browse
token: "{{ .Env.HOMEBREW_GITHUB_TOKEN }}" git:
folder: Formula url: 'ssh://forgejo@lmika.dev:casks/dynamo-browse.git'
homepage: https://dynamobrowse.app/ private_key: "{{ .Env.HOMEBREW_TAP_PRIVATE_KEY }}"
description: TUI tools for AWS administration directory: Casks
homepage: https://dynamo-browse.lmika.dev/
description: TUI tools for working with DynamoDB
license: MIT license: MIT
checksum: checksum:
name_template: 'checksums-macos.txt' name_template: 'checksums-macos.txt'
snapshot: snapshot:
name_template: "{{ .Tag }}-next" version_template: "{{ .Tag }}-next"

View file

@ -4,17 +4,17 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/brianvoe/gofakeit/v6" "github.com/brianvoe/gofakeit/v6"
"github.com/pkg/errors"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/models"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/dynamo" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/providers/dynamo"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables" "lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/services/tables"
"github.com/lmika/gopkgs/cli"
"github.com/pkg/errors"
"log"
) )
func main() { func main() {
@ -28,7 +28,7 @@ func main() {
cfg, err := config.LoadDefaultConfig(ctx) cfg, err := config.LoadDefaultConfig(ctx)
if err != nil { if err != nil {
cli.Fatalf("cannot load AWS config: %v", err) log.Fatalf("cannot load AWS config: %v", err)
} }
dynamoClient := dynamodb.NewFromConfig(cfg, dynamoClient := dynamodb.NewFromConfig(cfg,