15 Commits

10 changed files with 212 additions and 1081 deletions

9
.vscode/launch.json vendored
View File

@@ -9,13 +9,8 @@
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "${workspaceFolder}", "program": "${fileDirname}",
"args": [ "args": []
"-mode=json",
"$..name",
"v='pero'",
"test.json"
]
} }
] ]
} }

22
main.go
View File

@@ -32,6 +32,7 @@ var logger *log.Logger
var ( var (
fileModeFlag = flag.String("mode", "regex", "Processing mode: regex, xml, json") fileModeFlag = flag.String("mode", "regex", "Processing mode: regex, xml, json")
verboseFlag = flag.Bool("verbose", false, "Enable verbose output")
) )
func init() { func init() {
@@ -47,6 +48,12 @@ func main() {
fmt.Fprintf(os.Stderr, "\nOptions:\n") fmt.Fprintf(os.Stderr, "\nOptions:\n")
fmt.Fprintf(os.Stderr, " -mode string\n") fmt.Fprintf(os.Stderr, " -mode string\n")
fmt.Fprintf(os.Stderr, " Processing mode: regex, xml, json (default \"regex\")\n") fmt.Fprintf(os.Stderr, " Processing mode: regex, xml, json (default \"regex\")\n")
fmt.Fprintf(os.Stderr, " -xpath string\n")
fmt.Fprintf(os.Stderr, " XPath expression (for XML mode)\n")
fmt.Fprintf(os.Stderr, " -jsonpath string\n")
fmt.Fprintf(os.Stderr, " JSONPath expression (for JSON mode)\n")
fmt.Fprintf(os.Stderr, " -verbose\n")
fmt.Fprintf(os.Stderr, " Enable verbose output\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n") fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " Regex mode (default):\n") fmt.Fprintf(os.Stderr, " Regex mode (default):\n")
fmt.Fprintf(os.Stderr, " %s \"<value>(\\d+)</value>\" \"*1.5\" data.xml\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s \"<value>(\\d+)</value>\" \"*1.5\" data.xml\n", os.Args[0])
@@ -76,9 +83,15 @@ func main() {
var pattern, luaExpr string var pattern, luaExpr string
var filePatterns []string var filePatterns []string
if *fileModeFlag == "regex" {
pattern = args[0] pattern = args[0]
luaExpr = args[1] luaExpr = args[1]
filePatterns = args[2:] filePatterns = args[2:]
} else {
// For XML/JSON modes, pattern comes from flags
luaExpr = args[0]
filePatterns = args[1:]
}
// Prepare the Lua expression // Prepare the Lua expression
originalLuaExpr := luaExpr originalLuaExpr := luaExpr
@@ -111,10 +124,11 @@ func main() {
// pattern = *xpathFlag // pattern = *xpathFlag
// logger.Printf("Starting XML modifier with XPath %q, expression %q on %d files", // logger.Printf("Starting XML modifier with XPath %q, expression %q on %d files",
// pattern, luaExpr, len(files)) // pattern, luaExpr, len(files))
case "json": // case "json":
proc = &processor.JSONProcessor{} // proc = &processor.JSONProcessor{}
logger.Printf("Starting JSON modifier with JSONPath %q, expression %q on %d files", // pattern = *jsonpathFlag
pattern, luaExpr, len(files)) // logger.Printf("Starting JSON modifier with JSONPath %q, expression %q on %d files",
// pattern, luaExpr, len(files))
} }
var wg sync.WaitGroup var wg sync.WaitGroup

View File

@@ -3,10 +3,10 @@ package processor
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"modify/processor/jsonpath" "modify/processor/jsonpath"
"os" "os"
"path/filepath" "path/filepath"
"strings"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
) )
@@ -67,65 +67,72 @@ func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr s
return content, 0, 0, nil return content, 0, 0, nil
} }
modCount := 0
for _, node := range nodes {
log.Printf("Processing node at path: %s with value: %v", node.Path, node.Value)
// Initialize Lua // Initialize Lua
L, err := NewLuaState() L, err := NewLuaState()
if err != nil { if err != nil {
return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err) return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err)
} }
defer L.Close() defer L.Close()
log.Println("Lua state initialized successfully.")
err = p.ToLua(L, node.Value) err = p.ToLua(L, nodes)
if err != nil { if err != nil {
return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err) return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err)
} }
log.Printf("Converted node value to Lua: %v", node.Value)
originalScript := luaExpr
fullScript := BuildLuaScript(luaExpr)
log.Printf("Original script: %q, Full script: %q", originalScript, fullScript)
// Execute Lua script // Execute Lua script
log.Printf("Executing Lua script: %q", fullScript) if err := L.DoString(luaExpr); err != nil {
if err := L.DoString(fullScript); err != nil { return content, len(nodes), 0, fmt.Errorf("error executing Lua %s: %v", luaExpr, err)
return content, len(nodes), 0, fmt.Errorf("error executing Lua %q: %v", fullScript, err)
} }
log.Println("Lua script executed successfully.")
// Get modified value // Get modified value
result, err := p.FromLua(L) result, err := p.FromLua(L)
if err != nil { if err != nil {
return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err) return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err)
} }
log.Printf("Retrieved modified value from Lua: %v", result)
modified := false
modified = L.GetGlobal("modified").String() == "true"
if !modified {
log.Printf("No changes made to node at path: %s", node.Path)
continue
}
// Apply the modification to the JSON data // Apply the modification to the JSON data
err = p.updateJSONValue(jsonData, node.Path, result) err = p.updateJSONValue(jsonData, pattern, result)
if err != nil { if err != nil {
return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err) return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err)
} }
log.Printf("Updated JSON at path: %s with new value: %v", node.Path, result)
modCount++
}
// Convert the modified JSON back to a string with same formatting // Convert the modified JSON back to a string with same formatting
var jsonBytes []byte var jsonBytes []byte
if indent, err := detectJsonIndentation(content); err == nil && indent != "" {
// Use detected indentation for output formatting
jsonBytes, err = json.MarshalIndent(jsonData, "", indent)
} else {
// Fall back to standard 2-space indent
jsonBytes, err = json.MarshalIndent(jsonData, "", " ") jsonBytes, err = json.MarshalIndent(jsonData, "", " ")
if err != nil {
return content, modCount, matchCount, fmt.Errorf("error marshalling JSON: %v", err)
} }
return string(jsonBytes), modCount, matchCount, nil
// We changed all the nodes trust me bro
return string(jsonBytes), len(nodes), len(nodes), nil
}
// detectJsonIndentation tries to determine the indentation used in the original JSON
func detectJsonIndentation(content string) (string, error) {
lines := strings.Split(content, "\n")
if len(lines) < 2 {
return "", fmt.Errorf("not enough lines to detect indentation")
}
// Look for the first indented line
for i := 1; i < len(lines); i++ {
line := lines[i]
trimmed := strings.TrimSpace(line)
if trimmed == "" {
continue
}
// Calculate leading whitespace
indent := line[:len(line)-len(trimmed)]
if len(indent) > 0 {
return indent, nil
}
}
return "", fmt.Errorf("no indentation detected")
} }
// / Selects from the root node // / Selects from the root node
@@ -147,62 +154,12 @@ func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr s
// updateJSONValue updates a value in the JSON structure based on its JSONPath // updateJSONValue updates a value in the JSON structure based on its JSONPath
func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error { func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error {
// Special handling for root node
if path == "$" {
// For the root node, we'll copy the value to the jsonData reference
// This is a special case since we can't directly replace the interface{} variable
// We need to handle different types of root elements
switch rootValue := newValue.(type) {
case map[string]interface{}:
// For objects, we need to copy over all keys
rootMap, ok := jsonData.(map[string]interface{})
if !ok {
// If the original wasn't a map, completely replace it with the new map
// This is handled by the jsonpath.Set function
return jsonpath.Set(jsonData, path, newValue)
}
// Clear the original map
for k := range rootMap {
delete(rootMap, k)
}
// Copy all keys from the new map
for k, v := range rootValue {
rootMap[k] = v
}
return nil
case []interface{}:
// For arrays, we need to handle similarly
rootArray, ok := jsonData.([]interface{})
if !ok {
// If the original wasn't an array, use jsonpath.Set
return jsonpath.Set(jsonData, path, newValue)
}
// Clear and recreate the array
*&rootArray = rootValue
return nil
default:
// For other types, use jsonpath.Set
return jsonpath.Set(jsonData, path, newValue)
}
}
// For non-root paths, use the regular Set method
err := jsonpath.Set(jsonData, path, newValue)
if err != nil {
return fmt.Errorf("failed to update JSON value at path '%s': %w", path, err)
}
return nil return nil
} }
// ToLua converts JSON values to Lua variables // ToLua converts JSON values to Lua variables
func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error { func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error {
table, err := ToLua(L, data) table, err := ToLuaTable(L, data)
if err != nil { if err != nil {
return err return err
} }
@@ -213,5 +170,5 @@ func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error {
// FromLua retrieves values from Lua // FromLua retrieves values from Lua
func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) { func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) {
luaValue := L.GetGlobal("v") luaValue := L.GetGlobal("v")
return FromLua(L, luaValue) return FromLuaTable(L, luaValue.(*lua.LTable))
} }

View File

@@ -133,24 +133,24 @@ func TestJSONProcessor_Process_StringValues(t *testing.T) {
t.Logf("Result: %s", result) t.Logf("Result: %s", result)
t.Logf("Match count: %d, Mod count: %d", matchCount, modCount) t.Logf("Match count: %d, Mod count: %d", matchCount, modCount)
if matchCount != 1 { if matchCount != 3 {
t.Errorf("Expected 1 matches, got %d", matchCount) t.Errorf("Expected 3 matches, got %d", matchCount)
} }
if modCount != 1 { if modCount != 3 {
t.Errorf("Expected 1 modifications, got %d", modCount) t.Errorf("Expected 3 modifications, got %d", modCount)
} }
// Check that all expected values are in the result // Check that all expected values are in the result
if !strings.Contains(result, `"maxItems": 200`) { if !strings.Contains(result, `"maxItems": "200"`) {
t.Errorf("Result missing expected value: maxItems=200") t.Errorf("Result missing expected value: maxItems=200")
} }
if !strings.Contains(result, `"itemTimeoutSecs": 60`) { if !strings.Contains(result, `"itemTimeoutSecs": "60"`) {
t.Errorf("Result missing expected value: itemTimeoutSecs=60") t.Errorf("Result missing expected value: itemTimeoutSecs=60")
} }
if !strings.Contains(result, `"retryCount": 10`) { if !strings.Contains(result, `"retryCount": "10"`) {
t.Errorf("Result missing expected value: retryCount=10") t.Errorf("Result missing expected value: retryCount=10")
} }
} }
@@ -265,13 +265,13 @@ func TestJSONProcessor_NestedModifications(t *testing.T) {
"book": [ "book": [
{ {
"category": "reference", "category": "reference",
"price": 13.188, "title": "Learn Go in 24 Hours",
"title": "Learn Go in 24 Hours" "price": 13.188
}, },
{ {
"category": "fiction", "category": "fiction",
"price": 10.788, "title": "The Go Developer",
"title": "The Go Developer" "price": 10.788
} }
] ]
} }
@@ -373,20 +373,20 @@ func TestJSONProcessor_ComplexScript(t *testing.T) {
expected := `{ expected := `{
"products": [ "products": [
{ {
"discount": 0.1,
"name": "Basic Widget", "name": "Basic Widget",
"price": 8.991 "price": 8.991,
"discount": 0.1
}, },
{ {
"discount": 0.05,
"name": "Premium Widget", "name": "Premium Widget",
"price": 18.9905 "price": 18.9905,
"discount": 0.05
} }
] ]
}` }`
p := &JSONProcessor{} p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", "v.price = round(v.price * (1 - v.discount), 4)") result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", "v.price = v.price * (1 - v.discount)")
if err != nil { if err != nil {
t.Fatalf("Error processing content: %v", err) t.Fatalf("Error processing content: %v", err)
@@ -419,26 +419,13 @@ func TestJSONProcessor_SpecificItemUpdate(t *testing.T) {
] ]
}` }`
expected := ` expected := `{
{
"items": [ "items": [
{ {"id": 1, "name": "Item 1", "stock": 10},
"id": 1, {"id": 2, "name": "Item 2", "stock": 15},
"name": "Item 1", {"id": 3, "name": "Item 3", "stock": 0}
"stock": 10
},
{
"id": 2,
"name": "Item 2",
"stock": 15
},
{
"id": 3,
"name": "Item 3",
"stock": 0
}
] ]
} ` }`
p := &JSONProcessor{} p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$.items[1].stock", "v=v+10") result, modCount, matchCount, err := p.ProcessContent(content, "$.items[1].stock", "v=v+10")
@@ -467,9 +454,7 @@ func TestJSONProcessor_SpecificItemUpdate(t *testing.T) {
// TestJSONProcessor_RootElementUpdate tests updating the root element // TestJSONProcessor_RootElementUpdate tests updating the root element
func TestJSONProcessor_RootElementUpdate(t *testing.T) { func TestJSONProcessor_RootElementUpdate(t *testing.T) {
content := `{"value": 100}` content := `{"value": 100}`
expected := `{ expected := `{"value": 200}`
"value": 200
}`
p := &JSONProcessor{} p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$.value", "v=v*2") result, modCount, matchCount, err := p.ProcessContent(content, "$.value", "v=v*2")
@@ -486,11 +471,7 @@ func TestJSONProcessor_RootElementUpdate(t *testing.T) {
t.Errorf("Expected 1 modification, got %d", modCount) t.Errorf("Expected 1 modification, got %d", modCount)
} }
// Normalize whitespace for comparison if result != expected {
normalizedResult := normalizeWhitespace(result)
normalizedExpected := normalizeWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
} }
} }
@@ -506,9 +487,9 @@ func TestJSONProcessor_AddNewField(t *testing.T) {
expected := `{ expected := `{
"user": { "user": {
"name": "John",
"age": 30, "age": 30,
"email": "john@example.com", "email": "john@example.com"
"name": "John"
} }
}` }`
@@ -548,8 +529,8 @@ func TestJSONProcessor_RemoveField(t *testing.T) {
expected := `{ expected := `{
"user": { "user": {
"email": "john@example.com", "name": "John",
"name": "John" "email": "john@example.com"
} }
}` }`
@@ -583,13 +564,8 @@ func TestJSONProcessor_ArrayManipulation(t *testing.T) {
"tags": ["go", "json", "lua"] "tags": ["go", "json", "lua"]
}` }`
expected := ` { expected := `{
"tags": [ "tags": ["GO", "JSON", "LUA", "testing"]
"GO",
"JSON",
"LUA",
"testing"
]
}` }`
p := &JSONProcessor{} p := &JSONProcessor{}
@@ -648,29 +624,28 @@ func TestJSONProcessor_ConditionalModification(t *testing.T) {
expected := `{ expected := `{
"products": [ "products": [
{ {
"inStock": true,
"name": "Product A", "name": "Product A",
"price": 9.891 "price": 9.891,
"inStock": true
}, },
{ {
"inStock": false,
"name": "Product B", "name": "Product B",
"price": 5.99 "price": 5.99,
"inStock": false
}, },
{ {
"inStock": true,
"name": "Product C", "name": "Product C",
"price": 14.391 "price": 14.391,
"inStock": true
} }
] ]
}` }`
p := &JSONProcessor{} p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", ` result, modCount, matchCount, err := p.ProcessContent(content, "$.products[*]", `
if not v.inStock then if v.inStock then
return false
end
v.price = v.price * 0.9 v.price = v.price * 0.9
end
`) `)
if err != nil { if err != nil {
@@ -720,15 +695,15 @@ func TestJSONProcessor_DeepNesting(t *testing.T) {
"departments": { "departments": {
"engineering": { "engineering": {
"teams": { "teams": {
"backend": {
"members": 8,
"projects": 3,
"status": "active"
},
"frontend": { "frontend": {
"members": 12, "members": 12,
"projects": 5, "projects": 5,
"status": "active" "status": "active"
},
"backend": {
"members": 8,
"projects": 3,
"status": "active"
} }
} }
} }
@@ -788,31 +763,31 @@ func TestJSONProcessor_ComplexTransformation(t *testing.T) {
expected := `{ expected := `{
"order": { "order": {
"items": [
{
"product": "Widget A",
"quantity": 5,
"price": 10.0,
"total": 50.0,
"discounted_total": 45.0
},
{
"product": "Widget B",
"quantity": 3,
"price": 15.0,
"total": 45.0,
"discounted_total": 40.5
}
],
"customer": { "customer": {
"name": "John Smith", "name": "John Smith",
"tier": "gold" "tier": "gold"
}, },
"items": [
{
"discounted_total": 45,
"price": 10,
"product": "Widget A",
"quantity": 5,
"total": 50
},
{
"discounted_total": 40.5,
"price": 15,
"product": "Widget B",
"quantity": 3,
"total": 45
}
],
"summary": { "summary": {
"total_items": 8,
"subtotal": 95.0,
"discount": 9.5, "discount": 9.5,
"subtotal": 95, "total": 85.5
"total": 85.5,
"total_items": 8
} }
} }
}` }`
@@ -937,16 +912,16 @@ func TestJSONProcessor_RestructuringData(t *testing.T) {
"people": { "people": {
"developers": [ "developers": [
{ {
"age": 25,
"id": 1, "id": 1,
"name": "Alice" "name": "Alice",
"age": 25
} }
], ],
"managers": [ "managers": [
{ {
"age": 30,
"id": 2, "id": 2,
"name": "Bob" "name": "Bob",
"age": 30
} }
] ]
} }
@@ -1007,13 +982,7 @@ func TestJSONProcessor_FilteringArrayElements(t *testing.T) {
}` }`
expected := `{ expected := `{
"numbers": [ "numbers": [2, 4, 6, 8, 10]
2,
4,
6,
8,
10
]
}` }`
p := &JSONProcessor{} p := &JSONProcessor{}
@@ -1048,51 +1017,3 @@ func TestJSONProcessor_FilteringArrayElements(t *testing.T) {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result) t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
} }
} }
// TestJSONProcessor_RootNodeModification tests modifying the root node directly
func TestJSONProcessor_RootNodeModification(t *testing.T) {
content := `{
"name": "original",
"value": 100
}`
expected := `{
"description": "This is a completely modified root",
"name": "modified",
"values": [
1,
2,
3
]
}`
p := &JSONProcessor{}
result, modCount, matchCount, err := p.ProcessContent(content, "$", `
-- Completely replace the root node
v = {
name = "modified",
description = "This is a completely modified root",
values = {1, 2, 3}
}
`)
if err != nil {
t.Fatalf("Error processing content: %v", err)
}
if matchCount != 1 {
t.Errorf("Expected 1 match, got %d", matchCount)
}
if modCount != 1 {
t.Errorf("Expected 1 modification, got %d", modCount)
}
// Normalize whitespace for comparison
normalizedResult := normalizeWhitespace(result)
normalizedExpected := normalizeWhitespace(expected)
if normalizedResult != normalizedExpected {
t.Errorf("Expected content to be:\n%s\n\nGot:\n%s", expected, result)
}
}

View File

@@ -138,6 +138,10 @@ func Set(data interface{}, path string, value interface{}) error {
return fmt.Errorf("failed to parse JSONPath %q: %w", path, err) return fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
} }
if len(steps) <= 1 {
return fmt.Errorf("cannot set root node; the provided path %q is invalid", path)
}
success := false success := false
err = setWithPath(data, steps, &success, value, "$", ModifyFirstMode) err = setWithPath(data, steps, &success, value, "$", ModifyFirstMode)
if err != nil { if err != nil {
@@ -153,6 +157,10 @@ func SetAll(data interface{}, path string, value interface{}) error {
return fmt.Errorf("failed to parse JSONPath %q: %w", path, err) return fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
} }
if len(steps) <= 1 {
return fmt.Errorf("cannot set root node; the provided path %q is invalid", path)
}
success := false success := false
err = setWithPath(data, steps, &success, value, "$", ModifyAllMode) err = setWithPath(data, steps, &success, value, "$", ModifyAllMode)
if err != nil { if err != nil {
@@ -170,20 +178,17 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf
// Skip root step // Skip root step
actualSteps := steps actualSteps := steps
if len(steps) > 0 && steps[0].Type == RootStep { if len(steps) > 0 && steps[0].Type == RootStep {
if len(steps) == 1 {
return fmt.Errorf("cannot set root node; the provided path %q is invalid", currentPath)
}
actualSteps = steps[1:] actualSteps = steps[1:]
} }
// If we have no steps left, we're setting the root value // Process the first step
if len(actualSteps) == 0 { if len(actualSteps) == 0 {
// For the root node, we need to handle it differently depending on what's passed in return fmt.Errorf("cannot set root node; no steps provided for path %q", currentPath)
// since we can't directly replace the interface{} variable
// We'll signal success and let the JSONProcessor handle updating the root
*success = true
return nil
} }
// Process the first step
step := actualSteps[0] step := actualSteps[0]
remainingSteps := actualSteps[1:] remainingSteps := actualSteps[1:]
isLastStep := len(remainingSteps) == 0 isLastStep := len(remainingSteps) == 0

View File

@@ -2,6 +2,7 @@ package processor
import ( import (
"fmt" "fmt"
"reflect"
"strings" "strings"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
@@ -51,97 +52,65 @@ func NewLuaState() (*lua.LState, error) {
return L, nil return L, nil
} }
// ToLua converts a struct or map to a Lua table recursively // ToLuaTable converts a struct or map to a Lua table recursively
func ToLua(L *lua.LState, data interface{}) (lua.LValue, error) { func ToLuaTable(L *lua.LState, data interface{}) (*lua.LTable, error) {
luaTable := L.NewTable()
switch v := data.(type) { switch v := data.(type) {
case map[string]interface{}: case map[string]interface{}:
luaTable := L.NewTable()
for key, value := range v { for key, value := range v {
luaValue, err := ToLua(L, value) luaValue, err := ToLuaTable(L, value)
if err != nil { if err != nil {
return nil, err return nil, err
} }
luaTable.RawSetString(key, luaValue) luaTable.RawSetString(key, luaValue)
} }
return luaTable, nil case struct{}:
case []interface{}: val := reflect.ValueOf(v)
luaTable := L.NewTable() for i := 0; i < val.NumField(); i++ {
for i, value := range v { field := val.Type().Field(i)
luaValue, err := ToLua(L, value) luaValue, err := ToLuaTable(L, val.Field(i).Interface())
if err != nil { if err != nil {
return nil, err return nil, err
} }
luaTable.RawSetInt(i+1, luaValue) // Lua arrays are 1-indexed luaTable.RawSetString(field.Name, luaValue)
} }
return luaTable, nil
case string: case string:
return lua.LString(v), nil luaTable.RawSetString("v", lua.LString(v))
case bool: case bool:
return lua.LBool(v), nil luaTable.RawSetString("v", lua.LBool(v))
case float64: case float64:
return lua.LNumber(v), nil luaTable.RawSetString("v", lua.LNumber(v))
case nil:
return lua.LNil, nil
default: default:
return nil, fmt.Errorf("unsupported data type: %T", data) return nil, fmt.Errorf("unsupported data type: %T", data)
} }
return luaTable, nil
} }
// FromLua converts a Lua table to a struct or map recursively // FromLuaTable converts a Lua table to a struct or map recursively
func FromLua(L *lua.LState, luaValue lua.LValue) (interface{}, error) { func FromLuaTable(L *lua.LState, luaTable *lua.LTable) (map[string]interface{}, error) {
switch v := luaValue.(type) {
// Well shit...
// Tables in lua are both maps and arrays
// As arrays they are ordered and as maps, obviously, not
// So when we parse them to a go map we fuck up the order for arrays
// We have to find a better way....
case *lua.LTable:
isArray, err := IsLuaTableArray(L, v)
if err != nil {
return nil, err
}
if isArray {
result := make([]interface{}, 0)
v.ForEach(func(key lua.LValue, value lua.LValue) {
converted, _ := FromLua(L, value)
result = append(result, converted)
})
return result, nil
} else {
result := make(map[string]interface{}) result := make(map[string]interface{})
v.ForEach(func(key lua.LValue, value lua.LValue) {
converted, _ := FromLua(L, value)
result[key.String()] = converted
})
return result, nil
}
case lua.LString:
return string(v), nil
case lua.LBool:
return bool(v), nil
case lua.LNumber:
return float64(v), nil
default:
return nil, nil
}
}
func IsLuaTableArray(L *lua.LState, v *lua.LTable) (bool, error) { luaTable.ForEach(func(key lua.LValue, value lua.LValue) {
L.SetGlobal("table_to_check", v) switch v := value.(type) {
case *lua.LTable:
// Use our predefined helper function from InitLuaHelpers nestedMap, err := FromLuaTable(L, v)
err := L.DoString(`is_array = isArray(table_to_check)`)
if err != nil { if err != nil {
return false, fmt.Errorf("error determining if table is array: %w", err) return
} }
result[key.String()] = nestedMap
case lua.LString:
result[key.String()] = string(v)
case lua.LBool:
result[key.String()] = bool(v)
case lua.LNumber:
result[key.String()] = float64(v)
default:
result[key.String()] = nil
}
})
// Check the result of our Lua function return result, nil
isArray := L.GetGlobal("is_array")
// LVIsFalse returns true if a given LValue is a nil or false otherwise false.
if !lua.LVIsFalse(isArray) {
return true, nil
}
return false, nil
} }
// InitLuaHelpers initializes common Lua helper functions // InitLuaHelpers initializes common Lua helper functions
@@ -150,10 +119,7 @@ func InitLuaHelpers(L *lua.LState) error {
-- Custom Lua helpers for math operations -- Custom Lua helpers for math operations
function min(a, b) return math.min(a, b) end function min(a, b) return math.min(a, b) end
function max(a, b) return math.max(a, b) end function max(a, b) return math.max(a, b) end
function round(x, n) function round(x) return math.floor(x + 0.5) end
if n == nil then n = 0 end
return math.floor(x * 10^n + 0.5) / 10^n
end
function floor(x) return math.floor(x) end function floor(x) return math.floor(x) end
function ceil(x) return math.ceil(x) end function ceil(x) return math.ceil(x) end
function upper(s) return string.upper(s) end function upper(s) return string.upper(s) end
@@ -173,22 +139,6 @@ end
function is_number(str) function is_number(str)
return tonumber(str) ~= nil return tonumber(str) ~= nil
end end
function isArray(t)
if type(t) ~= "table" then return false end
local max = 0
local count = 0
for k, _ in pairs(t) do
if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then
return false
end
max = math.max(max, k)
count = count + 1
end
return max == count
end
modified = false
` `
if err := L.DoString(helperScript); err != nil { if err := L.DoString(helperScript); err != nil {
return fmt.Errorf("error loading helper functions: %v", err) return fmt.Errorf("error loading helper functions: %v", err)
@@ -207,7 +157,8 @@ func LimitString(s string, maxLen int) string {
return s[:maxLen-3] + "..." return s[:maxLen-3] + "..."
} }
func PrependLuaAssignment(luaExpr string) string { // BuildLuaScript prepares a Lua expression from shorthand notation
func BuildLuaScript(luaExpr string) string {
// Auto-prepend v1 for expressions starting with operators // Auto-prepend v1 for expressions starting with operators
if strings.HasPrefix(luaExpr, "*") || if strings.HasPrefix(luaExpr, "*") ||
strings.HasPrefix(luaExpr, "/") || strings.HasPrefix(luaExpr, "/") ||
@@ -225,30 +176,10 @@ func PrependLuaAssignment(luaExpr string) string {
if !strings.Contains(luaExpr, "=") { if !strings.Contains(luaExpr, "=") {
luaExpr = "v1 = " + luaExpr luaExpr = "v1 = " + luaExpr
} }
return luaExpr return luaExpr
} }
// BuildLuaScript prepares a Lua expression from shorthand notation
func BuildLuaScript(luaExpr string) string {
luaExpr = PrependLuaAssignment(luaExpr)
// This allows the user to specify whether or not they modified a value
// If they do nothing we assume they did modify (no return at all)
// If they return before our return then they themselves specify what they did
// If nothing is returned lua assumes nil
// So we can say our value was modified if the return value is either nil or true
// If the return value is false then the user wants to keep the original
fullScript := fmt.Sprintf(`
function run()
%s
end
local res = run()
modified = res == nil or res
`, luaExpr)
return fullScript
}
// Max returns the maximum of two integers // Max returns the maximum of two integers
func Max(a, b int) int { func Max(a, b int) int {
if a > b { if a > b {

View File

@@ -35,11 +35,10 @@ func TestBuildLuaScript(t *testing.T) {
{"v1 * 2", "v1 = v1 * 2"}, {"v1 * 2", "v1 = v1 * 2"},
{"v1 * v2", "v1 = v1 * v2"}, {"v1 * v2", "v1 = v1 * v2"},
{"v1 / v2", "v1 = v1 / v2"}, {"v1 / v2", "v1 = v1 / v2"},
{"12", "v1 = 12"},
} }
for _, c := range cases { for _, c := range cases {
result := PrependLuaAssignment(c.input) result := BuildLuaScript(c.input)
if result != c.expected { if result != c.expected {
t.Errorf("BuildLuaScript(%q): expected %q, got %q", c.input, c.expected, result) t.Errorf("BuildLuaScript(%q): expected %q, got %q", c.input, c.expected, result)
} }

View File

@@ -1,98 +0,0 @@
package xpath
import "errors"
// XPathStep represents a single step in an XPath expression
type XPathStep struct {
Type StepType
Name string
Predicate *Predicate
}
// StepType defines the type of XPath step
type StepType int
const (
// RootStep represents the root step (/)
RootStep StepType = iota
// ChildStep represents a child element step (element)
ChildStep
// RecursiveDescentStep represents a recursive descent step (//)
RecursiveDescentStep
// WildcardStep represents a wildcard step (*)
WildcardStep
// PredicateStep represents a predicate condition step ([...])
PredicateStep
)
// PredicateType defines the type of XPath predicate
type PredicateType int
const (
// IndexPredicate represents an index predicate [n]
IndexPredicate PredicateType = iota
// LastPredicate represents a last() function predicate
LastPredicate
// LastMinusPredicate represents a last()-n predicate
LastMinusPredicate
// PositionPredicate represents position()-based predicates
PositionPredicate
// AttributeExistsPredicate represents [@attr] predicate
AttributeExistsPredicate
// AttributeEqualsPredicate represents [@attr='value'] predicate
AttributeEqualsPredicate
// ComparisonPredicate represents element comparison predicates
ComparisonPredicate
)
// Predicate represents a condition in XPath
type Predicate struct {
Type PredicateType
Index int
Offset int
Attribute string
Value string
Expression string
}
// XMLNode represents a node in the result set with its value and path
type XMLNode struct {
Value interface{}
Path string
}
// ParseXPath parses an XPath expression into a series of steps
func ParseXPath(path string) ([]XPathStep, error) {
if path == "" {
return nil, errors.New("empty path")
}
// This is just a placeholder implementation for the tests
// The actual implementation would parse the XPath expression
return nil, errors.New("not implemented")
}
// Get retrieves nodes from XML data using an XPath expression
func Get(data interface{}, path string) ([]XMLNode, error) {
if data == "" {
return nil, errors.New("empty XML data")
}
// This is just a placeholder implementation for the tests
// The actual implementation would evaluate the XPath against the XML
return nil, errors.New("not implemented")
}
// Set updates a node in the XML data using an XPath expression
func Set(xmlData string, path string, value interface{}) (string, error) {
// This is just a placeholder implementation for the tests
// The actual implementation would modify the XML based on the XPath
return "", errors.New("not implemented")
}
// SetAll updates all nodes matching an XPath expression in the XML data
func SetAll(xmlData string, path string, value interface{}) (string, error) {
// This is just a placeholder implementation for the tests
// The actual implementation would modify all matching nodes
return "", errors.New("not implemented")
}

View File

@@ -1,545 +0,0 @@
package xpath
import (
"reflect"
"testing"
)
// XML test data as a string for our tests
var testXML = `
<store>
<book category="fiction">
<title lang="en">The Fellowship of the Ring</title>
<author>J.R.R. Tolkien</author>
<year>1954</year>
<price>22.99</price>
</book>
<book category="fiction">
<title lang="en">The Two Towers</title>
<author>J.R.R. Tolkien</author>
<year>1954</year>
<price>23.45</price>
</book>
<book category="technical">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
<bicycle>
<color>red</color>
<price>199.95</price>
</bicycle>
</store>
`
func TestParser(t *testing.T) {
tests := []struct {
path string
steps []XPathStep
wantErr bool
}{
{
path: "/store/bicycle/color",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "bicycle"},
{Type: ChildStep, Name: "color"},
},
},
{
path: "//price",
steps: []XPathStep{
{Type: RootStep},
{Type: RecursiveDescentStep, Name: "price"},
},
},
{
path: "/store/book/*",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "book"},
{Type: WildcardStep},
},
},
{
path: "/store/book[1]/title",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "book"},
{Type: PredicateStep, Predicate: &Predicate{Type: IndexPredicate, Index: 1}},
{Type: ChildStep, Name: "title"},
},
},
{
path: "//title[@lang]",
steps: []XPathStep{
{Type: RootStep},
{Type: RecursiveDescentStep, Name: "title"},
{Type: PredicateStep, Predicate: &Predicate{Type: AttributeExistsPredicate, Attribute: "lang"}},
},
},
{
path: "//title[@lang='en']",
steps: []XPathStep{
{Type: RootStep},
{Type: RecursiveDescentStep, Name: "title"},
{Type: PredicateStep, Predicate: &Predicate{
Type: AttributeEqualsPredicate,
Attribute: "lang",
Value: "en",
}},
},
},
{
path: "/store/book[price>35.00]/title",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "book"},
{Type: PredicateStep, Predicate: &Predicate{
Type: ComparisonPredicate,
Expression: "price>35.00",
}},
{Type: ChildStep, Name: "title"},
},
},
{
path: "/store/book[last()]",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "book"},
{Type: PredicateStep, Predicate: &Predicate{Type: LastPredicate}},
},
},
{
path: "/store/book[last()-1]",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "book"},
{Type: PredicateStep, Predicate: &Predicate{
Type: LastMinusPredicate,
Offset: 1,
}},
},
},
{
path: "/store/book[position()<3]",
steps: []XPathStep{
{Type: RootStep},
{Type: ChildStep, Name: "store"},
{Type: ChildStep, Name: "book"},
{Type: PredicateStep, Predicate: &Predicate{
Type: PositionPredicate,
Expression: "position()<3",
}},
},
},
{
path: "invalid/path",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
steps, err := ParseXPath(tt.path)
if (err != nil) != tt.wantErr {
t.Fatalf("ParseXPath() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && !reflect.DeepEqual(steps, tt.steps) {
t.Errorf("ParseXPath() steps = %+v, want %+v", steps, tt.steps)
}
})
}
}
func TestEvaluator(t *testing.T) {
tests := []struct {
name string
path string
expected []XMLNode
error bool
}{
{
name: "simple_element_access",
path: "/store/bicycle/color",
expected: []XMLNode{
{Value: "red", Path: "/store/bicycle/color"},
},
},
{
name: "recursive_element_access",
path: "//price",
expected: []XMLNode{
{Value: "22.99", Path: "/store/book[1]/price"},
{Value: "23.45", Path: "/store/book[2]/price"},
{Value: "39.95", Path: "/store/book[3]/price"},
{Value: "199.95", Path: "/store/bicycle/price"},
},
},
{
name: "wildcard_element_access",
path: "/store/book[1]/*",
expected: []XMLNode{
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
{Value: "J.R.R. Tolkien", Path: "/store/book[1]/author"},
{Value: "1954", Path: "/store/book[1]/year"},
{Value: "22.99", Path: "/store/book[1]/price"},
},
},
{
name: "indexed_element_access",
path: "/store/book[1]/title",
expected: []XMLNode{
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
},
},
{
name: "attribute_exists_predicate",
path: "//title[@lang]",
expected: []XMLNode{
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
{Value: "The Two Towers", Path: "/store/book[2]/title"},
{Value: "Learning XML", Path: "/store/book[3]/title"},
},
},
{
name: "attribute_equals_predicate",
path: "//title[@lang='en']",
expected: []XMLNode{
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
{Value: "The Two Towers", Path: "/store/book[2]/title"},
{Value: "Learning XML", Path: "/store/book[3]/title"},
},
},
{
name: "value_comparison_predicate",
path: "/store/book[price>35.00]/title",
expected: []XMLNode{
{Value: "Learning XML", Path: "/store/book[3]/title"},
},
},
{
name: "last_predicate",
path: "/store/book[last()]/title",
expected: []XMLNode{
{Value: "Learning XML", Path: "/store/book[3]/title"},
},
},
{
name: "last_minus_predicate",
path: "/store/book[last()-1]/title",
expected: []XMLNode{
{Value: "The Two Towers", Path: "/store/book[2]/title"},
},
},
{
name: "position_predicate",
path: "/store/book[position()<3]/title",
expected: []XMLNode{
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
{Value: "The Two Towers", Path: "/store/book[2]/title"},
},
},
{
name: "all_elements",
path: "//*",
expected: []XMLNode{
// For brevity, we'll just check the count, not all values
},
},
{
name: "invalid_index",
path: "/store/book[10]/title",
expected: []XMLNode{},
error: true,
},
{
name: "nonexistent_element",
path: "/store/nonexistent",
expected: []XMLNode{},
error: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Get(testXML, tt.path)
if err != nil {
if !tt.error {
t.Errorf("Get() returned error: %v", err)
}
return
}
// Special handling for the "//*" test case
if tt.path == "//*" {
// Just check that we got multiple elements, not the specific count
if len(result) < 10 { // We expect at least 10 elements
t.Errorf("Expected multiple elements for '//*', got %d", len(result))
}
return
}
if len(result) != len(tt.expected) {
t.Errorf("Expected %d items, got %d", len(tt.expected), len(result))
return
}
// Validate both values and paths
for i, e := range tt.expected {
if i < len(result) {
if !reflect.DeepEqual(result[i].Value, e.Value) {
t.Errorf("Value at [%d]: got %v, expected %v", i, result[i].Value, e.Value)
}
if result[i].Path != e.Path {
t.Errorf("Path at [%d]: got %s, expected %s", i, result[i].Path, e.Path)
}
}
}
})
}
}
func TestEdgeCases(t *testing.T) {
t.Run("empty_data", func(t *testing.T) {
result, err := Get("", "/store/book")
if err == nil {
t.Errorf("Expected error for empty data")
return
}
if len(result) > 0 {
t.Errorf("Expected empty result, got %v", result)
}
})
t.Run("empty_path", func(t *testing.T) {
_, err := ParseXPath("")
if err == nil {
t.Error("Expected error for empty path")
}
})
t.Run("invalid_xml", func(t *testing.T) {
_, err := Get("<invalid>xml", "/store")
if err == nil {
t.Error("Expected error for invalid XML")
}
})
t.Run("current_node", func(t *testing.T) {
result, err := Get(testXML, "/store/book[1]/.")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 1 {
t.Errorf("Expected 1 result, got %d", len(result))
}
})
t.Run("attributes", func(t *testing.T) {
result, err := Get(testXML, "/store/book[1]/title/@lang")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 1 || result[0].Value != "en" {
t.Errorf("Expected 'en', got %v", result)
}
})
}
func TestGetWithPaths(t *testing.T) {
tests := []struct {
name string
path string
expected []XMLNode
}{
{
name: "simple_element_access",
path: "/store/bicycle/color",
expected: []XMLNode{
{Value: "red", Path: "/store/bicycle/color"},
},
},
{
name: "indexed_element_access",
path: "/store/book[1]/title",
expected: []XMLNode{
{Value: "The Fellowship of the Ring", Path: "/store/book[1]/title"},
},
},
{
name: "recursive_element_access",
path: "//price",
expected: []XMLNode{
{Value: "22.99", Path: "/store/book[1]/price"},
{Value: "23.45", Path: "/store/book[2]/price"},
{Value: "39.95", Path: "/store/book[3]/price"},
{Value: "199.95", Path: "/store/bicycle/price"},
},
},
{
name: "attribute_access",
path: "/store/book[1]/title/@lang",
expected: []XMLNode{
{Value: "en", Path: "/store/book[1]/title/@lang"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Get(testXML, tt.path)
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
// Check if lengths match
if len(result) != len(tt.expected) {
t.Errorf("Get() returned %d items, expected %d", len(result), len(tt.expected))
return
}
// For each expected item, find its match in the results and verify both value and path
for _, expected := range tt.expected {
found := false
for _, r := range result {
// First verify the value matches
if reflect.DeepEqual(r.Value, expected.Value) {
found = true
// Then verify the path matches
if r.Path != expected.Path {
t.Errorf("Path mismatch for value %v: got %s, expected %s", r.Value, r.Path, expected.Path)
}
break
}
}
if !found {
t.Errorf("Expected node with value %v and path %s not found in results", expected.Value, expected.Path)
}
}
})
}
}
func TestSet(t *testing.T) {
t.Run("simple element", func(t *testing.T) {
xmlData := `<root><name>John</name></root>`
newXML, err := Set(xmlData, "/root/name", "Jane")
if err != nil {
t.Errorf("Set() returned error: %v", err)
return
}
// Verify the change
result, err := Get(newXML, "/root/name")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 1 || result[0].Value != "Jane" {
t.Errorf("Set() failed: expected name to be 'Jane', got %v", result)
}
})
t.Run("attribute", func(t *testing.T) {
xmlData := `<root><element id="123"></element></root>`
newXML, err := Set(xmlData, "/root/element/@id", "456")
if err != nil {
t.Errorf("Set() returned error: %v", err)
return
}
// Verify the change
result, err := Get(newXML, "/root/element/@id")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 1 || result[0].Value != "456" {
t.Errorf("Set() failed: expected id to be '456', got %v", result)
}
})
t.Run("indexed element", func(t *testing.T) {
xmlData := `<root><items><item>first</item><item>second</item></items></root>`
newXML, err := Set(xmlData, "/root/items/item[1]", "changed")
if err != nil {
t.Errorf("Set() returned error: %v", err)
return
}
// Verify the change
result, err := Get(newXML, "/root/items/item[1]")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 1 || result[0].Value != "changed" {
t.Errorf("Set() failed: expected item to be 'changed', got %v", result)
}
})
}
func TestSetAll(t *testing.T) {
t.Run("multiple elements", func(t *testing.T) {
xmlData := `<root><items><item>first</item><item>second</item></items></root>`
newXML, err := SetAll(xmlData, "//item", "changed")
if err != nil {
t.Errorf("SetAll() returned error: %v", err)
return
}
// Verify all items are changed
result, err := Get(newXML, "//item")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 2 {
t.Errorf("Expected 2 results, got %d", len(result))
return
}
for i, node := range result {
if node.Value != "changed" {
t.Errorf("Item %d not changed, got %v", i+1, node.Value)
}
}
})
t.Run("attributes", func(t *testing.T) {
xmlData := `<root><item id="1"/><item id="2"/></root>`
newXML, err := SetAll(xmlData, "//item/@id", "new")
if err != nil {
t.Errorf("SetAll() returned error: %v", err)
return
}
// Verify all attributes are changed
result, err := Get(newXML, "//item/@id")
if err != nil {
t.Errorf("Get() returned error: %v", err)
return
}
if len(result) != 2 {
t.Errorf("Expected 2 results, got %d", len(result))
return
}
for i, node := range result {
if node.Value != "new" {
t.Errorf("Attribute %d not changed, got %v", i+1, node.Value)
}
}
})
}

View File

@@ -1,48 +0,0 @@
#!/bin/bash
echo "Figuring out the tag..."
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
if [ -z "$TAG" ]; then
# Get the latest tag
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
# Increment the patch version
IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_TAG"
VERSION_PARTS[2]=$((VERSION_PARTS[2]+1))
TAG="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}"
# Create a new tag
git tag $TAG
git push origin $TAG
fi
echo "Tag: $TAG"
echo "Building the thing..."
go build -o BigChef.exe .
echo "Creating a release..."
TOKEN="$GITEA_API_KEY"
GITEA="https://git.site.quack-lab.dev"
REPO="dave/BigChef"
# Create a release
RELEASE_RESPONSE=$(curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"tag_name": "'"$TAG"'",
"name": "'"$TAG"'",
"draft": false,
"prerelease": false
}' \
$GITEA/api/v1/repos/$REPO/releases)
# Extract the release ID
echo $RELEASE_RESPONSE
RELEASE_ID=$(echo $RELEASE_RESPONSE | awk -F'"id":' '{print $2+0; exit}')
echo "Release ID: $RELEASE_ID"
echo "Uploading the things..."
curl -X POST \
-H "Authorization: token $TOKEN" \
-F "attachment=@BigChef.exe" \
"$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=BigChef.exe"
rm BigChef.exe