19 Commits

Author SHA1 Message Date
d94c9bf4e6 novalagung is going to love this.
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m13s
2025-05-22 20:28:01 -04:00
f00f9b0946 forgot a contact page woops haha
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 5m7s
2025-05-22 20:18:20 -04:00
b0d4f3f890 Updated
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Has been cancelled
2025-05-22 20:15:01 -04:00
e9e0d30c7a [FIX] asdf
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 1m44s
2025-05-22 18:46:03 -04:00
0c458dd965 you do wanna make me cry and i wanna say goodbye
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Has been cancelled
2025-05-22 18:38:25 -04:00
2b865568c0 That last commit was cringe
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 8m33s
2025-05-20 16:55:07 -04:00
05fc22e966 Plloi rebase plx?
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 5m18s
2025-05-20 16:37:16 -04:00
6159a1509d I'm sorry.
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 2m10s
2025-05-20 16:33:43 -04:00
e86c892b8f Fixing JC6's bug.
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 11m23s
2025-05-20 15:11:49 -04:00
61f42c5297 Nitpicking about alphabetizing methods, minor OCD thing
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 2m9s
2025-05-20 14:41:20 -04:00
17971f34c1 Programming the flux capacitor
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Has been cancelled
2025-05-20 14:31:53 -04:00
d66dbe89cb happy monday _ bleh _
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 5m26s
2025-05-20 11:18:41 -04:00
9af291f4d2 copy and paste is not a design pattern
All checks were successful
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Successful in 3m37s
2025-05-13 22:02:03 -04:00
3a61962c04 I expected something different.
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Failing after 3m7s
2025-05-13 21:55:09 -04:00
7a8d0714f5 bara bra grejjor
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Has been cancelled
2025-05-13 21:54:32 -04:00
bcd287b3b3 asdfasdfasdfasdfasdfasdfadsf
Some checks failed
🧪 ✨ Unit Tests Workflow / 🔍 🐹 Go Tests (push) Failing after 2m24s
2025-05-13 21:47:47 -04:00
2b1e5561a0 They came from... Behind 2025-05-13 21:44:19 -04:00
6804c31a33 Is there an achievement for this? 2025-05-13 10:04:59 -04:00
d890bcbd2e It's time to go home 2025-05-13 10:04:17 -04:00
16 changed files with 284 additions and 231 deletions

View File

@@ -0,0 +1,46 @@
on:
create:
tags:
- 'v*'
name: 🏗️✨ Build Workflow
jobs:
Build:
name: 🖥️ 🔨 Build
runs-on: ubuntu-latest
steps:
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 🔑 🔐 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🏗️ 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Replace entrypoint
run: |
sed -i 's/server/grpc/g' Dockerfile
sed -i 's/8080/9000/g' Dockerfile
- name: 🐳 🔨 Build Grpc Container
uses: docker/build-push-action@v6
with:
sbom: true
provenance: true
platforms: linux/arm64,linux/amd64
context: .
dockerfile: Dockerfile
push: true
tags: siteworxpro/img-proxy-url-generator:${{ gitea.ref_name }}-grpc

View File

@@ -0,0 +1,41 @@
on:
create:
tags:
- 'v*'
name: 🏗️✨ Build Workflow
jobs:
Build:
name: 🖥️ 🔨 Build
runs-on: ubuntu-latest
steps:
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 🔑 🔐 Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 🏗️ 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 🐳 🔨 Build Server Container
uses: docker/build-push-action@v6
with:
sbom: true
provenance: true
platforms: linux/arm64,linux/amd64
context: .
dockerfile: Dockerfile
push: true
tags: siteworxpro/img-proxy-url-generator:${{ gitea.ref_name }}

View File

@@ -0,0 +1,38 @@
on:
push:
branches:
- "*"
name: 🧪 ✨ Unit Tests Workflow
jobs:
test-go:
env:
GO_VERSION: '1.24.3'
name: 🔍 🐹 Go Tests
runs-on: ubuntu-latest
steps:
- name: 🛡️ 🔒 Add Siteworx CA Certificates
run: |
curl -Ls https://siteworxpro.com/hosted/Siteworx+Root+CA.pem -o /usr/local/share/ca-certificates/sw.crt
update-ca-certificates
- name: ⚙️ 🐹 Set up Go Environment
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: 📖 🔍 Checkout Repository Code
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: 📦 📥 Install Dependencies
run: |
go mod download
- name: ✅ 🔍 Run Go Tests
run: |
go test -v ./... -coverprofile=coverage.out

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,10 +0,0 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily

View File

@@ -1,27 +0,0 @@
name: Go Tests
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.22.x' ]
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Install dependencies
run: go get .
- name: Test with Go
run: go test ./... -json -cover > TestResults-${{ matrix.go-version }}.json
- name: Upload Go test results
uses: actions/upload-artifact@v4
with:
name: Go-results-${{ matrix.go-version }}
path: TestResults-${{ matrix.go-version }}.json

View File

@@ -4,7 +4,7 @@ WORKDIR /app
ADD . .
ENV GOPRIVATE=git.s.int
ENV GOPRIVATE=git.siteworxpro.com
ENV GOPROXY=direct
ENV CGO_ENABLED=0
@@ -12,7 +12,7 @@ RUN go mod tidy && go build -o imgproxy .
FROM alpine:latest AS runtime
EXPOSE 9000
EXPOSE 8080
WORKDIR /app
@@ -23,4 +23,4 @@ RUN adduser -u 1001 -g appuser appuser -D && \
USER 1001
ENTRYPOINT ["/app/imgproxy", "grpc"]
ENTRYPOINT ["/app/imgproxy", "server"]

View File

@@ -35,6 +35,17 @@ plain-url=1
## usage examples
### interactive
```shell
./imgproxy interactive
```
![img](./assets/interactive.gif)
### command line
generate a plain url with an insecure signature
```ini
[img-proxy]
@@ -134,3 +145,14 @@ curl --location 'http://localhost:8080/generate' \
}'
```
`https://i.fooo.com/UMkz4OUNw6P9ShLdewuvW3ValMgCt263vZzU5gN57WQ/h:200/sm:1/enc/ECYxMeVBTjRxB7F-jdQ7W_-Fnv4YbmSJIKie-Hdtxd9vsmEKjU1YuWVSzdN97Mod.bmp`
## gRPC server
you can also serve request via a gRPC request
```shell
./imgproxy grpc
```
```
listening on :9000
```

BIN
assets/interactive.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 KiB

View File

@@ -6,6 +6,7 @@ import (
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
)
@@ -15,6 +16,23 @@ func pkcs7pad(data []byte, blockSize int) []byte {
return append(data, padding...)
}
func pkcs7unpad(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("invalid padding size")
}
padLen := int(data[length-1])
if padLen > length || padLen == 0 {
return nil, errors.New("invalid padding")
}
for _, v := range data[length-padLen:] {
if int(v) != padLen {
return nil, errors.New("invalid padding")
}
}
return data[:length-padLen], nil
}
func (g *Generator) Decrypt(s string) (string, error) {
c, err := aes.NewCipher(g.encryptionKey)
if err != nil {
@@ -32,7 +50,12 @@ func (g *Generator) Decrypt(s string) (string, error) {
cbc.CryptBlocks(cryptText, cryptText)
return string(cryptText), err
decrypted, err := pkcs7unpad(cryptText)
if err != nil {
return "", err
}
return string(decrypted), err
}
func (g *Generator) generateBaseAesEncUrl(file []byte) (string, error) {

View File

@@ -41,9 +41,8 @@ func (g *Generator) GenerateUrl(file string, params []string, format Format) (st
if params == nil || len(params) == 0 || params[0] == "" {
params = []string{"raw:1"}
} else {
params = append(params, "sm:1")
}
params = append(params, "sm:1")
if PathPrefix != "" {
file = PathPrefix + file
@@ -54,11 +53,11 @@ func (g *Generator) GenerateUrl(file string, params []string, format Format) (st
var url string
var err error
if config.GetConfig().Generator.PlainUrl {
url, _ = g.generatePlainUrl(file)
url, err = g.generatePlainUrl(file)
} else if g.encryptionKey != nil {
url, err = g.generateBaseAesEncUrl([]byte(file))
} else {
url, _ = g.generateBase64Url([]byte(file))
url, err = g.generateBase64Url([]byte(file))
}
if err != nil {

4
go.mod
View File

@@ -5,7 +5,9 @@ go 1.24.0
require (
github.com/aws/aws-sdk-go v1.55.7
github.com/bigkevmcd/go-configparser v0.0.0-20250311182818-a679eef33309
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.5
github.com/charmbracelet/huh v0.7.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/redis/go-redis/v9 v9.8.0
github.com/urfave/cli/v2 v2.27.6
@@ -18,9 +20,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.0 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/huh v0.7.0 // indirect
github.com/charmbracelet/x/ansi v0.9.2 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect

68
go.sum
View File

@@ -1,15 +1,13 @@
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
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 v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/bigkevmcd/go-configparser v0.0.0-20240808124832-fc81059ea0bd h1:MsTk4yo6KVYdulsDscuH4AwiZN1CyuCJAg59EWE7HPQ=
github.com/bigkevmcd/go-configparser v0.0.0-20240808124832-fc81059ea0bd/go.mod h1:vzEQfW+A1T+AMJmTIX+SXNLNECHOM7GEinHhw0IjykI=
github.com/bigkevmcd/go-configparser v0.0.0-20250311182818-a679eef33309 h1:h2H7P1M0rXm8LTJMhZWr3SAleTmR6vg+7PM1BkTumaw=
github.com/bigkevmcd/go-configparser v0.0.0-20250311182818-a679eef33309/go.mod h1:vzEQfW+A1T+AMJmTIX+SXNLNECHOM7GEinHhw0IjykI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
@@ -24,33 +22,34 @@ github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/ansi v0.9.2 h1:92AGsQmNTRMzuzHEYfCdjQeUzTrgE1vfO5/7fEVoXdY=
github.com/charmbracelet/x/ansi v0.9.2/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
@@ -95,8 +94,6 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
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/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -105,61 +102,40 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -5,95 +5,104 @@ import (
"github.com/charmbracelet/huh"
)
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Helper: find index of current focusField in a given field slice.
//
//goland:noinspection GoMixedReceiverTypes
func (m *Model) findCurrentFieldIndex(fields []huh.Field) int {
for i, field := range fields {
if field == m.focusField {
return i
}
}
return -1
}
if m.focusField == nil {
m.Fields[0].Focus()
m.focusField = m.Fields[0]
// Helper: blur current, focus next (wraps to 0)
//
//goland:noinspection GoMixedReceiverTypes
func (m *Model) focusNextField(fields []huh.Field) {
index := m.findCurrentFieldIndex(fields)
next := (index + 1) % len(fields)
m.focusField.Blur()
m.focusField = fields[next]
m.focusField.Focus()
}
// Helper: focus a specific field by index
//
//goland:noinspection GoMixedReceiverTypes
func (m *Model) focusFieldByIndex(fields []huh.Field, index int) {
if index >= 0 && index < len(fields) {
m.focusField.Blur()
m.focusField = fields[index]
m.focusField.Focus()
}
}
// Helper: get all selected param fields as a flat slice
//
//goland:noinspection GoMixedReceiverTypes
func (m *Model) selectedParamFields() []huh.Field {
var fields []huh.Field
for _, param := range *m.selectedParams {
fields = append(fields, param.Input()...)
}
return fields
}
//goland:noinspection GoMixedReceiverTypes
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
model := m // copy, since Bubble Tea prefers value receivers, but we'll operate on &model internally
if model.focusField == nil && len(model.Fields) > 0 {
model.Fields[0].Focus()
model.focusField = model.Fields[0]
}
if msg, ok := msg.(tea.KeyMsg); ok {
switch msg.String() {
if keyMsg, ok := msg.(tea.KeyMsg); ok {
switch keyMsg.String() {
case "tab":
if m.focusField != nil {
mainFields := model.Fields
paramFields := model.selectedParamFields()
index := -1
selectedParamFields := make([]huh.Field, 0)
for _, field := range *m.selectedParams {
for _, f := range field.Input() {
selectedParamFields = append(selectedParamFields, f)
}
}
if m.inParamsFields {
for i, field := range selectedParamFields {
if field == m.focusField {
index = i
break
}
}
} else {
for i, field := range m.Fields {
if field == m.focusField {
index = i
break
}
}
}
// if the field is not found, return
if model.inParamsFields && len(paramFields) > 0 {
index := model.findCurrentFieldIndex(paramFields)
if index == -1 {
return m, nil
break
}
// if the field is the last one, and we have params selected go to the param fields
if !m.inParamsFields && index == len(m.Fields)-1 && len(selectedParamFields) > 0 {
m.focusField.Blur()
m.inParamsFields = true
paramsFields := selectedParamFields
m.focusField = paramsFields[0]
m.focusField.Focus()
// if the field is the last one, and we have params selected go to the first non params field
} else if m.inParamsFields && index == len(selectedParamFields)-1 {
m.focusField.Blur()
m.inParamsFields = false
m.focusField = m.Fields[0]
m.focusField.Focus()
// if not in the params fields and the field is the last one, go to the first one
} else if index == len(m.Fields)-1 && !m.inParamsFields {
m.focusField.Blur()
m.focusField = m.Fields[0]
m.focusField.Focus()
if index == len(paramFields)-1 {
// Last param field: cycle to main fields
model.inParamsFields = false
model.focusFieldByIndex(mainFields, 0)
} else {
// otherwise, go to the next field
m.focusField.Blur()
if m.inParamsFields {
m.focusField = selectedParamFields[index+1]
} else {
m.focusField = m.Fields[index+1]
}
m.focusField.Focus()
model.focusNextField(paramFields)
}
} else {
index := model.findCurrentFieldIndex(mainFields)
if index == -1 {
break
}
if index == len(mainFields)-1 && len(paramFields) > 0 {
// Last main field & params exist: go to params
model.inParamsFields = true
model.focusFieldByIndex(paramFields, 0)
} else {
model.focusNextField(mainFields)
}
}
case "ctrl+c", "esc":
return m, tea.Quit
return model, tea.Quit
case "enter":
return m, nil
return model, nil
default:
if m.focusField != nil {
md, cmd := m.focusField.(huh.Field).Update(msg)
if model.focusField != nil {
md, cmd := model.focusField.(huh.Field).Update(msg)
if md != nil {
m.focusField = md.(huh.Field)
model.focusField = md.(huh.Field)
}
return m, cmd
return model, cmd
}
}
}
return m, nil
return model, nil
}