imrprove code generation to support example
This commit is contained in:
@@ -6,22 +6,6 @@ import freeflowuniverse.herolib.schemas.openrpc {Example, ContentDescriptor}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen {schemaref_to_type}
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
|
||||
|
||||
fn generate_handle_example_file(spec ActorSpecification) !VFile {
|
||||
mut items := []CodeItem{}
|
||||
items << CustomCode{generate_handle_example_function(spec)}
|
||||
for method in spec.methods {
|
||||
items << generate_example_method_handle(spec.name, method)!
|
||||
}
|
||||
return VFile {
|
||||
name: 'act'
|
||||
imports: [
|
||||
Import{mod:'freeflowuniverse.herolib.baobab.stage' types:['Action']}
|
||||
Import{mod:'x.json2 as json'}
|
||||
]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_handle_file(spec ActorSpecification) !VFile {
|
||||
mut items := []CodeItem{}
|
||||
items << CustomCode{generate_handle_function(spec)}
|
||||
@@ -32,6 +16,7 @@ fn generate_handle_file(spec ActorSpecification) !VFile {
|
||||
name: 'act'
|
||||
imports: [
|
||||
Import{mod:'freeflowuniverse.herolib.baobab.stage' types:['Action']}
|
||||
Import{mod:'freeflowuniverse.herolib.core.texttools'}
|
||||
Import{mod:'x.json2 as json'}
|
||||
]
|
||||
items: items
|
||||
@@ -58,37 +43,7 @@ pub fn generate_handle_function(spec ActorSpecification) string {
|
||||
'// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY',
|
||||
'',
|
||||
'pub fn (mut actor ${actor_name_pascal}Actor) act(action Action) !Action {',
|
||||
' return match action.name {',
|
||||
routes.join('\n'),
|
||||
' else {',
|
||||
' return error("Unknown operation: \${action.name}")',
|
||||
' }',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
pub fn generate_handle_example_function(spec ActorSpecification) string {
|
||||
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
|
||||
mut operation_handlers := []string{}
|
||||
mut routes := []string{}
|
||||
|
||||
// Iterate over OpenAPI paths and operations
|
||||
for method in spec.methods {
|
||||
operation_id := method.name
|
||||
params := method.parameters.map(it.name).join(', ')
|
||||
|
||||
// Generate route case
|
||||
route := generate_route_case(operation_id, 'handle_${operation_id}_example')
|
||||
routes << route
|
||||
}
|
||||
|
||||
// Combine the generated handlers and main router into a single file
|
||||
return [
|
||||
'// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY',
|
||||
'',
|
||||
'pub fn (mut actor ${actor_name_pascal}Actor) act(action Action) !Action {',
|
||||
' return match action.name {',
|
||||
' return match texttools.snake_case(action.name) {',
|
||||
routes.join('\n'),
|
||||
' else {',
|
||||
' return error("Unknown operation: \${action.name}")',
|
||||
@@ -116,7 +71,7 @@ pub fn generate_method_handle(actor_name string, method ActorMethod) !Function {
|
||||
body += '${param_name} := ${decode_stmt}'
|
||||
}
|
||||
}
|
||||
call_stmt := generate_call_stmt(method)!
|
||||
call_stmt := generate_call_stmt(actor_name, method)!
|
||||
body += '${call_stmt}\n'
|
||||
body += '${generate_return_stmt(method)!}\n'
|
||||
return Function {
|
||||
@@ -153,13 +108,15 @@ pub fn generate_example_method_handle(actor_name string, method ActorMethod) !Fu
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_call_stmt(method ActorMethod) !string {
|
||||
fn generate_call_stmt(name string, method ActorMethod) !string {
|
||||
mut call_stmt := if schemaref_to_type(method.result.schema).vgen().trim_space() != '' {
|
||||
'${texttools.snake_case(method.result.name)} := '
|
||||
} else {''}
|
||||
name_fixed := texttools.snake_case(method.name)
|
||||
method_name := texttools.snake_case(method.name)
|
||||
snake_name := texttools.snake_case(name)
|
||||
|
||||
param_names := method.parameters.map(texttools.snake_case(it.name))
|
||||
call_stmt += 'actor.${name_fixed}(${param_names.join(", ")})!'
|
||||
call_stmt += 'actor.${snake_name}.${method_name}(${param_names.join(", ")})!'
|
||||
return call_stmt
|
||||
}
|
||||
|
||||
@@ -170,23 +127,23 @@ fn generate_return_stmt(method ActorMethod) !string {
|
||||
return "return action"
|
||||
}
|
||||
|
||||
// Helper function to generate a case block for the main router
|
||||
fn generate_route_case(case string, handler_name string) string {
|
||||
name_fixed := texttools.snake_case(handler_name)
|
||||
return "'${texttools.snake_case(case)}' {actor.${name_fixed}(action)}"
|
||||
}
|
||||
|
||||
// generates decode statement for variable with given name
|
||||
fn generate_decode_stmt(name string, param ContentDescriptor) !string {
|
||||
param_type := schemaref_to_type(param.schema)
|
||||
if param_type is Object {
|
||||
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})!'
|
||||
}
|
||||
// else if param.schema.typ == 'array' {
|
||||
// return 'json2.decode[${schemaref_to_type(param.schema)!.vgen()}](${name})'
|
||||
// }
|
||||
else if param_type is code.Array {
|
||||
return 'json.decode[${schemaref_to_type(param.schema).vgen()}](${name})'
|
||||
}
|
||||
param_symbol := param_type.vgen()
|
||||
return if param_symbol == 'string' {
|
||||
'${name}.str()'
|
||||
} else {'${name}.${param_type.vgen()}()'}
|
||||
}
|
||||
|
||||
// Helper function to generate a case block for the main router
|
||||
fn generate_route_case(case string, handler_name string) string {
|
||||
name_fixed := texttools.snake_case(handler_name)
|
||||
return "'${texttools.snake_case(case)}' {actor.${name_fixed}(action)}"
|
||||
}
|
||||
@@ -12,22 +12,14 @@ pub:
|
||||
interfaces []ActorInterface // the interfaces to be supported
|
||||
}
|
||||
|
||||
pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
pub fn generate_actor_folder(spec ActorSpecification, params Params) !Folder {
|
||||
mut files := []IFile{}
|
||||
mut folders := []IFolder{}
|
||||
|
||||
files = [
|
||||
generate_readme_file(spec)!,
|
||||
generate_actor_file(spec)!,
|
||||
generate_actor_test_file(spec)!,
|
||||
generate_specs_file(spec.name, params.interfaces)!,
|
||||
generate_handle_file(spec)!,
|
||||
generate_methods_file(spec)!
|
||||
generate_client_file(spec)!
|
||||
generate_model_file(spec)!
|
||||
]
|
||||
files = [generate_readme_file(spec)!]
|
||||
|
||||
mut docs_files := []IFile{}
|
||||
mut spec_files := []IFile{}
|
||||
|
||||
// generate code files for supported interfaces
|
||||
for iface in params.interfaces {
|
||||
@@ -35,31 +27,80 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
.openrpc {
|
||||
// convert actor spec to openrpc spec
|
||||
openrpc_spec := spec.to_openrpc()
|
||||
|
||||
// generate openrpc code files
|
||||
// files << generate_openrpc_client_file(openrpc_spec)!
|
||||
// files << generate_openrpc_client_test_file(openrpc_spec)!
|
||||
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
|
||||
// add openrpc.json to docs
|
||||
// TODO
|
||||
docs_files << generate_openrpc_file(openrpc_spec)!
|
||||
spec_files << generate_openrpc_file(openrpc_spec)!
|
||||
}
|
||||
.openapi {
|
||||
// convert actor spec to openrpc spec
|
||||
openapi_spec_raw := spec.to_openapi()
|
||||
docs_files << generate_openapi_file(openapi_spec_raw)!
|
||||
spec_files << generate_openapi_file(openapi_spec_raw)!
|
||||
|
||||
openapi_spec := openapi.process(openapi_spec_raw)!
|
||||
folders << generate_openapi_ts_client(openapi_spec)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
specs_folder := Folder {
|
||||
name: 'specs'
|
||||
files: spec_files
|
||||
}
|
||||
|
||||
// folder with docs
|
||||
folders << Folder {
|
||||
name: 'docs'
|
||||
files: docs_files
|
||||
folders: [specs_folder]
|
||||
}
|
||||
|
||||
folders << generate_scripts_folder(spec.name, false)
|
||||
folders << generate_examples_folder()!
|
||||
|
||||
// create module with code files and docs folder
|
||||
name_fixed := texttools.snake_case(spec.name)
|
||||
|
||||
return code.Folder{
|
||||
name: '${name_fixed}_actor'
|
||||
files: files
|
||||
folders: folders
|
||||
modules: [generate_actor_module(spec, params)!]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
mut files := []IFile{}
|
||||
mut folders := []IFolder{}
|
||||
|
||||
files = [
|
||||
generate_actor_file(spec)!,
|
||||
generate_actor_test_file(spec)!,
|
||||
generate_specs_file(spec.name, params.interfaces)!,
|
||||
generate_handle_file(spec)!,
|
||||
generate_methods_file(spec)!
|
||||
generate_methods_interface_file(spec)!
|
||||
generate_methods_example_file(spec)!
|
||||
generate_client_file(spec)!
|
||||
generate_model_file(spec)!
|
||||
]
|
||||
|
||||
// generate code files for supported interfaces
|
||||
for iface in params.interfaces {
|
||||
match iface {
|
||||
.openrpc {
|
||||
// convert actor spec to openrpc spec
|
||||
openrpc_spec := spec.to_openrpc()
|
||||
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
}
|
||||
.openapi {
|
||||
// convert actor spec to openrpc spec
|
||||
openapi_spec_raw := spec.to_openapi()
|
||||
openapi_spec := openapi.process(openapi_spec_raw)!
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_openapi_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
|
||||
// add openapi.json to docs
|
||||
folders << generate_openapi_ts_client(openapi_spec)!
|
||||
}
|
||||
.http {
|
||||
// interfaces that have http controllers
|
||||
@@ -77,15 +118,6 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// folder with docs
|
||||
folders << Folder {
|
||||
name: 'docs'
|
||||
files: docs_files
|
||||
}
|
||||
folders << generate_scripts_folder(spec.name, false)
|
||||
folders << generate_examples_folder(spec, params)!
|
||||
|
||||
// create module with code files and docs folder
|
||||
name_fixed := texttools.snake_case(spec.name)
|
||||
@@ -108,8 +140,9 @@ fn generate_readme_file(spec ActorSpecification) !File {
|
||||
|
||||
fn generate_actor_file(spec ActorSpecification) !VFile {
|
||||
dollar := '$'
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
|
||||
version := spec.version
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
name_pascal := texttools.snake_case_to_pascal(spec.name)
|
||||
actor_code := $tmpl('./templates/actor.v.template')
|
||||
return VFile {
|
||||
name: 'actor'
|
||||
@@ -117,17 +150,6 @@ fn generate_actor_file(spec ActorSpecification) !VFile {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_actor_example_file(spec ActorSpecification) !VFile {
|
||||
dollar := '$'
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
|
||||
actor_code := $tmpl('./templates/actor_example.v.template')
|
||||
return VFile {
|
||||
name: 'actor_example'
|
||||
items: [CustomCode{actor_code}]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_actor_test_file(spec ActorSpecification) !VFile {
|
||||
dollar := '$'
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
@@ -152,88 +174,8 @@ fn generate_specs_file(name string, interfaces []ActorInterface) !VFile {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_examples_folder(spec ActorSpecification, params Params) !Folder {
|
||||
pub fn generate_examples_folder() !Folder {
|
||||
return Folder {
|
||||
name: 'examples'
|
||||
modules: [generate_example_actor_module(spec, params)!]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_example_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
mut files := []IFile{}
|
||||
mut folders := []IFolder{}
|
||||
|
||||
files = [
|
||||
generate_readme_file(spec)!,
|
||||
generate_actor_example_file(spec)!,
|
||||
generate_specs_file(spec.name, params.interfaces)!,
|
||||
generate_handle_example_file(spec)!,
|
||||
generate_example_client_file(spec)!
|
||||
generate_model_file(spec)!
|
||||
]
|
||||
|
||||
mut docs_files := []IFile{}
|
||||
|
||||
// generate code files for supported interfaces
|
||||
for iface in params.interfaces {
|
||||
match iface {
|
||||
.openrpc {
|
||||
// convert actor spec to openrpc spec
|
||||
openrpc_spec := spec.to_openrpc()
|
||||
|
||||
// generate openrpc code files
|
||||
// files << generate_openrpc_client_file(openrpc_spec)!
|
||||
// files << generate_openrpc_client_test_file(openrpc_spec)!
|
||||
iface_file, iface_test_file := generate_openrpc_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
|
||||
// add openrpc.json to docs
|
||||
// TODO
|
||||
docs_files << generate_openrpc_file(openrpc_spec)!
|
||||
}
|
||||
.openapi {
|
||||
// convert actor spec to openrpc spec
|
||||
openapi_spec := spec.to_openapi()
|
||||
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_openapi_interface_files(params.interfaces)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
|
||||
// add openapi.json to docs
|
||||
docs_files << generate_openapi_file(openapi_spec)!
|
||||
}
|
||||
.http {
|
||||
// interfaces that have http controllers
|
||||
controllers := params.interfaces.filter(it == .openrpc || it == .openapi)
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_http_interface_files(controllers)
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
}
|
||||
.command {
|
||||
files << generate_command_file(spec)!
|
||||
}
|
||||
else {
|
||||
return error('unsupported interface ${iface}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// folder with docs
|
||||
folders << Folder {
|
||||
name: 'docs'
|
||||
files: docs_files
|
||||
}
|
||||
folders << generate_scripts_folder('example_${spec.name}', true)
|
||||
|
||||
// create module with code files and docs folder
|
||||
name_fixed := texttools.snake_case(spec.name)
|
||||
return code.new_module(
|
||||
name: 'example_${name_fixed}_actor'
|
||||
files: files
|
||||
folders: folders
|
||||
)
|
||||
}
|
||||
@@ -17,11 +17,9 @@ pub fn generate_client_file(spec ActorSpecification) !VFile {
|
||||
stage.Client
|
||||
}
|
||||
|
||||
fn new_client() !Client {
|
||||
mut redis := redisclient.new(\'localhost:6379\')!
|
||||
mut rpc_q := redis.rpc_get(\'actor_\${name}\')
|
||||
return Client{
|
||||
rpc: rpc_q
|
||||
fn new_client(config stage.ActorConfig) !Client {
|
||||
return Client {
|
||||
Client: stage.new_client(config)!
|
||||
}
|
||||
}'}
|
||||
|
||||
@@ -42,7 +40,7 @@ pub fn generate_client_file(spec ActorSpecification) !VFile {
|
||||
types: ['Any']
|
||||
}
|
||||
]
|
||||
name: 'client'
|
||||
name: 'client_actor'
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ fn generate_openrpc_interface_files(interfaces []ActorInterface) (VFile, VFile)
|
||||
|
||||
fn generate_openapi_interface_files(interfaces []ActorInterface) (VFile, VFile) {
|
||||
http := ActorInterface.http in interfaces
|
||||
|
||||
dollar := '$'
|
||||
iface_file := VFile {
|
||||
name: 'interface_openapi'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openapi.v.template')}]
|
||||
|
||||
@@ -8,12 +8,19 @@ import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecific
|
||||
const crud_prefixes = ['new', 'get', 'set', 'delete', 'list']
|
||||
|
||||
pub fn generate_methods_file(spec ActorSpecification) !VFile {
|
||||
actor_name_snake := texttools.snake_case(spec.name)
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
actor_name_pascal := texttools.snake_case_to_pascal(spec.name)
|
||||
|
||||
mut items := []CodeItem{}
|
||||
receiver := generate_methods_receiver(spec.name)
|
||||
receiver_param := Param {
|
||||
mutable: true
|
||||
name: name_snake
|
||||
typ: code.Result{code.Object{receiver.name}}
|
||||
}
|
||||
|
||||
mut items := [CodeItem(receiver), CodeItem(generate_core_factory(receiver_param))]
|
||||
for method in spec.methods {
|
||||
method_fn := generate_method_function(spec.name, method)!
|
||||
method_fn := generate_method_function(receiver_param, method)!
|
||||
// check if method is a Base Object CRUD Method and
|
||||
// if so generate the method's body
|
||||
body := match spec.method_type(method) {
|
||||
@@ -29,17 +36,34 @@ pub fn generate_methods_file(spec ActorSpecification) !VFile {
|
||||
|
||||
return VFile {
|
||||
name: 'methods'
|
||||
imports: [Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_methods_receiver(name string) code.Struct {
|
||||
return code.Struct {
|
||||
is_pub: true
|
||||
name: '${texttools.pascal_case(name)}'
|
||||
embeds: [code.Struct{name:'OSIS'}]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_core_factory(receiver code.Param) code.Function {
|
||||
return code.Function {
|
||||
is_pub: true
|
||||
name: 'new_${receiver.name}'
|
||||
body: "return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}"
|
||||
result: receiver
|
||||
}
|
||||
}
|
||||
|
||||
// returns bodyless method prototype
|
||||
pub fn generate_method_function(actor_name string, method ActorMethod) !Function {
|
||||
actor_name_pascal := texttools.snake_case_to_pascal(actor_name)
|
||||
pub fn generate_method_function(receiver code.Param, method ActorMethod) !Function {
|
||||
result_param := content_descriptor_to_parameter(method.result)!
|
||||
return Function{
|
||||
name: texttools.snake_case(method.name)
|
||||
receiver: code.new_param(v: 'mut actor ${actor_name_pascal}Actor')!
|
||||
receiver: receiver
|
||||
result: Param {...result_param, typ: Result{result_param.typ}}
|
||||
summary: method.summary
|
||||
description: method.description
|
||||
|
||||
65
lib/baobab/generator/generate_methods_example.v
Normal file
65
lib/baobab/generator/generate_methods_example.v
Normal file
@@ -0,0 +1,65 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc {Example}
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter}
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
|
||||
|
||||
pub fn generate_methods_example_file(spec ActorSpecification) !VFile {
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
name_pascal := texttools.pascal_case(spec.name)
|
||||
|
||||
receiver := generate_example_methods_receiver(spec.name)
|
||||
receiver_param := Param {
|
||||
mutable: true
|
||||
name: name_snake
|
||||
typ: code.Result{code.Object{receiver.name}}
|
||||
}
|
||||
mut items := [CodeItem(receiver), CodeItem(generate_core_example_factory(receiver_param))]
|
||||
for method in spec.methods {
|
||||
method_fn := generate_method_function(receiver_param, method)!
|
||||
items << Function{...method_fn,
|
||||
body: generate_method_example_body(method_fn, method)!
|
||||
}
|
||||
}
|
||||
|
||||
return VFile {
|
||||
name: 'methods_example'
|
||||
imports: [
|
||||
Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']},
|
||||
Import{mod: 'x.json2 as json'}
|
||||
]
|
||||
items: items
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_core_example_factory(receiver code.Param) code.Function {
|
||||
return code.Function {
|
||||
is_pub: true
|
||||
name: 'new_${receiver.name}_example'
|
||||
body: "return ${receiver.typ.symbol().trim_left('!?')}{OSIS: osis.new()!}"
|
||||
result: receiver
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_example_methods_receiver(name string) code.Struct {
|
||||
return code.Struct {
|
||||
is_pub: true
|
||||
name: '${texttools.pascal_case(name)}Example'
|
||||
embeds: [code.Struct{name:'OSIS'}]
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_method_example_body(func Function, method ActorMethod) !string {
|
||||
return if !method_is_void(method)! {
|
||||
if method.example.result is Example {
|
||||
"data := '${method.example.result.value}'
|
||||
return ${generate_decode_stmt('data', method.result)!}"
|
||||
} else {
|
||||
"return ${func.result.typ.empty_value()}"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
31
lib/baobab/generator/generate_methods_interface.v
Normal file
31
lib/baobab/generator/generate_methods_interface.v
Normal file
@@ -0,0 +1,31 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Array, Folder, IFile, VFile, CodeItem, File, Function, Param, Import, Module, Struct, CustomCode, Result }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen {content_descriptor_to_parameter}
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
|
||||
|
||||
pub fn generate_methods_interface_file(spec ActorSpecification) !VFile {
|
||||
return VFile {
|
||||
name: 'methods_interface'
|
||||
imports: [Import{mod: 'freeflowuniverse.herolib.baobab.osis', types: ['OSIS']}]
|
||||
items: [code.CodeItem(generate_methods_interface_declaration(spec)!)]
|
||||
}
|
||||
}
|
||||
|
||||
// returns bodyless method prototype
|
||||
pub fn generate_methods_interface_declaration(spec ActorSpecification) !code.Interface {
|
||||
name_snake := texttools.snake_case(spec.name)
|
||||
name_pascal := texttools.pascal_case(spec.name)
|
||||
receiver := generate_methods_receiver(spec.name)
|
||||
receiver_param := Param {
|
||||
mutable: true
|
||||
name: name_snake
|
||||
typ: code.Object{receiver.name}
|
||||
}
|
||||
return code.Interface {
|
||||
is_pub: true
|
||||
name: 'I${name_pascal}'
|
||||
methods: spec.methods.map(generate_method_function(receiver_param, it)!)
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ pub fn generate_scripts_folder(name string, example bool) Folder {
|
||||
files: [
|
||||
generate_run_script(actor_name),
|
||||
generate_docs_script(actor_name),
|
||||
generate_run_actor_script(actor_name),
|
||||
// generate_run_example_actor_script(actor_name),
|
||||
generate_run_http_server_script(actor_name, example),
|
||||
generate_run_actor_script(name),
|
||||
generate_run_actor_example_script(name),
|
||||
generate_run_http_server_script(name),
|
||||
// generate_compile_script(actor_name),
|
||||
// generate_generate_script(actor_name)
|
||||
]
|
||||
@@ -42,7 +42,9 @@ fn generate_docs_script(actor_name string) File {
|
||||
}
|
||||
|
||||
// Function to generate a script for running an actor
|
||||
fn generate_run_actor_script(actor_name string) File {
|
||||
fn generate_run_actor_script(name string) File {
|
||||
name_snake := texttools.snake_case(name)
|
||||
name_pascal := texttools.pascal_case(name)
|
||||
return File{
|
||||
name: 'run_actor'
|
||||
extension:'vsh'
|
||||
@@ -51,38 +53,23 @@ fn generate_run_actor_script(actor_name string) File {
|
||||
}
|
||||
|
||||
// Function to generate a script for running an example actor
|
||||
fn generate_run_example_actor_script(actor_name string) File {
|
||||
fn generate_run_actor_example_script(name string) File {
|
||||
name_snake := texttools.snake_case(name)
|
||||
name_pascal := texttools.pascal_case(name)
|
||||
return File{
|
||||
name: 'run_example_actor'
|
||||
name: 'run_actor_example'
|
||||
extension:'vsh'
|
||||
content: $tmpl('./templates/run_example_actor.vsh.template')
|
||||
content: $tmpl('./templates/run_actor_example.vsh.template')
|
||||
}
|
||||
}
|
||||
|
||||
// Function to generate a script for running an HTTP server
|
||||
fn generate_run_http_server_script(actor_name string, example bool) File {
|
||||
port := if example {8081} else {8080}
|
||||
fn generate_run_http_server_script(name string) File {
|
||||
port := 8080
|
||||
name_snake := texttools.snake_case(name)
|
||||
return File{
|
||||
name: 'run_http_server'
|
||||
extension:'vsh'
|
||||
content: $tmpl('./templates/run_http_server.vsh.template')
|
||||
}
|
||||
}
|
||||
|
||||
// // Function to generate a script for compiling the project
|
||||
// fn generate_compile_script(actor_name string) File {
|
||||
// return File{
|
||||
// name: 'compile'
|
||||
// extension:'sh'
|
||||
// content: $tmpl('./templates/run_http_server.vsh.template')
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Function to generate a script for general generation tasks
|
||||
// fn generate_generate_script(actor_name string) File {
|
||||
// return File{
|
||||
// name: 'generate'
|
||||
// extension: 'vsh'
|
||||
// content: $tmpl('./templates/run_http_server.vsh.template')
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -4,20 +4,26 @@ import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import time
|
||||
|
||||
const name = '@{actor_name_snake}'
|
||||
|
||||
@@[heap]
|
||||
struct @{actor_name_pascal}Actor {
|
||||
stage.Actor
|
||||
pub const configuration = stage.ActorConfig {
|
||||
name: '@{name_snake}'
|
||||
version: '@{version}'
|
||||
}
|
||||
|
||||
pub fn new() !&@{actor_name_pascal}Actor {
|
||||
return &@{actor_name_pascal}Actor {
|
||||
Actor: stage.new_actor('@{actor_name_snake}')!
|
||||
@@[heap]
|
||||
struct @{name_pascal}Actor {
|
||||
stage.Actor
|
||||
pub mut:
|
||||
@{name_snake} I@{name_pascal}
|
||||
}
|
||||
|
||||
pub fn new(@{name_snake} I@{name_pascal}, config stage.ActorConfig) !&@{name_pascal}Actor {
|
||||
return &@{name_pascal}Actor {
|
||||
Actor: stage.new_actor(config)!
|
||||
@{name_snake}: @{name_snake}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !string {
|
||||
pub fn (mut a @{name_pascal}Actor) handle(method string, data string) !string {
|
||||
action := a.act(
|
||||
name: method
|
||||
params: data
|
||||
@@ -26,11 +32,8 @@ pub fn (mut a @{actor_name_pascal}Actor) handle(method string, data string) !str
|
||||
}
|
||||
|
||||
// Actor listens to the Redis queue for method invocations
|
||||
pub fn (mut a @{actor_name_pascal}Actor) run() ! {
|
||||
mut redis := redisclient.new('localhost:6379') or { panic(err) }
|
||||
mut rpc := redis.rpc_get('actor_@{dollar}{a.name}')
|
||||
|
||||
println('Actor started and listening for tasks...')
|
||||
pub fn (mut a @{name_pascal}Actor) run() ! {
|
||||
mut rpc := a.get_redis_rpc()!
|
||||
for {
|
||||
rpc.process(a.handle)!
|
||||
time.sleep(time.millisecond * 100) // Prevent CPU spinning
|
||||
|
||||
@@ -5,42 +5,37 @@ import freeflowuniverse.herolib.baobab.stage.interfaces { HTTPServer, Context }
|
||||
import veb
|
||||
|
||||
@@[params]
|
||||
pub struct ServerParams {
|
||||
pub struct HTTPServerParams {
|
||||
pub:
|
||||
base_url string
|
||||
port int = 8080
|
||||
}
|
||||
|
||||
pub fn new_http_server(params ServerParams) !&HTTPServer {
|
||||
pub fn new_http_server(params HTTPServerParams) !&HTTPServer {
|
||||
mut s := interfaces.new_http_server()!
|
||||
@if openrpc
|
||||
mut openrpc_controller := new_openrpc_http_controller(ServerParams{
|
||||
mut openrpc_controller := new_openrpc_http_controller(HTTPServerParams{
|
||||
...params,
|
||||
base_url: '@{dollar}{params.base_url}/openrpc'
|
||||
})!
|
||||
s.register_controller[openrpc.HTTPController, Context]('/openrpc', mut openrpc_controller)!
|
||||
@end
|
||||
@if openapi
|
||||
mut openapi_controller := new_openapi_http_controller(ServerParams{
|
||||
...params,
|
||||
base_url: '@{dollar}{params.base_url}/openapi/v1'
|
||||
})!
|
||||
mut openapi_example_controller := new_openapi_http_controller(ServerParams{
|
||||
...params,
|
||||
base_url: '@{dollar}{params.base_url}/openapi/example'
|
||||
})!
|
||||
mut openapi_ctrl := new_openapi_http_controller(configuration, params)!
|
||||
mut openapi_ex_ctrl := new_openapi_http_controller(configuration.example().example(), params)!
|
||||
|
||||
mut openapi_playground_controller := openapi.new_playground_controller(
|
||||
base_url: '@{dollar}{params.base_url}/playground/openapi'
|
||||
specification_path: openapi_spec_path
|
||||
)!
|
||||
s.register_controller[openapi.HTTPController, Context]('/openapi/v1', mut openapi_controller)!
|
||||
s.register_controller[openapi.HTTPController, Context]('/openapi/example', mut openapi_example_controller)!
|
||||
s.register_controller[openapi.HTTPController, Context]('/openapi/v1', mut openapi_ctrl)!
|
||||
s.register_controller[openapi.HTTPController, Context]('/openapi/example', mut openapi_ex_ctrl)!
|
||||
s.register_controller[openapi.PlaygroundController, Context]('/playground/openapi', mut openapi_playground_controller)!
|
||||
@end
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn run_http_server(params ServerParams) ! {
|
||||
pub fn run_http_server(params HTTPServerParams) ! {
|
||||
mut server := new_http_server(params)!
|
||||
veb.run[HTTPServer, Context](mut server, params.port)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import freeflowuniverse.herolib.baobab.stage.interfaces
|
||||
import freeflowuniverse.herolib.baobab.stage
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
pub fn new_openapi_interface() !&interfaces.OpenAPIInterface {
|
||||
pub fn new_openapi_interface(config stage.ActorConfig) !&interfaces.OpenAPIInterface {
|
||||
// create OpenAPI Handler with actor's client
|
||||
client := new_client()!
|
||||
client := new_client(config)!
|
||||
return interfaces.new_openapi_interface(client.Client)
|
||||
}
|
||||
|
||||
@if http
|
||||
// creates HTTP controller with the actor's OpenAPI Handler
|
||||
// and OpenAPI Specification
|
||||
pub fn new_openapi_http_controller(params ServerParams) !&openapi.HTTPController {
|
||||
pub fn new_openapi_http_controller(config stage.ActorConfig, params HTTPServerParams) !&openapi.HTTPController {
|
||||
return openapi.new_http_controller(
|
||||
base_url: params.base_url
|
||||
base_url: '@{dollar}{params.base_url}/openapi/@{dollar}{config.version}'
|
||||
specification: openapi_specification
|
||||
specification_path: openapi_spec_path
|
||||
handler: new_openapi_interface()!
|
||||
handler: new_openapi_interface(config)!
|
||||
)
|
||||
}
|
||||
@end
|
||||
@@ -7,6 +7,10 @@ chmod +x @{dollar}{DIR}/run_actor.vsh
|
||||
@{dollar}{DIR}/run_actor.vsh &
|
||||
ACTOR_PID=@{dollar}!
|
||||
|
||||
chmod +x @{dollar}{DIR}/run_actor_example.vsh
|
||||
@{dollar}{DIR}/run_actor_example.vsh &
|
||||
EXAMPLE_ACTOR_PID=@{dollar}!
|
||||
|
||||
chmod +x @{dollar}{DIR}/run_http_server.vsh
|
||||
@{dollar}{DIR}/run_http_server.vsh &
|
||||
HTTP_SERVER_PID=@{dollar}!
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import @{actor_name}
|
||||
import @{name_snake}_actor
|
||||
|
||||
mut actor := @{actor_name}.new()!
|
||||
actor.run()!
|
||||
mut actor := @{name_snake}_actor.new(
|
||||
@{name_snake}_actor.new_@{name_snake}()!,
|
||||
@{name_snake}_actor.configuration
|
||||
)!
|
||||
|
||||
actor.run()!
|
||||
10
lib/baobab/generator/templates/run_actor_example.vsh.template
Executable file
10
lib/baobab/generator/templates/run_actor_example.vsh.template
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import @{name_snake}_actor
|
||||
|
||||
mut actor := @{name_snake}_actor.new(
|
||||
@{name_snake}_actor.new_@{name_snake}_example()!,
|
||||
@{name_snake}_actor.configuration.example()
|
||||
)!
|
||||
|
||||
actor.run()!
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
|
||||
import @{actor_name}
|
||||
|
||||
mut actor := @{actor_name}.new_example()!
|
||||
actor.run()!
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env -S v -w -n -enable-globals run
|
||||
import @{actor_name}
|
||||
import @{name_snake}_actor
|
||||
|
||||
@{actor_name}.run_http_server(
|
||||
@{name_snake}_actor.run_http_server(
|
||||
base_url: 'http://localhost:@{port}'
|
||||
port: @{port}
|
||||
)!
|
||||
|
||||
@@ -3,12 +3,12 @@ import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
@if support_openrpc
|
||||
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/openrpc.json'
|
||||
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/specs/openrpc.json'
|
||||
const openrpc_spec_json = os.read_file(openrpc_spec_path) or { panic(err) }
|
||||
const openrpc_specification = openrpc.decode(openrpc_spec_json)!
|
||||
@end
|
||||
@if support_openapi
|
||||
const openapi_spec_path = '@{dollar}{os.dir(os.dir(@@FILE))}/docs/openapi.json'
|
||||
const openapi_spec_path = '@{dollar}{os.dir(os.dir(@@FILE))}/docs/specs/openapi.json'
|
||||
const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
|
||||
const openapi_specification = openapi.json_decode(openapi_spec_json)!
|
||||
@end
|
||||
@@ -7,6 +7,7 @@ import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference}
|
||||
|
||||
pub struct ActorSpecification {
|
||||
pub mut:
|
||||
version string = '1.0.0'
|
||||
openapi ?openapi.OpenAPI
|
||||
openrpc ?openrpc.OpenRPC
|
||||
name string @[omitempty]
|
||||
|
||||
@@ -16,14 +16,14 @@ pub:
|
||||
}
|
||||
|
||||
pub struct ClientConfig {
|
||||
ActorConfig
|
||||
pub:
|
||||
redis_url string // url to redis server running
|
||||
redis_queue string // name of redis queue
|
||||
redis_url string = 'localhost:6379' // url to redis server running
|
||||
}
|
||||
|
||||
pub fn new_client(config ClientConfig) !Client {
|
||||
pub fn new_client(config ActorConfig) !Client {
|
||||
mut redis := redisclient.new(config.redis_url)!
|
||||
mut rpc_q := redis.rpc_get(config.redis_queue)
|
||||
mut rpc_q := redis.rpc_get(config.redis_queue_name())
|
||||
|
||||
return Client{
|
||||
rpc: rpc_q
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module stage
|
||||
|
||||
import freeflowuniverse.herolib.baobab.osis {OSIS}
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
|
||||
@[heap]
|
||||
pub interface IActor {
|
||||
@@ -10,17 +11,48 @@ mut:
|
||||
}
|
||||
|
||||
pub struct Actor {
|
||||
pub:
|
||||
name string
|
||||
redis_url string = 'localhost:6379'
|
||||
ActorConfig
|
||||
mut:
|
||||
osis OSIS
|
||||
}
|
||||
|
||||
pub fn new_actor(name string) !Actor {
|
||||
@[params]
|
||||
pub struct ActorConfig {
|
||||
pub:
|
||||
name string
|
||||
version string
|
||||
redis_url string = 'localhost:6379'
|
||||
}
|
||||
|
||||
pub fn (config ActorConfig) redis_queue_name() string {
|
||||
mut str := 'actor_${config.name}'
|
||||
if config.version != '' {
|
||||
str += '_${config.version}'
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
pub fn new_actor(config ActorConfig) !Actor {
|
||||
return Actor{
|
||||
ActorConfig: config
|
||||
osis: osis.new()!
|
||||
name: name
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (a ActorConfig) get_redis_rpc() !redisclient.RedisRpc {
|
||||
mut redis := redisclient.new(a.redis_url)!
|
||||
return redis.rpc_get(a.redis_queue_name())
|
||||
}
|
||||
|
||||
pub fn (a ActorConfig) version(v string) ActorConfig {
|
||||
return ActorConfig {...a,
|
||||
version: v
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (a ActorConfig) example() ActorConfig {
|
||||
return ActorConfig {...a,
|
||||
version: 'example'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
lib/baobab/stage/config.v
Normal file
2
lib/baobab/stage/config.v
Normal file
@@ -0,0 +1,2 @@
|
||||
module stage
|
||||
|
||||
@@ -13,6 +13,7 @@ pub struct Folder {
|
||||
pub:
|
||||
name string
|
||||
files []IFile
|
||||
folders []IFolder
|
||||
modules []Module
|
||||
}
|
||||
|
||||
@@ -29,6 +30,9 @@ pub fn (f Folder) write(path string, options WriteOptions) ! {
|
||||
for file in f.files {
|
||||
file.write(dir.path, options)!
|
||||
}
|
||||
for folder in f.folders {
|
||||
folder.write(dir.path, options)!
|
||||
}
|
||||
for mod in f.modules {
|
||||
mod.write(dir.path, options)!
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
module code
|
||||
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
pub struct Function {
|
||||
pub:
|
||||
name string @[omitempty]
|
||||
@@ -15,6 +17,61 @@ pub mut:
|
||||
has_return bool @[omitempty]
|
||||
}
|
||||
|
||||
|
||||
// vgen_function generates a function statement for a function
|
||||
pub fn (function Function) vgen(options WriteOptions) string {
|
||||
mut params_ := function.params.clone()
|
||||
optionals := function.params.filter(it.is_optional)
|
||||
options_struct := Struct{
|
||||
name: '${texttools.snake_case_to_pascal(function.name)}Options'
|
||||
attrs: [Attribute{
|
||||
name: 'params'
|
||||
}]
|
||||
fields: optionals.map(StructField{
|
||||
name: it.name
|
||||
description: it.description
|
||||
typ: it.typ
|
||||
})
|
||||
}
|
||||
if optionals.len > 0 {
|
||||
params_ << Param{
|
||||
name: 'options'
|
||||
typ: type_from_symbol(options_struct.name)
|
||||
}
|
||||
}
|
||||
|
||||
params := params_.filter(!it.is_optional).map(it.vgen()).join(', ')
|
||||
|
||||
receiver_ := Param{
|
||||
...function.receiver,
|
||||
typ: if function.receiver.typ is Result {
|
||||
function.receiver.typ.typ
|
||||
} else {function.receiver.typ}
|
||||
|
||||
}
|
||||
receiver := if receiver_.vgen().trim_space() != '' {
|
||||
'(${receiver_.vgen()})'
|
||||
} else {''}
|
||||
|
||||
result := function.result.typ.vgen()
|
||||
|
||||
mut function_str := $tmpl('templates/function/function.v.template')
|
||||
|
||||
// if options.format {
|
||||
// result := os.execute_opt('echo "${function_str.replace('$', '\\$')}" | v fmt') or {
|
||||
// panic('${function_str}\n${err}')
|
||||
// }
|
||||
// function_str = result.output
|
||||
// }
|
||||
function_str = function_str.split_into_lines().filter(!it.starts_with('import ')).join('\n')
|
||||
|
||||
return if options_struct.fields.len != 0 {
|
||||
'${options_struct.vgen()}\n${function_str}'
|
||||
} else {
|
||||
function_str
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_function(code string) !Function {
|
||||
// TODO: implement function from file line
|
||||
return parse_function(code)!
|
||||
|
||||
@@ -3,7 +3,7 @@ module code
|
||||
// Code is a list of statements
|
||||
// pub type Code = []CodeItem
|
||||
|
||||
pub type CodeItem = Alias | Comment | CustomCode | Function | Import | Struct | Sumtype
|
||||
pub type CodeItem = Alias | Comment | CustomCode | Function | Import | Struct | Sumtype | Interface
|
||||
|
||||
// item for adding custom code in
|
||||
pub struct CustomCode {
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn new_module(mod Module) Module {
|
||||
|
||||
pub fn (mod Module) write(path string, options WriteOptions) ! {
|
||||
mut module_dir := pathlib.get_dir(
|
||||
path: if mod.in_src { '${path}/${mod.name}/src' } else { '${path}/${mod.name}' }
|
||||
path: if mod.in_src { '${path}/src' } else { '${path}/${mod.name}' }
|
||||
empty: options.overwrite
|
||||
)!
|
||||
console.print_debug("write ${module_dir.path}")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
module code
|
||||
|
||||
import log
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
pub struct Struct {
|
||||
@@ -14,6 +16,79 @@ pub mut:
|
||||
fields []StructField
|
||||
}
|
||||
|
||||
// vgen_function generates a function statement for a function
|
||||
pub fn (struct_ Struct) vgen() string {
|
||||
name := if struct_.generics.len > 0 {
|
||||
'${struct_.name}${vgen_generics(struct_.generics)}'
|
||||
} else {
|
||||
struct_.name
|
||||
}
|
||||
|
||||
prefix := if struct_.is_pub {
|
||||
'pub'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
priv_fields := struct_.fields.filter(!it.is_mut && !it.is_pub).map(it.vgen())
|
||||
pub_fields := struct_.fields.filter(!it.is_mut && it.is_pub).map(it.vgen())
|
||||
mut_fields := struct_.fields.filter(it.is_mut && !it.is_pub).map(it.vgen())
|
||||
pub_mut_fields := struct_.fields.filter(it.is_mut && it.is_pub).map(it.vgen())
|
||||
|
||||
mut struct_str := $tmpl('templates/struct/struct.v.template')
|
||||
if false {
|
||||
result := os.execute_opt('echo "${struct_str.replace('$', '\$')}" | v fmt') or {
|
||||
log.debug(struct_str)
|
||||
panic(err)
|
||||
}
|
||||
return result.output
|
||||
}
|
||||
return struct_str
|
||||
}
|
||||
|
||||
|
||||
pub struct Interface {
|
||||
pub mut:
|
||||
name string
|
||||
description string
|
||||
is_pub bool
|
||||
embeds []Interface @[str: skip]
|
||||
attrs []Attribute
|
||||
fields []StructField
|
||||
methods []Function
|
||||
}
|
||||
|
||||
pub fn (iface Interface) vgen() string {
|
||||
name := texttools.pascal_case(iface.name)
|
||||
|
||||
prefix := if iface.is_pub {
|
||||
'pub'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
mut fields := iface.fields.filter(!it.is_mut).map(it.vgen())
|
||||
mut mut_fields := iface.fields.filter(it.is_mut).map(it.vgen())
|
||||
|
||||
fields << iface.methods.filter(!it.receiver.mutable).map(function_to_interface_field(it))
|
||||
mut_fields << iface.methods.filter(it.receiver.mutable).map(function_to_interface_field(it))
|
||||
|
||||
mut iface_str := $tmpl('templates/interface/interface.v.template')
|
||||
if false {
|
||||
result := os.execute_opt('echo "${iface_str.replace('$', '\$')}" | v fmt') or {
|
||||
log.debug(iface_str)
|
||||
panic(err)
|
||||
}
|
||||
return result.output
|
||||
}
|
||||
return iface_str
|
||||
}
|
||||
|
||||
pub fn function_to_interface_field(f Function) string {
|
||||
param_types := f.params.map(it.typ.vgen()).join(', ')
|
||||
return '${f.name}(${param_types}) ${f.result.typ.vgen()}'
|
||||
}
|
||||
|
||||
pub struct StructField {
|
||||
Param
|
||||
pub mut:
|
||||
|
||||
15
lib/core/code/templates/interface/interface.v.template
Normal file
15
lib/core/code/templates/interface/interface.v.template
Normal file
@@ -0,0 +1,15 @@
|
||||
// @{iface.description}
|
||||
@if iface.attrs.len > 0
|
||||
[
|
||||
@for attr in iface.attrs
|
||||
@{attr.name}
|
||||
@end
|
||||
]
|
||||
@end
|
||||
@{prefix} interface @{name} {
|
||||
@{fields.join_lines()}
|
||||
@if mut_fields.len > 0
|
||||
mut:
|
||||
@{mut_fields.join_lines()}
|
||||
@end
|
||||
}
|
||||
@@ -21,6 +21,9 @@ pub fn vgen(code []CodeItem) string {
|
||||
if item is Struct {
|
||||
str += '\n${item.vgen()}'
|
||||
}
|
||||
if item is Interface {
|
||||
str += '\n${item.vgen()}'
|
||||
}
|
||||
if item is CustomCode {
|
||||
str += '\n${item.vgen()}'
|
||||
}
|
||||
@@ -54,91 +57,6 @@ pub fn vgen_generics(generics map[string]string) string {
|
||||
return '${vstr}]'
|
||||
}
|
||||
|
||||
// vgen_function generates a function statement for a function
|
||||
pub fn (function Function) vgen(options WriteOptions) string {
|
||||
mut params_ := function.params.clone()
|
||||
optionals := function.params.filter(it.is_optional)
|
||||
options_struct := Struct{
|
||||
name: '${texttools.snake_case_to_pascal(function.name)}Options'
|
||||
attrs: [Attribute{
|
||||
name: 'params'
|
||||
}]
|
||||
fields: optionals.map(StructField{
|
||||
name: it.name
|
||||
description: it.description
|
||||
typ: it.typ
|
||||
})
|
||||
}
|
||||
if optionals.len > 0 {
|
||||
params_ << Param{
|
||||
name: 'options'
|
||||
typ: type_from_symbol(options_struct.name)
|
||||
}
|
||||
}
|
||||
|
||||
params := params_.filter(!it.is_optional).map(it.vgen()).join(', ')
|
||||
|
||||
receiver := if function.receiver.vgen().trim_space() != '' {
|
||||
'(${function.receiver.vgen()})'
|
||||
} else {''}
|
||||
|
||||
// generate anon result param
|
||||
result := Param{...function.result,
|
||||
name: ''
|
||||
}.vgen()
|
||||
|
||||
mut function_str := $tmpl('templates/function/function.v.template')
|
||||
|
||||
// if options.format {
|
||||
// result := os.execute_opt('echo "${function_str.replace('$', '\\$')}" | v fmt') or {
|
||||
// panic('${function_str}\n${err}')
|
||||
// }
|
||||
// function_str = result.output
|
||||
// }
|
||||
function_str = function_str.split_into_lines().filter(!it.starts_with('import ')).join('\n')
|
||||
|
||||
return if options_struct.fields.len != 0 {
|
||||
'${options_struct.vgen()}\n${function_str}'
|
||||
} else {
|
||||
function_str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// vgen_function generates a function statement for a function
|
||||
pub fn (struct_ Struct) vgen() string {
|
||||
name := if struct_.generics.len > 0 {
|
||||
'${struct_.name}${vgen_generics(struct_.generics)}'
|
||||
} else {
|
||||
struct_.name
|
||||
}
|
||||
|
||||
prefix := if struct_.is_pub {
|
||||
'pub'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
priv_fields := struct_.fields.filter(!it.is_mut && !it.is_pub).map(it.vgen())
|
||||
pub_fields := struct_.fields.filter(!it.is_mut && it.is_pub).map(it.vgen())
|
||||
mut_fields := struct_.fields.filter(it.is_mut && !it.is_pub).map(it.vgen())
|
||||
pub_mut_fields := struct_.fields.filter(it.is_mut && it.is_pub).map(it.vgen())
|
||||
|
||||
mut struct_str := $tmpl('templates/struct/struct.v.template')
|
||||
if false {
|
||||
result := os.execute_opt('echo "${struct_str.replace('$', '\$')}" | v fmt') or {
|
||||
log.debug(struct_str)
|
||||
panic(err)
|
||||
}
|
||||
return result.output
|
||||
}
|
||||
return struct_str
|
||||
// mut struct_str := $tmpl('templates/struct/struct.v.template')
|
||||
// return struct_str
|
||||
// result := os.execute_opt('echo "${struct_str.replace('$', '\$')}" | v fmt') or {panic(err)}
|
||||
// return result.output
|
||||
}
|
||||
|
||||
pub fn (custom CustomCode) vgen() string {
|
||||
return custom.text
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user