Skip to content

Commit

Permalink
initscript -- support for local files, and overrides in connections.j…
Browse files Browse the repository at this point in the history
…son (#1818)
  • Loading branch information
sawka authored Jan 23, 2025
1 parent 8bf90c0 commit 1913cc5
Show file tree
Hide file tree
Showing 8 changed files with 423 additions and 48 deletions.
170 changes: 170 additions & 0 deletions cmd/wsh/cmd/setmeta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"reflect"
"testing"
)

func TestParseMetaSets(t *testing.T) {
tests := []struct {
name string
input []string
want map[string]any
wantErr bool
}{
{
name: "basic types",
input: []string{"str=hello", "num=42", "float=3.14", "bool=true", "null=null"},
want: map[string]any{
"str": "hello",
"num": int64(42),
"float": float64(3.14),
"bool": true,
"null": nil,
},
},
{
name: "json values",
input: []string{
`arr=[1,2,3]`,
`obj={"foo":"bar"}`,
`str="quoted"`,
},
want: map[string]any{
"arr": []any{float64(1), float64(2), float64(3)},
"obj": map[string]any{"foo": "bar"},
"str": "quoted",
},
},
{
name: "nested paths",
input: []string{
"a/b=55",
"a/c=2",
},
want: map[string]any{
"a": map[string]any{
"b": int64(55),
"c": int64(2),
},
},
},
{
name: "deep nesting",
input: []string{
"a/b/c/d=hello",
},
want: map[string]any{
"a": map[string]any{
"b": map[string]any{
"c": map[string]any{
"d": "hello",
},
},
},
},
},
{
name: "override nested value",
input: []string{
"a/b/c=1",
"a/b=2",
},
want: map[string]any{
"a": map[string]any{
"b": int64(2),
},
},
},
{
name: "override with null",
input: []string{
"a/b=1",
"a/c=2",
"a=null",
},
want: map[string]any{
"a": nil,
},
},
{
name: "mixed types in path",
input: []string{
"a/b=1",
"a/c=[1,2,3]",
"a/d/e=true",
},
want: map[string]any{
"a": map[string]any{
"b": int64(1),
"c": []any{float64(1), float64(2), float64(3)},
"d": map[string]any{
"e": true,
},
},
},
},
{
name: "invalid format",
input: []string{"invalid"},
wantErr: true,
},
{
name: "invalid json",
input: []string{`a={"invalid`},
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseMetaSets(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("parseMetaSets() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseMetaSets() = %v, want %v", got, tt.want)
}
})
}
}

func TestParseMetaValue(t *testing.T) {
tests := []struct {
name string
input string
want any
wantErr bool
}{
{"empty string", "", nil, false},
{"null", "null", nil, false},
{"true", "true", true, false},
{"false", "false", false, false},
{"integer", "42", int64(42), false},
{"negative integer", "-42", int64(-42), false},
{"hex integer", "0xff", int64(255), false},
{"float", "3.14", float64(3.14), false},
{"string", "hello", "hello", false},
{"json array", "[1,2,3]", []any{float64(1), float64(2), float64(3)}, false},
{"json object", `{"foo":"bar"}`, map[string]any{"foo": "bar"}, false},
{"quoted string", `"quoted"`, "quoted", false},
{"invalid json", `{"invalid`, nil, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseMetaValue(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("parseMetaValue() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseMetaValue() = %v, want %v", got, tt.want)
}
})
}
}
104 changes: 76 additions & 28 deletions cmd/wsh/cmd/wshcmd-setmeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,40 +58,88 @@ func loadJSONFile(filepath string) (map[string]interface{}, error) {
return result, nil
}

func parseMetaSets(metaSets []string) (map[string]interface{}, error) {
meta := make(map[string]interface{})
func parseMetaValue(setVal string) (any, error) {
if setVal == "" || setVal == "null" {
return nil, nil
}
if setVal == "true" {
return true, nil
}
if setVal == "false" {
return false, nil
}
if setVal[0] == '[' || setVal[0] == '{' || setVal[0] == '"' {
var val any
err := json.Unmarshal([]byte(setVal), &val)
if err != nil {
return nil, fmt.Errorf("invalid json value: %v", err)
}
return val, nil
}

// Try parsing as integer
ival, err := strconv.ParseInt(setVal, 0, 64)
if err == nil {
return ival, nil
}

// Try parsing as float
fval, err := strconv.ParseFloat(setVal, 64)
if err == nil {
return fval, nil
}

// Fallback to string
return setVal, nil
}

func setNestedValue(meta map[string]any, path []string, value any) {
// For single key, just set directly
if len(path) == 1 {
meta[path[0]] = value
return
}

// For nested path, traverse or create maps as needed
current := meta
for i := 0; i < len(path)-1; i++ {
key := path[i]
// If next level doesn't exist or isn't a map, create new map
next, exists := current[key]
if !exists {
nextMap := make(map[string]any)
current[key] = nextMap
current = nextMap
} else if nextMap, ok := next.(map[string]any); ok {
current = nextMap
} else {
// If existing value isn't a map, replace with new map
nextMap = make(map[string]any)
current[key] = nextMap
current = nextMap
}
}

// Set the final value
current[path[len(path)-1]] = value
}

func parseMetaSets(metaSets []string) (map[string]any, error) {
meta := make(map[string]any)
for _, metaSet := range metaSets {
fields := strings.SplitN(metaSet, "=", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("invalid meta set: %q", metaSet)
}
setVal := fields[1]
if setVal == "" || setVal == "null" {
meta[fields[0]] = nil
} else if setVal == "true" {
meta[fields[0]] = true
} else if setVal == "false" {
meta[fields[0]] = false
} else if setVal[0] == '[' || setVal[0] == '{' || setVal[0] == '"' {
var val interface{}
err := json.Unmarshal([]byte(setVal), &val)
if err != nil {
return nil, fmt.Errorf("invalid json value: %v", err)
}
meta[fields[0]] = val
} else {
ival, err := strconv.ParseInt(setVal, 0, 64)
if err == nil {
meta[fields[0]] = ival
} else {
fval, err := strconv.ParseFloat(setVal, 64)
if err == nil {
meta[fields[0]] = fval
} else {
meta[fields[0]] = setVal
}
}

val, err := parseMetaValue(fields[1])
if err != nil {
return nil, err
}

// Split the key path and set nested value
path := strings.Split(fields[0], "/")
setNestedValue(meta, path, val)
}
return meta, nil
}
Expand Down
7 changes: 7 additions & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ declare global {
"term:fontsize"?: number;
"term:fontfamily"?: string;
"term:theme"?: string;
"cmd:env"?: {[key: string]: string};
"cmd:initscript"?: string;
"cmd:initscript.sh"?: string;
"cmd:initscript.bash"?: string;
"cmd:initscript.zsh"?: string;
"cmd:initscript.pwsh"?: string;
"cmd:initscript.fish"?: string;
"ssh:user"?: string;
"ssh:hostname"?: string;
"ssh:port"?: string;
Expand Down
Loading

0 comments on commit 1913cc5

Please sign in to comment.