refactor: Change hero action syntax to verb.noun
- Change action name format from `obj.verb` to `verb.obj` - Update decoder to look for `define.obj` or `configure.obj` - Modify encoder export to use the new `define.obj` prefix - Update all test constants and scripts to the new syntax - Make Remark struct public for test visibility
This commit is contained in:
@@ -20,11 +20,11 @@ fn decode_struct[T](_ T, data string) !T {
|
|||||||
// println(data)
|
// println(data)
|
||||||
$if T is $struct {
|
$if T is $struct {
|
||||||
obj_name := texttools.snake_case(T.name.all_after_last('.'))
|
obj_name := texttools.snake_case(T.name.all_after_last('.'))
|
||||||
mut action_name := '${obj_name}.define'
|
mut action_name := 'define.${obj_name}'
|
||||||
if !data.contains(action_name) {
|
if !data.contains(action_name) {
|
||||||
action_name = '${obj_name}.configure'
|
action_name = 'configure.${obj_name}'
|
||||||
if !data.contains(action_name) {
|
if !data.contains(action_name) {
|
||||||
return error('Data does not contain action name: ${obj_name}.define or ${action_name}')
|
return error('Data does not contain action name: define.${obj_name} or ${action_name}')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions_split := data.split('!!')
|
actions_split := data.split('!!')
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ struct ComplexStruct {
|
|||||||
child ChildStruct
|
child ChildStruct
|
||||||
}
|
}
|
||||||
|
|
||||||
const blank_complex = '!!define.complex_struct'
|
const blank_complex = '!!define.complex_struct
|
||||||
const partial_complex = '!!define.complex_struct id: 42 name: testcomplex'
|
!!define.child_struct'
|
||||||
|
|
||||||
|
const partial_complex = '!!define.complex_struct id: 42 name: testcomplex
|
||||||
|
!!define.child_struct'
|
||||||
|
|
||||||
const full_complex = '!!define.complex_struct id: 42 name: testobject
|
const full_complex = '!!define.complex_struct id: 42 name: testobject
|
||||||
!!define.complex_struct.child text: child_text number: 24
|
!!define.child_struct text: child_text number: 24
|
||||||
'
|
'
|
||||||
|
|
||||||
fn test_decode_complex() ! {
|
fn test_decode_complex() ! {
|
||||||
@@ -128,14 +132,14 @@ const person = Person{
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_decode() ! {
|
fn test_decode() ! {
|
||||||
mut object := decode[Person]('')!
|
// Test decoding with proper person data
|
||||||
assert object == Person{}
|
object := decode[Person](person_heroscript)!
|
||||||
|
|
||||||
object = decode[Person](person_heroscript)!
|
|
||||||
assert object == person
|
assert object == person
|
||||||
|
|
||||||
// object = decode[ComplexStruct](full_complex) or {
|
// Test that empty string fails as expected
|
||||||
// assert true
|
decode[Person]('') or {
|
||||||
// ComplexStruct{}
|
assert true // This should fail, which is correct
|
||||||
// }
|
return
|
||||||
|
}
|
||||||
|
assert false // Should not reach here
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub fn encode[T](val T) !string {
|
|||||||
// export exports an encoder into encoded heroscript
|
// export exports an encoder into encoded heroscript
|
||||||
pub fn (e Encoder) export() !string {
|
pub fn (e Encoder) export() !string {
|
||||||
mut script := e.params.export(
|
mut script := e.params.export(
|
||||||
pre: '!!${e.action_names.join('.')}.configure'
|
pre: '!!define.${e.action_names.join('.')}'
|
||||||
indent: ' '
|
indent: ' '
|
||||||
skip_empty: true
|
skip_empty: true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ struct MyStruct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// is the one we should skip
|
// is the one we should skip
|
||||||
struct Remark {
|
pub struct Remark {
|
||||||
id int
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ struct Base {
|
|||||||
remarks []Remark
|
remarks []Remark
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Remark {
|
pub struct Remark {
|
||||||
text string
|
text string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +75,7 @@ struct Profile {
|
|||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
const person_heroscript = "
|
const person_heroscript = "!!define.person id:1 name:Bob age:21 birthday:'2012-12-12 00:00:00'
|
||||||
!!define.person id:1 name:Bob birthday:'2012-12-12 00:00:00'
|
|
||||||
!!define.person.car name:'Bob\\'s car' year:2014
|
!!define.person.car name:'Bob\\'s car' year:2014
|
||||||
!!define.person.car.insurance provider:insurer
|
!!define.person.car.insurance provider:insurer
|
||||||
|
|
||||||
@@ -107,15 +106,14 @@ const person = Person{
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const company_script = "
|
const company_script = "!!define.company name:'Tech Corp' founded:'2022-12-05 20:14'
|
||||||
!!define.company name:'Tech Corp' founded:'2022-12-05 20:14'
|
!!define.company.person id:1 name:Bob age:21 birthday:'2012-12-12 00:00:00'
|
||||||
!!define.company.person id:1 name:Bob birthday:'2012-12-12 00:00:00'
|
|
||||||
!!define.company.person.car name:'Bob\\'s car' year:2014
|
!!define.company.person.car name:'Bob\\'s car' year:2014
|
||||||
!!define.company.person.car.insurance provider:insurer
|
!!define.company.person.car.insurance provider:insurer
|
||||||
|
|
||||||
!!define.company.person.profile platform:Github url:github.com/example
|
!!define.company.person.profile platform:Github url:github.com/example
|
||||||
|
|
||||||
!!define.company.person id:2 name:Alice birthday:'1990-06-20 00:00:00'
|
!!define.company.person id:2 name:Alice age:30 birthday:'1990-06-20 00:00:00'
|
||||||
!!define.company.person.car name:'Alice\\'s car' year:2018
|
!!define.company.person.car name:'Alice\\'s car' year:2018
|
||||||
!!define.company.person.car.insurance
|
!!define.company.person.car.insurance
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
module encoderhero
|
module encoderhero
|
||||||
|
|
||||||
|
|
||||||
pub struct PostgresqlClient {
|
pub struct PostgresqlClient {
|
||||||
pub mut:
|
pub mut:
|
||||||
name string = 'default'
|
name string = 'default'
|
||||||
@@ -11,14 +10,13 @@ pub mut:
|
|||||||
dbname string = 'postgres'
|
dbname string = 'postgres'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
const postgres_client_blank = '!!postgresql_client.configure'
|
const postgres_client_complex = '
|
||||||
const postgres_client_full = '!!postgresql_client.configure name:production user:app_user port:5433 host:db.example.com password:secret123 dbname:myapp'
|
!!define.postgresql_client name:staging user:stage_user port:5434 host:staging.db.com password:stagepass dbname:stagingdb
|
||||||
const postgres_client_partial = '!!postgresql_client.configure name:dev host:localhost password:devpass'
|
'
|
||||||
|
|
||||||
const postgres_client_complex = "
|
|
||||||
!!postgresql_client.configure name:staging user:stage_user port:5434 host:staging.db.com password:stagepass dbname:stagingdb
|
|
||||||
"
|
|
||||||
|
|
||||||
fn test_postgres_client_decode_blank() ! {
|
fn test_postgres_client_decode_blank() ! {
|
||||||
mut client := decode[PostgresqlClient](postgres_client_blank)!
|
mut client := decode[PostgresqlClient](postgres_client_blank)!
|
||||||
@@ -63,12 +61,12 @@ fn test_postgres_client_decode_complex() ! {
|
|||||||
fn test_postgres_client_encode_decode_roundtrip() ! {
|
fn test_postgres_client_encode_decode_roundtrip() ! {
|
||||||
// Test encoding and decoding roundtrip
|
// Test encoding and decoding roundtrip
|
||||||
original := PostgresqlClient{
|
original := PostgresqlClient{
|
||||||
name: 'testdb'
|
name: 'testdb'
|
||||||
user: 'testuser'
|
user: 'testuser'
|
||||||
port: 5435
|
port: 5435
|
||||||
host: 'test.host.com'
|
host: 'test.host.com'
|
||||||
password: 'testpass123'
|
password: 'testpass123'
|
||||||
dbname: 'testdb'
|
dbname: 'testdb'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode to heroscript
|
// Encode to heroscript
|
||||||
@@ -95,29 +93,29 @@ fn test_postgres_client_encode() ! {
|
|||||||
// Test encoding with different configurations
|
// Test encoding with different configurations
|
||||||
test_cases := [
|
test_cases := [
|
||||||
PostgresqlClient{
|
PostgresqlClient{
|
||||||
name: 'minimal'
|
name: 'minimal'
|
||||||
user: 'root'
|
user: 'root'
|
||||||
port: 5432
|
port: 5432
|
||||||
host: 'localhost'
|
host: 'localhost'
|
||||||
password: ''
|
password: ''
|
||||||
dbname: 'postgres'
|
dbname: 'postgres'
|
||||||
},
|
},
|
||||||
PostgresqlClient{
|
PostgresqlClient{
|
||||||
name: 'full_config'
|
name: 'full_config'
|
||||||
user: 'admin'
|
user: 'admin'
|
||||||
port: 5433
|
port: 5433
|
||||||
host: 'remote.server.com'
|
host: 'remote.server.com'
|
||||||
password: 'securepass'
|
password: 'securepass'
|
||||||
dbname: 'production'
|
dbname: 'production'
|
||||||
},
|
},
|
||||||
PostgresqlClient{
|
PostgresqlClient{
|
||||||
name: 'localhost_dev'
|
name: 'localhost_dev'
|
||||||
user: 'dev'
|
user: 'dev'
|
||||||
port: 5432
|
port: 5432
|
||||||
host: '127.0.0.1'
|
host: '127.0.0.1'
|
||||||
password: 'devpassword'
|
password: 'devpassword'
|
||||||
dbname: 'devdb'
|
dbname: 'devdb'
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
for client in test_cases {
|
for client in test_cases {
|
||||||
@@ -134,22 +132,22 @@ fn test_postgres_client_encode() ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Play script for interactive testing
|
// Play script for interactive testing
|
||||||
const play_script = "
|
const play_script = '
|
||||||
# PostgresqlClient Encode/Decode Play Script
|
# PostgresqlClient Encode/Decode Play Script
|
||||||
# This script demonstrates encoding and decoding PostgresqlClient configurations
|
# This script demonstrates encoding and decoding PostgresqlClient configurations
|
||||||
|
|
||||||
!!postgresql_client.configure name:playground user:play_user
|
!!define.postgresql_client name:playground user:play_user
|
||||||
port:5432
|
port:5432
|
||||||
host:localhost
|
host:localhost
|
||||||
password:playpass
|
password:playpass
|
||||||
dbname:playdb
|
dbname:playdb
|
||||||
|
|
||||||
# You can also use partial configurations
|
# You can also use partial configurations
|
||||||
!!postgresql_client.configure name:quick_test host:127.0.0.1
|
!!define.postgresql_client name:quick_test host:127.0.0.1
|
||||||
|
|
||||||
# Default configuration (all defaults)
|
# Default configuration (all defaults)
|
||||||
!!postgresql_client.configure
|
!!define.postgresql_client
|
||||||
"
|
'
|
||||||
|
|
||||||
fn test_play_script() ! {
|
fn test_play_script() ! {
|
||||||
// Test the play script with multiple configurations
|
// Test the play script with multiple configurations
|
||||||
@@ -160,7 +158,7 @@ fn test_play_script() ! {
|
|||||||
mut clients := []PostgresqlClient{}
|
mut clients := []PostgresqlClient{}
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if line.starts_with('!!postgresql_client.configure') {
|
if line.starts_with('!!define.postgresql_client') {
|
||||||
client := decode[PostgresqlClient](line)!
|
client := decode[PostgresqlClient](line)!
|
||||||
clients << client
|
clients << client
|
||||||
}
|
}
|
||||||
@@ -192,12 +190,12 @@ pub fn run_play_script() ! {
|
|||||||
// Test 1: Basic encoding
|
// Test 1: Basic encoding
|
||||||
println('\n1. Testing basic encoding...')
|
println('\n1. Testing basic encoding...')
|
||||||
client := PostgresqlClient{
|
client := PostgresqlClient{
|
||||||
name: 'example'
|
name: 'example'
|
||||||
user: 'example_user'
|
user: 'example_user'
|
||||||
port: 5432
|
port: 5432
|
||||||
host: 'example.com'
|
host: 'example.com'
|
||||||
password: 'example_pass'
|
password: 'example_pass'
|
||||||
dbname: 'example_db'
|
dbname: 'example_db'
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded := encode[PostgresqlClient](client)!
|
encoded := encode[PostgresqlClient](client)!
|
||||||
@@ -215,12 +213,12 @@ pub fn run_play_script() ! {
|
|||||||
// Test 3: Edge cases
|
// Test 3: Edge cases
|
||||||
println('\n3. Testing edge cases...')
|
println('\n3. Testing edge cases...')
|
||||||
edge_client := PostgresqlClient{
|
edge_client := PostgresqlClient{
|
||||||
name: 'edge'
|
name: 'edge'
|
||||||
user: ''
|
user: ''
|
||||||
port: 0
|
port: 0
|
||||||
host: ''
|
host: ''
|
||||||
password: ''
|
password: ''
|
||||||
dbname: ''
|
dbname: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
edge_encoded := encode[PostgresqlClient](edge_client)!
|
edge_encoded := encode[PostgresqlClient](edge_client)!
|
||||||
|
|||||||
@@ -107,99 +107,110 @@ pub fn encode[T](t T, args EncodeArgs) !Params {
|
|||||||
// struct_attrs := attrs_get_reflection(mytype)
|
// struct_attrs := attrs_get_reflection(mytype)
|
||||||
|
|
||||||
$for field in T.fields {
|
$for field in T.fields {
|
||||||
val := t.$(field.name)
|
// Check if field has skip attribute
|
||||||
field_attrs := attrs_get(field.attrs)
|
mut should_skip := false
|
||||||
mut key := field.name
|
for attr in field.attrs {
|
||||||
if 'alias' in field_attrs {
|
if attr.contains('skip') {
|
||||||
key = field_attrs['alias']
|
should_skip = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$if field.is_option {
|
|
||||||
// Handle optional fields
|
|
||||||
if val != none {
|
|
||||||
// Unwrap the optional value before type checking and encoding
|
|
||||||
// Get the unwrapped value using reflection
|
|
||||||
// This is a workaround for V's reflection limitations with optionals
|
|
||||||
// We assume that if val != none, then it can be safely unwrapped
|
|
||||||
// and its underlying type can be determined.
|
|
||||||
// This might require a more robust way to get the underlying value
|
|
||||||
// if V's reflection doesn't provide a direct 'unwrap' for generic `val`.
|
|
||||||
// For now, we'll rely on the type checks below.
|
|
||||||
// The `val` here is the actual value of the field, which is `?T`.
|
|
||||||
// We need to check the type of `field.typ` to know what `T` is.
|
|
||||||
|
|
||||||
// Revert to simpler handling for optional fields
|
if !should_skip {
|
||||||
// Rely on V's string interpolation for optional types
|
val := t.$(field.name)
|
||||||
// If val is none, this block will be skipped.
|
field_attrs := attrs_get(field.attrs)
|
||||||
// If val is not none, it will be converted to string.
|
mut key := field.name
|
||||||
params.set(key, '${val}')
|
if 'alias' in field_attrs {
|
||||||
|
key = field_attrs['alias']
|
||||||
}
|
}
|
||||||
} $else $if val is string || val is int || val is bool || val is i64 || val is u32
|
$if field.is_option {
|
||||||
|| val is time.Time || val is ourtime.OurTime {
|
// Handle optional fields
|
||||||
params.set(key, '${val}')
|
if val != none {
|
||||||
} $else $if field.is_enum {
|
// Unwrap the optional value before type checking and encoding
|
||||||
params.set(key, '${int(val)}')
|
// Get the unwrapped value using reflection
|
||||||
} $else $if field.typ is []string {
|
// This is a workaround for V's reflection limitations with optionals
|
||||||
mut v2 := ''
|
// We assume that if val != none, then it can be safely unwrapped
|
||||||
for i in val {
|
// and its underlying type can be determined.
|
||||||
if i.contains(' ') {
|
// This might require a more robust way to get the underlying value
|
||||||
v2 += "\"${i}\","
|
// if V's reflection doesn't provide a direct 'unwrap' for generic `val`.
|
||||||
} else {
|
// For now, we'll rely on the type checks below.
|
||||||
v2 += '${i},'
|
// The `val` here is the actual value of the field, which is `?T`.
|
||||||
}
|
// We need to check the type of `field.typ` to know what `T` is.
|
||||||
}
|
|
||||||
v2 = v2.trim(',')
|
// Revert to simpler handling for optional fields
|
||||||
params.params << Param{
|
// Rely on V's string interpolation for optional types
|
||||||
key: field.name
|
// If val is none, this block will be skipped.
|
||||||
value: v2
|
// If val is not none, it will be converted to string.
|
||||||
}
|
|
||||||
} $else $if field.typ is []int {
|
|
||||||
mut v2 := ''
|
|
||||||
for i in val {
|
|
||||||
v2 += '${i},'
|
|
||||||
}
|
|
||||||
v2 = v2.trim(',')
|
|
||||||
params.params << Param{
|
|
||||||
key: field.name
|
|
||||||
value: v2
|
|
||||||
}
|
|
||||||
} $else $if field.typ is []bool {
|
|
||||||
mut v2 := ''
|
|
||||||
for i in val {
|
|
||||||
v2 += '${i},'
|
|
||||||
}
|
|
||||||
v2 = v2.trim(',')
|
|
||||||
params.params << Param{
|
|
||||||
key: field.name
|
|
||||||
value: v2
|
|
||||||
}
|
|
||||||
} $else $if field.typ is []u32 {
|
|
||||||
mut v2 := ''
|
|
||||||
for i in val {
|
|
||||||
v2 += '${i},'
|
|
||||||
}
|
|
||||||
v2 = v2.trim(',')
|
|
||||||
params.params << Param{
|
|
||||||
key: field.name
|
|
||||||
value: v2
|
|
||||||
}
|
|
||||||
} $else $if field.typ is $struct {
|
|
||||||
// TODO: Handle embeds better
|
|
||||||
is_embed := field.name[0].is_capital()
|
|
||||||
if is_embed {
|
|
||||||
$if val is string || val is int || val is bool || val is i64 || val is u32
|
|
||||||
|| val is time.Time {
|
|
||||||
params.set(key, '${val}')
|
params.set(key, '${val}')
|
||||||
}
|
}
|
||||||
} else {
|
} $else $if val is string || val is int || val is bool || val is i64 || val is u32
|
||||||
if args.recursive {
|
|| val is time.Time || val is ourtime.OurTime {
|
||||||
child_params := encode(val)!
|
params.set(key, '${val}')
|
||||||
params.params << Param{
|
} $else $if field.is_enum {
|
||||||
key: field.name
|
params.set(key, '${int(val)}')
|
||||||
value: child_params.export()
|
} $else $if field.typ is []string {
|
||||||
|
mut v2 := ''
|
||||||
|
for i in val {
|
||||||
|
if i.contains(' ') {
|
||||||
|
v2 += "\"${i}\","
|
||||||
|
} else {
|
||||||
|
v2 += '${i},'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
v2 = v2.trim(',')
|
||||||
|
params.params << Param{
|
||||||
|
key: field.name
|
||||||
|
value: v2
|
||||||
|
}
|
||||||
|
} $else $if field.typ is []int {
|
||||||
|
mut v2 := ''
|
||||||
|
for i in val {
|
||||||
|
v2 += '${i},'
|
||||||
|
}
|
||||||
|
v2 = v2.trim(',')
|
||||||
|
params.params << Param{
|
||||||
|
key: field.name
|
||||||
|
value: v2
|
||||||
|
}
|
||||||
|
} $else $if field.typ is []bool {
|
||||||
|
mut v2 := ''
|
||||||
|
for i in val {
|
||||||
|
v2 += '${i},'
|
||||||
|
}
|
||||||
|
v2 = v2.trim(',')
|
||||||
|
params.params << Param{
|
||||||
|
key: field.name
|
||||||
|
value: v2
|
||||||
|
}
|
||||||
|
} $else $if field.typ is []u32 {
|
||||||
|
mut v2 := ''
|
||||||
|
for i in val {
|
||||||
|
v2 += '${i},'
|
||||||
|
}
|
||||||
|
v2 = v2.trim(',')
|
||||||
|
params.params << Param{
|
||||||
|
key: field.name
|
||||||
|
value: v2
|
||||||
|
}
|
||||||
|
} $else $if field.typ is $struct {
|
||||||
|
// TODO: Handle embeds better
|
||||||
|
is_embed := field.name[0].is_capital()
|
||||||
|
if is_embed {
|
||||||
|
$if val is string || val is int || val is bool || val is i64 || val is u32
|
||||||
|
|| val is time.Time {
|
||||||
|
params.set(key, '${val}')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if args.recursive {
|
||||||
|
child_params := encode(val)!
|
||||||
|
params.params << Param{
|
||||||
|
key: field.name
|
||||||
|
value: child_params.export()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} $else {
|
||||||
}
|
}
|
||||||
} $else {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return params
|
return params
|
||||||
|
|||||||
@@ -202,8 +202,6 @@ generate_test.v
|
|||||||
dbfs_test.v
|
dbfs_test.v
|
||||||
namedb_test.v
|
namedb_test.v
|
||||||
timetools_test.v
|
timetools_test.v
|
||||||
encoderhero/encoder_test.v
|
|
||||||
encoderhero/decoder_test.v
|
|
||||||
code/codeparser
|
code/codeparser
|
||||||
gittools_test.v
|
gittools_test.v
|
||||||
link_def_test.v
|
link_def_test.v
|
||||||
|
|||||||
Reference in New Issue
Block a user