diff --git a/README.md b/README.md index 4d6329f..66ea860 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ Download the latest [release](https://github.com/EliCDavis/polyform/releases) of polyform .json edit ``` +Or if you have golang installed, simply clone the repo and run: + +```bash +go run ./cmd/polyform ./examples/graphs/ufo.json edit +``` + ## Overview - [Formats](/formats/) diff --git a/cmd/polyform/main.go b/cmd/polyform/main.go index cd1b72f..90670d6 100644 --- a/cmd/polyform/main.go +++ b/cmd/polyform/main.go @@ -1,6 +1,8 @@ package main import ( + "os" + "github.com/EliCDavis/polyform/generator" "github.com/EliCDavis/polyform/generator/artifact" "github.com/EliCDavis/polyform/nodes" @@ -26,7 +28,7 @@ func main() { Producers: map[string]nodes.NodeOutput[artifact.Artifact]{}, } - if err := app.Run(); err != nil { + if err := app.Run(os.Args); err != nil { panic(err) } } diff --git a/examples/collar/main.go b/examples/collar/main.go index b0b0f8e..09405f0 100644 --- a/examples/collar/main.go +++ b/examples/collar/main.go @@ -4,6 +4,7 @@ import ( "image" "image/color" "math" + "os" "github.com/EliCDavis/polyform/drawing/coloring" "github.com/EliCDavis/polyform/formats/gltf" @@ -269,7 +270,7 @@ func main() { }, } - if err := app.Run(); err != nil { + if err := app.Run(os.Args); err != nil { panic(err) } diff --git a/examples/disco/main.go b/examples/disco/main.go index c4ab865..e993765 100644 --- a/examples/disco/main.go +++ b/examples/disco/main.go @@ -3,6 +3,7 @@ package main import ( "image" "image/color" + "os" "github.com/EliCDavis/polyform/drawing/coloring" "github.com/EliCDavis/polyform/formats/gltf" @@ -402,7 +403,7 @@ func main() { "rough.png": basics.NewImageNode(nodes.Value(texture(0, 1))), }, } - err := app.Run() + err := app.Run(os.Args) if err != nil { panic(err) } diff --git a/examples/edit-gaussian-splats/main.go b/examples/edit-gaussian-splats/main.go index 395ba5a..4a38036 100644 --- a/examples/edit-gaussian-splats/main.go +++ b/examples/edit-gaussian-splats/main.go @@ -2,6 +2,7 @@ package main import ( "math" + "os" "github.com/EliCDavis/polyform/drawing/coloring" "github.com/EliCDavis/polyform/formats/ply" @@ -172,7 +173,7 @@ func main() { // AvailableNodes: generator.Nodes(), } - if err := app.Run(); err != nil { + if err := app.Run(os.Args); err != nil { panic(err) } } diff --git a/examples/fired-heater/main.go b/examples/fired-heater/main.go index 2a6d93f..4112b14 100644 --- a/examples/fired-heater/main.go +++ b/examples/fired-heater/main.go @@ -2,6 +2,7 @@ package main import ( "math" + "os" "github.com/EliCDavis/polyform/drawing/coloring" "github.com/EliCDavis/polyform/formats/gltf" @@ -476,7 +477,7 @@ func main() { }, } - err := app.Run() + err := app.Run(os.Args) if err != nil { panic(err) } diff --git a/examples/graphs/ufo.json b/examples/graphs/ufo.json index 0f5673a..8fc1b3d 100644 --- a/examples/graphs/ufo.json +++ b/examples/graphs/ufo.json @@ -95,8 +95,8 @@ }, "Node-21": { "position": { - "x": 4859.852703995043, - "y": -1541.617893418497 + "x": 4836.669300279127, + "y": -1525.8862980398374 } }, "Node-22": { @@ -649,6 +649,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Weld Dist", + "description": "", "currentValue": 0.0001, "defaultValue": 0.0001, "cli": null @@ -658,6 +659,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector2.Vector[float64]]", "data": { "name": "UV Start", + "description": "", "currentValue": { "x": 0, "y": 0.5 @@ -673,6 +675,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "UFO Color", + "description": "", "currentValue": "#ffffff", "defaultValue": "#ffffff", "cli": null @@ -682,6 +685,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[string]", "data": { "name": "Color Tex URI", + "description": "", "currentValue": "brushed.png", "defaultValue": "brushed.png", "cli": null @@ -691,6 +695,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "UFO Metallic", + "description": "", "currentValue": 1, "defaultValue": 1, "cli": null @@ -700,6 +705,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "UFO Roughness", + "description": "", "currentValue": 0.3, "defaultValue": 0.3, "cli": null @@ -709,6 +715,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[string]", "data": { "name": "Metalic Tex URI", + "description": "", "currentValue": "rough.png", "defaultValue": "rough.png", "cli": null @@ -718,6 +725,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "UFO Clearcoat Factor", + "description": "", "currentValue": 0.25, "defaultValue": 0.25, "cli": null @@ -726,7 +734,8 @@ "Node-16": { "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { - "name": "UFO ClearcoatRoughnessFactor", + "name": "UFO Clearcoat Roughness Factor", + "description": "", "currentValue": 0.15, "defaultValue": 0.15, "cli": null @@ -801,6 +810,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector2.Vector[float64]]", "data": { "name": "UV End", + "description": "", "currentValue": { "x": 20, "y": 0.5 @@ -816,6 +826,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "Abduction Ring Color", + "description": "", "currentValue": "#00ff00", "defaultValue": "#00ff00", "cli": null @@ -825,6 +836,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Abduction Ring Emissive Strength", + "description": "The strength adjustment to be multiplied with the material's emissive value", "currentValue": 3, "defaultValue": 3, "cli": null @@ -854,6 +866,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "Abduction Ring Resolution", + "description": "", "currentValue": 20, "defaultValue": 20, "cli": null @@ -863,6 +876,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Abduction Ring Thickness Frequency", + "description": "", "currentValue": 37.69911184307752, "defaultValue": 37.69911184307752, "cli": null @@ -872,6 +886,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "Abduction Ring Path Resolution", + "description": "", "currentValue": 120, "defaultValue": 120, "cli": null @@ -896,6 +911,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Thickness Scale", + "description": "", "currentValue": 0.25, "defaultValue": 0.25, "cli": null @@ -920,6 +936,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Thickness Shift", + "description": "", "currentValue": 0.5, "defaultValue": 0.5, "cli": null @@ -959,6 +976,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "2Pi", + "description": "", "currentValue": 6.283185307179586, "defaultValue": 6.283185307179586, "cli": null @@ -983,6 +1001,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Abduction Ring Radius", + "description": "", "currentValue": 4, "defaultValue": 4, "cli": null @@ -1007,6 +1026,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Abduction Ring Frequency", + "description": "", "currentValue": 25.132741228718345, "defaultValue": 25.132741228718345, "cli": null @@ -1031,6 +1051,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Abduction Ring Amplitude", + "description": "", "currentValue": 0.5, "defaultValue": 0.5, "cli": null @@ -1070,6 +1091,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[[]github.com/EliCDavis/vector/vector3.Vector[float64]]", "data": { "name": "UFO Outline", + "description": "", "currentValue": [ { "x": 0, @@ -1181,6 +1203,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[bool]", "data": { "name": "Closed", + "description": "", "currentValue": true, "defaultValue": true, "cli": null @@ -1215,6 +1238,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Ring Start Position", + "description": "", "currentValue": -10, "defaultValue": -10, "cli": null @@ -1224,6 +1248,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Ring End Position", + "description": "", "currentValue": -3, "defaultValue": -3, "cli": null @@ -1233,6 +1258,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "Ring Count", + "description": "", "currentValue": 3, "defaultValue": 3, "cli": null @@ -1272,6 +1298,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Start Ring Scale", + "description": "", "currentValue": 1, "defaultValue": 1, "cli": null @@ -1281,6 +1308,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "End Ring Scale", + "description": "", "currentValue": 0.3, "defaultValue": 0.3, "cli": null @@ -1290,6 +1318,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "UFO Resolution", + "description": "", "currentValue": 40, "defaultValue": 40, "cli": null @@ -1374,6 +1403,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Hemisphere Radius", + "description": "", "currentValue": 3.1, "defaultValue": 3.1, "cli": null @@ -1383,6 +1413,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "UFO Dome Rows", + "description": "", "currentValue": 40, "defaultValue": 40, "cli": null @@ -1392,6 +1423,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "UFO Dome Columns", + "description": "", "currentValue": 40, "defaultValue": 40, "cli": null @@ -1431,6 +1463,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Revolutions", + "description": "", "currentValue": 1, "defaultValue": 1, "cli": null @@ -1440,6 +1473,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/vector/vector3.Vector[float64]]", "data": { "name": "Dome Position", + "description": "", "currentValue": { "x": 0, "y": 5, @@ -1472,6 +1506,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "Dome Color", + "description": "", "currentValue": "#c8c8c8", "defaultValue": "#c8c8c8", "cli": null @@ -1481,6 +1516,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Dome Metalic", + "description": "", "currentValue": 0, "defaultValue": 0, "cli": null @@ -1490,6 +1526,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Dome Roughness", + "description": "", "currentValue": 0.2, "defaultValue": 0.2, "cli": null @@ -1499,6 +1536,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Dome IOR", + "description": "", "currentValue": 1.52, "defaultValue": 1.52, "cli": null @@ -1508,6 +1546,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Dome Transmission", + "description": "", "currentValue": 0.9, "defaultValue": 0.9, "cli": null @@ -1527,6 +1566,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Dome Thickness", + "description": "", "currentValue": 0.5, "defaultValue": 0.5, "cli": null @@ -1546,6 +1586,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Height", + "description": "", "currentValue": 0, "defaultValue": 0, "cli": null @@ -1605,6 +1646,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "Contour Sides", + "description": "", "currentValue": 20, "defaultValue": 20, "cli": null @@ -1614,6 +1656,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[float64]", "data": { "name": "Contour Thickness", + "description": "", "currentValue": 0.2, "defaultValue": 0.2, "cli": null @@ -1643,6 +1686,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[int]", "data": { "name": "Countour Repeat Times", + "description": "", "currentValue": 10, "defaultValue": 10, "cli": null @@ -1747,6 +1791,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "Positive", + "description": "", "currentValue": "#dedede", "defaultValue": "#dedede", "cli": null @@ -1756,6 +1801,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "Negative", + "description": "", "currentValue": "#cacaca", "defaultValue": "#cacaca", "cli": null @@ -1790,6 +1836,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "Positive", + "description": "", "currentValue": "#0046ff", "defaultValue": "#0046ff", "cli": null @@ -1799,6 +1846,7 @@ "type": "github.com/EliCDavis/polyform/generator/parameter.Value[github.com/EliCDavis/polyform/drawing/coloring.WebColor]", "data": { "name": "Negative", + "description": "", "currentValue": "#00c8ff", "defaultValue": "#00c8ff", "cli": null diff --git a/examples/pumpkin/main.go b/examples/pumpkin/main.go index 73a60f2..64b168c 100644 --- a/examples/pumpkin/main.go +++ b/examples/pumpkin/main.go @@ -398,7 +398,7 @@ func main() { }, } - if err := app.Run(); err != nil { + if err := app.Run(os.Args); err != nil { panic(err) } } diff --git a/examples/rails/main.go b/examples/rails/main.go index fe07c78..13c0f58 100644 --- a/examples/rails/main.go +++ b/examples/rails/main.go @@ -1,6 +1,8 @@ package main import ( + "os" + "github.com/EliCDavis/polyform/drawing/coloring" "github.com/EliCDavis/polyform/formats/gltf" "github.com/EliCDavis/polyform/generator" @@ -236,7 +238,7 @@ func main() { }, } - if err := app.Run(); err != nil { + if err := app.Run(os.Args); err != nil { panic(err) } diff --git a/examples/structure/main.go b/examples/structure/main.go index 57405d2..578add3 100644 --- a/examples/structure/main.go +++ b/examples/structure/main.go @@ -4,6 +4,7 @@ import ( "image/color" "math" "math/rand" + "os" "time" "github.com/EliCDavis/polyform/drawing/coloring" @@ -489,7 +490,7 @@ func main() { }, } - err := app.Run() + err := app.Run(os.Args) if err != nil { panic(err) } diff --git a/examples/texturing/main.go b/examples/texturing/main.go index 5d9ceb0..b8eef2e 100644 --- a/examples/texturing/main.go +++ b/examples/texturing/main.go @@ -3,6 +3,7 @@ package main import ( "image" "image/draw" + "os" "github.com/EliCDavis/polyform/drawing/coloring" "github.com/EliCDavis/polyform/generator" @@ -85,7 +86,7 @@ func main() { }, } - if err := app.Run(); err != nil { + if err := app.Run(os.Args); err != nil { panic(err) } } diff --git a/formats/swagger/README.md b/formats/swagger/README.md index e2cfc6f..6bb05e3 100644 --- a/formats/swagger/README.md +++ b/formats/swagger/README.md @@ -1,3 +1,3 @@ # Swagger 2.0 -Loose implementation of [Swagger 2.0 specification](https://swagger.io/specification/v2/) for the needs of specifically polyform. This does not aim to be a full fledged implementation, so be cautious before considering using anything here for personal purposes. \ No newline at end of file +Loose implementation of [Swagger 2.0 specification](https://swagger.io/specification/v2/) for the needs of specifically polyform. This does not aim to be a full fledged implementation, so be cautious before considering using anything here (pro tip, you shouldn't). \ No newline at end of file diff --git a/generator/app.go b/generator/app.go index 8b33c4c..bcaa53d 100644 --- a/generator/app.go +++ b/generator/app.go @@ -32,6 +32,8 @@ type App struct { Authors []Author Producers map[string]nodes.NodeOutput[artifact.Artifact] + Out io.Writer + // Runtime data nodeIDs map[nodes.Node]string graphMetadata *NestedSyncMap @@ -392,8 +394,12 @@ func (a App) buildNodeGraphInstanceSchema(node nodes.Node, encoder *jbtf.Encoder func (a App) buildNodeInstanceSchema(node nodes.Node) schema.NodeInstance { var metadata map[string]any - if data := a.graphMetadata.Get("nodes." + a.nodeIDs[node]); data != nil { - metadata = data.(map[string]any) + metadataPath := "nodes." + a.nodeIDs[node] + + if a.graphMetadata.PathExists(metadataPath) { + if data := a.graphMetadata.Get(metadataPath); data != nil { + metadata = data.(map[string]any) + } } nodeInstance := schema.NodeInstance{ @@ -587,7 +593,7 @@ func (a *App) SetupProducers() { } } -func (a *App) Run() error { +func (a *App) Run(args []string) error { // if a.Producers == nil || len(a.Producers) == 0 { // return errors.New("application has not defined any producers") // } @@ -602,11 +608,11 @@ func (a *App) Run() error { Name: "Generate", Description: "Runs all producers the app has defined and saves it to the file system", Aliases: []string{"generate", "gen"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { generateCmd := flag.NewFlagSet("generate", flag.ExitOnError) a.initialize(generateCmd) folderFlag := generateCmd.String("folder", ".", "folder to save generated contents to") - if err := generateCmd.Parse(args); err != nil { + if err := generateCmd.Parse(appState.Args); err != nil { return err } return a.Generate(*folderFlag) @@ -616,7 +622,7 @@ func (a *App) Run() error { Name: "Edit", Description: "Starts an http server and hosts a webplayer for editing the execution graph", Aliases: []string{"edit"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { editCmd := flag.NewFlagSet("edit", flag.ExitOnError) a.initialize(editCmd) hostFlag := editCmd.String("host", "localhost", "interface to bind to") @@ -654,7 +660,7 @@ func (a *App) Run() error { "Time allowed to write a message to the peer over a websocketed connection.", ) - if err := editCmd.Parse(args); err != nil { + if err := editCmd.Parse(appState.Args); err != nil { return err } @@ -684,57 +690,75 @@ func (a *App) Run() error { }, { Name: "Outline", - Description: "Enumerates all generators, parameters, and producers in a heirarchial fashion formatted in JSON", + Description: "Enumerates all parameters and producers in a heirarchial fashion formatted in JSON", Aliases: []string{"outline"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { outlineCmd := flag.NewFlagSet("outline", flag.ExitOnError) a.initialize(outlineCmd) - if err := outlineCmd.Parse(args); err != nil { + if err := outlineCmd.Parse(appState.Args); err != nil { return err } - data, err := json.MarshalIndent(a.Schema(), "", " ") + schema := a.Schema() + + usedTypes := make(map[string]struct{}) + for _, n := range schema.Nodes { + usedTypes[n.Type] = struct{}{} + } + + for i := len(schema.Types) - 1; i >= 0; i-- { + if _, ok := usedTypes[schema.Types[i].Type]; !ok { + schema.Types = append(schema.Types[:i], schema.Types[i+1:]...) + } + } + + data, err := json.MarshalIndent(schema, "", " ") if err != nil { - panic(err) + return err } - os.Stdout.Write(data) - return nil + _, err = appState.Out.Write(data) + return err }, }, { Name: "Zip", Description: "Runs all producers defined and writes it to a zip file", Aliases: []string{"zip", "z"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { zipCmd := flag.NewFlagSet("zip", flag.ExitOnError) a.initialize(zipCmd) - fileFlag := zipCmd.String("file-name", "out.zip", "file to write the contents of the zip too") + fileFlag := zipCmd.String("out", "", "file to write the contents of the zip too") - if err := zipCmd.Parse(args); err != nil { + if err := zipCmd.Parse(appState.Args); err != nil { return err } - f, err := os.Create(*fileFlag) - if err != nil { - return err + var out io.Writer = appState.Out + + if fileFlag != nil && *fileFlag != "" { + f, err := os.Create(*fileFlag) + if err != nil { + return err + } + defer f.Close() + out = f } - defer f.Close() - return a.WriteZip(f) + return a.WriteZip(out) }, }, { Name: "Mermaid", Description: "Create a mermaid flow chart for a specific producer", Aliases: []string{"mermaid"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { mermaidCmd := flag.NewFlagSet("mermaid", flag.ExitOnError) a.initialize(mermaidCmd) fileFlag := mermaidCmd.String("file-name", "", "Optional path to file to write content to") - if err := mermaidCmd.Parse(args); err != nil { + if err := mermaidCmd.Parse(appState.Args); err != nil { return err } @@ -756,16 +780,16 @@ func (a *App) Run() error { Name: "Swagger", Description: "Create a swagger 2.0 file", Aliases: []string{"swagger"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { swaggerCmd := flag.NewFlagSet("swagger", flag.ExitOnError) a.initialize(swaggerCmd) fileFlag := swaggerCmd.String("file-name", "", "Optional path to file to write content to") - if err := swaggerCmd.Parse(args); err != nil { + if err := swaggerCmd.Parse(appState.Args); err != nil { return err } - var out io.Writer = os.Stdout + var out io.Writer = appState.Out if fileFlag != nil && *fileFlag != "" { f, err := os.Create(*fileFlag) @@ -783,7 +807,7 @@ func (a *App) Run() error { Name: "Help", Description: "", Aliases: []string{"help", "h"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { cliDetails := appCLI{ Name: a.Name, Version: a.Version, @@ -807,6 +831,7 @@ func (a *App) Run() error { cliApp := cli.App{ Commands: commands, + Out: a.Out, ConfigProvided: func(config string) error { fileData, err := os.ReadFile(config) if err != nil { @@ -824,5 +849,5 @@ func (a *App) Run() error { }, } - return cliApp.Run(os.Args) + return cliApp.Run(args) } diff --git a/generator/app_init_wasm.go b/generator/app_init_wasm.go index 4f5a96a..948a4a9 100644 --- a/generator/app_init_wasm.go +++ b/generator/app_init_wasm.go @@ -13,7 +13,7 @@ var globalApp *App func wasmZip(this js.Value, cb []js.Value) interface{} { // if globalApp == nil { - panic("global app not configured. Run app.Run()") + panic("global app not configured. Run app.Run(os.Args)") } b := bytes.Buffer{} diff --git a/generator/app_server.go b/generator/app_server.go index cf433f0..8479782 100644 --- a/generator/app_server.go +++ b/generator/app_server.go @@ -148,6 +148,7 @@ func (as *AppServer) Serve() error { http.Handle("/node/connection", nodeConnectionEndpoint(as)) http.Handle("/parameter/value/", parameterValueEndpoint(as)) http.Handle("/parameter/name/", parameterNameEndpoint(as)) + http.Handle("/parameter/description/", parameterDescriptionEndpoint(as)) http.Handle("/graph", graphEndpoint(as)) http.Handle("/graph/metadata/", graphMetadataEndpoint(as)) http.HandleFunc("/started", as.StartedEndpoint) diff --git a/generator/app_server_parameter.go b/generator/app_server_parameter.go index 5b52532..1d2d06a 100644 --- a/generator/app_server_parameter.go +++ b/generator/app_server_parameter.go @@ -74,3 +74,19 @@ func parameterNameEndpoint(as *AppServer) endpoint.Handler { }, } } + +func parameterDescriptionEndpoint(as *AppServer) endpoint.Handler { + return endpoint.Handler{ + Methods: map[string]endpoint.Method{ + http.MethodPost: endpoint.BodyMethod[string]{ + Request: endpoint.TextRequestReader{}, + Handler: func(req endpoint.Request[string]) error { + parameterId := path.Base(req.Url) + as.app.GetParameter(parameterId).SetDescription(req.Body) + as.AutosaveGraph() + return nil + }, + }, + }, + } +} diff --git a/generator/app_test.go b/generator/app_test.go index db52e4c..e49e189 100644 --- a/generator/app_test.go +++ b/generator/app_test.go @@ -1,7 +1,9 @@ package generator_test import ( + "archive/zip" "bytes" + "io" "testing" "github.com/EliCDavis/polyform/generator" @@ -74,3 +76,225 @@ func TestGetAndApplyGraph(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "yee", b.String()) } + +func TestAppCommand_Outline(t *testing.T) { + appName := "Test Graph" + appVersion := "Test Graph" + appDescription := "Test Graph" + producerFileName := "test.txt" + + outBuf := &bytes.Buffer{} + + app := generator.App{ + Name: appName, + Version: appVersion, + Description: appDescription, + Producers: map[string]nodes.NodeOutput[artifact.Artifact]{ + producerFileName: basics.NewTextNode(¶meter.String{ + Name: "Welp", + DefaultValue: "yee", + }), + }, + + Out: outBuf, + } + + // ACT ==================================================================== + err := app.Run([]string{"polyform", "outline"}) + contents, readErr := io.ReadAll(outBuf) + + // ASSERT ================================================================= + assert.NoError(t, err) + assert.NoError(t, readErr) + assert.Equal(t, `{ + "producers": { + "test.txt": { + "nodeID": "Node-1", + "port": "Out" + } + }, + "nodes": { + "Node-0": { + "type": "github.com/EliCDavis/polyform/generator/parameter.Value[string]", + "name": "Welp", + "version": 0, + "dependencies": [], + "parameter": { + "name": "Welp", + "type": "string", + "defaultValue": "yee", + "currentValue": "yee" + } + }, + "Node-1": { + "type": "github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/generator/artifact.Artifact,github.com/EliCDavis/polyform/generator/artifact/basics.TextNodeData]", + "name": "test.txt", + "version": 0, + "dependencies": [ + { + "dependencyID": "Node-0", + "dependencyPort": "Out", + "name": "In" + } + ] + } + }, + "types": [ + { + "displayName": "parameter.Value[string]", + "info": "", + "type": "github.com/EliCDavis/polyform/generator/parameter.Value[string]", + "path": "generator/parameter", + "outputs": [ + { + "name": "Out", + "type": "string" + } + ], + "parameter": { + "name": "", + "type": "string", + "defaultValue": "", + "currentValue": "" + } + }, + { + "displayName": "TextNodeData", + "info": "", + "type": "github.com/EliCDavis/polyform/nodes.Struct[github.com/EliCDavis/polyform/generator/artifact.Artifact,github.com/EliCDavis/polyform/generator/artifact/basics.TextNodeData]", + "path": "generator/artifact/basics", + "outputs": [ + { + "name": "Out", + "type": "github.com/EliCDavis/polyform/generator/artifact.Artifact" + } + ], + "inputs": { + "In": { + "type": "string", + "isArray": false + } + } + } + ], + "notes": null +}`, string(contents)) +} + +func TestAppCommand_Zip(t *testing.T) { + appName := "Test Graph" + appVersion := "Test Graph" + appDescription := "Test Graph" + producerFileName := "test.txt" + + outBuf := &bytes.Buffer{} + + app := generator.App{ + Name: appName, + Version: appVersion, + Description: appDescription, + Producers: map[string]nodes.NodeOutput[artifact.Artifact]{ + producerFileName: basics.NewTextNode(¶meter.String{ + Name: "Welp", + DefaultValue: "yee", + }), + }, + + Out: outBuf, + } + + // ACT ==================================================================== + err := app.Run([]string{"polyform", "zip"}) + data := outBuf.Bytes() + + r, zipErr := zip.NewReader(bytes.NewReader(data), int64(len(data))) + + // ASSERT ================================================================= + assert.NoError(t, err) + assert.NoError(t, zipErr) + assert.Len(t, r.File, 1) + assert.Equal(t, "test.txt", r.File[0].Name) + + rc, err := r.File[0].Open() + assert.NoError(t, err) + + buf, err := io.ReadAll(rc) + assert.NoError(t, err) + assert.Equal(t, "yee", string(buf)) +} + +func TestAppCommand_Swagger(t *testing.T) { + appName := "Test Graph" + appVersion := "Test Graph" + appDescription := "Test Graph" + producerFileName := "test.txt" + + outBuf := &bytes.Buffer{} + + app := generator.App{ + Name: appName, + Version: appVersion, + Description: appDescription, + Producers: map[string]nodes.NodeOutput[artifact.Artifact]{ + producerFileName: basics.NewTextNode(¶meter.String{ + Name: "Welp", + DefaultValue: "yee", + }), + }, + + Out: outBuf, + } + + // ACT ==================================================================== + err := app.Run([]string{"polyform", "swagger"}) + contents, readErr := io.ReadAll(outBuf) + + // ASSERT ================================================================= + assert.NoError(t, err) + assert.NoError(t, readErr) + assert.Equal(t, `{ + "swagger": "2.0", + "info": { + "title": "Test Graph", + "description": "Test Graph", + "version": "Test Graph" + }, + "paths": { + "/producer/value/test.txt": { + "post": { + "summary": "", + "description": "", + "produces": [], + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "Producer Payload" + } + }, + "parameters": [ + { + "in": "body", + "name": "Request", + "schema": { + "$ref": "#/definitions/TestTxtRequest" + } + } + ] + } + } + }, + "definitions": { + "TestTxtRequest": { + "type": "object", + "properties": { + "Welp": { + "type": "string", + "description": "" + } + } + } + } +}`, string(contents)) +} diff --git a/generator/cli/app.go b/generator/cli/app.go index fb753bc..9e7db66 100644 --- a/generator/cli/app.go +++ b/generator/cli/app.go @@ -3,15 +3,26 @@ package cli import ( "errors" "fmt" + "io" "os" ) type App struct { Commands []*Command + Out io.Writer ConfigProvided func(config string) error } func (a *App) Run(args []string) error { + + runState := &RunState{ + Out: a.Out, + } + + if a.Out == nil { + runState.Out = os.Stdout + } + commandMap := make(map[string]*Command) for _, cmd := range a.Commands { for _, alias := range cmd.Aliases { @@ -27,7 +38,8 @@ func (a *App) Run(args []string) error { firstArg := argsWithoutProg[0] if cmd, ok := commandMap[firstArg]; ok { - return cmd.Run(args[2:]) + runState.Args = args[2:] + return cmd.Run(runState) } if !fileExists(firstArg) { @@ -46,7 +58,8 @@ func (a *App) Run(args []string) error { firstArg = argsWithoutGraph[0] if cmd, ok := commandMap[firstArg]; ok { - return cmd.Run(argsWithoutGraph[1:]) + runState.Args = argsWithoutGraph[1:] + return cmd.Run(runState) } return fmt.Errorf("unrecognized command %s", firstArg) diff --git a/generator/cli/app_test.go b/generator/cli/app_test.go index fab6ed9..80aacb4 100644 --- a/generator/cli/app_test.go +++ b/generator/cli/app_test.go @@ -15,8 +15,8 @@ func TestRunCallsCommand(t *testing.T) { { Name: "command", Aliases: []string{"command", "c"}, - Run: func(args []string) error { - values = args + Run: func(appState *cli.RunState) error { + values = appState.Args return nil }, }, @@ -41,7 +41,7 @@ func TestRunCallsHelpOnUnknownCommand(t *testing.T) { { Name: "Help", Aliases: []string{"help"}, - Run: func(args []string) error { + Run: func(appState *cli.RunState) error { called = true return nil }, diff --git a/generator/cli/command.go b/generator/cli/command.go index d7982b2..c71d9cb 100644 --- a/generator/cli/command.go +++ b/generator/cli/command.go @@ -1,8 +1,15 @@ package cli +import "io" + type Command struct { Name string Description string Aliases []string - Run func(args []string) error + Run func(state *RunState) error +} + +type RunState struct { + Out io.Writer + Args []string } diff --git a/generator/html/js/node_manager.js b/generator/html/js/node_manager.js index b1f7208..4e27047 100644 --- a/generator/html/js/node_manager.js +++ b/generator/html/js/node_manager.js @@ -29,9 +29,9 @@ export class NodeManager { return; } - console.log(flowNode.metadata()) + // console.log(flowNode.metadata()) const nodeType = flowNode.metadata().typeData.type - console.log(nodeType, flowNode) + // console.log(nodeType, flowNode) this.app.RequestManager.createNode(nodeType, (resp) => { const isProducer = false; diff --git a/generator/html/js/nodes/node.js b/generator/html/js/nodes/node.js index c86c3c7..9475887 100644 --- a/generator/html/js/nodes/node.js +++ b/generator/html/js/nodes/node.js @@ -102,7 +102,7 @@ export class PolyNodeController { if (nodeData.metadata) { if (nodeData.metadata.position) { - console.log("setting position....", nodeData.metadata.position) + // console.log("setting position....", nodeData.metadata.position) this.flowNode.setPosition(nodeData.metadata.position); } } @@ -120,6 +120,9 @@ export class PolyNodeController { if (nodeData.parameter) { this.parameter = BuildParameter(flowNode, this.nodeManager, this.id, nodeData.parameter, this.app); + if(nodeData.parameter.description) { + flowNode.setInfo(nodeData.parameter.description); + } } // type TitleChangeCallback = (node: FlowNode, oldTitle: string, newTitle: string) => void @@ -143,6 +146,18 @@ export class PolyNodeController { } }); + + // Only parameters can change their info + if (!isProducer) { + this.flowNode.addInfoChangeListener((_, __, newTitle) => { + this.app.RequestManager.setParameterInfo( + this.flowNode.nodeInstanceID, + newTitle, + () => { } + ); + }); + } + if (this.isProducer) { const ext = getFileExtension(nodeData.name); if (ext === "png") { @@ -222,7 +237,7 @@ export class PolyNodeController { this.version = nodeData.version; this.dependencies = nodeData.dependencies; - console.log(nodeData); + // console.log(nodeData); if (nodeData.metadata) { if (nodeData.metadata.position) { this.flowNode.setPosition(nodeData.metadata.position); diff --git a/generator/html/js/requests.js b/generator/html/js/requests.js index 3922ea5..61f9c4d 100644 --- a/generator/html/js/requests.js +++ b/generator/html/js/requests.js @@ -154,6 +154,10 @@ class RequestManager { this.postTextBodyEmptyResponse("/parameter/name/"+inNodeID, value, callback); } + setParameterInfo(inNodeID, value, callback) { + this.postTextBodyEmptyResponse("/parameter/description/"+inNodeID, value, callback); + } + setProducerTitle(inNodeID, value, callback) { this.postTextBodyEmptyResponse("/producer/name/"+inNodeID, value, callback); } diff --git a/generator/html/server.html b/generator/html/server.html index 2db9071..01cbcb8 100644 --- a/generator/html/server.html +++ b/generator/html/server.html @@ -373,6 +373,7 @@ "parameters/int": { title: "Int Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -397,6 +398,7 @@ "parameters/float64": { title: "Float64 Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -421,6 +423,7 @@ "parameters/geometry.AABB": { title: "AABB Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -487,6 +490,7 @@ "parameters/image.Image": { title: "Image Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -510,6 +514,7 @@ "parameters/[]uint8": { title: "[]uint8 Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -533,6 +538,7 @@ "parameters/coloring.WebColor": { title: "Color Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -557,6 +563,7 @@ "parameters/vector3.Vector[float64]": { title: "Vector3 Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -593,6 +600,7 @@ "parameters/[]vector3.Vector[float64]": { title: "Vector3 Array Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -609,6 +617,7 @@ "parameters/vector2.Vector[float64]": { title: "Vector2 Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -639,6 +648,7 @@ "parameters/bool": { title: "Bool Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", @@ -663,6 +673,7 @@ "parameters/string": { title: "String Parameter", canEditTitle: true, + canEditInfo: true, outputs: [ { name: "Out", diff --git a/generator/parameter.go b/generator/parameter.go index 3dcffe3..9a107d7 100644 --- a/generator/parameter.go +++ b/generator/parameter.go @@ -12,6 +12,7 @@ type Parameter interface { Schema() schema.Parameter InitializeForCLI(set *flag.FlagSet) SetName(name string) + SetDescription(name string) ApplyMessage(msg []byte) (bool, error) ToMessage() []byte diff --git a/generator/parameter/value.go b/generator/parameter/value.go index d6c629a..783c5d6 100644 --- a/generator/parameter/value.go +++ b/generator/parameter/value.go @@ -56,6 +56,7 @@ type CliConfig[T any] struct { type parameterNodeGraphSchema[T any] struct { Name string `json:"name"` + Description string `json:"description"` CurrentValue T `json:"currentValue"` DefaultValue T `json:"defaultValue"` CLI *CliConfig[T] `json:"cli"` @@ -90,6 +91,10 @@ func (in *Value[T]) SetName(name string) { in.Name = name } +func (in *Value[T]) SetDescription(description string) { + in.Description = description +} + func (in *Value[T]) Port() string { return "Out" } @@ -147,6 +152,7 @@ func (pn *Value[T]) Value() T { func (pn *Value[T]) ToJSON(encoder *jbtf.Encoder) ([]byte, error) { return json.Marshal(parameterNodeGraphSchema[T]{ Name: pn.Name, + Description: pn.Description, CurrentValue: pn.Value(), DefaultValue: pn.DefaultValue, CLI: pn.CLI, @@ -161,6 +167,7 @@ func (pn *Value[T]) FromJSON(decoder jbtf.Decoder, body []byte) (err error) { } pn.Name = gn.Name + pn.Description = gn.Description pn.DefaultValue = gn.DefaultValue pn.CLI = gn.CLI pn.appliedProfile = &gn.CurrentValue @@ -172,8 +179,9 @@ func (pn *Value[T]) FromJSON(decoder jbtf.Decoder, body []byte) (err error) { func (pn *Value[T]) Schema() schema.Parameter { return ValueSchema[T]{ ParameterBase: schema.ParameterBase{ - Name: pn.Name, - Type: fmt.Sprintf("%T", *new(T)), + Name: pn.Name, + Description: pn.Description, + Type: fmt.Sprintf("%T", *new(T)), }, DefaultValue: pn.DefaultValue, CurrentValue: pn.Value(), @@ -255,7 +263,7 @@ func (pn Value[T]) SwaggerProperty() swagger.Property { Description: pn.Description, } switch any(pn).(type) { - case Value[string]: + case Value[string], *Value[string]: prop.Type = swagger.StringPropertyType case Value[time.Time]: @@ -287,9 +295,21 @@ func (pn Value[T]) SwaggerProperty() swagger.Property { case Value[vector3.Float64]: prop.Ref = "#/definitions/Vector3" + case Value[vector2.Float64]: + prop.Ref = "#/definitions/Vector2" + case Value[geometry.AABB]: prop.Ref = "#/definitions/AABB" + case Value[coloring.WebColor]: + prop.Type = swagger.StringPropertyType + + case Value[[]vector3.Float64]: + prop.Type = swagger.ArrayPropertyType + prop.Items = map[string]any{ + "$ref": "#/definitions/Vector3", + } + default: panic(fmt.Errorf("parameter node %s has a type that can not be converted to a swagger property. Please open up a issue on github.com/EliCDavis/polyform", pn.DisplayName())) } diff --git a/generator/parameter/value_test.go b/generator/parameter/value_test.go index a108b01..0fc89a9 100644 --- a/generator/parameter/value_test.go +++ b/generator/parameter/value_test.go @@ -15,6 +15,8 @@ func TestParameterNodeSwaggerProperty(t *testing.T) { input generator.SwaggerParameter propType swagger.PropertyType propFormat swagger.PropertyFormat + ref any + items any }{ "basic string parameter": { input: ¶meter.String{}, @@ -54,6 +56,30 @@ func TestParameterNodeSwaggerProperty(t *testing.T) { input: ¶meter.Bool{}, propType: swagger.BooleanPropertyType, }, + "vector2 parameter": { + input: ¶meter.Vector2{}, + ref: "#/definitions/Vector2", + }, + "vector3 parameter": { + input: ¶meter.Vector3{}, + ref: "#/definitions/Vector3", + }, + "aabb parameter": { + input: ¶meter.AABB{}, + ref: "#/definitions/AABB", + }, + "color": { + input: ¶meter.Color{}, + propType: swagger.StringPropertyType, + propFormat: "", + }, + "vector3 array parameter": { + input: ¶meter.Vector3Array{}, + propType: swagger.ArrayPropertyType, + items: map[string]any{ + "$ref": "#/definitions/Vector3", + }, + }, } for name, test := range tests { @@ -63,6 +89,8 @@ func TestParameterNodeSwaggerProperty(t *testing.T) { prop := test.input.SwaggerProperty() assert.Equal(t, test.propFormat, prop.Format) assert.Equal(t, test.propType, prop.Type) + assert.Equal(t, test.ref, prop.Ref) + assert.Equal(t, test.items, prop.Items) }) } } diff --git a/generator/schema/parameter.go b/generator/schema/parameter.go index 16b42c0..0f5c1f9 100644 --- a/generator/schema/parameter.go +++ b/generator/schema/parameter.go @@ -6,8 +6,9 @@ type Parameter interface { } type ParameterBase struct { - Name string `json:"name"` - Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description"` + Type string `json:"type"` } func (gps ParameterBase) DisplayName() string { diff --git a/generator/sync.go b/generator/sync.go index bc10000..f305829 100644 --- a/generator/sync.go +++ b/generator/sync.go @@ -51,7 +51,26 @@ func (sm *NestedSyncMap) OverwriteData(data map[string]any) { } else { sm.data = data } +} +func (sm *NestedSyncMap) PathExists(key string) bool { + sm.mutex.Lock() + defer sm.mutex.Unlock() + elements := strings.Split(key, ".") + current := sm.data + for i := 0; i < len(elements)-1; i++ { + v, ok := current[elements[i]] + if !ok { + return false + } + + casted, ok := v.(map[string]any) + if !ok { + return false + } + current = casted + } + return true } func (sm *NestedSyncMap) lookup(key string) (map[string]any, string) { diff --git a/generator/types.go b/generator/types.go index d94a529..0b16019 100644 --- a/generator/types.go +++ b/generator/types.go @@ -18,11 +18,3 @@ func RegisterTypes(typesToRegister *refutil.TypeFactory) { // log.Printf("Registered: %s\n", t) // } } - -func Nodes() *refutil.TypeFactory { - factory := &refutil.TypeFactory{} - return factory.Combine( - // parameter.Nodes(), - // artifact.Nodes(), - ) -}