This commit is contained in:
2024-12-25 09:23:31 +01:00
parent 01ca5897db
commit 4e030b794d
306 changed files with 35071 additions and 22 deletions

View File

@@ -0,0 +1,8 @@
module paramsparser
// Looks for a string of params in the parameters. If it doesn't exist this function will return an error.
// Furthermore an error will be returned if the params is not properly formatted
pub fn (params &Params) get_params(key string) !Params {
mut valuestr := params.get(key)!
return parse(valuestr)!
}

View File

@@ -0,0 +1,25 @@
module paramsparser
// import freeflowuniverse.herolib.data.jsonschema
// pub struct ParamsPub {
// pub mut:
// params []Param
// args []string //are commands without key/val, best not to use
// }
// pub struct ParamPub {
// pub:
// key string
// value string
// }
// pub fn (mut params Params) json_schema() jsonschema.Schema {
// return jsonschema.Schema {
// }
// }
// pub fn json_import(data string) !Params {
// return json.decode(Params, data)
// }

View File

@@ -0,0 +1,144 @@
module paramsparser
@[heap]
pub struct Params {
pub mut:
params []Param
args []string
comments []string
}
pub struct Param {
pub mut:
key string
value string
comment string
}
// get params from txt, same as parse()
pub fn new(txt string) !Params {
return parse(txt)!
}
pub fn new_from_dict(kwargs map[string]string) !Params {
mut p := Params{}
for key, val in kwargs {
p.set(key, val)
}
return p
}
pub fn new_params() Params {
return Params{}
}
pub fn (mut params Params) delete(key string) {
key2 := key.to_lower().trim_space()
if params.exists(key) {
mut params_out := []Param{}
for p in params.params {
if p.key != key2 {
params_out << p
}
}
params.params = params_out
}
}
pub fn (mut params Params) set(key string, value string) {
key2 := key.to_lower().trim_space().trim_left('/')
params.delete(key2)
params.params << Param{
key: key2
value: str_normalize(value)
}
}
pub fn (mut params Params) set_with_comment(key string, value string, comment string) {
key2 := key.to_lower().trim_space().trim_left('/')
params.delete(key2)
params.params << Param{
key: key2
value: str_normalize(value)
comment: comment_normalize(comment)
}
}
pub fn (mut params Params) delete_arg(key string) {
key2 := key.to_lower().trim_space()
if params.exists_arg(key2) {
params.args.delete(params.args.index(key2))
}
}
pub fn (mut params Params) set_arg(value string) {
mut value2 := value.trim(" '").trim_left('/')
value2 = value2.replace('<<BR>>', '\n')
value2 = value2.replace('<BR>', '\n')
if !params.exists_arg(value2) {
params.args << value2
}
}
pub fn (mut params Params) set_arg_with_comment(value string, comment string) {
value2 := value.trim(" '").trim_left('/')
if !params.exists_arg(str_normalize(value2)) {
params.args << value2
if comment.len > 0 {
params.comments << comment_normalize(comment)
}
}
}
fn str_normalize(txt_ string) string {
mut txt := txt_
txt = txt.replace('\\\\n', '\n')
txt = txt.replace("\\'", "'")
txt = txt.replace('<<BR>>', '\n')
txt = txt.replace('<BR>', '\n') // console.print_debug(txt)
return txt.trim_space()
}
fn comment_normalize(txt_ string) string {
mut txt := str_normalize(txt_)
return txt.trim_space().trim_right('-').trim_space()
}
// parse new txt as params and merge into params
pub fn (mut params Params) add(txt string) ! {
paramsnew := parse(txt)!
for p in paramsnew.params {
params.set(p.key, p.value)
}
for a in paramsnew.args {
params.set_arg(a)
}
}
pub fn (mut params Params) merge(params_to_merge Params) ! {
for p in params_to_merge.params {
params.set(p.key, p.value)
}
for a in params_to_merge.args {
params.set_arg(a)
}
}
pub fn (p Params) empty() bool {
if p.params.len == 0 && p.args.len == 0 {
return true
}
return false
}
pub fn (p Params) heroscript() string {
mut out := ''
out = p.export(
presort: ['id', 'cid', 'oid', 'name']
postsort: ['mtime', 'ctime', 'time']
indent: ' '
maxcolsize: 30
multiline: true
)
return out
}

View File

@@ -0,0 +1,41 @@
module paramsparser
import freeflowuniverse.herolib.data.currency
// TODO: fix if necessary
// see currency object, gets it from params
pub fn (params &Params) get_currencyamount(key string) !currency.Amount {
valuestr := params.get(key)!
return currency.amount_get(valuestr)!
}
pub fn (params &Params) get_currencyamount_default(key string, defval string) !currency.Amount {
if params.exists(key) {
return params.get_currencyamount(key)!
}
return currency.amount_get(defval)!
}
// get currency expressed in float in line to currency passed
pub fn (params &Params) get_currencyfloat(key string) !f64 {
valuestr := params.get(key)!
a := currency.amount_get(valuestr)!
return a.val
}
pub fn (params &Params) get_currencyfloat_default(key string, defval f64) !f64 {
if params.exists(key) {
return params.get_currencyfloat(key)!
}
return defval
}
// TODO: this probably does not belong here
// fn (mut cs Currency) default_set(cur string, usdval f64) {
// cur2 := cur.trim_space().to_upper()
// mut c1 := Currency{
// name: cur2
// usdval: usdval
// }
// cs.currency[cur2] = &c1
// }

View File

@@ -0,0 +1,50 @@
module paramsparser
import freeflowuniverse.herolib.data.currency
import os
const testparams = Params{
params: [
Param{
key: 'dollars'
value: '100USD'
},
Param{
key: 'euros'
value: '100EUR'
},
]
}
fn test_get_currencyamount() ! {
// testusd
os.setenv('OFFLINE', 'true', true)
mut amount := testparams.get_currencyamount('dollars')!
assert amount.currency.name == 'USD'
assert amount.currency.usdval == 1.0
assert amount.val == 100.0
// testeuro
amount = testparams.get_currencyamount('euros')!
assert amount.currency.name == 'EUR'
assert amount.currency.usdval >= 0.9 // may need revision in future
assert amount.val == 100.0
}
fn test_get_currencyamount_default() ! {
// testeuro
os.setenv('OFFLINE', 'true', true)
mut amount := testparams.get_currencyamount_default('na', '20EUR')!
assert amount.currency.name == 'EUR'
assert amount.currency.usdval >= 0.9 // may need revision in future
assert amount.val == 20
}
fn test_get_currency_float() ! {
// todo
// testeuro
// mut amount := testparams.get_currencyamount_default('na', '20EUR')!
// assert amount.currency.name == 'EUR'
// assert amount.currency.usdval > 1 // may need revision in future
// assert amount.val == 20
}

View File

@@ -0,0 +1,31 @@
module paramsparser
// check if kwarg exist
// line:
// arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok
// arg1 is an arg
// description is a kwarg
pub fn (params &Params) exists(key_ string) bool {
key := key_.to_lower().trim_space()
for p in params.params {
if p.key == key {
return true
}
}
return false
}
// check if arg exist (arg is just a value in the string e.g. red, not value:something)
// line:
// arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok
// arg1 is an arg
// description is a kwarg
pub fn (params &Params) exists_arg(key_ string) bool {
key := key_.to_lower().trim_space()
for p in params.args {
if p == key {
return true
}
}
return false
}

View File

@@ -0,0 +1,21 @@
module paramsparser
fn test_params_exists() {
test := '
key1: val1
key2: val2
key3: val3
arg1
'
params := new(test)!
assert params.exists('key1') == true
assert params.exists('KeY1') == true
assert params.exists('key2') == true
assert params.exists('key3') == true
assert params.exists('key4') == false
assert params.exists('arg1') == false
assert params.exists_arg('arg1') == true
assert params.exists_arg('ArG1') == true
assert params.exists_arg('key1') == false
}

View File

@@ -0,0 +1,297 @@
module paramsparser
import freeflowuniverse.herolib.core.texttools
import crypto.sha256
import freeflowuniverse.herolib.ui.console
struct ParamExportItem {
mut:
key string
value string
comment string
firstline bool // done by us to know if it still fits on the first line
isarg bool
}
fn (mut item ParamExportItem) check() {
// should not be needed in theory
item.value = item.value.replace('\\n', '\n').trim_space()
item.comment = item.comment.replace('\\n', '\n').trim_space()
}
// is to put on first line typically
fn (item ParamExportItem) oneline() string {
if item.isarg {
return '${item.key}'
} else if item.comment.len > 0 && item.value.len == 0 {
comment := item.comment.replace('\n', '\\n')
return '//${comment}-/'
} else {
txt := '${item.key}:${item.getval()}'
if item.comment.len > 0 {
comment := item.comment.replace('\n', '\\n')
return '//${comment}-/ ${txt}'
} else {
return txt
}
}
}
fn (item ParamExportItem) getval() string {
mut val := item.value
val = val.replace('\n', '\\n')
if val.contains(' ') || val.contains(':') || val.contains('\\n')
|| item.key in ['cid', 'oid', 'gid'] {
val = val.replace('"', "'")
if val.contains("'") {
val = val.replace("'", "\\'")
}
val = "'${val}'"
}
if val == '' {
// so empty strings are written as empty quotes
val = "''"
}
return val
}
// to put after the first line
fn (item ParamExportItem) afterline() string {
mut out := ''
if item.value.contains('\n') {
if item.comment.len > 0 {
out += texttools.indent(item.comment, ' // ')
}
out += '${item.key}:\'\n'
out += texttools.indent(item.value, ' ')
out += " '"
} else {
if item.comment.contains('\n') {
out += texttools.indent(item.comment, '// ')
}
out += '${item.key}:${item.getval()}'
if item.comment.len > 0 && !item.comment.contains('\n') {
out += ' //${item.comment}'
}
}
return out.trim_right(' \n')
}
// will first do the args, then the kwargs
// presort means will always come first
fn (p Params) export_helper(args_ ExportArgs) ![]ParamExportItem {
mut args := args_
if args.sortdefault && args.presort.len == 0 {
args.presort = ['id', 'cid', 'gid', 'oid', 'name', 'alias']
}
if p.args.len > 0 && args.args_allowed == false {
return error('args are not allowed')
}
mut args_done := []string{}
mut order := []string{}
mut order_delayed := []string{}
mut keys_to_be_sorted := []string{}
mut keys_existing := []string{}
mut dict_param := map[string]ParamExportItem{}
mut result_params := []ParamExportItem{} // the ones who always need to come first
mut firstlinesize := 0
// comments are always 1st
if args.comments_remove == false {
for comment in p.comments {
result_params << ParamExportItem{
key: ''
isarg: false
comment: comment
firstline: args.oneline
}
}
}
// args are always 2nd
if args.args_remove == false {
mut args2 := p.args.clone()
args2.sort()
for mut arg in args2 {
arg = texttools.name_fix(arg)
if arg in args_done {
return error('Double arg: ${arg}')
}
args_done << arg
result_params << ParamExportItem{
key: arg
isarg: true
comment: ''
firstline: true
}
firstlinesize += arg.len + 1
}
}
// now we will process the params (comments and args done)
for param in p.params {
// skip empty parameter when exporting
if args.skip_empty && val_is_empty(param.value) {
continue
}
mut key := texttools.name_fix(param.key)
keys_existing << key
if key !in args.presort && key !in args.postsort {
keys_to_be_sorted << key
}
dict_param[key] = ParamExportItem{
key: key
value: param.value
comment: param.comment
firstline: false
}
}
keys_to_be_sorted.sort()
for key in args.presort.reverse() {
if key in keys_existing {
keys_to_be_sorted.prepend(key) // make sure we have the presorted once first
}
}
for key in args.postsort {
if key in keys_existing {
keys_to_be_sorted << key // now add the ones at the end
}
}
// now we have all keys sorted
for keyname in keys_to_be_sorted {
param_export_item := dict_param[keyname] // or { panic("bug: can't find $keyname in dict in export/import for params.") }}
val := param_export_item.value
// if val.len == 0 {
// continue
// }
if val.len > 25 || param_export_item.comment.len > 0 || firstlinesize > args.maxcolsize
|| val.contains('\\n') {
order_delayed << keyname
continue
}
order << keyname
firstlinesize += keyname.len + val.len + 2
}
for key in order {
mut param_export_item := dict_param[key] // or { panic("bug: can't find $keyname in dict in export/import for params.") }
param_export_item.check()
param_export_item.firstline = true
result_params << param_export_item
}
for key in order_delayed {
mut param_export_item := dict_param[key] // or { panic("bug: can't find $keyname in dict in export/import for params.") }
param_export_item.check()
result_params << param_export_item
}
return result_params
}
fn val_is_empty(val string) bool {
return val == '' || val == '[]'
}
@[params]
pub struct ExportArgs {
pub mut:
presort []string
postsort []string
sortdefault bool = true // if set will do the default sorting
args_allowed bool = true
args_remove bool
comments_remove bool
maxcolsize int = 120
oneline bool // if set then will put all on oneline
multiline bool = true // if we will put the multiline strings as multiline in output
indent string
pre string // e.g. can be used to insert action e.g. !!remark.define (pre=prefix on the first line)
skip_empty bool
}
// standardised export format .
// .
// it outputs a default way sorted and readable .
//```js
// presort []string
// postsort []string
// sortdefault bool = true //if set will do the default sorting
// args_allowed bool = true
// args_remove bool
// comments_remove bool
// maxcolsize int = 120
// oneline bool // if set then will put all on oneline
// multiline bool = true // if we will put the multiline strings as multiline in output
// indent string
// pre string // e.g. can be used to insert action e.g. !!remark.define (pre=prefix on the first line)
//```
pub fn (p Params) export(args ExportArgs) string {
items := p.export_helper(args) or { panic(err) }
mut out_pre := []string{}
mut out := []string{}
mut out_post := []string{}
for item in items {
if args.oneline {
out << item.oneline()
} else {
if item.key == '' && item.comment.len > 0 {
out_pre << texttools.indent(item.comment, '// ')
continue
}
if item.isarg {
assert item.comment.len == 0
out << item.key
continue
}
if item.firstline {
out << item.oneline()
} else {
out_post << '${args.indent}${item.afterline()}'
}
}
}
mut outstr := ''
if args.oneline {
if args.pre.len > 0 {
outstr += args.pre + ' '
}
outstr += out.join(' ')
} else {
comments := out_pre.join('\n')
oneliner := out.join(' ') + '\n'
poststr := out_post.join('\n')
if args.pre.len > 0 {
outstr += comments
outstr += args.pre + ' ' + oneliner
outstr += poststr
} else {
outstr += comments
outstr += oneliner
outstr += poststr
}
}
return outstr
}
pub fn importparams(txt string) !Params {
return parse(txt)
}
pub fn (p Params) equal(p2 Params) bool {
a := p.export()
b := p2.export()
// console.print_debug("----------------\n$a")
// console.print_debug("================\n$b")
// console.print_debug("----------------")
return a == b
}
// returns a unique sha256 in hex, will allways return the same independent of order of params
pub fn (p Params) hexhash() string {
a := p.export(oneline: true, multiline: false)
console.print_debug(a)
return sha256.hexhash(a)
}

View File

@@ -0,0 +1,293 @@
module paramsparser
import crypto.sha256
import freeflowuniverse.herolib.ui.console
const textin5 = "
//this is first comment (line1)
//this is 2nd comment (line2)
red green blue
id:a5 name6:aaaaa //comment_line2
name:'need to do something 1'
//comment
description:'
## markdown works in it
description can be multiline
lets see what happens
- a
- something else
### subtitle
```python
#even code block in the other block, crazy parsing for sure
def test():
print()
```
'
name2: test
name3: hi name10:'this is with space' name11:aaa11
name4: 'aaa'
//somecomment
name5: 'aab'
"
fn test_export_helper1() {
params := parse(textin5) or { panic(err) }
// presort []string
// args_allowed bool=true
// maxcolsize int = 80
// oneline bool //if set then will put all on oneline
// multiline bool = true //if we will put the multiline strings as multiline in output
// indent string
o := params.export_helper(maxcolsize: 600) or { panic(err) }
// console.print_debug('${o}')
assert o.len == 14
// console.print_debug(o.len)
assert o[o.len - 1].firstline == false
// console.print_debug(o[o.len - 2])
assert o[o.len - 2].firstline == false
assert o[o.len - 1].key == 'name5'
// assert o[o.len - 2].key == 'name6'
assert o[0].comment.len > 0
assert o[0].key == ''
assert o[0].value == ''
assert sha256.hexhash(o.str()) == '3b3f7830145d5d1dfb01aea879ad052350f2ec4e921c2ee6cbfacbb0f1029ebd'
}
fn test_export_helper2() {
params := parse(textin5) or { panic(err) }
o := params.export_helper(maxcolsize: 60) or { panic(err) }
assert sha256.hexhash(o.str()) == '0bb7fb65107a69b1a0296dbdd1b0505b178e8bebdb33d2cec307f411b05c6033'
}
const textin6 = "
//mycomment
red blue
id:a6 name6:aaaaa
name4: 'a aa' //comment_line2
//somecomment
name5: 'aab'
"
fn test_export_6() {
params := parse(textin6) or { panic(err) }
o := params.export_helper(maxcolsize: 60) or { panic(err) }
paramsout := params.export()
console.print_debug(paramsout)
console.print_debug("EXPECT:
// mycomment
blue red id:a6 name6:aaaaa
name4:'a aa' //comment_line2
name5:aab //somecomment
")
assert sha256.hexhash(paramsout) == '7b45308e1b5e729e78c8b9b12cda6cd6da4919005a7d54ecc45f07a2d26c304b'
}
const textin7 = "
//mycomment
red blue
id:a7 name6:aaaaa
name4: '
multiline1
in
multiline2
'
//somecomment
name5: 'aab'
"
fn test_export_7() {
params := parse(textin7) or { panic(err) }
o := params.export_helper(maxcolsize: 60) or { panic(err) }
console.print_debug('${o}')
paramsout := params.export()
console.print_debug("EXPECT:
// mycomment
blue red id:a7 name6:aaaaa
name4:'
multiline1
in
multiline2
'
name5:aab //somecomment
")
console.print_debug(paramsout)
assert sha256.hexhash(paramsout) == '0407be8ca3abb9214e3579c40ae1b02297cf05bf50ae687ea6b2b57bb8c5a54e'
}
const textin8 = "
//mycomment
red blue
id:a8 name6:aaaaa
name4: '
multiline1
in
multiline2
'
//somecomment
//
//multiline
name5: 'aab'
"
fn test_export_8() {
params := parse(textin8) or { panic(err) }
o := params.export_helper(maxcolsize: 60) or { panic(err) }
console.print_debug('${o}')
paramsout := params.export()
console.print_debug(paramsout)
console.print_debug("EXPECT:
// mycomment
blue red id:a8 name6:aaaaa
name4:'
multiline1
in
multiline2
'
// somecomment
//
// multiline
name5:aab
")
assert sha256.hexhash(paramsout) == 'ed1188df660ab6c69ea5cb967df86e7f356b513045c66e8ebb109763f3110ea2'
}
const textin9 = "
//mycomment
red blue
id:a9 name6:aaaaa
name4: '
multiline1
in
multiline2
'
//somecomment
//
//multiline
name5: 'aab'
"
fn test_export_9() {
params := parse(textin9) or { panic(err) }
o := params.export_helper(oneline: true) or { panic(err) }
console.print_debug('${o}')
console.print_debug("EXPECT:
// mycomment
!!remark.define blue red id:a9 name6:aaaaa
name4:'
multiline1
in
multiline2
'
// somecomment
//
// multiline
name5:aab
")
paramsout := params.export(oneline: true)
console.print_debug(paramsout)
assert sha256.hexhash(paramsout) == '811bef1e55b3b4b1b725e1a7514b092526c8fdef8dddf6ad15f9d1ca49c3eb51'
}
const textin10 = "
//mycomment
red blue
id:a10 name6:aaaaa
name4: '
multiline1
in
multiline2
'
//somecomment
//
//multiline
name5: 'aab'
"
fn test_export_10() {
params := parse(textin10) or { panic(err) }
o := params.export_helper(oneline: false, pre: '!!remark.define') or { panic(err) }
console.print_debug('${o}')
paramsout := params.export(oneline: false, pre: '!!remark.define')
console.print_debug(paramsout)
// WE EXPECT
console.print_debug("EXPECT:
// mycomment
!!remark.define blue red id:a10 name6:aaaaa
name4:'
multiline1
in
multiline2
'
// somecomment
//
// multiline
name5:aab
")
assert sha256.hexhash(paramsout) == 'bf583f6b9afe84476eb3ade1bdd60ba4a6f1196d95a6e7d0fbe6f8b9fb5779d2'
}
fn test_export_list() {
params := parse("list0: 'Kiwi'\nlist1: 'Apple', 'Banana'") or { panic(err) }
paramsout := params.export()
assert paramsout.trim_space() == 'list0:Kiwi list1:Apple,Banana'
}
fn test_export_text() {
params := Params{
params: [
Param{
key: 'text'
value: "This content contains the character ' in it"
},
]
}
paramsout := params.export()
assert paramsout.trim_space() == "text:'This content contains the character \\' in it'"
}

View File

@@ -0,0 +1,110 @@
module paramsparser
@[params]
pub struct ParamsFilter {
pub mut:
include []string
exclude []string
}
// will return true if the params object match the filter (include and excludes)
// will first do the include filter then exclude, which means if matched and exclude is then matched won't count
// uses the filter_item function for each include or excluse (see further)
pub fn (params Params) filter_match(myfilter ParamsFilter) !bool {
mut inclok := true
if myfilter.include.len > 0 {
inclok = false
for incl in myfilter.include {
ok := params.filter_match_item(incl)!
// console.print_debug(params)
// console.print_debug(myfilter)
// console.print_debug(" - filter match: ok:$ok $incl")
if ok {
inclok = true
break // not more needed to check includes, we found one
}
}
}
if inclok == false {
return false
}
// no need to continue
if myfilter.exclude.len > 0 {
for excl in myfilter.exclude {
ok2 := params.filter_match_item(excl)!
if ok2 {
return false
}
}
}
return true
}
// match params for 1 string based match
// can be e.g.
// - hr+development, means look for argument hr or development
// - devel*, look for an argument which starts with devel (see match_glob)
// - color:*red*, look for a value with key color, which has 'red' string inside
// match_glob matches the string, with a Unix shell-style wildcard pattern
// The special characters used in shell-style wildcards are:
// * - matches everything
// ? - matches any single character
// [seq] - matches any of the characters in the sequence
// [^seq] - matches any character that is NOT in the sequence
// Any other character in pattern, is matched 1:1 to the corresponding character in name, including / and .
// You can wrap the meta-characters in brackets too, i.e. [?] matches ? in the string, and [*] matches * in the string.
pub fn (params Params) filter_match_item(myfilter string) !bool {
mut tests := [myfilter]
if myfilter.contains('+') {
tests = myfilter.split('+')
}
mut totalfound := 0
for test in tests {
mut found := false
mut key := ''
mut value := test.trim_space()
if test.contains(':') {
splitted := test.split(':')
key = splitted[0]
value = splitted[1]
key = key.trim_space()
value = value.trim_space()
}
if key == '' {
for arg in params.args {
if value.contains('*') || value.contains('[]') || value.contains('?') {
// we will do match_glob
if arg.match_glob(value) {
found = true
}
} else {
if arg == value {
found = true
}
}
}
} else {
for param in params.params {
if param.key == key {
if value.contains('*') || value.contains('[]') || value.contains('?') {
// we will do match_glob
if param.value.match_glob(value) {
found = true
}
} else {
if param.value == value {
found = true
}
}
}
}
}
if found {
totalfound += 1
}
}
if tests.len == totalfound {
return true
}
return false
}

View File

@@ -0,0 +1,39 @@
module paramsparser
fn test_macro_args() {
mut text := "arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok'"
mut params := parse(text) or { panic(err) }
assert params.filter_match(include: ['description:*see*'])!
assert params.filter_match(include: ['priority:incredible'])!
assert params.filter_match(include: ['priority:incredible+color:red'])!
assert params.filter_match(include: ['priority:incredible+color:green', 'arg1'])!
assert params.filter_match(include: ['priority:incredible+color:red', 'arg1+arg3'])!
assert params.filter_match(include: ['arg2'])!
c := params.filter_match(include: ['arg'])!
assert c == false
d := params.filter_match(include: ['a*rg'])!
assert d == false
e := params.filter_match(include: ['arg*'])!
assert e
f := params.filter_match(include: ['arg*'])!
assert f
f_ := params.filter_match(include: ['arg1+arg2'])!
assert f_
g := params.filter_match(include: ['arg1+arg3'])!
assert g == false
h := params.filter_match(include: ['priority:incredible+color:green'])!
assert h == false
h2 := params.filter_match(include: ['priority:incredible'], exclude: ['color:red'])!
assert h2 == false
}

View File

@@ -0,0 +1,49 @@
module paramsparser
import strconv
pub fn (params &Params) len_arg() int {
return params.args.len
}
// return the arg with nr, 0 is the first
pub fn (params &Params) get_arg(nr int) !string {
if nr >= params.args.len {
return error('Looking for arg nr ${nr}, not enough args available.\n${params}')
}
return params.args[nr] or { panic('error, above should have catched') }
}
// return the arg with nr, 0 is the first
// check the length of the args
pub fn (params &Params) get_arg_check(nr int, checknrargs int) !string {
params.check_arg_len(checknrargs)!
return params.get_arg(nr)
}
pub fn (params &Params) check_arg_len(checknrargs int) ! {
if checknrargs != params.args.len {
return error('the amount of expected args is ${checknrargs}, we found different.\n${params}')
}
}
// return arg, if the nr is larger than amount of args, will return the defval
pub fn (params &Params) get_arg_default(nr int, defval string) !string {
if nr >= params.args.len {
return defval
}
r := params.get_arg(nr)!
return r
}
// get arg return as int, if checknrargs is not 0, it will make sure the nr of args corresponds
pub fn (params &Params) get_arg_int(nr int) !int {
r := params.get_arg(nr)!
return strconv.atoi(r) or { return error('failed to convert argument ${r} to int: ${err}') }
}
// get arg return as int. defval is the default value specified
pub fn (params &Params) get_arg_int_default(nr int, defval int) !int {
r := params.get_arg_default(nr, '${defval}')!
return strconv.atoi(r) or { return error('failed to convert argument ${r} to int: ${err}') }
}

View File

@@ -0,0 +1,122 @@
module paramsparser
fn test_get_arg() {
text := '
key1: val1
key2: val2
arg1
arg2
'
params := new(text)!
assert params.get_arg(0)! == 'arg1'
assert params.get_arg(1)! == 'arg2'
arg3 := params.get_arg(2) or { 'arg3 does not exists' }
assert arg3 == 'arg3 does not exists'
}
fn test_get_arg_check() {
text := '
key1: val1
key2: val2
arg1
arg2
'
params := new(text)!
assert params.get_arg_check(0, 2)! == 'arg1'
assert params.get_arg_check(1, 2)! == 'arg2'
t3 := params.get_arg_check(1, 3) or { 'len is 2' }
assert t3 == 'len is 2'
t4 := params.get_arg_check(2, 2) or { 'len is 2' }
assert t4 == 'len is 2'
}
fn test_check_arg_len() {
text := '
key1: val1
key2: val2
arg1
arg2
'
params := new(text)!
if _ := params.check_arg_len(0) {
assert false, 'arg len should be 2'
}
if _ := params.check_arg_len(1) {
assert false, 'arg len should be 2'
}
params.check_arg_len(2) or { assert false }
if _ := params.check_arg_len(3) {
assert false, 'arg len should be 2'
}
}
fn test_get_arg_default() {
text := '
key1: val1
key2: val2
arg1
arg2
'
params := new(text)!
assert params.get_arg_default(0, 'arg3')! == 'arg1'
assert params.get_arg_default(1, 'arg3')! == 'arg2'
assert params.get_arg_default(2, 'arg3')! == 'arg3'
assert params.get_arg_default(3, 'arg3')! == 'arg3'
}
fn test_get_arg_int() {
text := '
key1: val1
key2: val2
arg1
arg2
13
'
params := new(text)!
if _ := params.get_arg_int(0) {
assert false, 'first argument is a string, not an int'
}
if _ := params.get_arg_int(1) {
assert false, 'second argument is a string, not an int'
}
assert params.get_arg_int(2)! == 13
if _ := params.get_arg_int(3) {
assert false, 'there is no 4th argument'
}
}
fn test_get_arg_int_default() {
text := '
key1: val1
key2: val2
arg1
arg2
13
'
params := new(text)!
if _ := params.get_arg_int_default(0, 5) {
assert false, '1st argument is a string, not an int'
}
if _ := params.get_arg_int_default(1, 5) {
assert false, '2nd argument is a string, not an int'
}
assert params.get_arg_int_default(2, 5)! == 13
assert params.get_arg_int_default(3, 5)! == 5
}

View File

@@ -0,0 +1,194 @@
module paramsparser
import freeflowuniverse.herolib.core.texttools
import strconv
// see if the kwarg with the key exists
// if yes return as string trimmed
pub fn (params &Params) get(key_ string) !string {
key := texttools.name_fix(key_)
for p in params.params {
if p.key == key {
return p.value.trim(' ')
}
}
// print_backtrace()
return error('Did not find key:${key} in ${params}')
}
pub fn (params &Params) get_map() map[string]string {
mut r := map[string]string{}
for p in params.params {
r[p.key] = p.value
}
return r
}
// get kwarg return as string, ifn't exist return the defval
// line:
// arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok
// arg1 is an arg
// description is a kwarg
pub fn (params &Params) get_default(key string, defval string) !string {
if params.exists(key) {
valuestr := params.get(key)!
return valuestr.trim(' ')
}
return defval
}
// get kwarg return as int
// line:
// arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok
// arg1 is an arg
// description is a kwarg
pub fn (params &Params) get_int(key string) !int {
valuestr := params.get(key)!
return strconv.atoi(valuestr) or {
return error('Parameter ${key} = ${valuestr} is not a valid signed 32-bit integer')
}
}
pub fn (params &Params) get_float(key string) !f64 {
valuestr := params.get(key)!
return strconv.atof64(valuestr) or {
return error('Parameter ${key} = ${valuestr} is not a valid 64-bit float')
}
}
pub fn (params &Params) get_float_default(key string, defval f64) !f64 {
if params.exists(key) {
valuestr := params.get_float(key)!
return valuestr
}
return defval
}
pub fn (params &Params) get_percentage(key string) !f64 {
mut valuestr := params.get(key)!
valuestr = valuestr.replace('%', '')
v := strconv.atof64(valuestr) or {
return error('Parameter ${key} = ${valuestr} is not a valid 64-bit float')
}
if v > 100 || v < 0 {
return error('percentage "${v}" needs to be between 0 and 100')
}
return v / 100
}
pub fn (params &Params) get_percentage_default(key string, defval string) !f64 {
mut v := params.get_default(key, defval)!
v = v.replace('%', '')
v2 := strconv.atof64(v) or {
return error('Parameter ${key} = ${v} is not a valid 64-bit float')
}
if v2 > 100 || v2 < 0 {
return error('percentage "${v2}" needs to be between 0 and 100')
}
return v2 / 100
}
pub fn (params &Params) get_u64(key string) !u64 {
valuestr := params.get(key)!
return strconv.parse_uint(valuestr, 10, 64) or {
return error('Parameter ${key} = ${valuestr} is not a valid unsigned 64-bit integer')
}
}
pub fn (params &Params) get_u64_default(key string, defval u64) !u64 {
if params.exists(key) {
valuestr := params.get_u64(key)!
return valuestr
}
return defval
}
pub fn (params &Params) get_u32(key string) !u32 {
valuestr := params.get(key)!
return u32(strconv.parse_uint(valuestr, 10, 32) or {
return error('Parameter ${key} = ${valuestr} is not a valid unsigned 32-bit integer')
})
}
pub fn (params &Params) get_u32_default(key string, defval u32) !u32 {
if params.exists(key) {
valuestr := params.get_u32(key)!
return valuestr
}
return defval
}
pub fn (params &Params) get_u8(key string) !u8 {
valuestr := params.get(key)!
return u8(strconv.parse_uint(valuestr, 10, 8) or {
return error('Parameter ${key} = ${valuestr} is not a valid unsigned 8-bit integer')
})
}
pub fn (params &Params) get_u8_default(key string, defval u8) !u8 {
if params.exists(key) {
valuestr := params.get_u8(key)!
return valuestr
}
return defval
}
// get kwarg return as int, if it doesnt' exist return a default
// line:
// arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok
// arg1 is an arg
// description is a kwarg
pub fn (params &Params) get_int_default(key string, defval int) !int {
if params.exists(key) {
valuestr := params.get_int(key)!
return valuestr
}
return defval
}
pub fn (params &Params) get_default_true(key string) bool {
mut r := params.get(key) or { '' }
r = texttools.name_fix_no_underscore(r)
if r == '' || r == '1' || r == 'true' || r == 'y' || r == 'yes' {
return true
}
return false
}
pub fn (params &Params) get_default_false(key string) bool {
mut r := params.get(key) or { '' }
r = texttools.name_fix_no_underscore(r)
if r == '' || r == '0' || r == 'false' || r == 'n' || r == 'no' {
return false
}
return true
}
fn matchhashmap(hashmap map[string]string, tofind_ string) string {
tofind := tofind_.to_lower().trim_space()
for key, val in hashmap {
for key2 in key.split(',') {
if key2.to_lower().trim_space() == tofind {
return val
}
}
}
return ''
}
pub fn (params &Params) get_from_hashmap(key_ string, defval string, hashmap map[string]string) !string {
key := texttools.name_fix(key_)
if val := params.get(key) {
r := matchhashmap(hashmap, val)
if r.len > 0 {
return r
}
}
r := matchhashmap(hashmap, defval)
if r.len > 0 {
return r
}
return error('Did not find key:${key} in ${params} with hashmap:${hashmap} and default:${defval}')
}

View File

@@ -0,0 +1,326 @@
module paramsparser
fn test_get() {
text := '
key1: val1
arg1
'
params := new(text)!
assert params.get('key1')! == 'val1'
if _ := params.get('key2') {
assert false, 'there is no param with key "key2"'
}
if _ := params.get('arg1') {
assert false, 'there is no param with key "arg1"'
}
}
fn test_get_default() {
text := '
key1: val1
arg1
'
params := new(text)!
assert params.get_default('key1', 'def')! == 'val1'
assert params.get_default('key2', 'def')! == 'def'
assert params.get_default('arg1', 'def')! == 'def'
}
fn test_get_int() {
text := '
key1: val1
key2: 19
arg1
'
params := new(text)!
if _ := params.get_int('key1') {
assert false, 'param with key "key1" is a string, not an int'
}
assert params.get_int('key2')! == 19
if _ := params.get_int('arg1') {
assert false, 'there is no param with key "arg1"'
}
}
fn test_get_int_default() {
text := '
key1: val1
key2: 19
arg1
'
params := new(text)!
if _ := params.get_int_default('key1', 10) {
assert false, 'the param with key "key1" is a string, not an int'
}
assert params.get_int_default('key2', 10)! == 19
assert params.get_int_default('arg1', 10)! == 10
}
fn test_get_float() {
text := '
key1: val1
key2: 19
key3: 1.9
arg1
'
params := new(text)!
if _ := params.get_float('key1') {
assert false, 'the param with key "key1" is a string, not a float'
}
assert params.get_float('key2')! == 19
assert params.get_float('key3')! == 1.9
if _ := params.get_float('arg1') {
assert false, 'there is no param with key "arg1"'
}
}
fn test_get_float_default() {
text := '
key1: val1
key2: 19
key3: 1.9
arg1
'
params := new(text)!
if _ := params.get_float_default('key1', 1.23) {
assert false, 'the param with key "key1" is a string, not a float'
}
assert params.get_float_default('key2', 1.23)! == 19
assert params.get_float_default('key3', 1.23)! == 1.9
assert params.get_float_default('arg1', 1.23)! == 1.23
}
fn test_get_percentage() {
text := '
key1: val1
key2: 19
key3: %1.9
key4: %500
'
params := new(text)!
if _ := params.get_percentage('key1') {
assert false, 'the param with key "key1" is a string, not a percentage'
}
assert params.get_percentage('key2')! == 0.19
assert params.get_percentage('key3')! == .019
if _ := params.get_percentage('key4') {
assert false, 'the param with key "key4" has an invalid percentage value "%500", it must be between 0 and 100'
}
}
fn test_get_percentage_default() {
text := '
key1: val1
key2: 19
key3: %1.9
key4: %500
'
params := new(text)!
if _ := params.get_percentage_default('key1', '.17') {
assert false, 'the param with key "key1" is a string, not a percentage'
}
assert params.get_percentage_default('key2', '.17')! == 0.19
assert params.get_percentage_default('key3', '.17')! == .019
if _ := params.get_percentage_default('key4', '.17') {
assert false, 'the param with key "key4" has an invalid percentage vale "%500", it must be between 0 and 100'
}
assert params.get_percentage_default('key5', '17')! == 0.17
}
fn test_get_u64() {
text := '
key1: val1
key2: 19
'
params := new(text)!
if _ := params.get_u64('key1') {
assert false, 'the param with key "key1" is a string, not a u64'
}
assert params.get_u64('key2')! == 19
if _ := params.get_u64('key3') {
assert false, 'there is no param with key "key3"'
}
}
fn test_get_u64_default() {
text := '
key1: val1
key2: 19
'
params := new(text)!
if _ := params.get_u64_default('key1', 17) {
assert false, 'the param with key "key1" is a string, not a u64'
}
assert params.get_u64_default('key2', 17)! == 19
assert params.get_u64_default('key3', 17)! == 17
}
fn test_get_u32() {
text := '
key1: val1
key2: 19
'
params := new(text)!
if _ := params.get_u32('key1') {
assert false, 'the param with key "key1" is a string, not a u32'
}
assert params.get_u32('key2')! == 19
if _ := params.get_u32('key3') {
assert false, 'there is no param with key "key3"'
}
}
fn test_get_u32_default() {
text := '
key1: val1
key2: 19
'
params := new(text)!
if _ := params.get_u32_default('key1', 17) {
assert false, 'the param with key "key1" is a string, not a u32'
}
assert params.get_u32_default('key2', 17)! == 19
assert params.get_u32_default('key3', 17)! == 17
}
fn test_get_u8() {
text := '
key1: val1
key2: 19
'
params := new(text)!
if _ := params.get_u8('key1') {
assert false, 'the param with key "key1" is a string, not a u8'
}
assert params.get_u8('key2')! == 19
if _ := params.get_u8('key3') {
assert false, 'there is no param with key "key3"'
}
}
fn test_get_u8_default() {
text := '
key1: val1
key2: 19
'
params := new(text)!
if _ := params.get_u8_default('key1', 17) {
assert false, 'the param with key "key1" is a string, not a u8'
}
assert params.get_u8_default('key2', 17)! == 19
assert params.get_u8_default('key3', 17)! == 17
}
fn test_get_default_true() {
text := '
key1: val1
key2: true
key3: 1
key4: 2
key5: y
key6: yes
'
params := new(text)!
assert params.get_default_true('key1') == false
assert params.get_default_true('key2') == true
assert params.get_default_true('key3') == true
assert params.get_default_true('key4') == false
assert params.get_default_true('key5') == true
assert params.get_default_true('key6') == true
assert params.get_default_true('key7') == true
}
fn test_get_default_false() {
text := '
key1: val1
key2: false
key3: 0
key4: 1
key5: n
key6: no
'
params := new(text)!
assert params.get_default_false('key1') == true
assert params.get_default_false('key2') == false
assert params.get_default_false('key3') == false
assert params.get_default_false('key4') == true
assert params.get_default_false('key5') == false
assert params.get_default_false('key6') == false
assert params.get_default_false('key7') == false
}
fn test_get_from_hashmap() {
text := '
key1: val1
key2: 19
'
params := new(text)!
mp := {
'key1,19, val1': 'val2'
}
assert params.get_from_hashmap('key1', 'def', mp)! == 'val2'
assert params.get_from_hashmap('key2', 'def', mp)! == 'val2'
assert params.get_from_hashmap('key3', 'key1', mp)! == 'val2'
}
fn test_matchhashmap() {
mp := {
'key1,key2,key3': 'val1'
}
assert matchhashmap(mp, 'key1') == 'val1'
assert matchhashmap(mp, 'key2') == 'val1'
assert matchhashmap(mp, 'key3') == 'val1'
assert matchhashmap(mp, 'key4') == ''
}
fn test_url() {
text := "url:'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'"
params := new(text)!
myurl := params.get('url')!
assert myurl == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'
}
fn test_url2() {
text := "url: 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'"
params := new(text)!
myurl := params.get('url')!
assert myurl == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'
}

View File

@@ -0,0 +1,36 @@
module paramsparser
pub fn (params &Params) get_email(key string) !string {
mut valuestr := params.get(key)!
return normalize_email(valuestr)
}
pub fn (params &Params) get_emails(key string) ![]string {
mut valuestr := params.get(key)!
valuestr = valuestr.trim('[] ')
split := valuestr.split(',')
mut res := []string{}
for item in split {
res << normalize_email(item)
}
return res
}
pub fn (params &Params) get_emails_default(key string, default []string) ![]string {
if params.exists(key) {
return params.get_emails(key)!
}
mut res := []string{}
for item in default {
res << normalize_email(item)
}
return res
}
fn normalize_email(email string) string {
return email.trim(' ').to_lower()
}

View File

@@ -0,0 +1,255 @@
module paramsparser
import freeflowuniverse.herolib.core.texttools
import strconv
// Looks for a list of strings in the parameters. If it doesn't exist this function will return an error. Furthermore an error will be returned if the list is not properly formatted
// Examples of valid lists:
// ["example", "another_example", "yes"]
// ['example', 'another_example']
// ['example "yes yes"', "another_example", "yes"]
// Invalid examples:
// [example, example, example]
// ['example, example, example]
pub fn (params &Params) get_list(key string) ![]string {
mut res := []string{}
mut valuestr := params.get(key)!
valuestr = valuestr.trim('[] ')
mut splitted := valuestr.split(',')
for mut item in splitted {
item = item.trim('"\' ')
if item != '' {
res << item
}
}
// THE IMPLEMENTATION BELOW IS TOO COMPLEX AND ALSO NOT DEFENSIVE ENOUGH
// mut res := []string{}
// mut valuestr := params.get(key)!
// valuestr = valuestr.trim('[] ,')
// if valuestr==""{
// return []
// }
// mut j := 0
// mut i := 0
// for i < valuestr.len {
// if valuestr[i] == 34 || valuestr[i] == 39 { // handle single or double quotes
// // console.print_debug("::::${valuestr[i]}")
// quote := valuestr[i..i + 1]
// j = valuestr.index_after('${quote}', i + 1)
// if j == -1 {
// return error('Invalid list at index ${i}: strings should surrounded by single or double quote')
// }
// if i + 1 < j {
// res << valuestr[i + 1..j]
// i = j + 1
// if i < valuestr.len && valuestr[i] != 44 { // handle comma
// return error('Invalid list at index ${i}: strings should be separated by a comma')
// }
// }
// } else if valuestr[i] == 32 { // handle space
// } else {
// res << valuestr[i..i + 1]
// }
// i += 1
// }
return res
}
// Looks for a list of strings in the parameters. If it doesn't exist this function the provided default value. Furthermore an error will be returned if the parameter exists and it's not a valid list.
// Please look at get_list for examples of valid and invalid lists
pub fn (params &Params) get_list_default(key string, def []string) ![]string {
if params.exists(key) {
return params.get_list(key)
}
return def
}
// Looks for a list of strings in the parameters. If it doesn't exist this function returns an error. Furthermore an error will be returned if the parameter exists and it's not a valid list.
// The items in the list will be namefixed
pub fn (params &Params) get_list_namefix(key string) ![]string {
mut res := params.get_list(key)!
return res.map(texttools.name_fix(it))
}
// Looks for a list of strings in the parameters. If it doesn't exist this function returns the provided default value. Furthermore an error will be returned if the parameter exists and it's not a valid list.
// The items in the list will be namefixed
pub fn (params &Params) get_list_namefix_default(key string, def []string) ![]string {
if params.exists(key) {
res := params.get_list(key)!
return res.map(texttools.name_fix(it))
}
return def
}
fn (params &Params) get_list_numbers(key string) ![]string {
mut valuestr := params.get(key)!
return valuestr.split(',').map(it.trim_space())
}
// Looks for a list of u8 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_u8(key string) ![]u8 {
mut res := params.get_list_numbers(key)!
return res.map(u8(strconv.parse_uint(it, 10, 8) or {
return error('${key} list entry ${it} is not a valid unsigned 8-bit integer')
}))
}
// Looks for a list of u8 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_u8_default(key string, def []u8) []u8 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.u8())
}
return def
}
// Looks for a list of u16 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_u16(key string) ![]u16 {
mut res := params.get_list_numbers(key)!
return res.map(u16(strconv.parse_uint(it, 10, 16) or {
return error('${key} list entry ${it} is not a valid unsigned 16-bit integer')
}))
}
// Looks for a list of u16 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_u16_default(key string, def []u16) []u16 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.u16())
}
return def
}
// Looks for a list of u32 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_u32(key string) ![]u32 {
mut res := params.get_list_numbers(key)!
return res.map(u32(strconv.parse_uint(it, 10, 32) or {
return error('${key} list entry ${it} is not a valid unsigned 32-bit integer')
}))
}
// Looks for a list of u32 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_u32_default(key string, def []u32) []u32 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.u32())
}
return def
}
// Looks for a list of u64 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_u64(key string) ![]u64 {
res := params.get_list_numbers(key)!
return res.map(strconv.parse_uint(it, 10, 64) or {
return error('${key} list entry ${it} is not a valid unsigned 64-bit integer')
})
}
// Looks for a list of u64 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_u64_default(key string, def []u64) []u64 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.u64())
}
return def
}
// Looks for a list of i8 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_i8(key string) ![]i8 {
mut res := params.get_list_numbers(key)!
return res.map(i8(strconv.atoi(it) or {
return error('${key} list entry ${it} is not a valid signed 8-bit integer')
}))
}
// Looks for a list of i8 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_i8_default(key string, def []i8) []i8 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.i8())
}
return def
}
// Looks for a list of i16 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_i16(key string) ![]i16 {
mut res := params.get_list_numbers(key)!
return res.map(i16(strconv.atoi(it) or {
return error('${key} list entry ${it} is not a valid signed 16-bit integer')
}))
}
// Looks for a list of i16 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_i16_default(key string, def []i16) []i16 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.i16())
}
return def
}
// Looks for a list of int with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_int(key string) ![]int {
mut res := params.get_list_numbers(key)!
return res.map(strconv.atoi(it) or {
return error('${key} list entry ${it} is not a valid signed 32-bit integer')
})
}
// Looks for a list of int with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_int_default(key string, def []int) []int {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.int())
}
return def
}
// Looks for a list of i64 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_i64(key string) ![]i64 {
mut res := params.get_list_numbers(key)!
return res.map(strconv.parse_int(it, 10, 64) or {
return error('${key} list entry ${it} is not a valid signed 64-bit integer')
})
}
// Looks for a list of i64 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_i64_default(key string, def []i64) []i64 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.i64())
}
return def
}
// Looks for a list of f32 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_f32(key string) ![]f32 {
mut res := params.get_list_numbers(key)!
return res.map(f32(strconv.atof64(it)!))
}
// Looks for a list of f32 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_f32_default(key string, def []f32) []f32 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.f32())
}
return def
}
// Looks for a list of f64 with the provided key. If it does not exist an error is returned.
pub fn (params &Params) get_list_f64(key string) ![]f64 {
mut res := params.get_list_numbers(key)!
return res.map(strconv.atof64(it)!)
}
// Looks for a list of f64 with the provided key. If it does not exist the provided default value is returned.
pub fn (params &Params) get_list_f64_default(key string, def []f64) []f64 {
if params.exists(key) {
res := params.get_list_numbers(key) or { return def }
return res.map(it.f64())
}
return def
}

View File

@@ -0,0 +1,304 @@
module paramsparser
fn test_get_list_single_quotes() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: 'A,A,A,A'
},
]
}
list := testparams.get_list('mylist')!
assert list == ['A', 'A', 'A', 'A']
}
fn test_get_list_smallstr() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: 'a'
},
]
}
list := testparams.get_list('mylist') or { panic(err) }
assert list == ['a']
// if true{panic("sdsdsdsdsdsdsdsd")}
}
fn test_get_list_smallstr2() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: 'a,b,dddeegggdf ,e'
},
]
}
list := testparams.get_list('mylist') or { panic(err) }
assert list == ['a', 'b', 'dddeegggdf', 'e']
}
fn test_get_list_double_quotes() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '["A","A","A","A"]'
},
]
}
list := testparams.get_list('mylist')!
assert list == ['A', 'A', 'A', 'A']
}
fn test_get_list_single_and_double_quotes() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '["A","A",\'A\',"A"]'
},
]
}
list := testparams.get_list('mylist')!
assert list == ['A', 'A', 'A', 'A']
}
fn test_get_list_double_quote_inside_single() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '["A",\'"A"\',"A","A"]'
},
]
}
list := testparams.get_list('mylist')!
assert list == ['A', 'A', 'A', 'A']
}
// we need to be more defensive this succeeds
// fn test_get_list_invalid() {
// testparams := Params{
// params: [
// Param{
// key: 'mylist'
// value: '["A,"A","A","A"]'
// },
// ]
// }
// list := testparams.get_list('mylist') or { return }
// assert false, 'expected get_list to throw an error'
// }
fn test_get_list_u8() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, 5, 7, 2'
},
]
}
list := testparams.get_list_u8('mylist')!
assert list == [u8(1), u8(5), u8(7), u8(2)]
}
fn test_get_list_u8_default() {
testparams := Params{
params: []
}
list := testparams.get_list_u8_default('mylist', []u8{})
assert list == []u8{}
}
fn test_get_list_u16() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, 5, 7, 2'
},
]
}
list := testparams.get_list_u16('mylist')!
assert list == [u16(1), u16(5), u16(7), u16(2)]
}
fn test_get_list_u16_default() {
testparams := Params{
params: []
}
list := testparams.get_list_u16_default('mylist', []u16{})
assert list == []u16{}
}
fn test_get_list_u32() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, 5, 7, 15148'
},
]
}
list := testparams.get_list_u32('mylist')!
assert list == [u32(1), u32(5), u32(7), u32(15148)]
}
fn test_get_list_u32_default() {
testparams := Params{
params: []
}
list := testparams.get_list_u32_default('mylist', []u32{})
assert list == []u32{}
}
fn test_get_list_u64() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, 5, 7, 15148'
},
]
}
list := testparams.get_list_u64('mylist')!
assert list == [u64(1), u64(5), u64(7), u64(15148)]
}
fn test_get_list_u64_default() {
testparams := Params{
params: []
}
list := testparams.get_list_u64_default('mylist', []u64{})
assert list == []u64{}
}
fn test_get_list_i8() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, -5, 10, -2'
},
]
}
list := testparams.get_list_i8('mylist')!
assert list == [i8(1), i8(-5), i8(10), i8(-2)]
}
fn test_get_list_i8_default() {
testparams := Params{
params: []
}
list := testparams.get_list_i8_default('mylist', []i8{})
assert list == []i8{}
}
fn test_get_list_i16() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, -25, 165, -148'
},
]
}
list := testparams.get_list_i16('mylist')!
assert list == [i16(1), i16(-25), i16(165), i16(-148)]
}
fn test_get_list_i16_default() {
testparams := Params{
params: []
}
list := testparams.get_list_i16_default('mylist', []i16{})
assert list == []i16{}
}
fn test_get_list_int() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, -25, 165, -1484984'
},
]
}
list := testparams.get_list_int('mylist')!
assert list == [1, -25, 165, -1484984]
}
fn test_get_list_int_default() {
testparams := Params{
params: []
}
list := testparams.get_list_int_default('mylist', []int{})
assert list == []int{}
}
fn test_get_list_i64() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1, -25, 165, -148'
},
]
}
list := testparams.get_list_i64('mylist')!
assert list == [i64(1), i64(-25), i64(165), i64(-148)]
}
fn test_get_list_i64_default() {
testparams := Params{
params: []
}
list := testparams.get_list_i64_default('mylist', []i64{})
assert list == []i64{}
}
fn test_get_list_f32() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1.5, 5.78, 7.478, 15148.4654'
},
]
}
list := testparams.get_list_f32('mylist')!
assert list == [f32(1.5), f32(5.78), f32(7.478), f32(15148.4654)]
}
fn test_get_list_f32_default() {
testparams := Params{
params: []
}
list := testparams.get_list_f32_default('mylist', []f32{})
assert list == []f32{}
}
fn test_get_list_f64() {
testparams := Params{
params: [
Param{
key: 'mylist'
value: '1.5, 5.78, 7.478, 15148.4654'
},
]
}
list := testparams.get_list_f64('mylist')!
assert list == [1.5, 5.78, 7.478, 15148.4654]
}
fn test_get_list_f64_default() {
testparams := Params{
params: []
}
list := testparams.get_list_f64_default('mylist', []f64{})
assert list == []f64{}
}

View File

@@ -0,0 +1,29 @@
module paramsparser
import os
// will get path and check it exists
pub fn (params &Params) get_path(key string) !string {
mut path := params.get(key)!
path = path.replace('~', os.home_dir())
if !os.exists(path) {
return error('Cannot find path: ${path} was for key:${key}')
}
return path
}
// create the path if it doesn't exist
pub fn (params &Params) get_path_create(key string) !string {
mut path := params.get(key)!
path = path.replace('~', os.home_dir())
if !os.exists(path) {
os.mkdir_all(path)!
}
return path
}

View File

@@ -0,0 +1,30 @@
module paramsparser
import os
fn test_get_path() {
os.create('/tmp/f1')!
text := '
key1: v1
key2: /tmp/f1
'
params := new(text)!
if _ := params.get_path('key1') {
assert false, '"path "v1" is invalid'
}
assert params.get_path('key2')! == '/tmp/f1'
}
fn test_get_path_create() {
text := '
key1: val2
key2: /tmp/f2
'
params := new(text)!
assert params.get_path_create('key2')! == '/tmp/f2'
}

View File

@@ -0,0 +1,81 @@
module paramsparser
import strconv
// convert GB, MB, KB to bytes
// e.g. 10 GB becomes bytes in u64
pub fn (params &Params) get_storagecapacity_in_bytes(key string) !u64 {
valuestr := params.get(key)!
mut times := 1
mut units_string_size := 0
if valuestr.len > 2 && !valuestr[valuestr.len - 2].is_digit()
&& !valuestr[valuestr.len - 1].is_digit() {
times = match valuestr[valuestr.len - 2..].to_upper() {
'GB' {
1024 * 1024 * 1024
}
'MB' {
1024 * 1024
}
'KB' {
1024
}
else {
0
}
}
if times == 0 {
return error('not valid: should end with kb, mb or gb')
}
units_string_size = 2
}
val := strconv.parse_uint(valuestr[0..valuestr.len - units_string_size], 10, 64) or {
return error('Parameter ${key} = ${valuestr} does not include a valid unsigned 64-bit integer')
}
return val * u64(times)
}
pub fn (params &Params) get_storagecapacity_in_bytes_default(key string, defval u64) !u64 {
if params.exists(key) {
return params.get_storagecapacity_in_bytes(key)!
}
return defval
}
// Parses the provided value to gigabytes, the value is rounded up while doing so.
pub fn (params &Params) get_storagecapacity_in_gigabytes(key string) !u64 {
valuestr := params.get(key)!
mut units := 1
mut units_string_size := 0
if valuestr.len > 2 && !valuestr[valuestr.len - 2].is_digit()
&& !valuestr[valuestr.len - 1].is_digit() {
units = match valuestr[valuestr.len - 2..].to_upper() {
'GB' {
1
}
'MB' {
1024
}
'KB' {
1024 * 1024
}
else {
0
}
}
if units == 0 {
return error('not valid: should end with kb, mb or gb')
}
units_string_size = 2
}
val := strconv.parse_uint(valuestr[0..valuestr.len - units_string_size], 10, 64) or {
return error('Parameter ${key} = ${valuestr} does not include a valid unsigned 64-bit integer')
}
mut ret := val / u64(units)
if val % u64(units) != 0 {
ret += 1
}
return ret
}

View File

@@ -0,0 +1,28 @@
module paramsparser
fn test_resource_capacity_in_bytes() {
text := 'memory: 10KB\ndisk: 10MB\nstorage: 10GB\nssd: 10'
params := parse(text)!
assert params.get_storagecapacity_in_bytes('memory')! == 10 * 1024
assert params.get_storagecapacity_in_bytes('disk')! == 10 * 1024 * 1024
assert params.get_storagecapacity_in_bytes('storage')! == u64(10) * 1024 * 1024 * 1024
assert params.get_storagecapacity_in_bytes('ssd')! == 10
}
fn test_resource_capacity_in_bytes_default() {
text := 'memory: 10KB\ndisk: 10MB\nstorage: 10GB'
params := parse(text)!
assert params.get_storagecapacity_in_bytes_default('memory', 10)! == 10 * 1024
assert params.get_storagecapacity_in_bytes_default('disk', 10)! == 10 * 1024 * 1024
assert params.get_storagecapacity_in_bytes_default('storage', 10)! == u64(10) * 1024 * 1024 * 1024
assert params.get_storagecapacity_in_bytes_default('nonexistent', 10)! == 10
}
fn test_resource_capacity_in_gigabytes() {
text := 'memory: 10KB\ndisk: 10MB\nstorage: 10GB\nssd: 10'
p := parse(text)!
assert p.get_storagecapacity_in_gigabytes('memory')! == 1
assert p.get_storagecapacity_in_gigabytes('disk')! == 1
assert p.get_storagecapacity_in_gigabytes('storage')! == 10
assert p.get_storagecapacity_in_gigabytes('ssd')! == 10
}

View File

@@ -0,0 +1,44 @@
module paramsparser
pub fn (params &Params) get_telnr(key string) !string {
mut valuestr := params.get(key)!
return normalize_telnr(valuestr)
}
pub fn (params &Params) get_telnrs(key string) ![]string {
mut valuestr := params.get(key)!
valuestr = valuestr.trim('[] ')
split := valuestr.split(',')
mut res := []string{}
for item in split {
res << normalize_telnr(item)
}
return res
}
pub fn (params &Params) get_telnrs_default(key string, default []string) ![]string {
if params.exists(key) {
return params.get_telnrs(key)!
}
mut res := []string{}
for item in default {
res << normalize_telnr(item)
}
return res
}
fn normalize_telnr(telnr string) string {
mut value := telnr.trim('+ ')
mut res := ''
mut splitted := value.split('-')
for item in splitted {
res += item
}
return res
}

View File

@@ -0,0 +1,96 @@
module paramsparser
import freeflowuniverse.herolib.data.ourtime
// import freeflowuniverse.herolib.core.texttools
// import os
import time { Duration }
// Get Expiration object from time string input
// input can be either relative or absolute
// ## Relative time
// #### time periods:
// - s -> second
// - h -> hour
// - d -> day
// - w -> week
// - M -> month
// - Q -> quarter
// - Y -> year
// 0 means right now
// input string example: "+1w +2d -4h"
// ## Absolute time
// inputs must be of the form: "YYYY-MM-DD HH:mm:ss" or "YYYY-MM-DD"
// input string examples:
//'2022-12-5 20:14:35'
//'2022-12-5' - sets hours, mins, seconds to 00
pub fn (params &Params) get_time(key string) !ourtime.OurTime {
valuestr := params.get(key)!
return ourtime.new(valuestr)!
}
pub fn (params &Params) get_time_default(key string, defval ourtime.OurTime) !ourtime.OurTime {
if params.exists(key) {
return params.get_time(key)!
}
return defval
}
// calculate difference in time, returled as u64 (is Duration type)
// format e.g.
// QUESTION: splitting by - doesn't work? Alternative?
pub fn (params &Params) get_time_interval(key string) !Duration {
valuestr := params.get(key)!
data := valuestr.split('-')
if data.len != 2 {
return error('Invalid time interval: begin and end time required')
}
start := params.get_time(data[0])!
end := params.get_time(data[1])!
if end.unix() < start.unix() {
return error('Invalid time interval: begin time cannot be after end time')
}
return end.unix() - start.unix()
// NEXT: document and give examples, make sure there is test
}
pub fn (params &Params) get_timestamp_default(key string, defval Duration) !Duration {
if params.exists(key) {
return params.get_timestamp(key)!
}
return defval
}
// Parses a timestamp. Can be 12h or 24h format
pub fn (params &Params) get_timestamp(key string) !Duration {
valuestr := params.get(key)!
return params.parse_timestamp(valuestr)!
}
// Parses a timestamp. Can be 12h or 24h format
fn (params &Params) parse_timestamp(value string) !Duration {
is_am := value.ends_with('AM')
is_pm := value.ends_with('PM')
is_am_pm := is_am || is_pm
data := if is_am_pm { value[..value.len - 2].split(':') } else { value.split(':') }
if data.len > 2 {
return error('Invalid duration value')
}
minute := if data.len == 2 { data[1].int() } else { 0 }
mut hour := data[0].int()
if is_am || is_pm {
if hour < 0 || hour > 12 {
return error('Invalid duration value')
}
if is_pm {
hour += 12
}
} else {
if hour < 0 || hour > 24 {
return error('Invalid duration value')
}
}
if minute < 0 || minute > 60 {
return error('Invalid duration value')
}
return Duration(time.hour * hour + time.minute * minute)
}

View File

@@ -0,0 +1,126 @@
module paramsparser
import freeflowuniverse.herolib.data.ourtime
import time
const testparams = Params{
params: [
Param{
key: 'when'
value: '2022-12-5 20:14:35'
},
Param{
key: 'date'
value: '2022-12-5'
},
Param{
key: 'interval'
value: '2022-12-5'
},
Param{
key: 'timestamp_12h_format_am'
value: '10AM'
},
Param{
key: 'timestamp_12h_format_pm'
value: '8PM'
},
Param{
key: 'timestamp_12h_format_am_minutes'
value: '10:13AM'
},
Param{
key: 'timestamp_12h_format_pm_minutes'
value: '8:07PM'
},
Param{
key: 'timestamp_12h_format_invalid'
value: '15AM'
},
Param{
key: 'timestamp_24h_format'
value: '16:21'
},
Param{
key: 'timestamp_24h_format_invalid'
value: '25:12'
},
]
}
fn test_get_time() ! {
sometime := testparams.get_time('when')!
assert sometime.unixt == 1670271275
anothertime := testparams.get_time('date')!
assert anothertime.unixt == 1670198400
}
fn test_get_time_default() ! {
now := ourtime.now()
notime := testparams.get_time_default('now', now)!
assert notime.day() == now.day()
}
fn test_get_time_interval() ! {
//
}
fn test_get_timestamp_12h_am() ! {
parsed_time := testparams.get_timestamp('timestamp_12h_format_am')!
expected := time.Duration(time.hour * 10)
assert parsed_time == expected
}
fn test_get_timestamp_12h_pm() ! {
parsed_time := testparams.get_timestamp('timestamp_12h_format_pm')!
assert parsed_time == time.Duration(time.hour * 20)
}
fn test_get_timestamp_12h_am_minutes() ! {
parsed_time := testparams.get_timestamp('timestamp_12h_format_am_minutes')!
assert parsed_time == time.Duration(time.hour * 10 + time.minute * 13)
}
fn test_get_timestamp_12h_pm_minutes() ! {
parsed_time := testparams.get_timestamp('timestamp_12h_format_pm_minutes')!
assert parsed_time == time.Duration(time.hour * 20 + time.minute * 7)
}
fn test_get_timestamp_12h_pm_fails() ! {
mut passed := true
parsed_time := testparams.get_timestamp('timestamp_12h_format_invalid') or {
passed = false
time.Duration(time.hour)
}
if passed {
return error('Did not throw error, it should')
}
}
fn test_get_timestamp_24h_format() ! {
parsed_time := testparams.get_timestamp('timestamp_24h_format')!
assert parsed_time == time.Duration(time.hour * 16 + time.minute * 21)
}
fn test_get_timestamp_24h_format_fails() ! {
mut passed := true
parsed_time := testparams.get_timestamp('timestamp_24h_format_invalid') or {
passed = false
time.Duration(time.hour)
}
if passed {
return error('Did not throw error, it should')
}
}
fn test_get_timestamp_default() ! {
default_duration := time.Duration(time.hour * 8 + time.minute * 30)
parsed_time := testparams.get_timestamp_default('timestamp_24h_format', default_duration)!
assert parsed_time == time.Duration(time.hour * 16 + time.minute * 21)
parsed_time_default := testparams.get_timestamp_default('non_existing_timestamp',
default_duration)!
assert parsed_time_default == default_duration
}

View File

@@ -0,0 +1,23 @@
module paramsparser
import json
// pub struct ParamsPub {
// pub mut:
// params []Param
// args []string //are commands without key/val, best not to use
// }
// pub struct ParamPub {
// pub:
// key string
// value string
// }
pub fn (mut params Params) export_json() string {
return json.encode(params)
}
pub fn json_import(data string) !Params {
return json.decode(Params, data)
}

View File

@@ -0,0 +1,186 @@
module paramsparser
import time
import v.reflection
// import freeflowuniverse.herolib.data.encoderhero
// TODO: support more field types
pub fn (params Params) decode[T]() !T {
// work around to allow recursive decoding
// otherwise v cant infer generic type for child fields that are structs
return params.decode_struct[T](T{})!
}
pub fn (params Params) decode_struct[T](_ T) !T {
mut t := T{}
$for field in T.fields {
$if field.is_enum {
t.$(field.name) = params.get_int(field.name) or { 0 }
} $else {
if field.name[0].is_capital() {
// embed := params.decode_struct(t.$(field.name))!
t.$(field.name) = params.decode_struct(t.$(field.name))!
} else {
t.$(field.name) = params.decode_value(t.$(field.name), field.name)!
}
}
}
return t
}
pub fn (params Params) decode_value[T](_ T, key string) !T {
// $if T is $option {
// // unwrap and encode optionals
// workaround := t
// if workaround != none {
// encode(t, args)!
// }
// }
// value := params.get(field.name)!
// TODO: handle required fields
if !params.exists(key) {
return T{}
}
$if T is string {
return params.get(key)!
} $else $if T is int {
return params.get_int(key)!
} $else $if T is u32 {
return params.get_u32(key)!
} $else $if T is bool {
return params.get_default_true(key)
} $else $if T is []string {
return params.get_list(key)!
} $else $if T is []int {
return params.get_list_int(key)!
} $else $if T is []u32 {
lst := params.get_list_u32(key)!
return lst
} $else $if T is time.Time {
time_str := params.get(key)!
// todo: 'handle other null times'
if time_str == '0000-00-00 00:00:00' {
return time.Time{}
}
return time.parse(time_str)!
} $else $if T is $struct {
child_params := params.get_params(key)!
child := child_params.decode_struct(T{})!
return child
}
return T{}
}
@[params]
pub struct EncodeArgs {
pub:
recursive bool = true
}
pub fn encode[T](t T, args EncodeArgs) !Params {
$if t is $option {
// unwrap and encode optionals
workaround := t
if workaround != none {
encode(t, args)!
}
}
mut params := Params{}
// struct_attrs := attrs_get_reflection(mytype)
$for field in T.fields {
val := t.$(field.name)
field_attrs := attrs_get(field.attrs)
mut key := field.name
if 'alias' in field_attrs {
key = field_attrs['alias']
}
$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 field.is_enum {
params.set(key, '${int(val)}')
} $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 []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 {
}
}
return params
}
// BACKLOG: can we do the encode recursive?
// if at top of struct we have: @[name:"teststruct " ; params] .
// will return {'name': 'teststruct', 'params': ''}
fn attrs_get_reflection(mytype reflection.Type) map[string]string {
if mytype.sym.info is reflection.Struct {
return attrs_get(mytype.sym.info.attrs)
}
return map[string]string{}
}
// will return {'name': 'teststruct', 'params': ''}
fn attrs_get(attrs []string) map[string]string {
mut out := map[string]string{}
for i in attrs {
if i.contains('=') {
kv := i.split('=')
out[kv[0].trim_space().to_lower()] = kv[1].trim_space().to_lower()
} else {
out[i.trim_space().to_lower()] = ''
}
}
return out
}

View File

@@ -0,0 +1,116 @@
module paramsparser
import time
struct TestStruct {
name string
nick ?string
birthday time.Time
number int
yesno bool
liststr []string
listint []int
listbool []bool
child TestChild
}
struct TestChild {
child_name string
child_number int
child_yesno bool
child_liststr []string
child_listint []int
child_listbool []bool
}
const test_child = TestChild{
child_name: 'test_child'
child_number: 3
child_yesno: false
child_liststr: ['three', 'four']
child_listint: [3, 4]
}
const test_struct = TestStruct{
name: 'test'
nick: 'test_nick'
birthday: time.new(
day: 12
month: 12
year: 2012
)
number: 2
yesno: true
liststr: ['one', 'two']
listint: [1, 2]
child: test_child
}
const test_child_params = Params{
params: [
Param{
key: 'child_name'
value: 'test_child'
},
Param{
key: 'child_number'
value: '3'
},
Param{
key: 'child_yesno'
value: 'false'
},
Param{
key: 'child_liststr'
value: 'three,four'
},
Param{
key: 'child_listint'
value: '3,4'
},
]
}
const test_params = Params{
params: [Param{
key: 'name'
value: 'test'
}, Param{
key: 'nick'
value: 'test_nick'
}, Param{
key: 'birthday'
value: '2012-12-12 00:00:00'
}, Param{
key: 'number'
value: '2'
}, Param{
key: 'yesno'
value: 'true'
}, Param{
key: 'liststr'
value: 'one,two'
}, Param{
key: 'listint'
value: '1,2'
}, Param{
key: 'child'
value: test_child_params.export()
}]
}
fn test_decode() {
// test single level struct
decoded_child := test_child_params.decode[TestChild]()!
assert decoded_child == test_child
// test recursive decode struct with child
decoded := test_params.decode[TestStruct]()!
assert decoded == test_struct
}
fn test_encode() {
// test single level struct
encoded_child := encode[TestChild](test_child)!
assert encoded_child == test_child_params
}

View File

@@ -0,0 +1,52 @@
module paramsparser
import freeflowuniverse.herolib.core.texttools.regext
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.ui.console
// find parts of text in PARAM values which are of form {NAME}, and replace those .
// .
// will walk over all elements of the params, and then replace all the values .
// .
// NAME is as follows: .
// Uppercase letters: A-Z .
// Lowercase letters: a-z .
// Digits: 0-9 .
// Underscore: _ .
// .
// the NAME is key of the map, the val of the map is what we replace with
pub fn (mut params Params) replace(args map[string]string) {
for mut p in params.params {
for i in regext.find_simple_vars(p.value) {
i2 := texttools.name_fix(i)
if i2 in args {
console.print_debug('${i} -> ${args[i2]}')
p.value = p.value.replace('{${i}}', args[i2])
}
}
}
}
pub fn (mut params Params) replace_from_params(params_ Params) {
p := params_.get_map()
params.replace(p)
}
// func main() {
// // Sample string
// str := "This is a sample string with ${ROOT} and another ${ROOT} instance."
// // Regex pattern to match `${ROOT}`
// pattern := `\$\\{(\w+)\\}`
// re := regexp.MustCompile(pattern)
// // Find all matches
// matches := re.FindAllStringSubmatch(str, -1)
// // Iterate over matches and print the captured group
// for _, match := range matches {
// if len(match) > 1 {
// fmt.Println(match[1]) // This will print "ROOT" for each instance found
// }
// }
// }

View File

@@ -0,0 +1,44 @@
module paramsparser
fn test_replace() {
text := "
key1:'{replace_me}'
key2: {Value}
key3: value
"
mut params := new(text)!
mp := {
'value': 'replaced_value'
'replace_me': 'also_replaced_value'
}
params.replace(mp)
assert params.get('key1')! == 'also_replaced_value'
assert params.get('key2')! == 'replaced_value'
assert params.get('key3')! == 'value'
}
fn test_replace2() {
params_to_replace_txt := "
key1:'{replace_me}'
key2: {Value}
key3: value
"
mut params_to_replace := new(params_to_replace_txt)!
params_args_txt := '
value:replaced_value
replace_me:also_replaced_value
'
mut params_args := new(params_args_txt)!
params_to_replace.replace_from_params(params_args)
assert params_to_replace.get('key1')! == 'also_replaced_value'
assert params_to_replace.get('key2')! == 'replaced_value'
assert params_to_replace.get('key3')! == 'value'
}

View File

@@ -0,0 +1,53 @@
module paramsparser
import freeflowuniverse.herolib.data.resp
// TODO: fix this. encoding some params and redecoding them does not work.
// encode using resp (redis procotol)
pub fn (mut p Params) to_resp() ![]u8 {
mut b_main := resp.builder_new()
mut b_param := resp.builder_new()
for param in p.params {
b_param.add(resp.r_list_string([param.key, param.value]))
}
mut b_arg := resp.builder_new()
for arg in p.args {
b_arg.add(resp.r_string(arg))
}
b_main.add(resp.r_list_bytestring([b_param.data, b_arg.data]))
return b_main.data
}
pub fn from_resp(data []u8) !Params {
mut p := Params{}
mut top_array_ := resp.decode(data)![0]
top_array := top_array_ as resp.RArray
params_string := top_array.values[0] as resp.RBString
params_array := resp.decode(params_string.value)!
for param_string_ in params_array {
param_string := param_string_ as resp.RArray
key_rstring := param_string.values[0] as resp.RString
value_rstring := param_string.values[1] as resp.RString
p.params << Param{
key: key_rstring.value
value: value_rstring.value
}
}
args_string := top_array.values[1] as resp.RBString
args_array := resp.decode(args_string.value)!
for arg_string_ in args_array {
arg_string := arg_string_ as resp.RString
p.args << arg_string.value
}
return p
}
// NEXT: needs to do a good test, this protocol is much smaller than default text format

View File

@@ -0,0 +1,19 @@
module paramsparser
fn test_to_resp() {
text := '
key1: {replace_me}
key2: {Value}
key3: value
arg1
arg2
'
mut params := new(text)!
encoded := params.to_resp()!
decoded_params := from_resp(encoded)!
assert params == decoded_params
}

View File

@@ -0,0 +1,369 @@
module paramsparser
import freeflowuniverse.herolib.core.texttools
import json
import freeflowuniverse.herolib.ui.console
const textin = "
id:a1 name6:aaaaa
name:'need to do something 1'
//comment
description:'
## markdown works in it
description can be multiline
lets see what happens
- a
- something else
### subtitle
```python
#even code block in the other block, crazy parsing for sure
def test():
console.print_debug()
```
'
name2: test
name3: hi name10:'this is with space' name11:aaa11
//some comment
name4: 'aaa'
//somecomment
name5: 'aab'
"
const textin2 = "
//this is a cool piece of text
//
//now end of comment
id:a1 name6:aaaaa
name:'need to do something 1'
description:'something<BR>yes' //comment 1
aa //arg comment
bb
name2: test
name3: hi name10:'this is with space' name11:aaa11
// some comment 2
name4: 'aaa'
name5: 'aab' //somecomment 3
"
const textin3 = '
zz //comment 1
id:a1 name6:aaaaa //comment 2
'
fn test_hexhash() {
mut params := parse(textin2)!
console.print_debug('${params}')
h := params.hexhash()
assert h == 'fca5c320391e7a91ec91999b1b3d66bf5cb7658905284c431777ff6d2fa4a4c3'
}
fn test_textin3() {
params := parse(textin3) or { panic(err) }
assert params == Params{
params: [
Param{
key: 'id'
value: 'a1'
comment: 'comment 2'
},
Param{
key: 'name6'
value: 'aaaaa'
comment: ''
},
]
args: ['zz']
comments: ['comment 1']
}
}
fn test_macro_args() {
mut text := "arg1 arg2 color:red priority:'incredible' description:'with spaces, lets see if ok'"
params := parse(text) or { panic(err) }
expexted_res := Params{
params: [Param{
key: 'color'
value: 'red'
}, Param{
key: 'priority'
value: 'incredible'
}, Param{
key: 'description'
value: 'with spaces, lets see if ok'
}]
args: ['arg1', 'arg2']
}
assert expexted_res == params
mut text2 := "arg1 color:red priority:'incredible' arg2 description:'with spaces, lets see if ok'"
params2 := parse(text2) or { panic(err) }
assert expexted_res == params2
}
fn test_args_get() {
mut text := "arg1 color:red priority:'2' description:'with spaces, lets see if ok' x:5 arg2"
mut params := parse(text) or { panic(err) }
assert params.exists_arg('arg1')
assert params.exists_arg('arg2')
assert !params.exists_arg('arg')
mut x := params.get_int('x') or { panic(err) }
assert x == 5
x = params.get_int('y') or { 6 }
assert x == 6
x = params.get_int('priority') or { panic(err) }
assert x == 2
mut y := params.get('priority') or { panic(err) }
assert y == '2'
}
fn test_url1() {
mut text := "color:red url:'https://github.com/freeflowuniverse/herolib/tree/development/examples/mdbook/books_to_include1'"
mut params := parse(text) or { panic(err) }
mut text2 := 'color:red url:"https://github.com/freeflowuniverse/herolib/tree/development/examples/mdbook/books_to_include1 "'
mut params2 := parse(text2) or { panic(err) }
assert params.get('url')? == 'https://github.com/freeflowuniverse/herolib/tree/development/examples/mdbook/books_to_include1'
assert params2.get('url')? == 'https://github.com/freeflowuniverse/herolib/tree/development/examples/mdbook/books_to_include1'
}
// // fn test_json() {
// // mut params := parse(textin) or { panic(err) }
// // d:=params.export_json()
// // mut params2 := json_import(d) or {panic(err)}
// // panic("ssss")
// // }
// fn test_export() {
// mut params := parse(textin)!
// d := params.export()
// mut out := "
// description:'## markdown works in it\\n\\ndescription can be multiline\\nlets see what happens\\n\\n- a\\n- something else\\n\\n### subtitle\\n\\n```python\\n#even code block in the other block, crazy parsing for sure\\ndef test():\\n console.print_debug()\\n```'
// id:a1
// name:'need to do something 1'
// name10:'this is with space'
// name11:aaa11
// name2:test
// name3:hi
// name4:aaa
// name5:aab
// name6:aaaaa
// "
// assert texttools.dedent(d) == texttools.dedent(out).trim_space()
// }
// fn test_export2() {
// mut params := parse(textin2) or { panic(err) }
// d := params.export()
// mut out := "
// description:something\\nyes
// id:a1
// name:'need to do something 1'
// name10:'this is with space'
// name11:aaa11
// name2:test
// name3:hi
// name4:aaa
// name5:aab
// name6:aaaaa
// aa
// bb
// zz
// "
// assert texttools.dedent(d) == texttools.dedent(out).trim_space()
// }
// fn test_import1() {
// mut params := parse(textin2) or { panic(err) }
// d := params.export()
// mut params2 := importparams(d) or { panic(err) }
// assert params.equal(params2)
// }
// fn test_import2() {
// mut params := parse(textin2) or { panic(err) }
// d := "
// id:a1
// zz
// name:'need to do something 1'
// name10:'this is with space'
// name11:aaa11
// name2:test
// name3:hi
// name4:aaa
// name5:aab
// name6:aaaaa
// aa
// bb
// description:something\\nyes
// "
// mut params2 := importparams(d)!
// assert params.equal(params2)
// }
// fn test_params_default_false() {
// mut params := parse('
// certified:false
// certified1:no
// certified2:n
// certified3:0
// ')!
// assert params.get_default_false('certified') == false
// assert params.get_default_false('certified1') == false
// assert params.get_default_false('certified2') == false
// assert params.get_default_false('certified3') == false
// assert params.get_default_false('certified4') == false
// }
// fn test_params_default_true() {
// mut params := parse('
// certified:true
// certified1:yes
// certified2:y
// certified3:1
// ')!
// assert params.get_default_true('certified') == true
// assert params.get_default_true('certified1') == true
// assert params.get_default_true('certified2') == true
// assert params.get_default_true('certified3') == true
// assert params.get_default_true('certified4') == true
// }
// fn test_kwargs_add() {
// mut params := parse(textin2) or { panic(err) }
// console.print_debug(params.params)
// assert params.params.len == 10
// params.set('name3', 'anotherhi')
// assert params.params.len == 10
// params.set('name7', 'anotherhi')
// assert params.params.len == 11 // because is new one
// assert params.get('name3') or { '' } == 'anotherhi'
// }
// fn test_args_add() {
// mut params := parse('urgency:yes red green') or { panic(err) }
// console.print_debug(params.args)
// assert params.params.len == 1
// assert params.args.len == 2
// params.set_arg('red')
// assert params.args.len == 2
// params.set_arg('yellow')
// assert params.args.len == 3
// }
// fn test_merge() {
// mut params := parse('urgency:yes red green') or { panic(err) }
// console.print_debug(params.args)
// params.merge('urgency:green blue') or { panic('s') }
// console.print_debug(params.args)
// assert params.params.len == 1
// assert params.args.len == 3
// assert params.get('urgency') or { '' } == 'green'
// }
// fn test_textin2() {
// params := parse(paramsparser.textin2) or { panic(err) }
// console.print_debug(params)
// assert params == Params{
// params: [
// Param{
// key: 'id'
// value: 'a1'
// comment: 'this is a cool piece of text
// now end of comment'
// },
// Param{
// key: 'name6'
// value: 'aaaaa'
// comment: ''
// },
// Param{
// key: 'name'
// value: 'need to do something 1'
// comment: ''
// },
// Param{
// key: 'description'
// value: 'something\\nyes'
// comment: 'comment 1'
// },
// Param{
// key: 'name2'
// value: 'test'
// comment: ''
// },
// Param{
// key: 'name3'
// value: 'hi'
// comment: ''
// },
// Param{
// key: 'name10'
// value: 'this is with space'
// comment: ''
// },
// Param{
// key: 'name11'
// value: 'aaa11'
// comment: ''
// },
// Param{
// key: 'name4'
// value: 'aaa'
// comment: 'some comment 2'
// },
// Param{
// key: 'name5'
// value: 'aab'
// comment: 'somecomment 3'
// },
// ]
// args: ['aa', 'bb']
// comments: ['arg comment']
// }
// }

View File

@@ -0,0 +1,226 @@
module paramsparser
import freeflowuniverse.herolib.core.texttools
enum ParamStatus {
start
name // found name of the var (could be an arg)
value_wait // wait for value to start (can be quote or end of spaces and first meaningful char)
value // value started, so was no quote
quote // quote found means value in between ''
value_end // quote found means value in between ''
comment
}
// convert text with e.g. color:red or color:'red' to arguments
// multiline is supported
// result is params object which allows you to query the info you need
// params is following:
//
// struct Params {
// params []Param
// args []Arg
// }
// struct Arg {
// value string
// }
// struct Param {
// key string
// value string
// }
// it has nice methods to query the params
pub fn parse(text string) !Params {
mut text2 := texttools.dedent(text)
// mut text2 := text
// console.print_debug("****PARSER")
// console.print_debug(text2)
// console.print_debug("****PARSER END")
text2 = text2.replace('"', "'")
text2 = texttools.multiline_to_single(text2)!
// console.print_debug("1")
validchars := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_,./'
mut ch := ''
mut ch_prev := ''
mut state := ParamStatus.start
mut result := Params{}
mut key := ''
mut value := ''
mut list := false
mut comment := ''
for i in 0 .. text2.len {
ch = text2[i..i + 1]
// println(" - '${ch_prev}${ch}' ${state}")
if state == .value_end {
if ch == ' ' {
ch_prev = ch
continue
} else if ch == ',' {
list = true
// means we are in list, must wait for next value
last_item := value.all_after_last(',')
if requires_quotes(last_item) {
value = '${value.all_before_last(',')},"${last_item}"'
}
state = .value_wait
} else if ch == '\n' && list {
list = false
} else {
state = .start
result.set_with_comment(key, value, comment)
key = ''
value = ''
comment = ''
}
}
// check for comments end
if state == .start {
if ch == ' ' {
ch_prev = ch
continue
}
state = .name
}
if state == .name {
if ch_prev == '/' && ch == '/' {
// we are now comment
state = .comment
ch_prev = ch
continue
}
if ch == ' ' && key == '' {
ch_prev = ch
continue
}
// waiting for :
if ch == ':' {
state = ParamStatus.value_wait
ch_prev = ch
continue
} else if ch == ' ' {
state = ParamStatus.start
result.set_arg_with_comment(key, comment)
key = ''
comment = ''
value = ''
ch_prev = ch
continue
} else if !validchars.contains(ch) {
print_backtrace()
return error("text to params processor: parameters can only be A-Za-z0-9 and _., found illegal char: '${key}${ch}' in\n${text2}\n\n")
} else {
key += ch
ch_prev = ch
continue
}
}
if state == .value_wait {
if ch == "'" {
state = .quote
ch_prev = ch
continue
}
// if ch == '[' {
// state = .array
// ch_prev = ch
// value = ''
// continue
// }
// means the value started, we can go to next state
if ch != ' ' {
state = .value
}
}
if state == .value {
if ch == ',' {
// means in list and our value has ended
value += ch
list = true
state = .value_wait
}
}
if state == .value {
if ch == ' ' {
state = .value_end
list = false
} else {
value += ch
}
ch_prev = ch
continue
}
if state == .quote {
if ch == "'" && ch_prev != '\\' {
state = .value_end
} else {
value += ch
}
ch_prev = ch
continue
}
// if state == .array {
// if ch == ']' {
// state = .start
// result.set_with_comment(key, value, comment)
// key = ''
// value = ''
// comment = ''
// } else {
// value += ch
// }
// ch_prev = ch
// continue
// }
if state == .value || state == ParamStatus.start {
if ch == '/' && ch_prev == '/' {
// we are now comment
state = .comment
}
}
if state == ParamStatus.comment {
if ch == '/' && ch_prev == '-' {
state = .start
ch_prev = ch
continue
}
comment += ch
}
ch_prev = ch
}
// last value
if state == ParamStatus.value || state == ParamStatus.quote || state == .value_end {
if list {
last_item := value.all_after_last(',')
if requires_quotes(last_item) {
value = '${value.all_before_last(',')},"${last_item}"'
}
}
result.set_with_comment(key, value, comment)
}
if state == ParamStatus.name {
if key != '' {
result.set_arg_with_comment(key, comment)
}
}
return result
}
// returns wether a provided value requires quotes
fn requires_quotes(value string) bool {
if value.contains(' ') {
return true
}
return false
}

View File

@@ -0,0 +1,101 @@
module paramsparser
fn test_parse_list() ! {
mut params := parse("list0: 'Apple'")!
// Test parsing lists with single string item
mut fruit_list := [
"list0: 'Apple'",
'list1: Apple',
'list2:Apple',
'list3: Apple ',
]
for i, param in fruit_list {
params = parse(param)!
assert params == Params{
params: [
Param{
key: 'list${i}'
value: 'Apple'
comment: ''
},
]
args: []
comments: []
}
}
// Test parsing lists with multiple string items
fruit_list = [
'list0: Apple, Banana',
'list1: Apple ,Banana',
'list2: Apple , Banana',
'list3: Apple , Banana',
"list4: 'Apple', Banana",
"list5: Apple, 'Banana'",
"list6: 'Apple', 'Banana'",
]
for i, param in fruit_list {
params = parse(param)!
assert params == Params{
params: [
Param{
key: 'list${i}'
value: 'Apple,Banana'
comment: ''
},
]
args: []
comments: []
}
}
// Test parsing lists with multi-word items
fruit_list = [
'list0: Apple, "Dragon Fruit", "Passion Fruit"',
'list1: "Apple", "Dragon Fruit", "Passion Fruit"',
"list2: Apple, 'Dragon Fruit', 'Passion Fruit'",
"list3: 'Apple', 'Dragon Fruit', 'Passion Fruit'",
]
for i, param in fruit_list {
params = parse(param)!
assert params == Params{
params: [
Param{
key: 'list${i}'
value: 'Apple,"Dragon Fruit","Passion Fruit"'
comment: ''
},
]
args: []
comments: []
}
}
// // test parsing lists in square brackets
// params = parse("list1: ['Kiwi']")!
// assert params == Params{
// params: [Param{
// key: 'list1'
// value: "['Kiwi']"
// comment: ''
// }]
// args: []
// comments: []
// }
// params = parse("list2: 'Apple', 'Banana'")!
// assert params == Params{
// params: [
// Param{
// key: 'list2'
// value: "['Apple', 'Banana']"
// comment: ''
// },
// ]
// args: []
// comments: []
// }
}

View File

@@ -0,0 +1,271 @@
# ParamsParser Module Documentation
The ParamsParser module provides a powerful way to parse and handle parameter strings in V. It's particularly useful for parsing command-line style arguments and key-value pairs from text.
## Basic Usage
```v
import freeflowuniverse.herolib.data.paramsparser
// Create new params from text
params := paramsparser.new("color:red size:'large' priority:1 enable:true")!
// Or create empty params and add later
mut params := paramsparser.new_params()
params.set("color", "red")
```
## Parameter Format
The parser supports several formats:
1. Key-value pairs: `key:value`
2. Quoted values: `key:'value with spaces'`
3. Arguments without keys: `arg1 arg2`
4. Comments: `// this is a comment`
Example:
```v
text := "name:'John Doe' age:30 active:true // user details"
params := paramsparser.new(text)!
```
## Getting Values
The module provides various methods to retrieve values:
```v
// Get string value
name := params.get("name")! // returns "John Doe"
// Get with default value
color := params.get_default("color", "blue")! // returns "blue" if color not set
// Get as integer
age := params.get_int("age")! // returns 30
// Get as boolean (true if value is "1", "true", "y", "yes")
is_active := params.get_default_true("active")
// Get as float
score := params.get_float("score")!
// Get as percentage (converts "80%" to 0.8)
progress := params.get_percentage("progress")!
```
## Type Conversion Methods
The module supports various type conversions:
### Basic Types
- `get_int()`: Convert to int32
- `get_u32()`: Convert to unsigned 32-bit integer
- `get_u64()`: Convert to unsigned 64-bit integer
- `get_u8()`: Convert to unsigned 8-bit integer
- `get_float()`: Convert to 64-bit float
- `get_percentage()`: Convert percentage string to float (e.g., "80%" → 0.8)
### Boolean Values
- `get_default_true()`: Returns true if value is empty, "1", "true", "y", or "yes"
- `get_default_false()`: Returns false if value is empty, "0", "false", "n", or "no"
### Lists
The module provides robust support for parsing and converting lists:
```v
// Basic list parsing
names := params.get_list("users")! // parses ["user1", "user2", "user3"]
// With default value
tags := params.get_list_default("tags", ["default"])!
// Lists with type conversion
numbers := params.get_list_int("ids")! // converts each item to int
amounts := params.get_list_f64("prices")! // converts each item to f64
// Name-fixed lists (normalizes each item)
clean_names := params.get_list_namefix("categories")!
```
Supported list types:
- `get_list()`: String list
- `get_list_u8()`, `get_list_u16()`, `get_list_u32()`, `get_list_u64()`: Unsigned integers
- `get_list_i8()`, `get_list_i16()`, `get_list_int()`, `get_list_i64()`: Signed integers
- `get_list_f32()`, `get_list_f64()`: Floating point numbers
Each list method has a corresponding `_default` version that accepts a default value.
Valid list formats:
```v
users: ["john", "jane", "bob"]
ids: 1,2,3,4,5
names: ['John Doe', 'Jane Smith']
```
## Working with Arguments
Arguments are values without keys:
```v
// Parse text with arguments
params := paramsparser.new("arg1 arg2 key:value")!
// Add an argument
params.set_arg("arg3")
// Check if argument exists
if params.exists_arg("arg1") {
// do something
}
```
## Additional Features
1. Case insensitive keys:
```v
params.set("Color", "red")
value := params.get("color")! // works
```
2. Map conversion:
```v
// Convert params to map
map_values := params.get_map()
```
3. Merging params:
```v
mut params1 := paramsparser.new("color:red")!
params2 := paramsparser.new("size:large")!
params1.merge(params2)!
```
4. Delete parameters:
```v
params.delete("color") // delete key-value pair
params.delete_arg("arg1") // delete argument
```
## Error Handling
Most methods return results that should be handled with V's error handling:
```v
// Using ! operator for methods that can fail
name := params.get("name")!
// Or with or {} block for custom error handling
name := params.get("name") or {
println("Error: ${err}")
"default_name"
}
```
## Parameter Validation
The parser enforces certain rules:
- Keys can only contain A-Z, a-z, 0-9, underscore, dot, and forward slash
- Values can contain any characters
- Spaces in values must be enclosed in quotes
- Lists are supported with comma separation
## Best Practices
1. Always handle potential errors with `!` or `or {}`
2. Use type-specific getters (`get_int`, `get_float`, etc.) when you know the expected type
3. Provide default values when appropriate using the `_default` methods
4. Use quotes for values containing spaces
5. Use lowercase keys for consistency (though the parser is case-insensitive)
# Params Details
```v
import freeflowuniverse.herolib.data.paramsparser
mut p:=paramsparser.new('
id:a1 name6:aaaaa
name:'need to do something 1'
)!
assert "a1"==p.get_default("id","")!
```
example text to parse
```yaml
id:a1 name6:aaaaa
name:'need to do something 1'
description:
## markdown works in it
description can be multiline
lets see what happens
- a
- something else
### subtitle
name2: test
name3: hi name10:'this is with space' name11:aaa11
#some comment
name4: 'aaa'
//somecomment
name5: 'aab'
```
results in
```go
Params{
params: [Param{
key: 'id'
value: 'a1'
}, Param{
key: 'name6'
value: 'aaaaa'
}, Param{
key: 'name'
value: 'need to do something 1'
}, Param{
key: 'description'
value: '## markdown works in it
description can be multiline
lets see what happens
- a
- something else
### subtitle
'
}, Param{
key: 'name2'
value: 'test'
}, Param{
key: 'name3'
value: 'hi'
}, Param{
key: 'name10'
value: 'this is with space'
}, Param{
key: 'name11'
value: 'aaa11'
}, Param{
key: 'name4'
value: 'aaa'
}, Param{
key: 'name5'
value: 'aab'
}]
}
```