Merge branch 'development' of github.com:Incubaid/herolib into development

* 'development' of github.com:Incubaid/herolib:
  chore: Add FsListArg struct and update imports
  refactor: Rename module and import path
  refactor: Use snake_case for object names and constants
  fix: fix conflicts
  refactor: Improve struct decoding and skip logic

# Conflicts:
#	libarchive/encoderherocomplex/any_base.v
This commit is contained in:
2025-10-13 05:36:31 +04:00
14 changed files with 86 additions and 431 deletions

View File

@@ -1,7 +1,7 @@
module vcode
import incubaid.herolib.ai.mcp
import incubaid.herolib.develop.codetools as code
import incubaid.herolib.core.code
import incubaid.herolib.schemas.jsonschema
import x.json2 { Any }

View File

@@ -1,4 +1,4 @@
module codetools
module code
import incubaid.herolib.ui.console
import os

View File

@@ -2,6 +2,7 @@ module encoderhero
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.ourtime
import incubaid.herolib.core.texttools
pub struct Decoder[T] {
pub mut:
@@ -19,7 +20,7 @@ fn decode_struct[T](_ T, data string) !T {
mut typ := T{}
$if T is $struct {
obj_name := T.name.all_after_last('.').to_lower()
obj_name := texttools.snake_case(T.name.all_after_last('.'))
// Define possible action name formats to try
action_names_to_try := [
@@ -79,7 +80,6 @@ fn decode_struct[T](_ T, data string) !T {
}
}
}
} $else {
return error("The type `${T.name}` can't be decoded. Only structs are supported.")
}
@@ -91,13 +91,9 @@ fn decode_struct[T](_ T, data string) !T {
fn should_skip_field_decode(attrs []string) bool {
for attr in attrs {
attr_clean := attr.to_lower().replace(' ', '').replace('\t', '')
if attr_clean == 'skip'
|| attr_clean.starts_with('skip;')
|| attr_clean.ends_with(';skip')
|| attr_clean.contains(';skip;')
|| attr_clean == 'skipdecode'
|| attr_clean.starts_with('skipdecode;')
|| attr_clean.ends_with(';skipdecode')
if attr_clean == 'skip' || attr_clean.starts_with('skip;') || attr_clean.ends_with(';skip')
|| attr_clean.contains(';skip;') || attr_clean == 'skipdecode'
|| attr_clean.starts_with('skipdecode;') || attr_clean.ends_with(';skipdecode')
|| attr_clean.contains(';skipdecode;') {
return true
}

View File

@@ -7,8 +7,8 @@ pub struct TestStruct {
name string
}
const blank_script = '!!define.teststruct'
const full_script = '!!define.teststruct id:42 name:testobject'
const blank_script = '!!define.test_struct'
const full_script = '!!define.test_struct id:42 name:testobject'
const invalid_script = '!!define.another_struct'
fn test_decode_simple() ! {
@@ -35,7 +35,7 @@ pub struct ConfigStruct {
hosts []string
}
const config_script = "!!define.configstruct name:production enabled:true timeout:60 hosts:'host1.com,host2.com,host3.com'"
const config_script = "!!define.config_struct name:production enabled:true timeout:60 hosts:'host1.com,host2.com,host3.com'"
fn test_decode_with_arrays() ! {
object := decode[ConfigStruct](config_script)!
@@ -52,7 +52,7 @@ pub struct Base {
}
pub struct Person {
Base // Embedded struct
Base // Embedded struct
mut:
name string
age int

View File

@@ -2,6 +2,7 @@ module encoderhero
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.ourtime
import incubaid.herolib.core.texttools
import v.reflection
// Encoder encodes a struct into HEROSCRIPT representation.
@@ -42,10 +43,10 @@ pub fn (mut e Encoder) encode_struct[T](t T) ! {
mut mytype := reflection.type_of[T](t)
struct_attrs := attrs_get_reflection(mytype)
mut action_name := T.name.all_after_last('.').to_lower()
mut action_name := texttools.snake_case(T.name.all_after_last('.'))
if 'alias' in struct_attrs {
action_name = struct_attrs['alias'].to_lower()
action_name = texttools.snake_case(struct_attrs['alias'])
}
e.action_names << action_name.to_lower()
@@ -81,11 +82,8 @@ pub fn (mut e Encoder) encode_struct[T](t T) ! {
fn should_skip_field(attrs []string) bool {
for attr in attrs {
attr_clean := attr.to_lower().replace(' ', '').replace('\t', '')
if attr_clean == 'skip'
|| attr_clean.starts_with('skip;')
|| attr_clean.ends_with(';skip')
|| attr_clean.contains(';skip;')
{
if attr_clean == 'skip' || attr_clean.starts_with('skip;') || attr_clean.ends_with(';skip')
|| attr_clean.contains(';skip;') {
return true
}
}

View File

@@ -32,11 +32,11 @@ fn test_encode_skip() ! {
fn test_encode_skip_multiple_attrs() ! {
struct SkipTest {
id int
name string
skip1 string @[skip]
skip2 int @[skip; other]
skip3 bool @[skipdecode]
id int
name string
skip1 string @[skip]
skip2 int @[other; skip]
skip3 bool @[skipdecode]
}
obj := SkipTest{

View File

@@ -8,7 +8,7 @@ pub struct Base {
}
pub struct Person {
Base // Embedded struct
Base // Embedded struct
mut:
name string
age int = 20
@@ -130,8 +130,10 @@ pub struct NestedParent {
fn test_encode_nested_fails() ! {
parent := NestedParent{
name: 'parent'
child: NestedChild{value: 'test'}
name: 'parent'
child: NestedChild{
value: 'test'
}
}
encode[NestedParent](parent) or {

View File

@@ -10,9 +10,9 @@ pub mut:
dbname string = 'postgres'
}
const postgres_client_blank = '!!define.postgresqlclient'
const postgres_client_full = '!!define.postgresqlclient name:production user:app_user port:5433 host:db.example.com password:secret123 dbname:myapp'
const postgres_client_partial = '!!define.postgresqlclient name:dev host:localhost password:devpass'
const postgres_client_blank = '!!define.postgresql_client'
const postgres_client_full = '!!define.postgresql_client name:production user:app_user port:5433 host:db.example.com password:secret123 dbname:myapp'
const postgres_client_partial = '!!define.postgresql_client name:dev host:localhost password:devpass'
fn test_postgres_client_decode_blank() ! {
mut client := decode[PostgresqlClient](postgres_client_blank)!

View File

@@ -7,12 +7,14 @@ HeroEncoder provides bidirectional conversion between V structs and HeroScript f
This module is designed for **simple, flat structs** only:
**Supported:**
- Basic types: `int`, `string`, `bool`, `f32`, `f64`, `u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`
- Arrays of basic types: `[]string`, `[]int`, etc.
- Time handling: `ourtime.OurTime`
- Embedded structs (for inheritance)
**Not Supported:**
- Nested structs (non-embedded fields)
- Arrays of structs
- Complex nested structures
@@ -222,11 +224,13 @@ encoded := encoderhero.encode[MyStruct](my_struct)!
## Limitations
**For Complex Data Structures, Use:**
- `incubaid.herolib.data.ourdb` - For nested data storage
- V's built-in `json` module - For JSON serialization
- Custom serialization - For specific needs
**This module is optimized for:**
- Configuration files
- Simple data exchange
- Flat data structures

View File

@@ -22,16 +22,15 @@ pub fn (params Params) decode_struct[T](start T) !T {
}
}
// println('Field: ${field.name}, should_skip: ${should_skip}, attrs: ${field.attrs}')
if ! should_skip {
if !should_skip {
$if field.is_enum {
t.$(field.name) = params.get_int(field.name) or { int(t.$(field.name)) }
} $else {
// super annoying didn't find other way, then to ignore options
$if field.is_option {
// For optional fields, if the key exists, decode it. Otherwise, leave it as none.
if params.exists(field.name) {
t.$(field.name) = params.decode_value(t.$(field.name), field.name)!
}
// For optional fields, skip decoding entirely
// They will remain as none (default value)
// This avoids type system issues with ?T vs !T
} $else {
if field.name[0].is_capital() {
t.$(field.name) = params.decode_struct(t.$(field.name))!
@@ -46,13 +45,9 @@ pub fn (params Params) decode_struct[T](start T) !T {
}
pub fn (params Params) decode_value[T](val T, key string) !T {
$if T is $option {
return error("is option")
}
// TODO: handle required fields
if !params.exists(key) {
return val // For optional types, this will be `none`. For non-optional, it's the default value.
return val // For non-optional types, this is the default value
}
$if T is string {
@@ -89,10 +84,10 @@ pub fn (params Params) decode_value[T](val T, key string) !T {
child_params := params.get_params(key)!
child := child_params.decode_struct(T{})!
return child
} $else {
// For any other type, return the default
return val
}
// If no specific decode path is found, return the default value for T.
// For optional types, this will be `none`.
return T{}
}
pub fn (params Params) get_list_bool(key string) ![]bool {
@@ -124,8 +119,10 @@ pub fn encode[T](t T, args EncodeArgs) !Params {
// Check each attribute for skip patterns
for attr in field.attrs {
attr_clean := attr.to_lower()
if attr_clean.contains('skip') {
attr_clean := attr.to_lower().replace(' ', '').replace('\t', '')
// During encoding, only skip fields with @[skip], not @[skipdecode]
if attr_clean == 'skip' || attr_clean.starts_with('skip;')
|| attr_clean.ends_with(';skip') || attr_clean.contains(';skip;') {
should_skip = true
break
}

View File

@@ -110,6 +110,13 @@ pub mut:
messages []db.MessageArg
}
@[params]
pub struct FsListArg {
pub mut:
group_id u32
limit int = 100 // Default limit is 100
}
// get new filesystem, not from the DB
pub fn (mut self DBFs) new(args FsArg) !Fs {
mut o := Fs{

View File

@@ -1,349 +0,0 @@
module encoderherocomplex
// import time
// // i8 uses `Any` as a 16-bit integer.
// pub fn (f Any) i8() i8 {
// match f {
// i8 {
// return f
// }
// i16, i32, int, i64, u8, u16, u32, u64, f32, f64, bool {
// return i8(f)
// }
// string {
// return f.i8()
// }
// else {
// return 0
// }
// }
// }
// // i16 uses `Any` as a 16-bit integer.
// pub fn (f Any) i16() i16 {
// match f {
// i16 {
// return f
// }
// i8, i32, int, i64, u8, u16, u32, u64, f32, f64, bool {
// return i16(f)
// }
// string {
// return f.i16()
// }
// else {
// return 0
// }
// }
// }
// // int uses `Any` as an integer.
// pub fn (f Any) int() int {
// match f {
// int {
// return f
// }
// i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool {
// return int(f)
// }
// string {
// return f.int()
// }
// else {
// return 0
// }
// }
// }
// // i32 uses `Any` as a 32-bit integer.
// pub fn (f Any) i32() i32 {
// match f {
// i32 {
// return f
// }
// i8, i16, int, i64, u8, u16, u32, u64, f32, f64, bool {
// return i32(f)
// }
// string {
// return f.i32()
// }
// else {
// return 0
// }
// }
// }
// // i64 uses `Any` as a 64-bit integer.
// pub fn (f Any) i64() i64 {
// match f {
// i64 {
// return f
// }
// i8, i16, i32, int, u8, u16, u32, u64, f32, f64, bool {
// return i64(f)
// }
// string {
// return f.i64()
// }
// else {
// return 0
// }
// }
// }
// // u64 uses `Any` as a 64-bit unsigned integer.
// pub fn (f Any) u64() u64 {
// match f {
// u64 {
// return f
// }
// u8, u16, u32, i8, i16, i32, int, i64, f32, f64, bool {
// return u64(f)
// }
// string {
// return f.u64()
// }
// else {
// return 0
// }
// }
// }
// // f32 uses `Any` as a 32-bit float.
// pub fn (f Any) f32() f32 {
// match f {
// f32 {
// return f
// }
// bool, i8, i16, i32, int, i64, u8, u16, u32, u64, f64 {
// return f32(f)
// }
// string {
// return f.f32()
// }
// else {
// return 0.0
// }
// }
// }
// // f64 uses `Any` as a 64-bit float.
// pub fn (f Any) f64() f64 {
// match f {
// f64 {
// return f
// }
// i8, i16, i32, int, i64, u8, u16, u32, u64, f32 {
// return f64(f)
// }
// string {
// return f.f64()
// }
// else {
// return 0.0
// }
// }
// }
// // bool uses `Any` as a bool.
// pub fn (f Any) bool() bool {
// match f {
// bool {
// return f
// }
// string {
// if f == 'false' {
// return false
// }
// if f == 'true' {
// return true
// }
// if f.len > 0 {
// return f != '0' && f != '0.0'
// } else {
// return false
// }
// }
// i8, i16, i32, int, i64 {
// return i64(f) != 0
// }
// u8, u16, u32, u64 {
// return u64(f) != 0
// }
// f32, f64 {
// return f64(f) != 0.0
// }
// else {
// return false
// }
// }
// }
// // arr uses `Any` as an array.
// pub fn (f Any) arr() []Any {
// if f is []Any {
// return f
// } else if f is map[string]Any {
// mut arr := []Any{}
// for _, v in f {
// arr << v
// }
// return arr
// }
// return [f]
// }
// // as_map uses `Any` as a map.
// pub fn (f Any) as_map() map[string]Any {
// if f is map[string]Any {
// return f
// } else if f is []Any {
// mut mp := map[string]Any{}
// for i, fi in f {
// mp['${i}'] = fi
// }
// return mp
// }
// return {
// '0': f
// }
// }
// // to_time uses `Any` as a time.Time.
// pub fn (f Any) to_time() !time.Time {
// match f {
// time.Time {
// return f
// }
// i64 {
// return time.unix(f)
// }
// string {
// is_iso8601 := f[4] == `-` && f[7] == `-`
// if is_iso8601 {
// return time.parse_iso8601(f)!
// }
// is_rfc3339 := f.len == 24 && f[23] == `Z` && f[10] == `T`
// if is_rfc3339 {
// return time.parse_rfc3339(f)!
// }
// mut is_unix_timestamp := true
// for c in f {
// if c == `-` || (c >= `0` && c <= `9`) {
// continue
// }
// is_unix_timestamp = false
// break
// }
// if is_unix_timestamp {
// return time.unix(f.i64())
// }
// // TODO: parse_iso8601
// // TODO: parse_rfc2822
// return time.parse(f)!
// }
// else {
// return error('not a time value: ${f} of type: ${f.type_name()}')
// }
// }
// }
// // map_from convert a struct to map of Any
// pub fn map_from[T](t T) map[string]Any {
// mut m := map[string]Any{}
// $if T is $struct {
// $for field in T.fields {
// value := t.$(field.name)
// $if field.is_array {
// mut arr := []Any{}
// for variable in value {
// arr << Any(variable)
// }
// m[field.name] = arr
// arr.clear()
// } $else $if field.is_struct {
// m[field.name] = map_from(value)
// } $else $if field.is_map {
// // TODO
// } $else $if field.is_alias {
// // TODO
// } $else $if field.is_option {
// // TODO
// } $else {
// // TODO: improve memory usage when convert
// $if field.typ is string {
// m[field.name] = value.str()
// } $else $if field.typ is bool {
// m[field.name] = t.$(field.name).str().bool()
// } $else $if field.typ is i8 {
// m[field.name] = t.$(field.name).str().i8()
// } $else $if field.typ is i16 {
// m[field.name] = t.$(field.name).str().i16()
// } $else $if field.typ is i32 {
// m[field.name] = t.$(field.name).str().i32()
// } $else $if field.typ is int {
// m[field.name] = t.$(field.name).str().int()
// } $else $if field.typ is i64 {
// m[field.name] = t.$(field.name).str().i64()
// } $else $if field.typ is f32 {
// m[field.name] = t.$(field.name).str().f32()
// } $else $if field.typ is f64 {
// m[field.name] = t.$(field.name).str().f64()
// } $else $if field.typ is u8 {
// m[field.name] = t.$(field.name).str().u8()
// } $else $if field.typ is u16 {
// m[field.name] = t.$(field.name).str().u16()
// } $else $if field.typ is u32 {
// m[field.name] = t.$(field.name).str().u32()
// } $else $if field.typ is u64 {
// m[field.name] = t.$(field.name).str().u64()
// } $else {
// // return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose")
// }
// }
// }
// }
// return m
// }
// // str returns the JSON string representation of the `map[string]Any` type.
// pub fn (f map[string]Any) str() string {
// return Any(f).json_str()
// }
// // str returns the JSON string representation of the `[]Any` type.
// pub fn (f []Any) str() string {
// return Any(f).json_str()
// }
// // str returns the string representation of the `Any` type. Use the `json_str` method
// // if you want to use the escaped str() version of the `Any` type.
// pub fn (f Any) str() string {
// if f is string {
// return f
// } else {
// return f.json_str()
// }
// }
// // json_str returns the JSON string representation of the `Any` type.
// pub fn (f Any) json_str() string {
// return encode(f)
// }
// // prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type.
// @[manualfree]
// pub fn (f Any) prettify_json_str() string {
// mut params := []u8{}
// defer {
// unsafe { params.free() }
// }
// mut enc := Encoder{
// newline: `\n`
// newline_spaces_count: 2
// }
// enc.encode_value(f, mut params) or {}
// return params.bytestr()
// }

View File

@@ -169,7 +169,7 @@ lib/lang
lib/clients
lib/core
lib/develop
lib/hero
// lib/hero
// lib/vfs The vfs folder is not exists on the development branch, so we need to uncomment it after merging this PR https://github.com/incubaid/herolib/pull/68
// lib/crypt
'