Compare commits
2 commits
main
...
feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f221939e3 | |||
| 7c9023ae20 |
8 changed files with 69 additions and 232 deletions
|
|
@ -21,14 +21,12 @@ jobs:
|
|||
- name: Running Wails doctor
|
||||
run: |
|
||||
wails doctor
|
||||
- name: Installing Dependencies
|
||||
run: |
|
||||
npm install
|
||||
- name: Build
|
||||
run: |
|
||||
npm install
|
||||
wails build -clean -platform darwin/arm64 -ldflags "-X main.VersionNumber=`git describe --tags --abbrev=0`"
|
||||
- name: Release
|
||||
uses: https://lmika.dev/actions/wails-release@v1.0.4
|
||||
uses: https://lmika.dev/actions/wails-release@v1.0.3
|
||||
with:
|
||||
developer-id-cert-base64: ${{ secrets.MACOS_SIGN_P12 }}
|
||||
developer-id-cert-password: ${{ secrets.MACOS_SIGN_PASSWORD }}
|
||||
|
|
@ -37,7 +35,7 @@ jobs:
|
|||
notarization-api-issuer-id: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||
extra-build-flags: -ldflags "-X main.VersionNumber=${{ github.ref_name }}"
|
||||
s3-bucket: lmika-public-files
|
||||
s3-key: 'Apps/Dequoter/{version}/{filename},Apps/Dequoter/latest/{filename}'
|
||||
s3-key: Apps/Dequoter/{version}/{filename}
|
||||
s3-region: ap-southeast-2
|
||||
s3-acl: public-read
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
</dialog>
|
||||
<dialog id="command-dialog" data-controller="commands"
|
||||
data-action="dq-showcommands@window->commands#showCommands dq-rerunlastcommand@window->commands#rerunLastCommand">
|
||||
data-action="dq-showcommands@window->commands#showCommands dq-rerunlastcommand@window->commands#rerunLastCommand dq-rerunlastcommand-line@window->commands#rerunLastCommandOnLine">
|
||||
<div class="dialog-body">
|
||||
<div class="command-input">
|
||||
<input data-commands-target="commandInput" type="text" placeholder="Enter command"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,14 @@ export const commandPalette = keymap.of([{
|
|||
return true;
|
||||
}
|
||||
}, {
|
||||
key: "Cmd-l",
|
||||
run: () => {
|
||||
let event = new CustomEvent('dq-rerunlastcommand-line');
|
||||
window.dispatchEvent(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
}, {
|
||||
key: "Cmd-k",
|
||||
run: (view) => {
|
||||
const {state} = view;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ export class CommandsController extends Controller {
|
|||
"commandSelect",
|
||||
];
|
||||
|
||||
initialize() {
|
||||
this._lineModeOnce = false;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
this._lastCommand = null;
|
||||
|
||||
|
|
@ -47,6 +51,7 @@ export class CommandsController extends Controller {
|
|||
runCommand(ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
this._promptController()?.clearUseLastValue();
|
||||
textProcessor.runTextCommand(this.commandSelectTarget.value);
|
||||
this._lastCommand = this.commandSelectTarget.value;
|
||||
|
||||
|
|
@ -60,7 +65,21 @@ export class CommandsController extends Controller {
|
|||
return;
|
||||
}
|
||||
|
||||
textProcessor.runTextCommand(this._lastCommand);
|
||||
this._promptController()?.useLastValueForNextPrompt();
|
||||
const lineMode = this._lineModeOnce;
|
||||
this._lineModeOnce = false;
|
||||
textProcessor.runTextCommand(this._lastCommand, { lineMode });
|
||||
}
|
||||
|
||||
rerunLastCommandOnLine(ev) {
|
||||
this._lineModeOnce = true;
|
||||
this.rerunLastCommand(ev);
|
||||
}
|
||||
|
||||
_promptController() {
|
||||
const el = document.getElementById("prompt-dialog");
|
||||
if (!el) return null;
|
||||
return this.application.getControllerForElementAndIdentifier(el, "prompt");
|
||||
}
|
||||
|
||||
_filterOptions(filterText) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,16 @@ export class PromptController extends Controller {
|
|||
|
||||
connect() {
|
||||
this._callback = null;
|
||||
this._lastValue = null;
|
||||
this._useLastNext = false;
|
||||
|
||||
window.runtime.EventsOn("prompt-request", (data) => {
|
||||
if (this._useLastNext && this._lastValue !== null) {
|
||||
this._useLastNext = false;
|
||||
window.runtime.EventsEmit("prompt-response", this._lastValue);
|
||||
return;
|
||||
}
|
||||
this._useLastNext = false;
|
||||
this.prompt(data.label, (res) => {
|
||||
window.runtime.EventsEmit("prompt-response", res);
|
||||
});
|
||||
|
|
@ -19,14 +27,16 @@ export class PromptController extends Controller {
|
|||
prompt(label, callback) {
|
||||
this._callback = callback;
|
||||
this.labelTarget.textContent = label;
|
||||
this.inputTarget.value = "";
|
||||
this.inputTarget.value = this._lastValue || "";
|
||||
this.element.showModal();
|
||||
this.inputTarget.select();
|
||||
this.inputTarget.focus();
|
||||
}
|
||||
|
||||
submit(ev) {
|
||||
ev.preventDefault();
|
||||
let value = this.inputTarget.value;
|
||||
this._lastValue = value;
|
||||
this.element.close();
|
||||
if (this._callback) {
|
||||
this._callback(value);
|
||||
|
|
@ -39,4 +49,12 @@ export class PromptController extends Controller {
|
|||
this._callback = null;
|
||||
this.element.close();
|
||||
}
|
||||
|
||||
useLastValueForNextPrompt() {
|
||||
this._useLastNext = true;
|
||||
}
|
||||
|
||||
clearUseLastValue() {
|
||||
this._useLastNext = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,16 +46,31 @@ class TextProcessor {
|
|||
});
|
||||
}
|
||||
|
||||
async runTextCommand(command) {
|
||||
async runTextCommand(command, opts) {
|
||||
if (this._editor === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineMode = opts && opts.lineMode;
|
||||
let ranges = this._editor.state.selection.ranges;
|
||||
let shouldBeAll = ranges.reduce((a, r) => a && r.from === r.to, true);
|
||||
let hasSelection = ranges.some(r => r.from !== r.to);
|
||||
|
||||
let inputs = [];
|
||||
if (shouldBeAll) {
|
||||
if (lineMode && !hasSelection) {
|
||||
this._appendInsertPos = undefined;
|
||||
const doc = this._editor.state.doc;
|
||||
const seen = new Set();
|
||||
for (let r of ranges) {
|
||||
const line = doc.lineAt(r.head);
|
||||
if (seen.has(line.number)) continue;
|
||||
seen.add(line.number);
|
||||
inputs.push({
|
||||
text: line.text,
|
||||
pos: line.from,
|
||||
len: line.to - line.from,
|
||||
});
|
||||
}
|
||||
} else if (!hasSelection) {
|
||||
this._appendInsertPos = this._editor.state.selection.main.head;
|
||||
inputs.push({
|
||||
text: this._editor.state.doc.toString(),
|
||||
|
|
|
|||
129
textfilters.go
129
textfilters.go
|
|
@ -4,8 +4,6 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -13,7 +11,6 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"text/template"
|
||||
|
||||
|
|
@ -126,32 +123,6 @@ var TextFilters = map[string]TextProcessor{
|
|||
return TextFilterResponse{Output: result}, nil
|
||||
},
|
||||
},
|
||||
"compact-json": {
|
||||
Label: "JSON: Compact",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
var (
|
||||
dst bytes.Buffer
|
||||
inBfr bytes.Buffer
|
||||
outBfr bytes.Buffer
|
||||
)
|
||||
|
||||
scnr := bufio.NewScanner(strings.NewReader(input))
|
||||
for scnr.Scan() {
|
||||
line := scnr.Text()
|
||||
inBfr.WriteString(line)
|
||||
inBfr.WriteString("\n")
|
||||
|
||||
if err := json.Compact(&outBfr, inBfr.Bytes()); err == nil {
|
||||
dst.WriteString(outBfr.String())
|
||||
dst.WriteString("\n")
|
||||
inBfr.Reset()
|
||||
outBfr.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
return TextFilterResponse{Output: dst.String() + inBfr.String()}, nil
|
||||
},
|
||||
},
|
||||
"format-json": {
|
||||
Label: "JSON: Format",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
|
|
@ -186,64 +157,6 @@ var TextFilters = map[string]TextProcessor{
|
|||
return TextFilterResponse{Output: dst.String()}, nil
|
||||
},
|
||||
},
|
||||
"csv-to-ascii-table": {
|
||||
Label: "Convert: CSV to ASCII Table",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
endNL := ""
|
||||
if strings.HasSuffix(input, "\n") {
|
||||
endNL = "\n"
|
||||
}
|
||||
|
||||
reader := csv.NewReader(strings.NewReader(input))
|
||||
reader.FieldsPerRecord = -1
|
||||
records, err := reader.ReadAll()
|
||||
if err != nil {
|
||||
return TextFilterResponse{}, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return TextFilterResponse{Output: input}, nil
|
||||
}
|
||||
|
||||
var colWidths []int
|
||||
for _, record := range records {
|
||||
for i, cell := range record {
|
||||
if i >= len(colWidths) {
|
||||
colWidths = append(colWidths, 0)
|
||||
}
|
||||
colWidths[i] = max(colWidths[i], utf8.RuneCountInString(cell))
|
||||
}
|
||||
}
|
||||
|
||||
writeRow := func(dst *strings.Builder, cells []string) {
|
||||
line := make([]string, len(colWidths))
|
||||
for i, width := range colWidths {
|
||||
cell := ""
|
||||
if i < len(cells) {
|
||||
cell = cells[i]
|
||||
}
|
||||
line[i] = cell + strings.Repeat(" ", width-utf8.RuneCountInString(cell))
|
||||
}
|
||||
dst.WriteString(strings.TrimRight(strings.Join(line, " "), " "))
|
||||
dst.WriteString("\n")
|
||||
}
|
||||
|
||||
dashes := make([]string, len(colWidths))
|
||||
for i, width := range colWidths {
|
||||
dashes[i] = strings.Repeat("-", width)
|
||||
}
|
||||
dashLine := strings.Join(dashes, " ") + "\n"
|
||||
|
||||
var dst strings.Builder
|
||||
writeRow(&dst, records[0])
|
||||
dst.WriteString(dashLine)
|
||||
for _, record := range records[1:] {
|
||||
writeRow(&dst, record)
|
||||
}
|
||||
dst.WriteString(dashLine)
|
||||
|
||||
return TextFilterResponse{Output: strings.TrimSuffix(dst.String(), "\n") + endNL}, nil
|
||||
},
|
||||
},
|
||||
"convert-yaml-to-json": {
|
||||
Label: "Convert: YAML to JSON",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
|
|
@ -260,48 +173,6 @@ var TextFilters = map[string]TextProcessor{
|
|||
return TextFilterResponse{Output: string(jsonBytes)}, nil
|
||||
},
|
||||
},
|
||||
"jwt-decode": {
|
||||
Label: "JWT: Decode",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
tokens := strings.Split(input, ".")
|
||||
if len(tokens) != 3 {
|
||||
return TextFilterResponse{}, errors.New("invalid JWT format")
|
||||
}
|
||||
|
||||
header, err := base64.RawURLEncoding.DecodeString(tokens[0])
|
||||
if err != nil {
|
||||
return TextFilterResponse{}, err
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(tokens[1])
|
||||
if err != nil {
|
||||
return TextFilterResponse{}, err
|
||||
}
|
||||
|
||||
var headerJSON, payloadJSON bytes.Buffer
|
||||
json.Indent(&headerJSON, header, "", " ")
|
||||
json.Indent(&payloadJSON, payload, "", " ")
|
||||
|
||||
return TextFilterResponse{Output: headerJSON.String() + "\n" + payloadJSON.String()}, nil
|
||||
},
|
||||
},
|
||||
"base64-decode": {
|
||||
Label: "Base64: Decode",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
dst, err := base64.StdEncoding.DecodeString(input)
|
||||
if err != nil {
|
||||
return TextFilterResponse{}, err
|
||||
}
|
||||
|
||||
return TextFilterResponse{Output: string(dst)}, nil
|
||||
},
|
||||
},
|
||||
"base64-encode": {
|
||||
Label: "Base64: Encode",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
dst := base64.StdEncoding.EncodeToString([]byte(input))
|
||||
return TextFilterResponse{Output: dst}, nil
|
||||
},
|
||||
},
|
||||
"lorem-ipsum": {
|
||||
Label: "Generate: Lorem Ipsum",
|
||||
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCSVToASCIITable(t *testing.T) {
|
||||
filter := TextFilters["csv-to-ascii-table"].Filter
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic table with quoted value",
|
||||
input: "alpha,bravo\n" +
|
||||
"1,b\n" +
|
||||
"\"long value\",something\n",
|
||||
want: "alpha bravo\n" +
|
||||
"---------- ---------\n" +
|
||||
"1 b\n" +
|
||||
"long value something\n" +
|
||||
"---------- ---------\n",
|
||||
},
|
||||
{
|
||||
name: "no trailing newline",
|
||||
input: "a,b\n1,2",
|
||||
want: "a b\n" +
|
||||
"- -\n" +
|
||||
"1 2\n" +
|
||||
"- -",
|
||||
},
|
||||
{
|
||||
name: "quoted field containing comma",
|
||||
input: "name,desc\n\"x, y\",z\n",
|
||||
want: "name desc\n" +
|
||||
"---- ----\n" +
|
||||
"x, y z\n" +
|
||||
"---- ----\n",
|
||||
},
|
||||
{
|
||||
name: "ragged rows padded with empty cells",
|
||||
input: "a,b,c\n1\n2,3\n",
|
||||
want: "a b c\n" +
|
||||
"- - -\n" +
|
||||
"1\n" +
|
||||
"2 3\n" +
|
||||
"- - -\n",
|
||||
},
|
||||
{
|
||||
name: "header only",
|
||||
input: "one,two\n",
|
||||
want: "one two\n" +
|
||||
"--- ---\n" +
|
||||
"--- ---\n",
|
||||
},
|
||||
{
|
||||
name: "empty input unchanged",
|
||||
input: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "invalid CSV returns error",
|
||||
input: "a,\"b\nc",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resp, err := filter(context.Background(), tt.input)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got output %q", resp.Output)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Output != tt.want {
|
||||
t.Errorf("output mismatch\ngot:\n%s\nwant:\n%s", resp.Output, tt.want)
|
||||
}
|
||||
if resp.Append {
|
||||
t.Errorf("Append should be false")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue