Add Recipemanager that loads recipes from YAML
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[gd_resource type="CanvasItemMaterial" format=3 uid="uid://g4tjqwskvd62"]
|
||||
|
||||
[resource]
|
||||
@@ -0,0 +1,3 @@
|
||||
[gd_resource type="Resource" format=3 uid="uid://pxagv74g8x67"]
|
||||
|
||||
[resource]
|
||||
@@ -0,0 +1,21 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://cw7ukcablgkur"]
|
||||
|
||||
[ext_resource type="Material" uid="uid://g4tjqwskvd62" path="res://addons/yaml/examples/assets/material.tres" id="1_1ej5c"]
|
||||
[ext_resource type="Texture2D" uid="uid://duefsqwwnmduy" path="res://icon.svg" id="1_t3dkf"]
|
||||
|
||||
[node name="Sprite" type="Sprite2D"]
|
||||
material = ExtResource("1_1ej5c")
|
||||
texture = ExtResource("1_t3dkf")
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -155.0
|
||||
offset_top = -64.0
|
||||
offset_right = 27.0
|
||||
offset_bottom = -41.0
|
||||
grow_horizontal = 2
|
||||
text = "I was loaded with YAML"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
@@ -0,0 +1,53 @@
|
||||
class_name MyCustomClass extends Node
|
||||
|
||||
@export var string_val: String
|
||||
@export var int_val: int
|
||||
@export var float_val: float
|
||||
@export var color_val: Color
|
||||
|
||||
func _init(p_string := "", p_int := 0, p_float := 0.0, p_color = Color.WHITE) -> void:
|
||||
string_val = p_string
|
||||
int_val = p_int
|
||||
float_val = p_float
|
||||
color_val = p_color
|
||||
|
||||
func hello():
|
||||
print(string_val)
|
||||
|
||||
static func deserialize(data: Variant):
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return YAMLResult.error("Deserializing MyCustomClass expects Dictionary, received %s" % [type_string(typeof(data))])
|
||||
|
||||
var dict: Dictionary = data
|
||||
|
||||
if !dict.has("string_val"):
|
||||
return YAMLResult.error("Missing string_val field")
|
||||
if !dict.has("int_val"):
|
||||
return YAMLResult.error("Missing int_val field")
|
||||
if !dict.has("float_val"):
|
||||
return YAMLResult.error("Missing float_val field")
|
||||
if !dict.has("color_val"):
|
||||
return YAMLResult.error("Missing color_val field")
|
||||
|
||||
var string_val: String = dict.get("string_val")
|
||||
var int_val: int = dict.get("int_val")
|
||||
var float_val: float = dict.get("float_val")
|
||||
var color_val: Color = dict.get("color_val")
|
||||
|
||||
return MyCustomClass.new(
|
||||
string_val,
|
||||
int_val,
|
||||
float_val,
|
||||
color_val
|
||||
)
|
||||
|
||||
func serialize() -> Dictionary:
|
||||
return {
|
||||
"string_val": string_val,
|
||||
"int_val": int_val,
|
||||
"float_val": float_val,
|
||||
"color_val": color_val,
|
||||
}
|
||||
|
||||
func _to_string() -> String:
|
||||
return "MyCustomClass(%s)" % string_val
|
||||
@@ -0,0 +1 @@
|
||||
uid://c3bjbffcq7oc5
|
||||
@@ -0,0 +1,53 @@
|
||||
class_name MyCustomResource extends YAMLResource
|
||||
|
||||
@export var string_val: String
|
||||
@export var int_val: int
|
||||
@export var float_val: float
|
||||
@export var color_val: Color
|
||||
|
||||
func _init(p_string := "", p_int := 0, p_float := 0.0, p_color = Color.WHITE) -> void:
|
||||
string_val = p_string
|
||||
int_val = p_int
|
||||
float_val = p_float
|
||||
color_val = p_color
|
||||
|
||||
func hello():
|
||||
print(string_val)
|
||||
|
||||
static func deserialize(data: Variant):
|
||||
if typeof(data) != TYPE_DICTIONARY:
|
||||
return YAMLResult.error("Deserializing MyCustomResource expects Dictionary, received %s" % [type_string(typeof(data))])
|
||||
|
||||
var dict: Dictionary = data
|
||||
|
||||
if !dict.has("string_val"):
|
||||
return YAMLResult.error("Missing string_val field")
|
||||
if !dict.has("int_val"):
|
||||
return YAMLResult.error("Missing int_val field")
|
||||
if !dict.has("float_val"):
|
||||
return YAMLResult.error("Missing float_val field")
|
||||
if !dict.has("color_val"):
|
||||
return YAMLResult.error("Missing color_val field")
|
||||
|
||||
var string_val: String = dict.get("string_val")
|
||||
var int_val: int = dict.get("int_val")
|
||||
var float_val: float = dict.get("float_val")
|
||||
var color_val: Color = dict.get("color_val")
|
||||
|
||||
return MyCustomResource.new(
|
||||
string_val,
|
||||
int_val,
|
||||
float_val,
|
||||
color_val
|
||||
)
|
||||
|
||||
func serialize() -> Dictionary:
|
||||
return {
|
||||
"string_val": string_val,
|
||||
"int_val": int_val,
|
||||
"float_val": float_val,
|
||||
"color_val": color_val,
|
||||
}
|
||||
|
||||
func _to_string() -> String:
|
||||
return "MyCustomResource(%s)" % string_val
|
||||
@@ -0,0 +1 @@
|
||||
uid://dlwcjlb23nl02
|
||||
@@ -0,0 +1,18 @@
|
||||
class_name MyStringClass extends Object
|
||||
|
||||
@export var value: String
|
||||
|
||||
func _init(p_value := "") -> void:
|
||||
value = p_value
|
||||
|
||||
static func deserialize(data: Variant):
|
||||
if typeof(data) != TYPE_STRING:
|
||||
return YAMLResult.error("Deserializing MyStringClass expects String, received %s" % [type_string(typeof(data))])
|
||||
|
||||
return MyStringClass.new(data)
|
||||
|
||||
func serialize() -> String:
|
||||
return value
|
||||
|
||||
func _to_string() -> String:
|
||||
return "MyStringClass(%s)" % value
|
||||
@@ -0,0 +1 @@
|
||||
uid://b6ljbsllu8xqn
|
||||
@@ -0,0 +1,5 @@
|
||||
!ruby:object/MyCustomClass
|
||||
string_val: alternate tag
|
||||
int_val: 42
|
||||
float_val: 1.61803398875
|
||||
color_val: !Color 000000
|
||||
@@ -0,0 +1 @@
|
||||
uid://deqds0xtv6aoi
|
||||
@@ -0,0 +1,5 @@
|
||||
title: Document 1
|
||||
---
|
||||
title: Document 2
|
||||
---
|
||||
title: Document 3
|
||||
@@ -0,0 +1 @@
|
||||
uid://cwyofk32x3sba
|
||||
@@ -0,0 +1,16 @@
|
||||
# The $id string is used as an identifier
|
||||
$schema: "res://my_custom_class.yaml"
|
||||
|
||||
type: object
|
||||
properties:
|
||||
string_val:
|
||||
type: string
|
||||
minLength: 0
|
||||
maxLength: 5
|
||||
int_val:
|
||||
type: int
|
||||
max: 10
|
||||
float_val:
|
||||
type: float
|
||||
min: 1
|
||||
max: 999
|
||||
@@ -0,0 +1 @@
|
||||
uid://cpf0ajyys57i2
|
||||
@@ -0,0 +1,17 @@
|
||||
# The $id string is used as an identifier
|
||||
$id: "res://my_custom_class_schema.yaml"
|
||||
|
||||
type: object
|
||||
x-yaml-tag: foobar
|
||||
properties:
|
||||
string_val:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 5
|
||||
int_val:
|
||||
type: int
|
||||
max: 10
|
||||
float_val:
|
||||
type: float
|
||||
min: 0
|
||||
max: 999
|
||||
@@ -0,0 +1 @@
|
||||
uid://fkxxl0sy2xrm
|
||||
@@ -0,0 +1,7 @@
|
||||
title: Godot YAML
|
||||
author: FimbulWorks
|
||||
release_year: 2025
|
||||
features:
|
||||
- Speaks fluent Variant and can be taught custom classes
|
||||
- Strong document formatting capabilities
|
||||
- Blazingly fast
|
||||
@@ -0,0 +1 @@
|
||||
uid://dn5kwv25bhn5g
|
||||
@@ -0,0 +1,179 @@
|
||||
# Strings
|
||||
plain_string: Hello World
|
||||
single_quoted_string: 'Hello, ''World'''
|
||||
double_quoted_string: "Hello, World!"
|
||||
literal_string: |
|
||||
This is a long string
|
||||
that will preserve
|
||||
newlines
|
||||
folded_string: >
|
||||
This is a long string
|
||||
that will be folded
|
||||
into a single line
|
||||
|
||||
# Integer formats
|
||||
integer: 42
|
||||
hex: 0xff
|
||||
octal: 0o700
|
||||
binary: 0b1010
|
||||
|
||||
# Float formats
|
||||
float: 3.14159
|
||||
scientific_notation: 6.022e23
|
||||
infinity: .inf
|
||||
not_a_number: .nan
|
||||
|
||||
# Boolean types
|
||||
boolean_true: true
|
||||
boolean_false: false
|
||||
|
||||
# Null
|
||||
null_value: null
|
||||
null_value_2: ~
|
||||
|
||||
# Dates and times are treated as strings
|
||||
date: 2023-04-01
|
||||
canonical_datetime: 2023-04-01T12:00:00Z
|
||||
iso8601_datetime: 2023-04-01t12:00:00.000-05:00
|
||||
|
||||
# Arrays
|
||||
fruits:
|
||||
- apple
|
||||
- banana
|
||||
- cherry
|
||||
|
||||
nested_array:
|
||||
- - nested
|
||||
- items
|
||||
- - more
|
||||
- nested
|
||||
- items
|
||||
|
||||
## Dictionaries
|
||||
person:
|
||||
name: John Doe
|
||||
age: 30
|
||||
occupation: Developer
|
||||
|
||||
## Flow style array
|
||||
flow_style_array: [1, 2, 3, 4, 5]
|
||||
|
||||
## Flow style dictionary
|
||||
flow_style_dict: {key1: value1, key2: value2, key3: 124.5}
|
||||
|
||||
## Nested flow array
|
||||
nested_flow_style_array: ["hello", 10, {foo: bar}, [4.20, 6.9]]
|
||||
|
||||
## Nested flow dictionary
|
||||
nested_flow_style_dictionary: { key1: {foo: 'bar'}, key2: [4.20, 6.9] }
|
||||
|
||||
# Anchors and aliases get resolved during parsing
|
||||
anchor_example: &anchor_name
|
||||
key1: value1
|
||||
key2: value2
|
||||
|
||||
alias_example: *anchor_name
|
||||
|
||||
# Merge keys are also resolved during parsing
|
||||
base: &base
|
||||
name: John Doe
|
||||
age: 30
|
||||
|
||||
merge_example:
|
||||
<<: *base
|
||||
occupation: Developer
|
||||
|
||||
# Binary data - !!binary is converted to !PackedByteArray
|
||||
binary_data: !!binary |
|
||||
R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
|
||||
OTk6enp56enmleECcgggoBADs=
|
||||
|
||||
# Custom tags are preserved, and can describe Godot Variants,
|
||||
# and classes registered using YAML.register_class()
|
||||
game_object: !MyHeroClass
|
||||
name: "Hero"
|
||||
health: 100
|
||||
inventory:
|
||||
- sword
|
||||
- shield
|
||||
- potion_healing
|
||||
|
||||
# Variants
|
||||
AABB: !AABB
|
||||
position: {x: 1,y: 2,z: 4}
|
||||
size: {x: 8,y: 16,z: 32}
|
||||
Basis: !Basis
|
||||
x: {x: 1,y: 2,z: 4}
|
||||
y: {x: 8,y: 16,z: 32}
|
||||
z: {x: 64,y: 128,z: 256}
|
||||
Color: !Color ff804080
|
||||
NodePath: !NodePath "root/level/player"
|
||||
PackedByteArray: !PackedByteArray |
|
||||
AQIECA==
|
||||
PackedColorArray: !PackedColorArray
|
||||
- ff0000
|
||||
- 00ff00
|
||||
- 0000ff
|
||||
- red
|
||||
- green
|
||||
- blue
|
||||
PackedFloat32Array: !PackedFloat32Array
|
||||
- 3.1415927
|
||||
- 6.2831855
|
||||
- 12.566371
|
||||
PackedFloat64Array: !PackedFloat64Array
|
||||
- 3.141592653589793
|
||||
- 6.283185307179586
|
||||
- 12.566370614359172
|
||||
PackedInt32Array: !PackedInt32Array
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 8
|
||||
PackedInt64Array: !PackedInt64Array
|
||||
- 1
|
||||
- 2
|
||||
- 4
|
||||
- 8
|
||||
PackedStringArray: !PackedStringArray
|
||||
- "one"
|
||||
- "one\ntwo"
|
||||
- "one\ntwo\nthree"
|
||||
PackedVector2Array: !PackedVector2Array
|
||||
- {x: 1,y: 2}
|
||||
- {x: 4,y: 8}
|
||||
PackedVector3Array: !PackedVector3Array
|
||||
- {x: 1,y: 2,z: 4}
|
||||
- {x: 8,y: 16,z: 32}
|
||||
Plane: !Plane
|
||||
normal: {x: 1,y: 2,z: 4}
|
||||
d: 3.1415927
|
||||
Projection: !Projection
|
||||
x: {x: 1,y: 2,z: 4,w: 8}
|
||||
y: {x: 16,y: 32,z: 64,w: 128}
|
||||
z: {x: 256,y: 512,z: 1024,w: 2048}
|
||||
w: {x: 4096,y: 8192,z: 16384,w: 32768}
|
||||
Quaternion: !Quaternion {x: 3.1415927,y: 6.2831855,z: 12.566371,w: 25.132742}
|
||||
Rect2: !Rect2
|
||||
position: {x: 1,y: 2}
|
||||
size: {x: 4,y: 8}
|
||||
Rect2i: !Rect2i
|
||||
position: {x: 2,y: 4}
|
||||
size: {x: 8,y: 16}
|
||||
StringName: !StringName test_string_name
|
||||
Transform2D: !Transform2D
|
||||
x: {x: -1,y: -8.742278e-08}
|
||||
y: {x: 8.742278e-08,y: -1}
|
||||
origin: {x: 6.2831855,y: 12.566371}
|
||||
Transform3D: !Transform3D
|
||||
basis:
|
||||
x: {x: 1,y: 0,z: 0}
|
||||
y: {x: 0,y: 1,z: 0}
|
||||
z: {x: 0,y: 0,z: 1}
|
||||
origin: {x: 1,y: 2,z: 4}
|
||||
Vector2: !Vector2 {x: 1,y: 2}
|
||||
Vector2i: !Vector2i {x: 2,y: 4}
|
||||
Vector3: !Vector3 {x: 1,y: 2,z: 4}
|
||||
Vector3i: !Vector3i {x: 2,y: 4,z: 8}
|
||||
Vector4: !Vector4 {x: 1,y: 2,z: 4,w: 8}
|
||||
Vector4i: !Vector4i {x: 2,y: 4,z: 8,w: 16}
|
||||
@@ -0,0 +1 @@
|
||||
uid://b24xb1mhxmg8i
|
||||
@@ -0,0 +1,50 @@
|
||||
$schema: "res://addons/yaml/examples/data/fimbul.generator.schema.yaml"
|
||||
params:
|
||||
x: float
|
||||
y: float
|
||||
noiseScale: float
|
||||
noise2D: Callable
|
||||
|
||||
functions:
|
||||
- name: continentShape
|
||||
params: ["x", "y"]
|
||||
returns: float
|
||||
code: abs(cos(x * PI * 2 + PI * 0.5) * sin(y * PI))
|
||||
|
||||
- name: heightNoise
|
||||
params: ["x", "y", "noiseScale", "noise2D"]
|
||||
returns: float
|
||||
code: noise2D(x * noiseScale, y * noiseScale) * 0.5 + 0.5
|
||||
|
||||
- name: height
|
||||
dependencies: ["continentShape", "heightNoise"]
|
||||
returns: float
|
||||
code: continentShape * heightNoise
|
||||
|
||||
- name: temperature
|
||||
params: ["y"]
|
||||
dependencies: ["height"]
|
||||
returns: float
|
||||
code: y - (height - 0.4) * 2 if height > 0.4 else y
|
||||
|
||||
- name: precipitation
|
||||
params: ["y"]
|
||||
dependencies: ["temperature"]
|
||||
returns: float
|
||||
code: 1 - temperature
|
||||
|
||||
- name: biome
|
||||
returns: String
|
||||
dependencies: ["height", "temperature", "precipitation"]
|
||||
code: |
|
||||
if height < 0.2023:
|
||||
return 'ocean'
|
||||
if temperature >= 0.666:
|
||||
return 'desert'
|
||||
if temperature > 0.42 && precipitation > 0.42:
|
||||
return 'rainforest'
|
||||
if temperature > 0.3 && precipitation > 0.3:
|
||||
return 'forest'
|
||||
if temperature <= 0.21:
|
||||
return 'tundra'
|
||||
return 'meadows'
|
||||
@@ -0,0 +1 @@
|
||||
uid://drfjm1uj06e4x
|
||||
@@ -0,0 +1,66 @@
|
||||
class_name ExampleBase extends Node2D
|
||||
|
||||
# Whether to show detailed logs
|
||||
var LOG_VERBOSE := false
|
||||
|
||||
# Extra emoji to make logs visually distinct
|
||||
var icon := ""
|
||||
|
||||
# Hook that runs all examples in the class
|
||||
func _ready() -> void:
|
||||
if !visible:
|
||||
return
|
||||
|
||||
print_rich("\n[b][font_size=16]%s%s[/font_size][/b]" % [
|
||||
"%s " % icon if icon.length() > 0 else "",
|
||||
name
|
||||
])
|
||||
|
||||
run_examples()
|
||||
|
||||
# Override this to run examples in your class
|
||||
func run_examples() -> void:
|
||||
# Child classes should override this method
|
||||
pass
|
||||
|
||||
# Logging Helpers
|
||||
func log_header(text: String) -> void:
|
||||
print_rich("\n[b][font_size=16]%s[/font_size][/b]" % text)
|
||||
|
||||
func log_subheader(text: String) -> void:
|
||||
print_rich("\n[b][font_size=14]%s[/font_size][/b]" % text)
|
||||
|
||||
func log_success(text: Variant) -> void:
|
||||
print_rich("[color=green]✅ %s[/color]" % str(text))
|
||||
|
||||
func log_error(text: Variant) -> void:
|
||||
print_rich("[color=red]❌ %s[/color]" % str(text))
|
||||
|
||||
func log_warning(text: Variant) -> void:
|
||||
print_rich("[color=yellow]⚠️ %s[/color]" % str(text))
|
||||
|
||||
func log_info(text: Variant) -> void:
|
||||
print_rich("%s" % str(text))
|
||||
|
||||
func log_code_block(code: String) -> void:
|
||||
print_rich("\n[b]Code:[/b]")
|
||||
print_rich("[color=#aaaaff]%s" % code)
|
||||
|
||||
func log_result(text: Variant) -> void:
|
||||
print_rich("\n[b]Result:[/b]\n%s" % str(text))
|
||||
|
||||
# Run a single example with timing
|
||||
func run_example(title: String, method: Callable) -> void:
|
||||
print_rich("\n[b][font_size=14]%s[/font_size][/b]" % title)
|
||||
var start_time := Time.get_ticks_usec()
|
||||
|
||||
method.call()
|
||||
|
||||
var elapsed := Time.get_ticks_usec() - start_time
|
||||
var t: float = elapsed
|
||||
var tl = "µsec"
|
||||
if t > 1000:
|
||||
t /= 1000.0
|
||||
tl = "ms"
|
||||
|
||||
print_rich("\n[color=#888888]Completed in %.2f %s[/color]" % [t, tl])
|
||||
@@ -0,0 +1 @@
|
||||
uid://bfxd6dh800cuf
|
||||
@@ -0,0 +1,140 @@
|
||||
extends ExampleBase
|
||||
|
||||
const YAML_FILE = "res://addons/yaml/examples/data/supported_syntax.yaml"
|
||||
const OUTPUT_FILE = "user://supported_syntax_copy.yaml"
|
||||
|
||||
var yaml_text := """
|
||||
string: string_value
|
||||
number: 1234
|
||||
list:
|
||||
- apples
|
||||
- oranges
|
||||
"""
|
||||
|
||||
var data
|
||||
|
||||
func _init() -> void:
|
||||
icon = "📝"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Validate YAML String Syntax", validate_yaml_string_syntax)
|
||||
run_example("Parse YAML Text", parse_yaml_text)
|
||||
run_example("Stringify Data", stringify_data)
|
||||
run_example("Validate File Syntax", validate_file_syntax)
|
||||
run_example("Load File", load_file)
|
||||
run_example("Save File", save_file)
|
||||
run_example("Load Saved File", load_saved_file)
|
||||
|
||||
func validate_yaml_string_syntax() -> void:
|
||||
log_code_block(yaml_text)
|
||||
|
||||
log_info("Validating YAML string...")
|
||||
var result := YAML.validate_syntax(yaml_text)
|
||||
|
||||
if result.has_error():
|
||||
log_error("Validation failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("YAML is valid!")
|
||||
|
||||
func parse_yaml_text() -> void:
|
||||
log_info("Parsing YAML text...")
|
||||
var result := YAML.parse(yaml_text)
|
||||
|
||||
if result.has_error():
|
||||
log_error("Parse failed: " + result.get_error())
|
||||
return
|
||||
|
||||
data = result.get_data()
|
||||
log_success("YAML parsed successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result(str(data))
|
||||
|
||||
log_info("Accessing data values:")
|
||||
log_info("• string value: " + data.string)
|
||||
log_info("• number value: " + str(data.number))
|
||||
log_info("• list items: " + str(data.list))
|
||||
|
||||
func stringify_data() -> void:
|
||||
# First ensure we have data to stringify
|
||||
if data == null:
|
||||
data = YAML.parse(yaml_text).get_data()
|
||||
|
||||
log_info("Converting data structure to YAML string...")
|
||||
var result := YAML.stringify(data)
|
||||
|
||||
if result.has_error():
|
||||
log_error("Stringify failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("Data converted to YAML successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result(result.get_data())
|
||||
|
||||
# Verify the round trip
|
||||
var yaml_output = result.get_data().strip_edges()
|
||||
var original = yaml_text.strip_edges()
|
||||
if yaml_output == original:
|
||||
log_success("Round-trip verification: Output matches original")
|
||||
else:
|
||||
log_warning("Round-trip produced different output (semantically equivalent)")
|
||||
log_info("Original:\n" + original)
|
||||
log_info("Output:\n" + yaml_output)
|
||||
|
||||
func validate_file_syntax() -> void:
|
||||
log_info("Validating YAML file: " + YAML_FILE)
|
||||
var result := YAML.validate_file_syntax(YAML_FILE)
|
||||
|
||||
if result.has_error():
|
||||
log_error("File validation failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("YAML file is valid")
|
||||
|
||||
func load_file() -> void:
|
||||
log_info("Loading YAML file: " + YAML_FILE)
|
||||
var result := YAML.load_file(YAML_FILE)
|
||||
|
||||
if result.has_error():
|
||||
log_error("File loading failed: " + result.get_error())
|
||||
return
|
||||
|
||||
data = result.get_data()
|
||||
log_success("YAML file loaded successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_info("File contains " + str(data.size()) + " keys")
|
||||
log_result(str(data).substr(0, 500) + "...\n(output truncated)")
|
||||
|
||||
func save_file() -> void:
|
||||
# Make sure we have data
|
||||
if data == null:
|
||||
data = {"example": "data", "created": "now", "values": [1, 2, 3]}
|
||||
|
||||
log_info("Saving data to YAML file: " + OUTPUT_FILE)
|
||||
var result := YAML.save_file(data, OUTPUT_FILE)
|
||||
|
||||
if result.has_error():
|
||||
log_error("File saving failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("Data saved to YAML file successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result(result.get_data())
|
||||
|
||||
func load_saved_file() -> void:
|
||||
log_info("Loading previously saved file: " + OUTPUT_FILE)
|
||||
var result := YAML.load_file(OUTPUT_FILE)
|
||||
|
||||
if result.has_error():
|
||||
log_error("File loading failed: " + result.get_error())
|
||||
return
|
||||
|
||||
var loaded_data = result.get_data()
|
||||
log_success("Saved file loaded successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result(str(loaded_data))
|
||||
@@ -0,0 +1 @@
|
||||
uid://cemrjp34s7ujk
|
||||
@@ -0,0 +1,239 @@
|
||||
extends ExampleBase
|
||||
|
||||
func _init() -> void:
|
||||
icon = "🧩"
|
||||
LOG_VERBOSE = true
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# Register our custom classes when the node enters the tree
|
||||
YAML.register_class(MyCustomClass, "serialize", "deserialize", "ruby/object:MyCustomClass")
|
||||
YAML.register_class(MyCustomResource)
|
||||
YAML.register_class(MyStringClass)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
# Clean up registrations when the node exits the tree
|
||||
YAML.unregister_class(MyCustomClass)
|
||||
YAML.unregister_class(MyCustomResource)
|
||||
YAML.unregister_class(MyStringClass)
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Custom Node Class", custom_node_class)
|
||||
run_example("Custom Class Errors", custom_class_errors)
|
||||
run_example("String-Based Custom Class", custom_string_class)
|
||||
run_example("Custom Resource Class", custom_resource)
|
||||
run_example("Custom Resource Errors", custom_resource_errors)
|
||||
run_example("Class Registration Management", class_registration_management)
|
||||
|
||||
func custom_node_class() -> void:
|
||||
log_info("Creating instance of MyCustomClass...")
|
||||
var object = MyCustomClass.new("hello world", 123, PI)
|
||||
|
||||
log_info("Stringifying custom class instance to YAML...")
|
||||
var str_result := YAML.stringify(object)
|
||||
|
||||
if str_result.has_error():
|
||||
log_error("Stringify failed: " + str_result.get_error())
|
||||
return
|
||||
|
||||
var yaml_text: String = str_result.get_data()
|
||||
log_success("Custom class stringified successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_info("MyCustomClass as YAML:\n" + yaml_text)
|
||||
|
||||
log_info("Parsing YAML back into MyCustomClass...")
|
||||
var parse_result := YAML.parse(yaml_text)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_error("Parse failed: " + parse_result.get_error())
|
||||
return
|
||||
|
||||
var obj: MyCustomClass = parse_result.get_data()
|
||||
|
||||
if obj is MyCustomClass:
|
||||
log_success("YAML parsed back into MyCustomClass")
|
||||
log_info("string_val: " + obj.string_val)
|
||||
log_info("int_val: " + str(obj.int_val))
|
||||
log_info("float_val: " + str(obj.float_val))
|
||||
log_info("color_val: " + str(obj.color_val))
|
||||
else:
|
||||
log_error("Failed to parse back into MyCustomClass")
|
||||
|
||||
func custom_class_errors() -> void:
|
||||
log_info("Testing error handling with invalid MyCustomClass YAML...")
|
||||
|
||||
# Missing required field
|
||||
var yaml_text = """
|
||||
!MyCustomClass
|
||||
string_val: foo
|
||||
"""
|
||||
log_code_block(yaml_text)
|
||||
log_info("Attempting to parse with missing required fields...")
|
||||
|
||||
var result := YAML.parse(yaml_text)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Correctly detected missing field error")
|
||||
log_info("Error message: " + result.get_error())
|
||||
else:
|
||||
log_error("Failed to detect missing required field")
|
||||
|
||||
# Invalid data structure
|
||||
var invalid_yaml_text = """
|
||||
!MyCustomClass
|
||||
[1, 2, 3]
|
||||
"""
|
||||
log_code_block(invalid_yaml_text)
|
||||
log_info("Attempting to parse with wrong data structure...")
|
||||
|
||||
var bad_result := YAML.parse(invalid_yaml_text)
|
||||
|
||||
if bad_result.has_error():
|
||||
log_success("Correctly detected wrong data structure error")
|
||||
log_info("Error message: " + bad_result.get_error())
|
||||
else:
|
||||
log_error("Failed to detect wrong data structure")
|
||||
|
||||
func custom_string_class() -> void:
|
||||
log_info("Creating instance of string-based MyStringClass...")
|
||||
var object = MyStringClass.new("hello world")
|
||||
|
||||
log_info("Stringifying string-based class to YAML...")
|
||||
var str_result := YAML.stringify(object)
|
||||
|
||||
if str_result.has_error():
|
||||
log_error("Stringify failed: " + str_result.get_error())
|
||||
return
|
||||
|
||||
var text: String = str_result.get_data()
|
||||
log_success("String-based class stringified successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("MyStringClass as YAML:\n" + text)
|
||||
|
||||
log_info("Parsing YAML back into MyStringClass...")
|
||||
var parse_result := YAML.parse(text)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_error("Parse failed: " + parse_result.get_error())
|
||||
return
|
||||
|
||||
var obj = parse_result.get_data()
|
||||
|
||||
if obj is MyStringClass:
|
||||
log_success("YAML parsed back into MyStringClass")
|
||||
log_info("Value: " + obj.value)
|
||||
else:
|
||||
log_error("Failed to parse back into MyStringClass")
|
||||
|
||||
func custom_resource() -> void:
|
||||
log_info("Creating instance of MyCustomResource...")
|
||||
var resource = MyCustomResource.new("I am resource", 42, 69.69)
|
||||
|
||||
log_info("Stringifying resource class to YAML...")
|
||||
var str_result := YAML.stringify(resource)
|
||||
|
||||
if str_result.has_error():
|
||||
log_error("Stringify failed: " + str_result.get_error())
|
||||
return
|
||||
|
||||
var yaml_text: String = str_result.get_data()
|
||||
log_success("Resource class stringified successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("MyCustomResource as YAML:\n" + yaml_text)
|
||||
|
||||
log_info("Parsing YAML back into MyCustomResource...")
|
||||
var parse_result := YAML.parse(yaml_text)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_error("Parse failed: " + parse_result.get_error())
|
||||
return
|
||||
|
||||
var obj = parse_result.get_data()
|
||||
|
||||
if obj is MyCustomResource:
|
||||
log_success("YAML parsed back into MyCustomResource")
|
||||
log_info("string_val: " + obj.string_val)
|
||||
log_info("int_val: " + str(obj.int_val))
|
||||
log_info("float_val: " + str(obj.float_val))
|
||||
log_info("color_val: " + str(obj.color_val))
|
||||
else:
|
||||
log_error("Failed to parse back into MyCustomResource")
|
||||
|
||||
func custom_resource_errors() -> void:
|
||||
log_info("Testing error handling with invalid MyCustomResource YAML...")
|
||||
|
||||
# Missing required field
|
||||
var yaml_text = """
|
||||
!MyCustomResource
|
||||
color_val: black
|
||||
"""
|
||||
log_code_block(yaml_text)
|
||||
log_info("Attempting to parse with missing required fields...")
|
||||
|
||||
var result := YAML.parse(yaml_text)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Correctly detected missing field error")
|
||||
log_info("Error message: " + result.get_error())
|
||||
else:
|
||||
log_error("Failed to detect missing required field")
|
||||
|
||||
# Invalid data structure
|
||||
var invalid_yaml_text = """
|
||||
!MyCustomResource
|
||||
[1, 2, 3]
|
||||
"""
|
||||
log_code_block(invalid_yaml_text)
|
||||
log_info("Attempting to parse with wrong data structure...")
|
||||
|
||||
var bad_result := YAML.parse(invalid_yaml_text)
|
||||
|
||||
if bad_result.has_error():
|
||||
log_success("Correctly detected wrong data structure error")
|
||||
log_info("Error message: " + bad_result.get_error())
|
||||
else:
|
||||
log_error("Failed to detect wrong data structure")
|
||||
|
||||
func class_registration_management() -> void:
|
||||
log_subheader("Class Registration Management")
|
||||
|
||||
log_info("Checking if classes are registered...")
|
||||
|
||||
if YAML.has_registered_class("MyCustomClass2"):
|
||||
log_success("MyCustomClass is registered")
|
||||
else:
|
||||
log_error("MyCustomClass is not registered properly")
|
||||
|
||||
if YAML.has_registered_class("MyCustomResource"):
|
||||
log_success("MyCustomResource is registered")
|
||||
else:
|
||||
log_error("MyCustomResource is not registered properly")
|
||||
|
||||
if YAML.has_registered_class("MyStringClass"):
|
||||
log_success("MyStringClass is registered")
|
||||
else:
|
||||
log_error("MyStringClass is not registered properly")
|
||||
|
||||
log_info("\nExample of registering with custom methods:")
|
||||
var code = """
|
||||
# Register with custom method names
|
||||
YAML.register_class(MyCustomClass, "to_yaml", "from_yaml")
|
||||
|
||||
# Methods in the class would then be:
|
||||
func to_yaml():
|
||||
# Custom serialization code
|
||||
return {...}
|
||||
|
||||
static func from_yaml(data):
|
||||
# Custom deserialization code
|
||||
return MyCustomClass.new(...)
|
||||
"""
|
||||
log_code_block(code)
|
||||
|
||||
log_info("\nBest practices for class registration:")
|
||||
log_info("1. Register classes at startup in an autoload/singleton")
|
||||
log_info("2. Use consistent naming for serialize/deserialize methods")
|
||||
log_info("3. Implement thorough validation in deserialize methods")
|
||||
log_info("4. For scripts that might be unloaded, register in _enter_tree and unregister in _exit_tree")
|
||||
@@ -0,0 +1 @@
|
||||
uid://ch12k840j6ak6
|
||||
@@ -0,0 +1,161 @@
|
||||
extends ExampleBase
|
||||
|
||||
func _init() -> void:
|
||||
icon = "❌"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Invalid Indentation", invalid_indentation)
|
||||
run_example("Unmatched Quotes", unmatched_quotes)
|
||||
run_example("Circular Reference", circular_reference)
|
||||
run_example("Validation Example", validation_example)
|
||||
run_example("Error Details", error_details)
|
||||
run_example("Error Handling Patterns", error_handling_patterns)
|
||||
|
||||
func invalid_indentation() -> void:
|
||||
var invalid_yaml := """
|
||||
key: value
|
||||
indentation: wrong
|
||||
"""
|
||||
log_code_block(invalid_yaml)
|
||||
log_info("Parsing YAML with invalid indentation...")
|
||||
|
||||
var result := YAML.parse(invalid_yaml)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Correctly detected error: " + result.get_error())
|
||||
log_info("Line: " + str(result.get_error_line()))
|
||||
log_info("Column: " + str(result.get_error_column()))
|
||||
return
|
||||
|
||||
log_error("Failed to detect invalid indentation")
|
||||
|
||||
func unmatched_quotes() -> void:
|
||||
var unmatched_quotes := """
|
||||
message: "This quote is not closed
|
||||
next_line: value
|
||||
"""
|
||||
log_code_block(unmatched_quotes)
|
||||
log_info("Parsing YAML with unmatched quotes...")
|
||||
|
||||
var result = YAML.parse(unmatched_quotes)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Correctly detected error: " + result.get_error())
|
||||
return
|
||||
|
||||
log_error("Failed to detect unmatched quotes")
|
||||
|
||||
func circular_reference() -> void:
|
||||
log_info("Creating circular reference in data structure...")
|
||||
|
||||
# Create a circular reference
|
||||
var dict1 = {}
|
||||
var dict2 = {"ref": dict1}
|
||||
dict1["circular"] = dict2
|
||||
|
||||
log_info("Attempting to stringify circular reference...")
|
||||
var result := YAML.stringify(dict1)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Correctly detected error: " + result.get_error())
|
||||
return
|
||||
|
||||
log_error("Failed to detect circular reference")
|
||||
|
||||
func validation_example() -> void:
|
||||
var invalid_yaml := """
|
||||
key: value
|
||||
- invalid
|
||||
list
|
||||
format
|
||||
"""
|
||||
log_code_block(invalid_yaml)
|
||||
log_info("Validating incorrect YAML...")
|
||||
|
||||
var result = YAML.validate_syntax(invalid_yaml)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Validation correctly detected error: " + result.get_error())
|
||||
return
|
||||
|
||||
log_error("Validation failed to detect invalid YAML")
|
||||
|
||||
func error_details() -> void:
|
||||
var yaml_with_error := """
|
||||
valid_line: value
|
||||
- invalid line: value
|
||||
another_line: value
|
||||
"""
|
||||
log_code_block(yaml_with_error)
|
||||
log_info("Examining error details...")
|
||||
|
||||
var result := YAML.parse(yaml_with_error)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Error detected: " + result.get_error())
|
||||
|
||||
# Show detailed error information
|
||||
log_info("Error message: " + result.get_error_message())
|
||||
log_info("Error line: " + str(result.get_error_line()))
|
||||
log_info("Error column: " + str(result.get_error_column()))
|
||||
|
||||
# Highlight the error line
|
||||
var yaml_lines = yaml_with_error.split("\n")
|
||||
if result.get_error_line() > 0 and result.get_error_line() <= yaml_lines.size():
|
||||
var error_line = yaml_lines[result.get_error_line() - 1]
|
||||
log_info("Line content: " + error_line)
|
||||
|
||||
if result.get_error_column() > 0:
|
||||
var pointer = " ".repeat(result.get_error_column() - 1) + "^"
|
||||
log_info(pointer + " Error position")
|
||||
return
|
||||
|
||||
log_error("Failed to detect error")
|
||||
|
||||
func error_handling_patterns() -> void:
|
||||
log_subheader("Error Handling Patterns")
|
||||
|
||||
# Pattern 1: Try-parse pattern
|
||||
log_info("Pattern 1: Using try_parse for simplified error handling")
|
||||
var yaml_text = """
|
||||
name: Example
|
||||
valid: true
|
||||
"""
|
||||
var data = YAML.try_parse(yaml_text)
|
||||
if data:
|
||||
log_success("try_parse succeeded")
|
||||
log_info("Data: " + str(data))
|
||||
else:
|
||||
log_error("try_parse failed")
|
||||
|
||||
# Pattern 2: get_error vs get_error_message
|
||||
log_info("\nPattern 2: Detailed vs Simple Error Messages")
|
||||
var invalid_yaml = "key: [invalid"
|
||||
var result = YAML.parse(invalid_yaml)
|
||||
if result.has_error():
|
||||
log_info("Detailed error: " + result.get_error())
|
||||
log_info("Simple message: " + result.get_error_message())
|
||||
|
||||
# Pattern 3: Creating custom errors
|
||||
log_info("\nPattern 3: Creating custom validation errors")
|
||||
|
||||
var config_yaml = """
|
||||
name: MyApp
|
||||
# version is missing
|
||||
"""
|
||||
var parsed = YAML.parse(config_yaml)
|
||||
if !parsed.has_error():
|
||||
var validation = validate_config(parsed.get_data())
|
||||
if validation.has_error():
|
||||
log_success("Custom validation error: " + validation.get_error_message())
|
||||
else:
|
||||
log_info("Config is valid")
|
||||
else:
|
||||
log_error("Parse error: " + parsed.get_error())
|
||||
|
||||
func validate_config(data):
|
||||
if !data.has("name"):
|
||||
return YAMLResult.error("Configuration missing 'name' field")
|
||||
if !data.has("version"):
|
||||
return YAMLResult.error("Configuration missing 'version' field")
|
||||
return data
|
||||
@@ -0,0 +1 @@
|
||||
uid://d015opxjbyce4
|
||||
@@ -0,0 +1,258 @@
|
||||
extends ExampleBase
|
||||
|
||||
const MULTI_DOC_FILE = "res://addons/yaml/examples/data/multi_document.yaml"
|
||||
const OUTPUT_FILE = "user://multi_document_copy.yaml"
|
||||
|
||||
var multi_doc_yaml := """
|
||||
# Configuration Document
|
||||
name: MyApplication
|
||||
version: 1.2.3
|
||||
environment: production
|
||||
---
|
||||
# Database Settings
|
||||
database:
|
||||
host: localhost
|
||||
port: 5432
|
||||
name: myapp_db
|
||||
credentials:
|
||||
username: admin
|
||||
password: secret123
|
||||
---
|
||||
# Feature Flags
|
||||
features:
|
||||
enable_new_ui: true
|
||||
enable_analytics: false
|
||||
enable_caching: true
|
||||
max_connections: 100
|
||||
---
|
||||
# Logging Configuration
|
||||
logging:
|
||||
level: INFO
|
||||
handlers:
|
||||
- console
|
||||
- file
|
||||
file_path: /var/log/myapp.log
|
||||
""".replace(" ", " ") # Handle Godot's tab indentation
|
||||
|
||||
var parsed_documents
|
||||
|
||||
func _init() -> void:
|
||||
icon = "📑"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Validate Multi-Document YAML", validate_multi_document)
|
||||
run_example("Parse Multi-Document YAML", parse_multi_document)
|
||||
run_example("Access Individual Documents", access_individual_documents)
|
||||
run_example("Work with Document Count", work_with_document_count)
|
||||
run_example("Process All Documents", process_all_documents)
|
||||
run_example("Create Multi-Document YAML", create_multi_document)
|
||||
run_example("Save Multi-Document File", save_multi_document_file)
|
||||
run_example("Load Multi-Document File", load_multi_document_file)
|
||||
|
||||
func validate_multi_document() -> void:
|
||||
log_info("Validating multi-document YAML string...")
|
||||
var result := YAML.validate_syntax(multi_doc_yaml)
|
||||
|
||||
if result.has_error():
|
||||
log_error("Validation failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("Multi-document YAML is valid!")
|
||||
|
||||
func parse_multi_document() -> void:
|
||||
log_info("Parsing multi-document YAML...")
|
||||
var result := YAML.parse(multi_doc_yaml)
|
||||
|
||||
if result.has_error():
|
||||
log_error("Parse failed: " + result.get_error())
|
||||
return
|
||||
|
||||
parsed_documents = result
|
||||
log_success("Multi-document YAML parsed successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_info("Has multiple documents: %s" % result.has_multiple_documents())
|
||||
log_info("Found " + str(result.get_document_count()) + " documents")
|
||||
|
||||
func access_individual_documents() -> void:
|
||||
# Ensure we have parsed documents
|
||||
if parsed_documents == null:
|
||||
parsed_documents = YAML.parse(multi_doc_yaml)
|
||||
|
||||
log_info("Accessing individual documents...")
|
||||
|
||||
# Access first document (configuration)
|
||||
var config_doc = parsed_documents.get_document(0)
|
||||
if config_doc != null:
|
||||
log_success("Document 0 - Configuration:")
|
||||
log_info("• App name: " + str(config_doc.name))
|
||||
log_info("• Version: " + str(config_doc.version))
|
||||
log_info("• Environment: " + str(config_doc.environment))
|
||||
|
||||
# Access second document (database settings)
|
||||
var db_doc = parsed_documents.get_document(1)
|
||||
if db_doc != null:
|
||||
log_success("Document 1 - Database Settings:")
|
||||
log_info("• Host: " + str(db_doc.database.host))
|
||||
log_info("• Port: " + str(db_doc.database.port))
|
||||
log_info("• Database: " + str(db_doc.database.name))
|
||||
|
||||
# Access third document (feature flags)
|
||||
var features_doc = parsed_documents.get_document(2)
|
||||
if features_doc != null:
|
||||
log_success("Document 2 - Feature Flags:")
|
||||
log_info("• New UI enabled: " + str(features_doc.features.enable_new_ui))
|
||||
log_info("• Analytics enabled: " + str(features_doc.features.enable_analytics))
|
||||
log_info("• Max connections: " + str(features_doc.features.max_connections))
|
||||
|
||||
# Try to access non-existent document
|
||||
var non_existent = parsed_documents.get_document(10)
|
||||
if non_existent == null:
|
||||
log_info("Document 10 (non-existent): null")
|
||||
|
||||
func work_with_document_count() -> void:
|
||||
if parsed_documents == null:
|
||||
parsed_documents = YAML.parse(multi_doc_yaml)
|
||||
|
||||
var count = parsed_documents.get_document_count()
|
||||
log_info("Total document count: " + str(count))
|
||||
|
||||
log_info("Iterating through all documents by index:")
|
||||
for i in range(count):
|
||||
var doc = parsed_documents.get_document(i)
|
||||
var doc_type = "Unknown"
|
||||
|
||||
# Identify document type by its content
|
||||
if doc.has("name") and doc.has("version"):
|
||||
doc_type = "Configuration"
|
||||
elif doc.has("database"):
|
||||
doc_type = "Database Settings"
|
||||
elif doc.has("features"):
|
||||
doc_type = "Feature Flags"
|
||||
elif doc.has("logging"):
|
||||
doc_type = "Logging Configuration"
|
||||
|
||||
log_info("• Document " + str(i) + ": " + doc_type)
|
||||
|
||||
func process_all_documents() -> void:
|
||||
if parsed_documents == null:
|
||||
parsed_documents = YAML.parse(multi_doc_yaml)
|
||||
|
||||
log_info("Processing all documents at once...")
|
||||
var all_docs = parsed_documents.get_documents()
|
||||
|
||||
log_success("Retrieved " + str(all_docs.size()) + " documents as array")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
for i in range(all_docs.size()):
|
||||
log_info("Document " + str(i) + " keys: " + str(all_docs[i].keys()))
|
||||
|
||||
func create_multi_document() -> void:
|
||||
log_info("Creating multi-document YAML from separate data structures...")
|
||||
|
||||
# Create individual document data
|
||||
var user_doc = {
|
||||
"user": {
|
||||
"id": 12345,
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com"
|
||||
}
|
||||
}
|
||||
|
||||
var preferences_doc = {
|
||||
"preferences": {
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"notifications": true
|
||||
}
|
||||
}
|
||||
|
||||
var session_doc = {
|
||||
"session": {
|
||||
"token": "abc123xyz",
|
||||
"expires": "2024-12-31T23:59:59Z",
|
||||
"permissions": ["read", "write"]
|
||||
}
|
||||
}
|
||||
|
||||
# Convert each to YAML and combine
|
||||
var documents_yaml = []
|
||||
|
||||
for doc_data in [user_doc, preferences_doc, session_doc]:
|
||||
var result = YAML.stringify(doc_data)
|
||||
if result.has_error():
|
||||
log_error("Failed to stringify document: " + result.get_error())
|
||||
return
|
||||
documents_yaml.append(result.get_data())
|
||||
|
||||
# Combine with document separator
|
||||
var combined_yaml = "\n---\n".join(documents_yaml)
|
||||
|
||||
log_success("Created multi-document YAML successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result(combined_yaml)
|
||||
|
||||
# Verify by parsing it back
|
||||
var verify_result = YAML.parse(combined_yaml)
|
||||
if !verify_result.has_error():
|
||||
log_success("Verification: " + str(verify_result.get_document_count()) + " documents parsed")
|
||||
|
||||
func save_multi_document_file() -> void:
|
||||
log_info("Saving multi-document YAML to file: " + OUTPUT_FILE)
|
||||
|
||||
# Create sample multi-document data
|
||||
var documents = [
|
||||
{"metadata": {"created": "2024-01-01", "version": 1}},
|
||||
{"data": {"items": [1, 2, 3], "total": 6}},
|
||||
{"summary": {"status": "complete", "processed": true}}
|
||||
]
|
||||
|
||||
var yaml_parts = []
|
||||
for doc in documents:
|
||||
var result = YAML.stringify(doc)
|
||||
if result.has_error():
|
||||
log_error("Failed to stringify document: " + result.get_error())
|
||||
return
|
||||
yaml_parts.append(result.get_data())
|
||||
|
||||
var multi_doc_content = "\n---\n".join(yaml_parts)
|
||||
|
||||
# Save to file (note: this saves as plain text, not using YAML.save_file)
|
||||
var file = FileAccess.open(OUTPUT_FILE, FileAccess.WRITE)
|
||||
if file == null:
|
||||
log_error("Failed to open file for writing")
|
||||
return
|
||||
|
||||
file.store_string(multi_doc_content)
|
||||
file.close()
|
||||
|
||||
log_success("Multi-document YAML saved successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("File content:\n" + multi_doc_content)
|
||||
|
||||
func load_multi_document_file() -> void:
|
||||
log_info("Loading multi-document YAML file: " + OUTPUT_FILE)
|
||||
|
||||
# Check if file exists
|
||||
if !FileAccess.file_exists(OUTPUT_FILE):
|
||||
log_warning("File doesn't exist. Run 'Save Multi-Document File' first.")
|
||||
return
|
||||
|
||||
var result := YAML.load_file(OUTPUT_FILE)
|
||||
|
||||
if result.has_error():
|
||||
log_error("File loading failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("Multi-document YAML file loaded successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
var doc_count = result.get_document_count()
|
||||
log_info("Loaded " + str(doc_count) + " documents from file")
|
||||
|
||||
for i in range(doc_count):
|
||||
var doc = result.get_document(i)
|
||||
log_info("Document " + str(i) + ": " + str(doc.keys()))
|
||||
log_result(" " + str(doc))
|
||||
@@ -0,0 +1 @@
|
||||
uid://bpwc2ppvsfwo5
|
||||
@@ -0,0 +1,174 @@
|
||||
extends ExampleBase
|
||||
|
||||
# This example requires a local texture reference
|
||||
# For real use, export a texture in your scene
|
||||
@export var local_texture: Texture2D
|
||||
|
||||
func _init() -> void:
|
||||
icon = "🔗"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Parsing Resources", parsing_resources)
|
||||
run_example("Stringifying Resources", stringifying_resources)
|
||||
run_example("Resource Security", resource_security)
|
||||
run_example("Resource Path Management", resource_path_management)
|
||||
|
||||
func parsing_resources() -> void:
|
||||
var yaml_text := """
|
||||
scene: !Resource 'res://addons/yaml/examples/assets/simple_scene.tscn'
|
||||
texture: !Resource 'res://icon.svg'
|
||||
yaml: !Resource 'res://addons/yaml/examples/data/simple.yaml'
|
||||
"""
|
||||
log_code_block(yaml_text)
|
||||
|
||||
log_info("Parsing YAML with resource references...")
|
||||
var parse_result := YAML.parse(yaml_text)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_error("Parse failed: " + parse_result.get_error())
|
||||
return
|
||||
|
||||
var data: Dictionary = parse_result.get_data()
|
||||
log_success("YAML with resources parsed successfully")
|
||||
|
||||
if data.scene is PackedScene:
|
||||
log_success("PackedScene loaded successfully")
|
||||
var scene = data.scene.instantiate()
|
||||
var screen_width: int = ProjectSettings.get_setting("display/window/size/viewport_width")
|
||||
var screen_height: int = ProjectSettings.get_setting("display/window/size/viewport_height")
|
||||
scene.position = Vector2(screen_width / 2, screen_height / 2)
|
||||
add_child(scene)
|
||||
else:
|
||||
log_warning("PackedScene not loaded (might be missing file or security restrictions)")
|
||||
|
||||
if data.texture is Texture2D:
|
||||
log_success("Texture loaded successfully")
|
||||
else:
|
||||
log_warning("Texture not loaded (might be missing file or security restrictions)")
|
||||
|
||||
if data.yaml is Dictionary:
|
||||
log_success("Nested YAML loaded successfully")
|
||||
else:
|
||||
log_warning("Nested YAML not loaded (might be missing file or security restrictions)")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_info("Resource types:")
|
||||
log_info("• scene type: " + str(typeof(data.scene)))
|
||||
log_info("• texture type: " + str(typeof(data.texture)))
|
||||
log_info("YAML resource:")
|
||||
log_info(str(data.yaml))
|
||||
|
||||
func stringifying_resources() -> void:
|
||||
log_info("Stringifying a resource...")
|
||||
|
||||
var resource = load("res://icon.svg") # Project icon should always exist
|
||||
if !resource:
|
||||
log_error("Could not load resource")
|
||||
return
|
||||
|
||||
var str_result := YAML.stringify(resource)
|
||||
if str_result.has_error():
|
||||
log_error("Stringify failed: " + str_result.get_error())
|
||||
return
|
||||
|
||||
log_success("Resource stringified successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("Stringified Resource:\n" + str_result.get_data())
|
||||
|
||||
# Test stringifying local resource (should fail)
|
||||
if local_texture:
|
||||
log_info("Attempting to stringify local (non-file) resource...")
|
||||
var invalid_result := YAML.stringify(local_texture)
|
||||
|
||||
if invalid_result.has_error():
|
||||
log_success("Correctly failed to serialize local resource")
|
||||
log_info("Error: " + invalid_result.get_error())
|
||||
else:
|
||||
log_error("Incorrectly serialized local resource")
|
||||
else:
|
||||
log_warning("No local texture assigned, skipping local resource test")
|
||||
|
||||
func resource_security() -> void:
|
||||
log_info("Testing resource security...")
|
||||
|
||||
var yaml_text := """
|
||||
script: !Resource 'res://addons/yaml/examples/scripts/dangerous_script.gd'
|
||||
"""
|
||||
log_code_block(yaml_text)
|
||||
|
||||
log_info("Parsing with default security...")
|
||||
var parse_result := YAML.parse(yaml_text)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_success("Default security correctly blocked script resource")
|
||||
log_info("Error: " + parse_result.get_error())
|
||||
else:
|
||||
log_error("Default security failed to block script resource")
|
||||
|
||||
log_info("Creating custom security with explicit type blocks...")
|
||||
var security := YAML.create_security()
|
||||
security.block_type("Script")
|
||||
security.block_type("GDExtension")
|
||||
security.allow_path("res://**", ["Texture2D", "PackedScene"])
|
||||
|
||||
log_info("Same file with custom security...")
|
||||
parse_result = YAML.parse(yaml_text, security)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_success("Custom security correctly blocked script resource")
|
||||
log_info("Error: " + parse_result.get_error())
|
||||
else:
|
||||
log_error("Custom security failed to block script resource")
|
||||
|
||||
func resource_path_management() -> void:
|
||||
log_subheader("Resource Path Management")
|
||||
|
||||
log_info("Using relative paths vs absolute paths:")
|
||||
|
||||
var yaml_with_absolute_path := """
|
||||
texture: !Resource 'res://icon.svg'
|
||||
"""
|
||||
log_code_block(yaml_with_absolute_path)
|
||||
|
||||
log_info("Absolute paths are fixed to specific locations")
|
||||
|
||||
var yaml_with_relative_path := """
|
||||
# When file is in res://addons/yaml/examples/
|
||||
texture: !Resource '../../../icon.svg'
|
||||
"""
|
||||
log_code_block(yaml_with_relative_path)
|
||||
|
||||
log_warning("Note: Godot doesn't natively support relative paths in Resource paths")
|
||||
log_info("You need to implement custom path resolution for relative paths")
|
||||
|
||||
log_info("\nSuggested best practices:")
|
||||
log_info("1. Use absolute paths (res://, user://) for resources")
|
||||
log_info("2. Keep resources in a structured directory hierarchy")
|
||||
log_info("3. Use security settings to limit resource access")
|
||||
log_info("4. For mod support, create custom path remapping in your game code")
|
||||
|
||||
# Example of custom path handling
|
||||
log_info("\nExample of custom path handling:")
|
||||
var code := """
|
||||
# Custom path resolver for mod resources
|
||||
func resolve_mod_path(path: String, mod_id: String) -> String:
|
||||
if path.begins_with("mod://"):
|
||||
# Transform mod:// protocol to user://mods/{mod_id}/
|
||||
return path.replace("mod://", "user://mods/" + mod_id + "/")
|
||||
return path
|
||||
|
||||
# Example usage with YAML
|
||||
func load_mod_config(mod_id: String) -> Dictionary:
|
||||
var yaml_text = FileAccess.open("user://mods/" + mod_id + "/config.yaml", FileAccess.READ).get_as_text()
|
||||
var data = YAML.parse(yaml_text).get_data()
|
||||
|
||||
# Process all resource paths in the data
|
||||
for key in data:
|
||||
if data[key] is String and data[key].begins_with("mod://"):
|
||||
# Resolve the path
|
||||
data[key] = resolve_mod_path(data[key], mod_id)
|
||||
|
||||
return data
|
||||
"""
|
||||
log_code_block(code)
|
||||
@@ -0,0 +1 @@
|
||||
uid://csoaahlo0042h
|
||||
@@ -0,0 +1,206 @@
|
||||
extends ExampleBase
|
||||
|
||||
func _init() -> void:
|
||||
icon = "🔒"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Default Security Behavior", default_security)
|
||||
run_example("Custom Security: Allow Path", custom_security_allow_path)
|
||||
run_example("Wildcard Path Patterns", wildcard_paths)
|
||||
run_example("Blocking Resource Types", block_type)
|
||||
run_example("Clearing Type Restrictions", clear_type_restrictions)
|
||||
run_example("Resetting Security", reset_security)
|
||||
run_example("Security Best Practices", security_best_practices)
|
||||
|
||||
func default_security() -> void:
|
||||
log_info("Testing default security settings...")
|
||||
|
||||
# Default security should block Script and GDExtension
|
||||
var yaml_text := """
|
||||
dangerous: !Resource 'res://addons/yaml/examples/classes/my_custom_class.gd'
|
||||
"""
|
||||
log_code_block(yaml_text)
|
||||
|
||||
var result := YAML.parse(yaml_text)
|
||||
|
||||
if result.has_error():
|
||||
log_success("Default security correctly blocked Script resource")
|
||||
log_info("Error message: " + result.get_error())
|
||||
return
|
||||
|
||||
log_error("Security failed to block unsafe resource")
|
||||
|
||||
func custom_security_allow_path() -> void:
|
||||
log_info("Creating custom security policy...")
|
||||
var security := YAML.create_security()
|
||||
|
||||
# Allow only textures from a specific path
|
||||
security.allow_path("res://addons/yaml/examples/assets", ["Texture2D"])
|
||||
|
||||
log_info("Testing allowed path and type...")
|
||||
var allowed_yaml := """
|
||||
texture: !Resource 'res://addons/yaml/icon.svg'
|
||||
"""
|
||||
log_code_block(allowed_yaml)
|
||||
|
||||
var result := YAML.parse(allowed_yaml, security)
|
||||
if !result.has_error():
|
||||
log_success("Correctly allowed texture in permitted path")
|
||||
else:
|
||||
log_error("Incorrectly blocked permitted resource: " + result.get_error())
|
||||
|
||||
log_info("Testing incorrect path...")
|
||||
var wrong_path_yaml := """
|
||||
texture: !Resource 'res://addons/yaml/examples/wrong_path/test.png'
|
||||
"""
|
||||
log_code_block(wrong_path_yaml)
|
||||
|
||||
result = YAML.parse(wrong_path_yaml, security)
|
||||
if result.has_error():
|
||||
log_success("Correctly blocked resource outside allowed path")
|
||||
log_info("Error message: " + result.get_error())
|
||||
else:
|
||||
log_error("Security failed to block resource outside allowed path")
|
||||
|
||||
log_info("Testing incorrect type...")
|
||||
var wrong_type_yaml := """
|
||||
scene: !Resource 'res://addons/yaml/examples/assets/textures/test.tscn'
|
||||
"""
|
||||
log_code_block(wrong_type_yaml)
|
||||
|
||||
result = YAML.parse(wrong_type_yaml, security)
|
||||
if result.has_error():
|
||||
log_success("Correctly blocked non-texture resource")
|
||||
log_info("Error message: " + result.get_error())
|
||||
else:
|
||||
log_error("Security failed to block non-texture resource")
|
||||
|
||||
func wildcard_paths() -> void:
|
||||
log_info("Testing wildcard path patterns...")
|
||||
|
||||
# Create security configuration
|
||||
var security := YAML.create_security()
|
||||
|
||||
# Test single segment wildcard (*)
|
||||
log_info("Single segment wildcard (*) example:")
|
||||
security.allow_path("res://addons/yaml/*/assets", ["Texture2D"])
|
||||
|
||||
var single_wildcard_yaml := """
|
||||
texture: !Resource 'res://addons/yaml/icon.svg'
|
||||
"""
|
||||
log_code_block(single_wildcard_yaml)
|
||||
|
||||
var result := YAML.parse(single_wildcard_yaml, security)
|
||||
if !result.has_error():
|
||||
log_success("Single wildcard pattern matched correctly")
|
||||
else:
|
||||
log_error("Single wildcard failed: " + result.get_error())
|
||||
|
||||
# Test recursive wildcard (**)
|
||||
log_info("\nRecursive wildcard (**) example:")
|
||||
security.clear_path_restrictions()
|
||||
security.allow_path("res://**", ["PackedScene"])
|
||||
|
||||
var recursive_wildcard_yaml := """
|
||||
scene: !Resource 'res://addons/yaml/examples/assets/simple_scene.tscn'
|
||||
"""
|
||||
log_code_block(recursive_wildcard_yaml)
|
||||
|
||||
result = YAML.parse(recursive_wildcard_yaml, security)
|
||||
if !result.has_error():
|
||||
log_success("Recursive wildcard pattern matched correctly")
|
||||
else:
|
||||
log_error("Recursive wildcard failed: " + result.get_error())
|
||||
|
||||
func block_type() -> void:
|
||||
log_info("Testing type blocking functionality...")
|
||||
|
||||
var security := YAML.create_security()
|
||||
security.allow_path("res://**") # Allow all paths
|
||||
security.block_type("PackedScene") # But block all scenes
|
||||
|
||||
var blocked_yaml := """
|
||||
scene: !Resource 'res://addons/yaml/examples/assets/test.tscn'
|
||||
"""
|
||||
log_code_block(blocked_yaml)
|
||||
|
||||
var result := YAML.parse(blocked_yaml, security)
|
||||
if result.has_error():
|
||||
log_success("Correctly blocked resource of blocked type")
|
||||
log_info("Error message: " + result.get_error())
|
||||
else:
|
||||
log_error("Failed to block specified resource type")
|
||||
|
||||
func clear_type_restrictions() -> void:
|
||||
log_info("Testing clearing type restrictions...")
|
||||
|
||||
var security := YAML.create_security()
|
||||
security.allow_path("res://**")
|
||||
security.clear_type_restrictions() # This removes default blocks on Script and GDExtension
|
||||
|
||||
var script_yaml := """
|
||||
script: !Resource 'res://addons/yaml/examples/classes/my_custom_class.gd'
|
||||
"""
|
||||
log_code_block(script_yaml)
|
||||
log_warning("Note: Allowing scripts can be dangerous with untrusted content")
|
||||
|
||||
var result := YAML.parse(script_yaml, security)
|
||||
if !result.has_error():
|
||||
log_success("Script resource allowed after clearing restrictions")
|
||||
else:
|
||||
log_error("Failed to allow script after clearing restrictions: " + result.get_error())
|
||||
|
||||
func reset_security() -> void:
|
||||
log_info("Testing security reset functionality...")
|
||||
|
||||
var security := YAML.create_security()
|
||||
security.allow_path("res://**")
|
||||
security.clear_type_restrictions()
|
||||
|
||||
log_info("Before reset: All paths allowed, all types allowed")
|
||||
|
||||
security.reset() # Should revert to default security
|
||||
log_info("After reset: Default restrictions should apply")
|
||||
|
||||
var script_yaml := """
|
||||
script: !Resource 'res://addons/yaml/examples/assets/my_custom_class.gd'
|
||||
"""
|
||||
log_code_block(script_yaml)
|
||||
|
||||
var result := YAML.parse(script_yaml, security)
|
||||
if result.has_error():
|
||||
log_success("Script correctly blocked after security reset")
|
||||
log_info("Error message: " + result.get_error())
|
||||
else:
|
||||
log_error("Security reset failed to restore default restrictions")
|
||||
|
||||
func security_best_practices() -> void:
|
||||
log_subheader("Security Best Practices")
|
||||
|
||||
log_info("1. Default security blocks Script and GDExtension resources")
|
||||
log_info("2. Only allow specific paths and types needed by your application")
|
||||
log_info("3. Use the most specific path patterns possible")
|
||||
log_info("4. For user content, create a dedicated directory and apply strict type limitations")
|
||||
|
||||
log_info("\nExample for mod content security:")
|
||||
var code := """
|
||||
# Create security for user mods
|
||||
var mod_security = YAML.create_security()
|
||||
|
||||
# Only allow textures, audio, and text files
|
||||
mod_security.allow_path("user://mods/**", [
|
||||
"Texture2D",
|
||||
"CompressedTexture2D",
|
||||
"AudioStreamOggVorbis",
|
||||
"AudioStreamMP3"
|
||||
])
|
||||
|
||||
# Explicitly block potentially dangerous types
|
||||
mod_security.block_type("Script")
|
||||
mod_security.block_type("GDExtension")
|
||||
mod_security.block_type("PackedScene")
|
||||
|
||||
# Use this security when loading mod content
|
||||
var result = YAML.load_file("user://mods/my_mod/config.yaml", mod_security)
|
||||
"""
|
||||
log_code_block(code)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bdbxqi1f27ndt
|
||||
@@ -0,0 +1,283 @@
|
||||
extends ExampleBase
|
||||
|
||||
## Number of iterations for each benchmark
|
||||
const ITERATIONS: int = 10
|
||||
|
||||
## Thread counts for multithreaded benchmarks
|
||||
const THREAD_COUNTS: Array = [1, 2, 4, 8, 16, 32, 64, 100]
|
||||
|
||||
## Path to the YAML file to benchmark
|
||||
const YAML_PATH: String = "res://addons/yaml/examples/data/supported_syntax.yaml"
|
||||
|
||||
func _init() -> void:
|
||||
icon = "⚡"
|
||||
|
||||
func run_examples() -> void:
|
||||
log_header("YAML Speed Benchmark")
|
||||
print(YAML.version())
|
||||
|
||||
run_example("Load YAML File", load_yaml_file_benchmark)
|
||||
run_example("Parse (No Style) Benchmark", parse_benchmark)
|
||||
run_example("Parse (With Style) Benchmark", parse_with_style_benchmark)
|
||||
run_example("Stringify (No Style) Benchmark", stringify_benchmark)
|
||||
run_example("Stringify (With Style) Benchmark", stringify_with_style_benchmark)
|
||||
run_example("Compare Results", compare_results)
|
||||
run_example("Parse (Threaded) Bechmark", parse_threaded)
|
||||
run_example("Stringify (Threaded) Benchmark", stringify_threaded)
|
||||
|
||||
var yaml_input: String
|
||||
var parse_times := []
|
||||
var style_parse_times := []
|
||||
var stringify_times := []
|
||||
var style_stringify_times := []
|
||||
var data = null
|
||||
var style = null
|
||||
|
||||
func load_yaml_file_benchmark() -> void:
|
||||
log_info("Loading YAML file: " + YAML_PATH)
|
||||
|
||||
var file := FileAccess.open(YAML_PATH, FileAccess.READ)
|
||||
if !file:
|
||||
log_error("Could not open file: " + YAML_PATH)
|
||||
return
|
||||
|
||||
yaml_input = file.get_as_text()
|
||||
log_success("File loaded, size: " + str(yaml_input.length()) + " characters")
|
||||
|
||||
var result := YAML.parse(yaml_input)
|
||||
data = result.get_data()
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("First 200 characters:\n" + yaml_input.substr(0, 200) + "...")
|
||||
|
||||
func parse_benchmark() -> void:
|
||||
if yaml_input.is_empty():
|
||||
log_error("No YAML input loaded")
|
||||
return
|
||||
|
||||
log_info("Running parse benchmark (" + str(ITERATIONS) + " iterations)...")
|
||||
|
||||
for i in range(ITERATIONS):
|
||||
var start := Time.get_ticks_usec()
|
||||
var result = YAML.parse(yaml_input)
|
||||
var elapsed := Time.get_ticks_usec() - start
|
||||
|
||||
if result.has_error():
|
||||
log_error("Iteration " + str(i + 1) + " failed: " + result.get_error())
|
||||
continue
|
||||
|
||||
parse_times.append(elapsed)
|
||||
log_info("Iteration " + str(i + 1) + ": " + str(elapsed) + " µs")
|
||||
|
||||
if parse_times.size() > 0:
|
||||
var avg = float(parse_times.reduce(func(a, b): return a + b)) / parse_times.size()
|
||||
log_success("Average parse time: " + str(avg) + " µs")
|
||||
else:
|
||||
log_error("No successful parse iterations")
|
||||
|
||||
func parse_with_style_benchmark() -> void:
|
||||
if yaml_input.is_empty():
|
||||
log_error("No YAML input loaded")
|
||||
return
|
||||
|
||||
log_info("Running parse with style benchmark (" + str(ITERATIONS) + " iterations)...")
|
||||
|
||||
for i in range(ITERATIONS):
|
||||
var start := Time.get_ticks_usec()
|
||||
var result = YAML.parse(yaml_input, YAML.create_security(), true)
|
||||
var elapsed := Time.get_ticks_usec() - start
|
||||
|
||||
if result.has_error():
|
||||
log_error("Iteration " + str(i + 1) + " failed: " + result.get_error())
|
||||
continue
|
||||
|
||||
if i == 0:
|
||||
style = result.get_style()
|
||||
|
||||
style_parse_times.append(elapsed)
|
||||
log_info("Iteration " + str(i + 1) + ": " + str(elapsed) + " µs")
|
||||
|
||||
if style_parse_times.size() > 0:
|
||||
var avg = float(style_parse_times.reduce(func(a, b): return a + b)) / style_parse_times.size()
|
||||
log_success("Average parse with style time: " + str(avg) + " µs")
|
||||
else:
|
||||
log_error("No successful parse with style iterations")
|
||||
|
||||
func stringify_benchmark() -> void:
|
||||
if data == null:
|
||||
log_error("No data available for stringify tests")
|
||||
return
|
||||
|
||||
log_info("Running stringify benchmark (" + str(ITERATIONS) + " iterations)...")
|
||||
|
||||
for i in range(ITERATIONS):
|
||||
var start := Time.get_ticks_usec()
|
||||
var result = YAML.stringify(data)
|
||||
var elapsed := Time.get_ticks_usec() - start
|
||||
|
||||
if result.has_error():
|
||||
log_error("Iteration " + str(i + 1) + " failed: " + result.get_error())
|
||||
continue
|
||||
|
||||
stringify_times.append(elapsed)
|
||||
log_info("Iteration " + str(i + 1) + ": " + str(elapsed) + " µs")
|
||||
|
||||
if stringify_times.size() > 0:
|
||||
var avg = float(stringify_times.reduce(func(a, b): return a + b)) / stringify_times.size()
|
||||
log_success("Average stringify time: " + str(avg) + " µs")
|
||||
else:
|
||||
log_error("No successful stringify iterations")
|
||||
|
||||
func stringify_with_style_benchmark() -> void:
|
||||
if data == null or style == null:
|
||||
log_error("No data or style available for stringify tests")
|
||||
return
|
||||
|
||||
log_info("Running stringify with style benchmark (" + str(ITERATIONS) + " iterations)...")
|
||||
|
||||
for i in range(ITERATIONS):
|
||||
var start := Time.get_ticks_usec()
|
||||
var result = YAML.stringify(data, style)
|
||||
var elapsed := Time.get_ticks_usec() - start
|
||||
|
||||
if result.has_error():
|
||||
log_error("Iteration " + str(i + 1) + " failed: " + result.get_error())
|
||||
continue
|
||||
|
||||
style_stringify_times.append(elapsed)
|
||||
log_info("Iteration " + str(i + 1) + ": " + str(elapsed) + " µs")
|
||||
|
||||
if style_stringify_times.size() > 0:
|
||||
var avg = float(style_stringify_times.reduce(func(a, b): return a + b)) / style_stringify_times.size()
|
||||
log_success("Average stringify with style time: " + str(avg) + " µs")
|
||||
else:
|
||||
log_error("No successful stringify with style iterations")
|
||||
|
||||
func compare_results() -> void:
|
||||
log_subheader("Performance Comparison")
|
||||
|
||||
# Collect all test results
|
||||
var all_tests = [
|
||||
{
|
||||
"name": "Parse (no style)",
|
||||
"times": parse_times
|
||||
},
|
||||
{
|
||||
"name": "Parse (with style)",
|
||||
"times": style_parse_times
|
||||
},
|
||||
{
|
||||
"name": "Stringify (no style)",
|
||||
"times": stringify_times
|
||||
},
|
||||
{
|
||||
"name": "Stringify (with style)",
|
||||
"times": style_stringify_times
|
||||
}
|
||||
]
|
||||
|
||||
# Calculate and print stats
|
||||
for test in all_tests:
|
||||
var times = test.times
|
||||
if times.is_empty():
|
||||
log_warning(test.name + ": No valid results")
|
||||
continue
|
||||
|
||||
var avg: float = float(times.reduce(func(a, b): return a + b)) / times.size()
|
||||
var min_time: int = times.min()
|
||||
var max_time: int = times.max()
|
||||
|
||||
log_info(test.name + ":")
|
||||
log_info(" Average: %.2f µs" % avg)
|
||||
log_info(" Min: %d µs" % min_time)
|
||||
log_info(" Max: %d µs" % max_time)
|
||||
|
||||
log_info("\nPerformance Insights:")
|
||||
|
||||
# Compare parse with and without style
|
||||
if !parse_times.is_empty() and !style_parse_times.is_empty():
|
||||
var avg_parse = float(parse_times.reduce(func(a, b): return a + b)) / parse_times.size()
|
||||
var avg_style_parse = float(style_parse_times.reduce(func(a, b): return a + b)) / style_parse_times.size()
|
||||
|
||||
var style_overhead = ((avg_style_parse / avg_parse) - 1.0) * 100.0
|
||||
log_info("Style detection adds approximately %.1f%% overhead to parsing" % style_overhead)
|
||||
|
||||
# Compare stringify with and without style
|
||||
if !stringify_times.is_empty() and !style_stringify_times.is_empty():
|
||||
var avg_stringify = float(stringify_times.reduce(func(a, b): return a + b)) / stringify_times.size()
|
||||
var avg_style_stringify = float(style_stringify_times.reduce(func(a, b): return a + b)) / style_stringify_times.size()
|
||||
|
||||
var style_overhead = ((avg_style_stringify / avg_stringify) - 1.0) * 100.0
|
||||
log_info("Using style adds approximately %.1f%% overhead to stringify" % style_overhead)
|
||||
|
||||
func parse_threaded() -> void:
|
||||
if yaml_input.is_empty():
|
||||
log_error("No YAML input loaded")
|
||||
return
|
||||
|
||||
log_subheader("Threaded Parse Benchmark")
|
||||
|
||||
for thread_count in THREAD_COUNTS:
|
||||
var threads: Array[Thread] = []
|
||||
|
||||
var start := Time.get_ticks_usec()
|
||||
|
||||
for i in range(thread_count):
|
||||
var thread := Thread.new()
|
||||
thread.start(_parse_thread_func.bind(yaml_input))
|
||||
threads.append(thread)
|
||||
|
||||
var total_parse_time := 0
|
||||
for thread in threads:
|
||||
total_parse_time += thread.wait_to_finish()
|
||||
|
||||
var wall_time := Time.get_ticks_usec() - start
|
||||
var avg_thread_time := float(total_parse_time / thread_count)
|
||||
|
||||
log_info("%d thread(s): wall=%d µs | avg thread=%d µs | throughput=%.1f parses/ms" % [
|
||||
thread_count,
|
||||
wall_time,
|
||||
avg_thread_time,
|
||||
thread_count / (wall_time / 1000.0)
|
||||
])
|
||||
|
||||
func stringify_threaded() -> void:
|
||||
if data == null:
|
||||
log_error("No data available for stringify tests")
|
||||
return
|
||||
|
||||
log_subheader("Threaded Stringify Benchmark")
|
||||
|
||||
for thread_count in THREAD_COUNTS:
|
||||
var threads: Array[Thread] = []
|
||||
|
||||
var start := Time.get_ticks_usec()
|
||||
|
||||
for i in range(thread_count):
|
||||
var thread := Thread.new()
|
||||
thread.start(_stringify_thread_func.bind(data))
|
||||
threads.append(thread)
|
||||
|
||||
var total_stringify_time := 0
|
||||
for thread in threads:
|
||||
total_stringify_time += thread.wait_to_finish()
|
||||
|
||||
var wall_time := Time.get_ticks_usec() - start
|
||||
var avg_thread_time := float(total_stringify_time / thread_count)
|
||||
|
||||
log_info("%d thread(s): wall=%d µs | avg thread=%d µs | throughput=%.1f stringifies/ms" % [
|
||||
thread_count,
|
||||
wall_time,
|
||||
avg_thread_time,
|
||||
thread_count / (wall_time / 1000.0)
|
||||
])
|
||||
|
||||
static func _parse_thread_func(input: String) -> int:
|
||||
var start := Time.get_ticks_usec()
|
||||
YAML.parse(input)
|
||||
return Time.get_ticks_usec() - start
|
||||
|
||||
static func _stringify_thread_func(input_data: Variant) -> int:
|
||||
var start := Time.get_ticks_usec()
|
||||
YAML.stringify(input_data)
|
||||
return Time.get_ticks_usec() - start
|
||||
@@ -0,0 +1 @@
|
||||
uid://bcyghdxg5kxhk
|
||||
@@ -0,0 +1,359 @@
|
||||
extends ExampleBase
|
||||
|
||||
const YAML_FILE = "res://addons/yaml/examples/data/supported_syntax.yaml"
|
||||
const STYLE_FILE = "user://supported_syntax.style.yaml"
|
||||
|
||||
var data
|
||||
var style: YAMLStyle
|
||||
|
||||
func _init() -> void:
|
||||
icon = "🎨"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Style Extraction", style_extraction)
|
||||
run_example("Stringify with Style", stringify_with_style)
|
||||
run_example("Style Cloning", style_cloning)
|
||||
run_example("Style Merging", style_merging)
|
||||
run_example("Child Styles", child_styles)
|
||||
run_example("Path-based Styles", get_at_path)
|
||||
run_example("Style Propagation", propagate_scalar_styles)
|
||||
run_example("Various Style Combinations", various_style_combinations)
|
||||
run_example("Style Serialization", to_from_dictionary)
|
||||
|
||||
func style_extraction() -> void:
|
||||
log_info("Loading YAML file with style detection...")
|
||||
|
||||
var result = YAML.load_file(YAML_FILE, null, true)
|
||||
if result.has_error():
|
||||
log_error("Failed to load file: " + result.get_error())
|
||||
return
|
||||
|
||||
data = result.get_data()
|
||||
style = result.get_style()
|
||||
|
||||
if !style:
|
||||
log_error("Could not extract style")
|
||||
return
|
||||
|
||||
log_success("Style extracted successfully")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("Extracted Styles:\n" + style.get_debug_string())
|
||||
|
||||
log_info("Saving style to file: " + STYLE_FILE)
|
||||
var save_result = style.save_file(STYLE_FILE)
|
||||
|
||||
if save_result.has_error():
|
||||
log_error("Failed to save style: " + save_result.get_error())
|
||||
return
|
||||
|
||||
log_success("Style saved to file")
|
||||
|
||||
func stringify_with_style() -> void:
|
||||
log_info("Loading style from file...")
|
||||
|
||||
var load_result = YAMLStyle.load_file(STYLE_FILE)
|
||||
if load_result.has_error():
|
||||
log_error("Failed to load style: " + load_result.get_error())
|
||||
return
|
||||
|
||||
var load_style = load_result.get_style()
|
||||
|
||||
if style && load_style.hash() == style.hash():
|
||||
log_success("Loaded style matches the original style")
|
||||
else:
|
||||
log_warning("Loaded style does not match the original style")
|
||||
|
||||
if data == null:
|
||||
data = {"test": "value"}
|
||||
|
||||
log_info("Stringifying data with loaded style...")
|
||||
var stringify_result = YAML.stringify(data, load_style)
|
||||
|
||||
if stringify_result.has_error():
|
||||
log_error("Stringify failed: " + stringify_result.get_error())
|
||||
return
|
||||
|
||||
log_success("Data stringified with style")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("Styled YAML output:\n" + stringify_result.get_data())
|
||||
|
||||
func style_cloning() -> void:
|
||||
log_info("Creating and configuring base style...")
|
||||
|
||||
var style := YAML.create_style()
|
||||
style.set_string_style(YAMLStyle.STRING_QUOTE_DOUBLE)
|
||||
style.set_flow_style(YAMLStyle.FLOW_NONE)
|
||||
|
||||
log_info("Cloning style...")
|
||||
var cloned_style := style.clone()
|
||||
|
||||
if style.get_string_style() == cloned_style.get_string_style() && \
|
||||
style.get_flow_style() == cloned_style.get_flow_style():
|
||||
log_success("Cloned style has same properties as original")
|
||||
else:
|
||||
log_error("Cloned style properties don't match original")
|
||||
|
||||
log_info("Modifying the cloned style...")
|
||||
cloned_style.set_string_style(YAMLStyle.STRING_QUOTE_SINGLE)
|
||||
|
||||
if style.get_string_style() != cloned_style.get_string_style():
|
||||
log_success("Modifying clone doesn't affect original")
|
||||
else:
|
||||
log_error("Modifying clone affected original")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
var test_data = {"message": "Hello, World!"}
|
||||
|
||||
var orig_result = YAML.stringify(test_data, style)
|
||||
var clone_result = YAML.stringify(test_data, cloned_style)
|
||||
|
||||
log_result("Original style output:\n" + orig_result.get_data())
|
||||
log_result("Cloned style output:\n" + clone_result.get_data())
|
||||
|
||||
func style_merging() -> void:
|
||||
log_info("Creating two different styles...")
|
||||
|
||||
var style1 := YAML.create_style()
|
||||
style1.set_string_style(YAMLStyle.STRING_QUOTE_DOUBLE)
|
||||
|
||||
var style2 := YAML.create_style()
|
||||
style2.set_flow_style(YAMLStyle.FLOW_SINGLE)
|
||||
|
||||
log_info("Merging style2 into style1...")
|
||||
style1.merge_with(style2)
|
||||
|
||||
if style1.get_string_style() == YAMLStyle.STRING_QUOTE_DOUBLE:
|
||||
log_success("Original style properties preserved")
|
||||
else:
|
||||
log_error("Original style properties were lost")
|
||||
|
||||
if style1.get_flow_style() == YAMLStyle.FLOW_SINGLE:
|
||||
log_success("Properties from merged style were added")
|
||||
else:
|
||||
log_error("Properties from merged style were not added")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
var test_data = {
|
||||
"message": "Hello, World!",
|
||||
"items": ["one", "two", "three"]
|
||||
}
|
||||
|
||||
var result = YAML.stringify(test_data, style1)
|
||||
log_result("Merged style output:\n" + result.get_data())
|
||||
|
||||
func child_styles() -> void:
|
||||
log_info("Creating parent style with child styles...")
|
||||
|
||||
var style := YAML.create_style()
|
||||
style.set_string_style(YAMLStyle.STRING_QUOTE_DOUBLE)
|
||||
|
||||
log_info("Creating child style for 'list' key...")
|
||||
var list_style := style.create_child("list")
|
||||
list_style.set_flow_style(YAMLStyle.FLOW_SINGLE)
|
||||
|
||||
if style.get_child("list") == list_style:
|
||||
log_success("get_child() retrieves the correct child style")
|
||||
else:
|
||||
log_error("get_child() failed to retrieve the correct child style")
|
||||
|
||||
if style.has_child("list"):
|
||||
log_success("has_child() correctly identifies existing child")
|
||||
else:
|
||||
log_error("has_child() failed to identify existing child")
|
||||
|
||||
var child_keys := style.list_children()
|
||||
if child_keys.has("list"):
|
||||
log_success("list_children() includes the child key")
|
||||
else:
|
||||
log_error("list_children() failed to include the child key")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
var test_data = {
|
||||
"name": "Example",
|
||||
"list": ["one", "two", "three"]
|
||||
}
|
||||
|
||||
var result = YAML.stringify(test_data, style)
|
||||
log_result("Output with child styles:\n" + result.get_data())
|
||||
|
||||
func get_at_path() -> void:
|
||||
log_info("Creating nested style structure...")
|
||||
|
||||
var style := YAML.create_style()
|
||||
|
||||
# Create a nested style structure
|
||||
var maps_style := style.create_child("maps")
|
||||
var items_style := maps_style.create_child("items")
|
||||
var first_item_style := items_style.create_child("0")
|
||||
first_item_style.set_string_style(YAMLStyle.STRING_LITERAL)
|
||||
|
||||
log_info("Getting style at path 'maps/items/0'...")
|
||||
var path_style := style.get_at_path("maps/items/0")
|
||||
|
||||
if path_style && path_style.get_string_style() == YAMLStyle.STRING_LITERAL:
|
||||
log_success("get_at_path() correctly retrieved the style")
|
||||
else:
|
||||
log_error("get_at_path() failed to retrieve the correct style")
|
||||
|
||||
log_info("Creating missing path 'maps/items/1/properties'...")
|
||||
var new_path_style := style.get_at_path("maps/items/1/properties", true)
|
||||
|
||||
if new_path_style != null:
|
||||
log_success("Successfully created missing path nodes")
|
||||
else:
|
||||
log_error("Failed to create missing path nodes")
|
||||
|
||||
log_info("Getting non-existent path without creating...")
|
||||
var missing_style := style.get_at_path("non/existent/path", false)
|
||||
|
||||
if missing_style == null:
|
||||
log_success("Correctly returned null for non-existent path")
|
||||
else:
|
||||
log_error("Incorrectly returned non-null for non-existent path")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
var test_data = {
|
||||
"maps": {
|
||||
"items": [
|
||||
{"name": "Item 1", "description": "Line 1\nLine 2\nLine 3"},
|
||||
{"name": "Item 2", "properties": {"a": 1, "b": 2}}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
var result = YAML.stringify(test_data, style)
|
||||
log_result("Output with path-based styles:\n" + result.get_data())
|
||||
|
||||
func propagate_scalar_styles() -> void:
|
||||
log_info("Creating parent style with scalar formats...")
|
||||
|
||||
var parent_style := YAML.create_style()
|
||||
parent_style.set_string_style(YAMLStyle.STRING_QUOTE_DOUBLE)
|
||||
parent_style.set_integer_format(YAMLStyle.INT_HEX)
|
||||
parent_style.set_float_format(YAMLStyle.FLOAT_SCIENTIFIC)
|
||||
|
||||
log_info("Creating child style and propagating scalar styles...")
|
||||
var child_style := YAML.create_style()
|
||||
parent_style.propagate_scalar_styles(child_style)
|
||||
|
||||
if child_style.get_string_style() == YAMLStyle.STRING_QUOTE_DOUBLE:
|
||||
log_success("String style was propagated")
|
||||
else:
|
||||
log_error("String style was not propagated")
|
||||
|
||||
if child_style.get_integer_format() == YAMLStyle.INT_HEX:
|
||||
log_success("Integer format was propagated")
|
||||
else:
|
||||
log_error("Integer format was not propagated")
|
||||
|
||||
if child_style.get_float_format() == YAMLStyle.FLOAT_SCIENTIFIC:
|
||||
log_success("Float format was propagated")
|
||||
else:
|
||||
log_error("Float format was not propagated")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
var test_data = {
|
||||
"text": "Hello, World!",
|
||||
"number": 255,
|
||||
"decimal": 3.14159
|
||||
}
|
||||
|
||||
var result = YAML.stringify(test_data, child_style)
|
||||
log_result("Output with propagated styles:\n" + result.get_data())
|
||||
|
||||
func various_style_combinations() -> void:
|
||||
log_info("Creating data with various types...")
|
||||
|
||||
var data := {
|
||||
"string_value": "Test string with \"quotes\" and newlines\nto test",
|
||||
"int_value": 255,
|
||||
"float_value": 3.14159,
|
||||
"list": ["item1", "item2", "item3"],
|
||||
"dict": {"key1": "val1", "key2": "val2"}
|
||||
}
|
||||
|
||||
log_info("Creating style with various formatting options...")
|
||||
var style := YAML.create_style()
|
||||
style.set_string_style(YAMLStyle.STRING_LITERAL)
|
||||
style.set_integer_format(YAMLStyle.INT_HEX)
|
||||
style.set_float_format(YAMLStyle.FLOAT_SCIENTIFIC)
|
||||
|
||||
# List should be compact
|
||||
var list_style := style.create_child("list")
|
||||
list_style.set_flow_style(YAMLStyle.FLOW_SINGLE)
|
||||
|
||||
# Dict should be expanded
|
||||
var dict_style := style.create_child("dict")
|
||||
dict_style.set_flow_style(YAMLStyle.FLOW_NONE)
|
||||
|
||||
log_info("Stringifying with custom styles...")
|
||||
var result := YAML.stringify(data, style)
|
||||
|
||||
if result.has_error():
|
||||
log_error("Stringify failed: " + result.get_error())
|
||||
return
|
||||
|
||||
log_success("Data stringified with custom styles")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("Styled YAML output:\n" + result.get_data())
|
||||
|
||||
log_info("Parsing styled output with style detection...")
|
||||
var parse_result := YAML.parse(result.get_data(), null, true)
|
||||
|
||||
if parse_result.has_error():
|
||||
log_error("Parse failed: " + parse_result.get_error())
|
||||
return
|
||||
|
||||
var detected_style := parse_result.get_style()
|
||||
|
||||
if detected_style != null:
|
||||
log_success("Style was detected during parsing")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("Detected Style Tree:\n" + detected_style.get_debug_string())
|
||||
else:
|
||||
log_error("Style was not detected during parsing")
|
||||
|
||||
func to_from_dictionary() -> void:
|
||||
log_info("Creating style for serialization...")
|
||||
|
||||
var style := YAML.create_style()
|
||||
style.set_string_style(YAMLStyle.STRING_QUOTE_DOUBLE)
|
||||
|
||||
var child := style.create_child("child")
|
||||
child.set_flow_style(YAMLStyle.FLOW_SINGLE)
|
||||
|
||||
log_info("Converting style to dictionary...")
|
||||
var dict := style.to_dictionary()
|
||||
|
||||
if dict.has("string") && dict.has("children"):
|
||||
log_success("Dictionary contains style properties and children")
|
||||
else:
|
||||
log_error("Dictionary missing expected keys")
|
||||
|
||||
log_info("Rebuilding style from dictionary...")
|
||||
var rebuilt_style := YAMLStyle.from_dictionary(dict)
|
||||
|
||||
if rebuilt_style.get_string_style() == YAMLStyle.STRING_QUOTE_DOUBLE:
|
||||
log_success("Rebuilt style maintained properties")
|
||||
else:
|
||||
log_error("Rebuilt style lost properties")
|
||||
|
||||
if rebuilt_style.has_child("child"):
|
||||
log_success("Rebuilt style maintains children")
|
||||
else:
|
||||
log_error("Rebuilt style lost children")
|
||||
|
||||
var rebuilt_hash := rebuilt_style.hash()
|
||||
var original_hash := style.hash()
|
||||
|
||||
if rebuilt_hash == original_hash:
|
||||
log_success("Style hashes match")
|
||||
else:
|
||||
log_error("Style hashes don't match")
|
||||
|
||||
if LOG_VERBOSE:
|
||||
log_result("Style dictionary:\n" + str(dict))
|
||||
@@ -0,0 +1 @@
|
||||
uid://00j04245e7jl
|
||||
@@ -0,0 +1,107 @@
|
||||
extends ExampleBase
|
||||
|
||||
var schema: Schema
|
||||
|
||||
func _init() -> void:
|
||||
icon = "🛡️"
|
||||
|
||||
func run_examples() -> void:
|
||||
run_example("Load YAML Schema", load_yaml_schema)
|
||||
run_example("Validate Using Schema", validate_using_schema)
|
||||
run_example("Validate While Parsing", validate_while_parsing)
|
||||
run_example("Validate With Defaults", validate_with_defaults)
|
||||
|
||||
func load_yaml_schema() -> void:
|
||||
var yaml_schema_text = """
|
||||
# The $id string is used as an identifier
|
||||
$id: "http://example.com/user.yaml"
|
||||
|
||||
# Definitions
|
||||
$defs:
|
||||
settings:
|
||||
type: object
|
||||
properties:
|
||||
theme:
|
||||
type: string
|
||||
default: dark
|
||||
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
minLength: 3
|
||||
maxLength: 20
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
role:
|
||||
type: string
|
||||
default: user
|
||||
x-yaml-tag: UserRole
|
||||
settings:
|
||||
$ref: "#/$defs/settings"
|
||||
required:
|
||||
- username
|
||||
- email
|
||||
""".replace("\t", " ")
|
||||
|
||||
schema = YAML.load_schema_from_string(yaml_schema_text)
|
||||
if !schema:
|
||||
log_error("Schema failed to load!")
|
||||
else:
|
||||
log_success("Schema '%s' loaded" % schema.get_schema_definition().get("$id"))
|
||||
|
||||
func validate_using_schema() -> void:
|
||||
# Validate using the Schema object
|
||||
log_subheader("Successful validation")
|
||||
var result := schema.validate({
|
||||
"username": "alice",
|
||||
"email": "alice@example.com"
|
||||
})
|
||||
if result.is_valid():
|
||||
log_success(result.get_summary())
|
||||
else:
|
||||
log_error(result.get_summary())
|
||||
|
||||
log_subheader("Failed validation")
|
||||
result = schema.validate({
|
||||
"username": "alice",
|
||||
"email": "invalid email"
|
||||
})
|
||||
if !result.is_valid():
|
||||
log_success(result.get_summary())
|
||||
else:
|
||||
log_error(result.get_summary())
|
||||
|
||||
func validate_while_parsing() -> void:
|
||||
# YAML tag validation
|
||||
var tagged_yaml = """
|
||||
username: bob
|
||||
email: bob@example.com
|
||||
role: !UserRole admin
|
||||
settings:
|
||||
theme: white
|
||||
"""
|
||||
# Parse and validate using Schema $id property
|
||||
var result := YAML.parse_and_validate(tagged_yaml, "http://example.com/user.yaml")
|
||||
if result.has_validation_errors():
|
||||
log_error(result.get_validation_summary())
|
||||
return
|
||||
|
||||
log_success(result.get_data())
|
||||
|
||||
func validate_with_defaults() -> void:
|
||||
# Default values
|
||||
var yaml_str = """
|
||||
$schema: "http://example.com/user.yaml" # This refers to the $id
|
||||
username: alice
|
||||
email: alice@example.com
|
||||
settings: {} # Empty dictionary is required to set default values
|
||||
"""
|
||||
# Parse and validate using YAML $schema property
|
||||
var result := YAML.parse_and_validate(yaml_str)
|
||||
if result.has_validation_errors():
|
||||
log_error(result.get_validation_summary())
|
||||
return
|
||||
|
||||
log_success(result.get_data())
|
||||
@@ -0,0 +1 @@
|
||||
uid://eduyiegmiod
|
||||
@@ -0,0 +1,163 @@
|
||||
extends ExampleBase
|
||||
|
||||
const EPSILON := 0.000001 # Tolerance for floating point comparisons
|
||||
|
||||
func _init() -> void:
|
||||
icon = "🧩"
|
||||
|
||||
func run_examples():
|
||||
var variants := get_variant_dict()
|
||||
|
||||
# Test each type individually
|
||||
for key in variants:
|
||||
run_example(key, func(): run_variant_conversion(key, variants[key]))
|
||||
|
||||
# Test full dictionary conversion
|
||||
print_rich("\n[b]Testing Full Dictionary Conversion:[/b]")
|
||||
var yaml_result := YAML.stringify(variants)
|
||||
if yaml_result.has_error():
|
||||
print_rich("[color=red]Dictionary stringify failed: %s[/color]" % yaml_result.get_error())
|
||||
return
|
||||
|
||||
var yaml_text = yaml_result.get_data()
|
||||
var parse_result := YAML.parse(yaml_text)
|
||||
if parse_result.has_error():
|
||||
print_rich("[color=red]Dictionary parse failed: %s[/color]" % parse_result.get_error())
|
||||
return
|
||||
|
||||
var decoded = parse_result.get_data()
|
||||
var all_passed := true
|
||||
for key in variants:
|
||||
if !is_approximately_equal(variants[key], decoded[key]):
|
||||
print_rich("[color=red]Dictionary value mismatch for %s:[/color]" % key)
|
||||
print_rich(" Expected: %s" % variants[key])
|
||||
print_rich(" Got: %s" % decoded[key])
|
||||
all_passed = false
|
||||
|
||||
if all_passed:
|
||||
print_rich("[color=green]✓ All variant type conversions passed![/color]")
|
||||
|
||||
func run_variant_conversion(type_name: String, value: Variant) -> void:
|
||||
print_rich("\n[b]Testing %s:[/b]" % type_name)
|
||||
print_rich("[i]Original:[/i] %s" % str(value))
|
||||
|
||||
# Test stringification
|
||||
var yaml_result := YAML.stringify(value)
|
||||
if yaml_result.has_error():
|
||||
print_rich("[color=red]Stringify failed: %s[/color]" % yaml_result.get_error())
|
||||
return
|
||||
|
||||
var yaml = yaml_result.get_data()
|
||||
print_rich("[i]As YAML:[/i]\n%s" % yaml)
|
||||
|
||||
# Test parsing
|
||||
var parse_result := YAML.parse(yaml)
|
||||
if parse_result.has_error():
|
||||
print_rich("[color=red]Parse failed: %s[/color]" % parse_result.get_error())
|
||||
return
|
||||
|
||||
var decoded = parse_result.get_data()
|
||||
print_rich("[i]Decoded:[/i] %s" % str(decoded))
|
||||
|
||||
# Verify value equality
|
||||
if !is_approximately_equal(value, decoded):
|
||||
print_rich("[color=red]Value mismatch:[/color]")
|
||||
print_rich(" Expected: %s" % str(value))
|
||||
print_rich(" Got: %s" % decoded)
|
||||
else:
|
||||
print_rich("[color=green]✓ Values match[/color]")
|
||||
|
||||
func is_approximately_equal(a: Variant, b: Variant) -> bool:
|
||||
if typeof(b) == TYPE_STRING:
|
||||
return str(a) == b
|
||||
match typeof(a):
|
||||
TYPE_STRING, TYPE_STRING_NAME:
|
||||
return a == b
|
||||
TYPE_FLOAT:
|
||||
return abs(a - b) < EPSILON
|
||||
TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4:
|
||||
return a.is_equal_approx(b)
|
||||
TYPE_ARRAY, TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY:
|
||||
if a.size() != b.size():
|
||||
return false
|
||||
for i in a.size():
|
||||
if not is_approximately_equal(a[i], b[i]):
|
||||
return false
|
||||
return true
|
||||
TYPE_QUATERNION, TYPE_BASIS, TYPE_TRANSFORM2D, TYPE_TRANSFORM3D:
|
||||
return a.is_equal_approx(b)
|
||||
TYPE_COLOR:
|
||||
var va = Vector4(a.r, a.g, a.b, a.a)
|
||||
var vb = Vector4(b.r, b.g, b.b, b.a)
|
||||
var dist = (va - vb).length()
|
||||
return dist < 0.01
|
||||
TYPE_DICTIONARY:
|
||||
if a.size() != b.size():
|
||||
return false
|
||||
for key in a:
|
||||
if not b.has(key) or not is_approximately_equal(a[key], b[key]):
|
||||
return false
|
||||
return true
|
||||
_:
|
||||
return a == b
|
||||
|
||||
func get_variant_dict() -> Dictionary:
|
||||
return {
|
||||
# Vector types
|
||||
"Vector2": Vector2(1, 2),
|
||||
"Vector2i": Vector2i(2, 4),
|
||||
"Vector3": Vector3(1, 2, 4),
|
||||
"Vector3i": Vector3i(2, 4, 8),
|
||||
"Vector4": Vector4(1, 2, 4, 8),
|
||||
"Vector4i": Vector4i(2, 4, 8, 16),
|
||||
|
||||
# Geometric types
|
||||
"AABB": AABB(Vector3(1, 2, 4), Vector3(8, 16, 32)),
|
||||
"Basis": Basis(Vector3(1, 2, 4), Vector3(8, 16, 32), Vector3(64, 128, 256)),
|
||||
"Plane": Plane(Vector3(1, 2, 4), PI),
|
||||
"Quaternion": Quaternion(PI, 2*PI, 4*PI, 8*PI),
|
||||
"Rect2": Rect2(1, 2, 4, 8),
|
||||
"Rect2i": Rect2i(2, 4, 8, 16),
|
||||
"Transform2D": Transform2D(PI, Vector2(2*PI, 4*PI)),
|
||||
"Transform3D": Transform3D(Basis(), Vector3(1, 2, 4)),
|
||||
|
||||
# Color types
|
||||
"Color": Color(1.0, 0.5, 0.25, 1.0),
|
||||
|
||||
# Array types
|
||||
"PackedByteArray": PackedByteArray([1, 2, 4, 8]),
|
||||
"PackedColorArray": PackedColorArray([
|
||||
Color(1, 0, 0),
|
||||
Color(0, 1, 0),
|
||||
Color(0, 0, 1)
|
||||
]),
|
||||
"PackedFloat32Array": PackedFloat32Array([PI, 2*PI, 4*PI]),
|
||||
"PackedFloat64Array": PackedFloat64Array([PI, 2*PI, 4*PI]),
|
||||
"PackedInt32Array": PackedInt32Array([1, 2, 4, 8]),
|
||||
"PackedInt64Array": PackedInt64Array([1, 2, 4, 8]),
|
||||
"PackedStringArray": PackedStringArray([
|
||||
"one",
|
||||
"one\ntwo",
|
||||
"one\ntwo\nthree"
|
||||
]),
|
||||
"PackedVector2Array": PackedVector2Array([
|
||||
Vector2(1, 2),
|
||||
Vector2(4, 8)
|
||||
]),
|
||||
"PackedVector3Array": PackedVector3Array([
|
||||
Vector3(1, 2, 4),
|
||||
Vector3(8, 16, 32)
|
||||
]),
|
||||
|
||||
# Matrix type
|
||||
"Projection": Projection(
|
||||
Vector4(1, 2, 4, 8),
|
||||
Vector4(16, 32, 64, 128),
|
||||
Vector4(256, 512, 1024, 2048),
|
||||
Vector4(4096, 8192, 16384, 32768)
|
||||
),
|
||||
|
||||
# Reference types
|
||||
"NodePath": NodePath("root/level/player"),
|
||||
"StringName": &"test_string_name",
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://ba73ypvsmf88o
|
||||
@@ -0,0 +1,26 @@
|
||||
$schema: "http://json-schema.org/draft-07/schema#"
|
||||
$id: "res://addons/yaml/examples/data/fimbul.generator.schema.yaml"
|
||||
title: "Fimbul world generator schema definition"
|
||||
|
||||
params:
|
||||
type: object
|
||||
properties:
|
||||
x: float
|
||||
y: float
|
||||
noiseScale: float
|
||||
noise2D: Callable
|
||||
|
||||
functions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
params:
|
||||
$ref: "http://json-schema.org/draft-07/schema#/definitions/stringArray"
|
||||
dependencies:
|
||||
$ref: "http://json-schema.org/draft-07/schema#/definitions/stringArray"
|
||||
returns:
|
||||
type: string
|
||||
required: [name, code]
|
||||
@@ -0,0 +1 @@
|
||||
uid://st36arm7pgkb
|
||||
@@ -0,0 +1,47 @@
|
||||
[gd_scene load_steps=12 format=3 uid="uid://dogc1x0c3re7o"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cemrjp34s7ujk" path="res://addons/yaml/examples/example_basic_usage.gd" id="1_iyu1q"]
|
||||
[ext_resource type="Script" uid="uid://ba73ypvsmf88o" path="res://addons/yaml/examples/example_variants.gd" id="4_4pc1t"]
|
||||
[ext_resource type="Script" uid="uid://00j04245e7jl" path="res://addons/yaml/examples/example_style_system.gd" id="5_61ckj"]
|
||||
[ext_resource type="Script" uid="uid://bpwc2ppvsfwo5" path="res://addons/yaml/examples/example_multi_document.gd" id="7_wcdgp"]
|
||||
[ext_resource type="Script" uid="uid://bcyghdxg5kxhk" path="res://addons/yaml/examples/example_speed_benchmark.gd" id="9_eedai"]
|
||||
[ext_resource type="Script" uid="uid://bdbxqi1f27ndt" path="res://addons/yaml/examples/example_security.gd" id="9_pih52"]
|
||||
[ext_resource type="Script" uid="uid://eduyiegmiod" path="res://addons/yaml/examples/example_validation.gd" id="10_kxy66"]
|
||||
[ext_resource type="Script" uid="uid://ch12k840j6ak6" path="res://addons/yaml/examples/example_custom_class.gd" id="33_2d3w6"]
|
||||
[ext_resource type="Script" uid="uid://d015opxjbyce4" path="res://addons/yaml/examples/example_error_handling.gd" id="34_84xsb"]
|
||||
[ext_resource type="Script" uid="uid://csoaahlo0042h" path="res://addons/yaml/examples/example_resource_referencing.gd" id="35_jnbc0"]
|
||||
|
||||
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_grb45"]
|
||||
|
||||
[node name="Examples" type="Node2D"]
|
||||
|
||||
[node name="Basic Usage" type="Node2D" parent="."]
|
||||
script = ExtResource("1_iyu1q")
|
||||
|
||||
[node name="Variants" type="Node2D" parent="."]
|
||||
script = ExtResource("4_4pc1t")
|
||||
|
||||
[node name="Error Handling" type="Node2D" parent="."]
|
||||
script = ExtResource("34_84xsb")
|
||||
|
||||
[node name="Custom Class" type="Node2D" parent="."]
|
||||
script = ExtResource("33_2d3w6")
|
||||
|
||||
[node name="Style System" type="Node2D" parent="."]
|
||||
script = ExtResource("5_61ckj")
|
||||
|
||||
[node name="Resource Referencing" type="Node2D" parent="."]
|
||||
script = ExtResource("35_jnbc0")
|
||||
local_texture = SubResource("PlaceholderTexture2D_grb45")
|
||||
|
||||
[node name="Security" type="Node2D" parent="."]
|
||||
script = ExtResource("9_pih52")
|
||||
|
||||
[node name="Multiple Documents" type="Node2D" parent="."]
|
||||
script = ExtResource("7_wcdgp")
|
||||
|
||||
[node name="Speech Benchmark" type="Node2D" parent="."]
|
||||
script = ExtResource("9_eedai")
|
||||
|
||||
[node name="Schema Validation" type="Node2D" parent="."]
|
||||
script = ExtResource("10_kxy66")
|
||||
Reference in New Issue
Block a user