From a0dd8aa4373ba51b0def07aad662c2e1b794786d Mon Sep 17 00:00:00 2001 From: Adrian Shum Date: Tue, 22 Sep 2026 18:08:46 +0800 Subject: [PATCH] feat: add min max to introspection metadata --- internal/introspection/introspection.c | 15 ++++++ internal/introspection/introspection.h | 3 ++ internal/introspection/operation.go | 37 ++++++++++++++ internal/introspection/operation_test.go | 64 ++++++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 internal/introspection/operation_test.go diff --git a/internal/introspection/introspection.c b/internal/introspection/introspection.c index 49e3ae9..870cb3e 100644 --- a/internal/introspection/introspection.c +++ b/internal/introspection/introspection.c @@ -115,6 +115,9 @@ static void collect_argument(VipsObject* object, GParamSpec* pspec, arg->int_default = 0; arg->double_default = 0.0; arg->string_default = NULL; + arg->has_range = 0; + arg->minimum = 0.0; + arg->maximum = 0.0; arg->is_image = 0; arg->is_buffer = 0; arg->is_array = 0; @@ -165,24 +168,36 @@ static void collect_argument(VipsObject* object, GParamSpec* pspec, arg->has_default = 1; arg->default_type = 2; // int arg->int_default = pspec_int->default_value; + arg->has_range = 1; + arg->minimum = pspec_int->minimum; + arg->maximum = pspec_int->maximum; } else if (G_IS_PARAM_SPEC_UINT(pspec)) { GParamSpecUInt *pspec_uint = G_PARAM_SPEC_UINT(pspec); arg->has_default = 1; arg->default_type = 2; // int arg->int_default = (gint)pspec_uint->default_value; + arg->has_range = 1; + arg->minimum = pspec_uint->minimum; + arg->maximum = pspec_uint->maximum; } else if (G_IS_PARAM_SPEC_DOUBLE(pspec)) { GParamSpecDouble *pspec_double = G_PARAM_SPEC_DOUBLE(pspec); arg->has_default = 1; arg->default_type = 3; // double arg->double_default = pspec_double->default_value; + arg->has_range = 1; + arg->minimum = pspec_double->minimum; + arg->maximum = pspec_double->maximum; } else if (G_IS_PARAM_SPEC_FLOAT(pspec)) { GParamSpecFloat *pspec_float = G_PARAM_SPEC_FLOAT(pspec); arg->has_default = 1; arg->default_type = 3; // double arg->double_default = (gdouble)pspec_float->default_value; + arg->has_range = 1; + arg->minimum = pspec_float->minimum; + arg->maximum = pspec_float->maximum; } else if (G_IS_PARAM_SPEC_STRING(pspec)) { GParamSpecString *pspec_string = G_PARAM_SPEC_STRING(pspec); diff --git a/internal/introspection/introspection.h b/internal/introspection/introspection.h index ec6aa4e..75c2b9a 100644 --- a/internal/introspection/introspection.h +++ b/internal/introspection/introspection.h @@ -47,6 +47,9 @@ typedef struct { gint int_default; gdouble double_default; char *string_default; + int has_range; + gdouble minimum; + gdouble maximum; } ArgInfo; // Get all arguments of an operation diff --git a/internal/introspection/operation.go b/internal/introspection/operation.go index cb6a416..25631f9 100644 --- a/internal/introspection/operation.go +++ b/internal/introspection/operation.go @@ -50,6 +50,10 @@ type Argument struct { Flags int IsEnum bool EnumType string + EnumValues []int + HasRange bool + Minimum float64 + Maximum float64 NInputFrom string DefaultValue interface{} } @@ -218,6 +222,9 @@ func (v *Introspection) DiscoverOperationArguments(opName string) ([]Argument, e IsSource: isSource, IsTarget: isTarget, Flags: int(arg.flags), + HasRange: int(arg.has_range) != 0, + Minimum: float64(arg.minimum), + Maximum: float64(arg.maximum), } // Check if this is an enum or flags type @@ -242,6 +249,7 @@ func (v *Introspection) DiscoverOperationArguments(opName string) ([]Argument, e if goArg.IsEnum { enumName := C.GoString(C.g_type_name(arg.type_val)) goArg.EnumType = v.getGoEnumName(enumName) + goArg.EnumValues = getEnumValues(arg.type_val) v.addEnumType(enumName, goArg.EnumType) } if isArray && isInput && required && !isAffineMatrix { @@ -459,6 +467,35 @@ func (v *Introspection) DiscoverOperationArguments(opName string) ([]Argument, e return goArgs, nil } +func getEnumValues(gtype C.GType) []int { + typeName := C.g_type_name(gtype) + if typeName == nil { + return nil + } + + cTypeName := C.CString(C.GoString(typeName)) + defer C.free(unsafe.Pointer(cTypeName)) + + isFlags := C.int(0) + if C.is_type_flags(gtype) != 0 { + isFlags = 1 + } + + var count C.int + values := C.get_enum_or_flag_values(cTypeName, &count, isFlags) + if values == nil || count <= 0 { + return nil + } + defer C.free_enum_values(values, count) + + valueSlice := (*[1 << 30]C.EnumValueInfo)(unsafe.Pointer(values))[:count:count] + result := make([]int, 0, int(count)) + for _, value := range valueSlice { + result = append(result, int(value.value)) + } + return result +} + // Helper function to extract default values based on type func (v *Introspection) extractDefaultValue(arg C.ArgInfo, goType string) interface{} { // Check if there's a default value diff --git a/internal/introspection/operation_test.go b/internal/introspection/operation_test.go new file mode 100644 index 0000000..068446d --- /dev/null +++ b/internal/introspection/operation_test.go @@ -0,0 +1,64 @@ +package introspection + +import "testing" + +func TestDiscoverOperationArgumentMetadata(t *testing.T) { + introspection := NewIntrospection(false) + args, err := introspection.DiscoverOperationArguments("icc_transform") + if err != nil { + t.Fatal(err) + } + + intent := findArgument(t, args, "intent") + if !intent.IsEnum || intent.EnumType != "Intent" { + t.Fatalf("intent metadata = %+v, want Intent enum", intent) + } + if intent.DefaultValue != 1 { + t.Fatalf("intent default = %v, want 1", intent.DefaultValue) + } + if !containsInt(intent.EnumValues, 0) { + t.Fatalf("intent enum values = %v, want zero-valued member", intent.EnumValues) + } + + blackPointCompensation := findArgument(t, args, "black_point_compensation") + if blackPointCompensation.DefaultValue != false { + t.Fatalf("black point compensation default = %v, want false", blackPointCompensation.DefaultValue) + } + + inputProfile := findArgument(t, args, "input_profile") + if inputProfile.HasRange { + t.Fatalf("string input_profile unexpectedly has numeric range: %+v", inputProfile) + } + + thumbnailArgs, err := introspection.DiscoverOperationArguments("thumbnail") + if err != nil { + t.Fatal(err) + } + thumbnailHeight := findArgument(t, thumbnailArgs, "height") + if !thumbnailHeight.HasRange { + t.Fatal("thumbnail height has no numeric range metadata") + } + if thumbnailHeight.Maximum < thumbnailHeight.Minimum { + t.Fatalf("thumbnail height range = [%v, %v]", thumbnailHeight.Minimum, thumbnailHeight.Maximum) + } +} + +func findArgument(t *testing.T, args []Argument, name string) Argument { + t.Helper() + for _, arg := range args { + if arg.Name == name { + return arg + } + } + t.Fatalf("argument %q not found", name) + return Argument{} +} + +func containsInt(values []int, wanted int) bool { + for _, value := range values { + if value == wanted { + return true + } + } + return false +}