dequoter/textfilters.go
Leon Mika c5f8d9e82e
Some checks failed
Build / build (push) Successful in 2m4s
Release Build / build (push) Failing after 45s
Added Lines:UCL for processing UCL commands
2026-03-15 10:17:23 +11:00

300 lines
8.7 KiB
Go

package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"text/template"
"gopkg.in/yaml.v3"
"ucl.lmika.dev/ucl"
)
type TextProcessor struct {
Label string
Description string
// Filters the supplied text. This is used for manipulating the text input.
Filter TextFilter
// Analyses the supplied text. This is used for extracting information from the text input. The result
// will be displayed in the status bar. Multiple selected regions will be returned as a single string separated by line numbers.
Analyze TextAnalyzer
// FilterWithArg requests some input from the user. If the user supplies it, it will return a text filter
// to process the input.
FilterWithArg *FilterWithArg
}
type TextFilter func(ctx context.Context, input string) (resp TextFilterResponse, err error)
type TextAnalyzer func(ctx context.Context, input string) (resp string, err error)
type FilterWithArg struct {
Label string
OnConfirm func(ctx context.Context, input string) (filter TextFilter, err error)
}
type TextFilterResponse struct {
Output string
Append bool
}
var TextFilters = map[string]TextProcessor{
"upper-case": {
Label: "String: To Upper Case",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{Output: strings.ToUpper(input)}, nil
},
},
"lower-case": {
Label: "String: To Lower Case",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{Output: strings.ToLower(input)}, nil
},
},
"unquote": {
Label: "String: Unquote",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
out, err := strconv.Unquote(input)
if err != nil {
return TextFilterResponse{}, err
}
return TextFilterResponse{Output: out}, nil
},
},
"join-lines-with-commas": {
Label: "Lines: Join with Commas",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{Output: strings.Replace(input, "\n", ",", -1)}, nil
},
},
"split-lines-on-commas": {
Label: "Lines: Split on Commas",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{Output: strings.Replace(input, ",", "\n", -1)}, nil
},
},
"join-lines-with-semicolons": {
Label: "Lines: Join with Semicolons",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{Output: strings.Replace(input, "\n", ";", -1)}, nil
},
},
"split-lines-on-semicolon": {
Label: "Lines: Split on Semicolons",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{Output: strings.Replace(input, ";", "\n", -1)}, nil
},
},
"trim-space": {
Label: "Lines: Trim Spaces",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
lines := strings.Split(input, "\n")
for i, line := range lines {
lines[i] = strings.TrimSpace(line)
}
result := strings.Join(lines, "\n")
return TextFilterResponse{Output: result}, nil
},
},
"format-json": {
Label: "JSON: Format",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
var dst bytes.Buffer
scnr := bufio.NewScanner(strings.NewReader(input))
for scnr.Scan() {
line := scnr.Text()
if err := json.Indent(&dst, []byte(line), "", " "); err == nil {
dst.WriteString("\n")
} else {
return TextFilterResponse{}, err
}
}
return TextFilterResponse{Output: dst.String()}, nil
},
},
"convert-json-to-yaml": {
Label: "Convert: JSON to YAML",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
var data interface{}
if err := json.Unmarshal([]byte(input), &data); err != nil {
return TextFilterResponse{}, err
}
var dst bytes.Buffer
if err := yaml.NewEncoder(&dst).Encode(data); err != nil {
return TextFilterResponse{}, err
}
return TextFilterResponse{Output: dst.String()}, nil
},
},
"convert-yaml-to-json": {
Label: "Convert: YAML to JSON",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
var data interface{}
if err := yaml.Unmarshal([]byte(input), &data); err != nil {
return TextFilterResponse{}, err
}
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return TextFilterResponse{}, err
}
return TextFilterResponse{Output: string(jsonBytes)}, nil
},
},
"lorem-ipsum": {
Label: "Generate: Lorem Ipsum",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
return TextFilterResponse{
Output: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " +
"incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " +
"exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " +
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " +
"mollit anim id est laborum.",
Append: true,
}, nil
},
},
"lines": {
Label: "Lines: Count",
Analyze: func(ctx context.Context, input string) (result string, err error) {
lineCount := len(strings.Split(input, "\n"))
return fmt.Sprintf("Lines = %v", lineCount), nil
},
},
"template-each-line": {
Label: "Lines: Go Template…",
Description: "Evaluates the input as a Go template and replaces each line with the result.",
FilterWithArg: &FilterWithArg{
Label: "Template",
OnConfirm: func(ctx context.Context, prompt string) (filter TextFilter, err error) {
tmpl, err := template.New("").Parse(prompt)
if err != nil {
return nil, err
}
return func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
var (
dst bytes.Buffer
dstLine bytes.Buffer
)
scnr := bufio.NewScanner(strings.NewReader(input))
isFirst := true
for scnr.Scan() {
if isFirst {
isFirst = false
} else {
dst.WriteString("\n")
}
dstLine.Reset()
line := scnr.Text()
if err := tmpl.Execute(&dstLine, line); err != nil {
return TextFilterResponse{}, err
}
dst.WriteString(dstLine.String())
}
return TextFilterResponse{Output: dst.String()}, nil
}, nil
},
},
},
"ucl-each-line": {
Label: "Lines: UCL…",
Description: "Transform each line as the result of a UCL expression.",
FilterWithArg: &FilterWithArg{
Label: "UCL Expression",
OnConfirm: func(ctx context.Context, prompt string) (filter TextFilter, err error) {
spv := strPseudoVar{}
uclCtx := uclInstFromContext(ctx)
uclCtx.SetPseudoVar(".", &spv)
return func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
var (
dst bytes.Buffer
)
scnr := bufio.NewScanner(strings.NewReader(input))
isFirst := true
for scnr.Scan() {
if isFirst {
isFirst = false
} else {
dst.WriteString("\n")
}
line := scnr.Text()
spv.str = line
if res, err := uclCtx.EvalString(ctx, prompt); err == nil {
dst.WriteString(fmt.Sprint(res))
} else if errors.Is(err, ucl.ErrNotConvertable) {
dst.WriteString(line)
} else {
return TextFilterResponse{}, err
}
}
return TextFilterResponse{Output: dst.String()}, nil
}, nil
},
},
},
"ucl-evaluate": {
Label: "UCL: Evaluate",
Description: "Evaluates the input as a UCL expression and displays the result in the status bar.",
Analyze: func(ctx context.Context, input string) (result string, err error) {
res, err := uclInstFromContext(ctx).EvalString(ctx, input)
if err != nil {
if errors.Is(err, ucl.ErrNotConvertable) {
return "Evaluated successfully. No result.", err
}
return "", err
}
return fmt.Sprintf("Result: %v", res), nil
},
},
"ucl-replace": {
Label: "UCL: Replace",
Description: "Evaluates the input as a UCL expression and replaces it with the result.",
Filter: func(ctx context.Context, input string) (resp TextFilterResponse, err error) {
res, err := uclInstFromContext(ctx).EvalString(ctx, input)
if err != nil {
if errors.Is(err, ucl.ErrNotConvertable) {
return TextFilterResponse{}, err
}
return TextFilterResponse{}, err
}
return TextFilterResponse{Output: fmt.Sprint(res)}, nil
},
},
}
type strPseudoVar struct {
str string
}
func (s strPseudoVar) Get(ctx context.Context) (any, error) {
return s.str, nil
}