diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..10a53b4e --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,46 @@ +name: Deploy Documentation to Pages + +on: + push: + branches: ["development"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy-documentation: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Install Vlang dependencies + run: sudo apt update && sudo apt install -y libgc-dev + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Vlang + run: ./install_v.sh + + - name: Generate documentation + run: ./doc.vsh + + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: "/home/runner/work/crystallib/crystallib/docs" + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/hero_build_s3.yml b/.github/workflows/hero_build_s3.yml new file mode 100644 index 00000000..b2b5e86c --- /dev/null +++ b/.github/workflows/hero_build_s3.yml @@ -0,0 +1,55 @@ +name: Test Crystal & Release to S3 + +permissions: + contents: write + + +on: + push: + branches: ["development"] + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + include: + - target: x86_64-unknown-linux-musl + os: ubuntu-latest + short-name: linux-i64 + # - target: aarch64-unknown-linux-musl + # os: ubuntu-latest + # short-name: linux-arm64 + # - target: aarch64-apple-darwin + # os: macos-latest + # short-name: macos-arm64 + # - target: x86_64-apple-darwin + # os: macos-latest + # short-name: macos-i64 + runs-on: ${{ matrix.os }} + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref_name }} and your repository is ${{ github.repository }}." + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Setup Vlang + run: ./install_v.sh + + - name: Setup Herolib + run: ./install_herolib.vsh + + - name: Do all the basic tests + run: ./test_basic.vsh + + # - name: Upload to S3 + # run: | + # echo 'export S3KEYID=${{ secrets.S3KEYID }}' > ${HOME}/mysecrets.sh + # echo 'export S3APPID=${{ secrets.S3APPID }}' >> ${HOME}/mysecrets.sh + # set -e && cat ${HOME}/mysecrets.sh + # sudo bash +x scripts/githubactions.sh + + - name: Extract tag name + run: echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV diff --git a/.gitignore b/.gitignore index c6a49afd..5d7b55f0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ output/ .stellar vdocs/ data.ms/ +test_basic \ No newline at end of file diff --git a/lib/code/codeparser/parse_example.v b/lib/code/codeparser/parse_example.v index 222f1a21..f2249434 100644 --- a/lib/code/codeparser/parse_example.v +++ b/lib/code/codeparser/parse_example.v @@ -1,6 +1,6 @@ module codeparser -// import freeflowuniverse.herolib.core.codemodel {Example} +// import freeflowuniverse.herolib.code.codemodel {Example} // import freeflowuniverse.herolib.rpc.openrpc {ExamplePairing} // pub fn parse_example_pairing(text_ string) !ExamplePairing { diff --git a/lib/code/codeparser/vparser.v b/lib/code/codeparser/vparser.v index 0d40c477..50dbe98e 100644 --- a/lib/code/codeparser/vparser.v +++ b/lib/code/codeparser/vparser.v @@ -4,7 +4,7 @@ import v.ast import v.parser import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.core.codemodel { CodeFile, CodeItem, Function, Import, Module, Param, Result, Struct, StructField, Sumtype, Type, parse_consts, parse_import } +import freeflowuniverse.herolib.code.codemodel { CodeFile, CodeItem, Function, Import, Module, Param, Result, Struct, StructField, Sumtype, Type, parse_consts, parse_import } import v.pref // VParser holds configuration of parsing diff --git a/lib/code/codeparser/vparser_test.v b/lib/code/codeparser/vparser_test.v index b1f72448..2046ade1 100644 --- a/lib/code/codeparser/vparser_test.v +++ b/lib/code/codeparser/vparser_test.v @@ -1,6 +1,6 @@ module codeparser -import freeflowuniverse.herolib.core.codemodel { CodeItem, Function, Struct } +import freeflowuniverse.herolib.code.codemodel { CodeItem, Function, Struct } import os import freeflowuniverse.herolib.ui.console @@ -20,7 +20,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the first function of file' params: [] body: '' @@ -43,7 +43,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the second function of file' params: [ codemodel.Param{ @@ -68,7 +68,7 @@ const testcode = { CodeItem(Struct{ name: 'AnotherfileStruct0' description: 'AnotherfileStruct0 defines the configuration params of anotherfile_func2' - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' is_pub: true attrs: [ codemodel.Attribute{ @@ -118,7 +118,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the third function of the file' params: [ codemodel.Param{ @@ -143,7 +143,7 @@ const testcode = { CodeItem(Struct{ name: 'AnotherfileStruct1' description: '' - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' is_pub: true fields: [ codemodel.StructField{ @@ -172,7 +172,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the fourth function of the file is does something with param1 and param2 and creates AnotherfileStruct1' params: [ codemodel.Param{ @@ -214,7 +214,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the first function of file' params: [] body: '' @@ -237,7 +237,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the second function of file' params: [ codemodel.Param{ @@ -262,7 +262,7 @@ const testcode = { CodeItem(Struct{ name: 'SubfileStruct0' description: 'SubfileStruct0 defines the configuration params of subfile_func2' - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' is_pub: true attrs: [ codemodel.Attribute{ @@ -312,7 +312,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the third function of the file' params: [ codemodel.Param{ @@ -337,7 +337,7 @@ const testcode = { CodeItem(Struct{ name: 'SubfileStruct1' description: '' - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' is_pub: true fields: [ codemodel.StructField{ @@ -366,7 +366,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata.flatdir' + mod: 'code.codeparser.testdata.flatdir' description: 'is the fourth function of the file is does something with param1 and param2 and creates SubfileStruct1' params: [ codemodel.Param{ @@ -408,7 +408,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata' + mod: 'code.codeparser.testdata' description: 'is the first function of file' params: [] body: '' @@ -431,7 +431,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata' + mod: 'code.codeparser.testdata' description: 'is the second function of file' params: [ codemodel.Param{ @@ -456,7 +456,7 @@ const testcode = { CodeItem(Struct{ name: 'FileStruct0' description: 'FileStruct0 defines the configuration params of file_func2' - mod: 'core.codeparser.testdata' + mod: 'code.codeparser.testdata' is_pub: true attrs: [ codemodel.Attribute{ @@ -506,7 +506,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata' + mod: 'code.codeparser.testdata' description: 'is the third function of the file' params: [ codemodel.Param{ @@ -532,7 +532,7 @@ const testcode = { name: 'FileStruct1' description: '' fields: [] - mod: 'core.codeparser.testdata' + mod: 'code.codeparser.testdata' is_pub: true }), CodeItem(Function{ @@ -545,7 +545,7 @@ const testcode = { symbol: 'void' } } - mod: 'core.codeparser.testdata' + mod: 'code.codeparser.testdata' description: 'is the fourth function of the file is does something with param1 and param2 and creates FileStruct1' params: [ codemodel.Param{ diff --git a/lib/code/generator/generic/factory.v b/lib/code/generator/generic/factory.v new file mode 100644 index 00000000..0a6aba7b --- /dev/null +++ b/lib/code/generator/generic/factory.v @@ -0,0 +1,210 @@ +module generic + +import freeflowuniverse.herolib.ui.console +import os +import freeflowuniverse.herolib.core.pathlib + +// will ask questions when not in force mode +// & generate the module +pub fn generate(args_ GeneratorArgs) ! { + mut myconsole := console.new() + mut args := args_ + + console.print_header('Generate code for path: ${args.path} (reset:${args.force}, force:${args.force})') + console.print_debug(args) + if args.path == '' { + args.path = os.getwd() + } + + if args.name == '' { + args.name = os.base(args.path) + } + + if args.force { + mut config_path0 := pathlib.get_file(path: '${args.path}/.heroscript', create: false)! + if !config_path0.exists() { + return error("can't generate in force mode (non interactive) if ${config_path0.path} not found.") + } + generate_exec(args.path, args.reset)! + return + } + + console.clear() + console.print_header('Configure generation of code for a module on path:') + console.print_green('Path: ${args.path}') + console.lf() + + mut config_path := pathlib.get_file(path: '${args.path}/.heroscript', create: false)! + mut pathok := false + if config_path.exists() { + console.print_stdout(config_path.read()!) + console.lf() + myyes := myconsole.ask_yesno( + description: 'We found this heroscript, do you want to make a new one?' + )! + if myyes { + config_path.delete()! + pathok = true + } else { + myyes2 := myconsole.ask_yesno(description: 'Do you want to run it?')! + if myyes2 { + generate_exec(args.path, args.reset)! + } else { + console.print_stderr('Generation aborted.') + } + return + } + } + + if pathok == false { + yesno := myconsole.ask_yesno(description: 'Is this path ok?')! + if !yesno { + return error("can't continue without a valid path") + } + } + + mycat := myconsole.ask_dropdown( + description: 'Category of the generator' + question: 'What is the category of the generator?' + items: [ + 'installer', + 'client', + ] + warning: 'Please select a category' + )! + + if mycat == 'installer' { + args.cat = .installer + } else { + args.cat = .client + } + + // if args.name==""{ + // yesno := myconsole.ask_yesno(description: 'Are you happy with name ${args.name}?')! + // if !yesno { + // return error("can't continue without a valid name, rename the directory you operate in.") + // } + // } + + args.classname = myconsole.ask_question( + description: 'Class name of the ${mycat}' + question: 'What is the class name of the generator e.g. MyClass ?' + warning: 'Please provide a valid class name for the generator' + minlen: 4 + )! + + args.title = myconsole.ask_question( + description: 'Title of the ${mycat} (optional)' + )! + + if args.cat == .installer { + args.hasconfig = myconsole.ask_yesno( + description: 'Does your installer have a config (normally yes)?' + )! + } + + if args.hasconfig { + args.default = myconsole.ask_yesno( + description: 'Is it ok when doing new() that a default is created (normally yes)?' + )! + args.singleton = !myconsole.ask_yesno( + description: 'Can there be multiple instances (normally yes)?' + )! + } + + // args.supported_platforms = myconsole.ask_dropdown_multiple( + // description: 'Supported platforms' + // question: 'Which platforms are supported?' + // items: [ + // 'osx', + // 'ubuntu', + // 'arch', + // ] + // warning: 'Please select one or more platforms' + // )! + + if args.cat == .installer { + args.templates = myconsole.ask_yesno( + description: 'Will there be templates available for your installer?' + )! + + args.startupmanager = myconsole.ask_yesno( + description: 'Is this an installer which will be managed by a startup mananger?' + )! + + args.build = myconsole.ask_yesno( + description: 'Are there builders for the installers (compilation)' + )! + } + + // args.reset = myconsole.ask_yesno( + // description: 'Reset, overwrite code.' + // question: 'This will overwrite all files in your existing dir, be carefule?' + // )! + create_heroscript(args)! + generate_exec(args.path, true)! +} + +pub fn create_heroscript(args GeneratorArgs) ! { + mut script := '' + if args.cat == .installer { + script = " +!!hero_code.generate_installer + name:'${args.name}' + classname:'${args.classname}' + singleton:${if args.singleton { + '1' + } else { + '0' + }} + templates:${if args.templates { '1' } else { '0' }} + default:${if args.default { + '1' + } else { + '0' + }} + title:'${args.title}' + supported_platforms:'' + reset:${if args.reset { + '1' + } else { + '0' + }} + startupmanager:${if args.startupmanager { '1' } else { '0' }} + hasconfig:${if args.hasconfig { + '1' + } else { + '0' + }} + build:${if args.build { + '1' + } else { + '0' + }}" + } else { + script = " +!!hero_code.generate_client + name:'${args.name}' + classname:'${args.classname}' + singleton:${if args.singleton { + '1' + } else { + '0' + }} + default:${if args.default { '1' } else { '0' }} + hasconfig:${if args.hasconfig { + '1' + } else { + '0' + }} + reset:${if args.reset { + '1' + } else { + '0' + }}" + } + if !os.exists(args.path) { + os.mkdir(args.path)! + } + os.write_file('${args.path}/.heroscript', script)! +} diff --git a/lib/code/generator/generic/generator.v b/lib/code/generator/generic/generator.v new file mode 100644 index 00000000..7dc56c89 --- /dev/null +++ b/lib/code/generator/generic/generator.v @@ -0,0 +1,78 @@ +module generic + +import freeflowuniverse.herolib.ui.console +import freeflowuniverse.herolib.core.pathlib + +fn generate_exec(path string, reset bool) ! { + mut args := args_get(path)! + console.print_debug('generate code for path: ${path}') + + if reset { + args.reset = true + } + + mut path_actions := pathlib.get(args.path + '/${args.name}_actions.v') + if args.reset { + path_actions.delete()! + } + if !path_actions.exists() && args.cat == .installer { + console.print_debug('write installer actions') + mut templ_1 := $tmpl('templates/objname_actions.vtemplate') + pathlib.template_write(templ_1, '${args.path}/${args.name}_actions.v', true)! + } + + mut templ_2 := $tmpl('templates/objname_factory_.vtemplate') + + pathlib.template_write(templ_2, '${args.path}/${args.name}_factory_.v', true)! + + mut path_model := pathlib.get(args.path + '/${args.name}_model.v') + if args.reset || !path_model.exists() { + console.print_debug('write model.') + mut templ_3 := $tmpl('templates/objname_model.vtemplate') + pathlib.template_write(templ_3, '${args.path}/${args.name}_model.v', true)! + } + + // TODO: check case sensistivity for delete + mut path_readme := pathlib.get(args.path + '/readme.md') + if args.reset || !path_readme.exists() { + mut templ_readme := $tmpl('templates/readme.md') + pathlib.template_write(templ_readme, '${args.path}/readme.md', true)! + } + + mut path_templ_dir := pathlib.get_dir(path: args.path + '/templates', create: false)! + if args.reset { + path_templ_dir.delete()! + } + if args.templates { + if !path_templ_dir.exists() { + mut templ_6 := $tmpl('templates/atemplate.yaml') + pathlib.template_write(templ_6, '${args.path}/templates/atemplate.yaml', true)! + } + } +} + +fn platform_check(args GeneratorArgs) ! { + ok := 'osx,ubuntu,arch' + ok2 := ok.split(',') + for i in args.supported_platforms { + if i !in ok2 { + return error('cannot find ${i} in choices for supported_platforms. Valid ones are ${ok}') + } + } +} + +pub fn (args GeneratorArgs) platform_check_str() string { + mut out := '' + + if 'osx' in args.supported_platforms { + out += 'myplatform == .osx || ' + } + if 'ubuntu' in args.supported_platforms { + out += 'myplatform == .ubuntu ||' + } + if 'arch' in args.supported_platforms { + out += 'myplatform == .arch ||' + } + out = out.trim_right('|') + return out +} diff --git a/lib/code/generator/generic/model.v b/lib/code/generator/generic/model.v new file mode 100644 index 00000000..d2bd0148 --- /dev/null +++ b/lib/code/generator/generic/model.v @@ -0,0 +1,84 @@ +module generic + +import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.ui.console + +pub struct GeneratorArgs { +pub mut: + name string + classname string + default bool = true // means user can just get the object and a default will be created + title string + supported_platforms []string // only relevant for installers for now + singleton bool // means there can only be one + templates bool // means we will use templates in the installer, client doesn't do this' + reset bool // regenerate all, dangerous !!! + startupmanager bool = true + build bool + cat Cat + path string + force bool + hasconfig bool = true +} + +pub enum Cat { + installer + client +} + +fn args_get(path string) !GeneratorArgs { + console.print_debug('play installer code for path: ${path}') + + mut config_path := pathlib.get_file(path: '${path}/.heroscript', create: false)! + + if !config_path.exists() { + return error("can't find path with .heroscript in ${path}") + } + + mut plbook := playbook.new(text: config_path.read()!)! + + mut install_actions := plbook.find(filter: 'hero_code.generate_installer')! + if install_actions.len > 0 { + for install_action in install_actions { + mut p := install_action.params + mut args := GeneratorArgs{ + name: p.get('name')! + classname: p.get('classname')! + title: p.get_default('title', '')! + default: p.get_default_true('default') + supported_platforms: p.get_list('supported_platforms')! + singleton: p.get_default_false('singleton') + templates: p.get_default_false('templates') + reset: p.get_default_false('reset') + startupmanager: p.get_default_true('startupmanager') + hasconfig: p.get_default_true('hasconfig') + build: p.get_default_false('build') + force: p.get_default_false('force') + cat: .installer + path: path + } + return args + } + } + + mut client_actions := plbook.find(filter: 'hero_code.generate_client')! + if client_actions.len > 0 { + for client_action in client_actions { + mut p := client_action.params + args := GeneratorArgs{ + name: p.get('name')! + classname: p.get('classname')! + title: p.get_default('title', '')! + default: p.get_default_true('default') + singleton: p.get_default_false('singleton') + reset: p.get_default_false('reset') + cat: .client + path: path + } + return args + } + } + return error("can't find hero_code.generate_client or hero_code.generate_installer in ${path}") + // return GeneratorArgs{} +} diff --git a/lib/code/generator/generic/readme.md b/lib/code/generator/generic/readme.md new file mode 100644 index 00000000..cb60105c --- /dev/null +++ b/lib/code/generator/generic/readme.md @@ -0,0 +1,74 @@ +# generation framework + +```bash +#will ask questions if .heroscript is not there yet +hero generate -p thepath_is_optional +# to generate without questions +hero generate -p thepath_is_optional -t client +#if installer, default is a client +hero generate -p thepath_is_optional -t installer + +#when you want to scan over multiple directories +hero generate -p thepath_is_optional -t installer -s + +``` + +there will be a ```.heroscript``` in the director you want to generate for, the format is as follows: + +```hero +//for a server +!!hero_code.generate_installer + name:'daguserver' + classname:'DaguServer' + singleton:1 //there can only be 1 object in the globals, is called 'default' + templates:1 //are there templates for the installer + default:1 //can we create a default when the factory is used + title:'' + supported_platforms:'' //osx, ... (empty means all) + reset:0 // regenerate all, dangerous !!! + startupmanager:1 //managed by a startup manager, default true + build:1 //will we also build the component + +//or for a client + +!!hero_code.generate_client + name:'mail' + classname:'MailClient' + singleton:0 //default is 0 + default:1 //can we create a default when the factory is used + reset:0 // regenerate all, dangerous !!! + +``` + +needs to be put as .heroscript in the directories which we want to generate + + +## templates remarks + +in templates: + +- ^^ or @@ > gets replaced to @ +- ?? > gets replaced to $ + +this is to make distinction between processing at compile time (pre-compile) or at runtime. + +## call by code + +to call in code + +```v +#!/usr/bin/env -S v -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run + +import freeflowuniverse.herolib.code.generator.generic + +generic.scan(path:"~/code/github/freeflowuniverse/herolib/herolib/installers",force:true)! + + +``` + +to run from bash + +```bash +~/code/github/freeflowuniverse/herolib/scripts/fix_installers.vsh +``` + diff --git a/lib/code/generator/generic/scanner.v b/lib/code/generator/generic/scanner.v new file mode 100644 index 00000000..a5bfc717 --- /dev/null +++ b/lib/code/generator/generic/scanner.v @@ -0,0 +1,30 @@ +module generic + +import os +import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.ui.console + +// scan over a set of directories call the play where +pub fn scan(args_ GeneratorArgs) ! { + mut args := args_ + console.print_header('Scan for generation of code for path: ${args.path} (reset:${args.force}, force:${args.force})') + + if args.path.len == 0 { + args.path = os.getwd() + } + + // now walk over all directories, find .heroscript + mut pathroot := pathlib.get_dir(path: args.path, create: false)! + mut plist := pathroot.list( + recursive: true + ignoredefault: false + regex: ['.heroscript'] + )! + + for mut p in plist.paths { + pparent := p.parent()! + args.path = pparent.path + // println("-- ${pparent}") + generate(args)! + } +} diff --git a/lib/code/generator/generic/templates/atemplate.yaml b/lib/code/generator/generic/templates/atemplate.yaml new file mode 100644 index 00000000..083e4a0f --- /dev/null +++ b/lib/code/generator/generic/templates/atemplate.yaml @@ -0,0 +1,5 @@ + + +name: ??{cfg.configpath} + + diff --git a/lib/code/generator/generic/templates/heroscript_example b/lib/code/generator/generic/templates/heroscript_example new file mode 100644 index 00000000..b8b90603 --- /dev/null +++ b/lib/code/generator/generic/templates/heroscript_example @@ -0,0 +1,13 @@ + +!!hero_code.generate_installer + name:'daguserver' + classname:'DaguServer' + singleton:1 //there can only be 1 object in the globals, is called 'default' + default:1 //can we create a default when the factory is used + title:'' + supported_platforms:'' //osx, ... (empty means all) + reset:0 // regenerate all, dangerous !!! + //next only relevant for installer + startupmanager:1 //managed by a startup manager, default true + templates:1 //are there templates for the installer + build:1 //will we also build the component diff --git a/lib/code/generator/generic/templates/objname_actions.vtemplate b/lib/code/generator/generic/templates/objname_actions.vtemplate new file mode 100644 index 00000000..ac4f00d7 --- /dev/null +++ b/lib/code/generator/generic/templates/objname_actions.vtemplate @@ -0,0 +1,212 @@ +module ${args.name} + +import freeflowuniverse.herolib.osal +import freeflowuniverse.herolib.ui.console +import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.core.pathlib + +@if args.startupmanager +import freeflowuniverse.herolib.osal.systemd +import freeflowuniverse.herolib.osal.zinit +@end + +@if args.build +import freeflowuniverse.herolib.installers.ulist +import freeflowuniverse.herolib.installers.lang.golang +import freeflowuniverse.herolib.installers.lang.rust +import freeflowuniverse.herolib.installers.lang.python +@end + +import os + +@if args.startupmanager +fn startupcmd () ![]zinit.ZProcessNewArgs{ + mut installer := get()! + mut res := []zinit.ZProcessNewArgs{} + //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED + // res << zinit.ZProcessNewArgs{ + // name: '${args.name}' + // cmd: '${args.name} server' + // env: { + // 'HOME': '/root' + // } + // } + + return res + +} + +fn running() !bool { + mut installer := get()! + //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED + // this checks health of ${args.name} + // curl http://localhost:3333/api/v1/s --oauth2-bearer 1234 works + // url:='http://127.0.0.1:??{cfg.port}/api/v1' + // mut conn := httpconnection.new(name: '${args.name}', url: url)! + + // if cfg.secret.len > 0 { + // conn.default_header.add(.authorization, 'Bearer ??{cfg.secret}') + // } + // conn.default_header.add(.content_type, 'application/json') + // console.print_debug("curl -X 'GET' '??{url}'/tags --oauth2-bearer ??{cfg.secret}") + // r := conn.get_json_dict(prefix: 'tags', debug: false) or {return false} + // println(r) + // if true{panic("ssss")} + // tags := r['Tags'] or { return false } + // console.print_debug(tags) + // console.print_debug('${args.name} is answering.') + return false +} + +fn start_pre()!{ + +} + +fn start_post()!{ + +} + +fn stop_pre()!{ + +} + +fn stop_post()!{ + +} + +@end + +//////////////////// following actions are not specific to instance of the object + +@if args.cat == .installer +// checks if a certain version or above is installed +fn installed() !bool { + //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED + // res := os.execute('??{osal.profile_path_source_and()} ${args.name} version') + // if res.exit_code != 0 { + // return false + // } + // r := res.output.split_into_lines().filter(it.trim_space().len > 0) + // if r.len != 1 { + // return error("couldn't parse ${args.name} version.\n??{res.output}") + // } + // if texttools.version(version) == texttools.version(r[0]) { + // return true + // } + return false +} + +//get the Upload List of the files +fn ulist_get() !ulist.UList { + //optionally build a UList which is all paths which are result of building, is then used e.g. in upload + return ulist.UList{} +} + +//uploads to S3 server if configured +fn upload() ! { + // installers.upload( + // cmdname: '${args.name}' + // source: '??{gitpath}/target/x86_64-unknown-linux-musl/release/${args.name}' + // )! + +} + +fn install() ! { + console.print_header('install ${args.name}') + //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED + // mut url := '' + // if osal.is_linux_arm() { + // url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_linux_arm64.tar.gz' + // } else if osal.is_linux_intel() { + // url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_linux_amd64.tar.gz' + // } else if osal.is_osx_arm() { + // url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_darwin_arm64.tar.gz' + // } else if osal.is_osx_intel() { + // url = 'https://github.com/${args.name}-dev/${args.name}/releases/download/v??{version}/${args.name}_??{version}_darwin_amd64.tar.gz' + // } else { + // return error('unsported platform') + // } + + // mut dest := osal.download( + // url: url + // minsize_kb: 9000 + // expand_dir: '/tmp/${args.name}' + // )! + + // //dest.moveup_single_subdir()! + + // mut binpath := dest.file_get('${args.name}')! + // osal.cmd_add( + // cmdname: '${args.name}' + // source: binpath.path + // )! +} + +@if args.build +fn build() ! { + //url := 'https://github.com/threefoldtech/${args.name}' + + // make sure we install base on the node + // if osal.platform() != .ubuntu { + // return error('only support ubuntu for now') + // } + // golang.install()! + + // console.print_header('build ${args.name}') + + // gitpath := gittools.get_repo(coderoot: '/tmp/builder', url: url, reset: true, pull: true)! + + // cmd := ' + // cd ??{gitpath} + // source ~/.cargo/env + // exit 1 #todo + // ' + // osal.execute_stdout(cmd)! + // + // //now copy to the default bin path + // mut binpath := dest.file_get('...')! + // adds it to path + // osal.cmd_add( + // cmdname: 'griddriver2' + // source: binpath.path + // )! + +} +@end + +fn destroy() ! { + + // mut systemdfactory := systemd.new()! + // systemdfactory.destroy("zinit")! + + // osal.process_kill_recursive(name:'zinit')! + // osal.cmd_delete('zinit')! + + // osal.package_remove(' + // podman + // conmon + // buildah + // skopeo + // runc + // ')! + + // //will remove all paths where go/bin is found + // osal.profile_path_add_remove(paths2delete:"go/bin")! + + // osal.rm(" + // podman + // conmon + // buildah + // skopeo + // runc + // /var/lib/containers + // /var/lib/podman + // /var/lib/buildah + // /tmp/podman + // /tmp/conmon + // ")! + + +} + +@end \ No newline at end of file diff --git a/lib/code/generator/generic/templates/objname_factory_.vtemplate b/lib/code/generator/generic/templates/objname_factory_.vtemplate new file mode 100644 index 00000000..ae07ee74 --- /dev/null +++ b/lib/code/generator/generic/templates/objname_factory_.vtemplate @@ -0,0 +1,313 @@ + +module ${args.name} + +import freeflowuniverse.herolib.core.base +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.ui.console + +@if args.cat == .installer +import freeflowuniverse.herolib.sysadmin.startupmanager +import freeflowuniverse.herolib.osal.zinit +import time +@end + +__global ( + ${args.name}_global map[string]&${args.classname} + ${args.name}_default string +) + +/////////FACTORY + +^^[params] +pub struct ArgsGet{ +pub mut: + name string +} + +@if args.hasconfig +fn args_get (args_ ArgsGet) ArgsGet { + mut args:=args_ + if args.name == ""{ + args.name = ${args.name}_default + } + if args.name == ""{ + args.name = "default" + } + return args +} + +pub fn get(args_ ArgsGet) !&${args.classname} { + mut args := args_get(args_) + if !(args.name in ${args.name}_global) { + if args.name=="default"{ + if ! config_exists(args){ + if default{ + config_save(args)! + } + } + config_load(args)! + } + } + return ${args.name}_global[args.name] or { + println(${args.name}_global) + panic("could not get config for ${args.name} with name:??{args.name}") + } +} + + +@else +pub fn get(args_ ArgsGet) !&${args.classname} { + return &${args.classname}{} +} +@end + +@if args.hasconfig +fn config_exists(args_ ArgsGet) bool { + mut args := args_get(args_) + mut context:=base.context() or { panic("bug") } + return context.hero_config_exists("${args.name}",args.name) +} + +fn config_load(args_ ArgsGet) ! { + mut args := args_get(args_) + mut context:=base.context()! + mut heroscript := context.hero_config_get("${args.name}",args.name)! + play(heroscript:heroscript)! +} + +fn config_save(args_ ArgsGet) ! { + mut args := args_get(args_) + mut context:=base.context()! + context.hero_config_set("${args.name}",args.name,heroscript_default()!)! +} + + +fn set(o ${args.classname})! { + mut o2:=obj_init(o)! + ${args.name}_global[o.name] = &o2 + ${args.name}_default = o.name +} + + +^^[params] +pub struct PlayArgs { +pub mut: + heroscript string //if filled in then plbook will be made out of it + plbook ?playbook.PlayBook + reset bool +} + +pub fn play(args_ PlayArgs) ! { + + mut args:=args_ + + @if args.hasconfig + if args.heroscript == "" { + args.heroscript = heroscript_default()! + } + @end + mut plbook := args.plbook or { + playbook.new(text: args.heroscript)! + } + + @if args.hasconfig + mut install_actions := plbook.find(filter: '${args.name}.configure')! + if install_actions.len > 0 { + for install_action in install_actions { + mut p := install_action.params + mycfg:=cfg_play(p)! + console.print_debug("install action ${args.name}.configure\n??{mycfg}") + set(mycfg)! + } + } + @end + + @if args.cat == .installer + mut other_actions := plbook.find(filter: '${args.name}.')! + for other_action in other_actions { + if other_action.name in ["destroy","install","build"]{ + mut p := other_action.params + reset:=p.get_default_false("reset") + if other_action.name == "destroy" || reset{ + console.print_debug("install action ${args.name}.destroy") + destroy()! + } + if other_action.name == "install"{ + console.print_debug("install action ${args.name}.install") + install()! + } + } + @if args.startupmanager + if other_action.name in ["start","stop","restart"]{ + mut p := other_action.params + name := p.get('name')! + mut ${args.name}_obj:=get(name:name)! + console.print_debug("action object:\n??{${args.name}_obj}") + if other_action.name == "start"{ + console.print_debug("install action ${args.name}.??{other_action.name}") + ${args.name}_obj.start()! + } + + if other_action.name == "stop"{ + console.print_debug("install action ${args.name}.??{other_action.name}") + ${args.name}_obj.stop()! + } + if other_action.name == "restart"{ + console.print_debug("install action ${args.name}.??{other_action.name}") + ${args.name}_obj.restart()! + } + } + @end + } + @end + +} + +@end + +@if args.cat == .installer + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { + // unknown + // screen + // zinit + // tmux + // systemd + match cat{ + .zinit{ + console.print_debug("startupmanager: zinit") + return startupmanager.get(cat:.zinit)! + } + .systemd{ + console.print_debug("startupmanager: systemd") + return startupmanager.get(cat:.systemd)! + }else{ + console.print_debug("startupmanager: auto") + return startupmanager.get()! + } + } +} + +@if args.hasconfig +//load from disk and make sure is properly intialized +pub fn (mut self ${args.classname}) reload() ! { + switch(self.name) + self=obj_init(self)! +} +@end + +@if args.startupmanager +pub fn (mut self ${args.classname}) start() ! { + switch(self.name) + if self.running()!{ + return + } + + console.print_header('${args.name} start') + + if ! installed()!{ + install()! + } + + configure()! + + start_pre()! + + for zprocess in startupcmd()!{ + mut sm:=startupmanager_get(zprocess.startuptype)! + + console.print_debug('starting ${args.name} with ??{zprocess.startuptype}...') + + sm.new(zprocess)! + + sm.start(zprocess.name)! + } + + start_post()! + + for _ in 0 .. 50 { + if self.running()! { + return + } + time.sleep(100 * time.millisecond) + } + return error('${args.name} did not install properly.') + +} + +pub fn (mut self ${args.classname}) install_start(args InstallArgs) ! { + switch(self.name) + self.install(args)! + self.start()! +} + +pub fn (mut self ${args.classname}) stop() ! { + switch(self.name) + stop_pre()! + for zprocess in startupcmd()!{ + mut sm:=startupmanager_get(zprocess.startuptype)! + sm.stop(zprocess.name)! + } + stop_post()! +} + +pub fn (mut self ${args.classname}) restart() ! { + switch(self.name) + self.stop()! + self.start()! +} + +pub fn (mut self ${args.classname}) running() !bool { + switch(self.name) + + //walk over the generic processes, if not running return + for zprocess in startupcmd()!{ + mut sm:=startupmanager_get(zprocess.startuptype)! + r:=sm.running(zprocess.name)! + if r==false{ + return false + } + } + return running()! +} +@end + +@@[params] +pub struct InstallArgs{ +pub mut: + reset bool +} + +pub fn (mut self ${args.classname}) install(args InstallArgs) ! { + switch(self.name) + if args.reset || (!installed()!) { + install()! + } +} + +@if args.build +pub fn (mut self ${args.classname}) build() ! { + switch(self.name) + build()! +} +@end + +pub fn (mut self ${args.classname}) destroy() ! { + switch(self.name) +@if args.startupmanager + self.stop() or {} +@end + destroy()! +} + +@end + + +//switch instance to be used for ${args.name} +pub fn switch(name string) { + ${args.name}_default = name +} diff --git a/lib/code/generator/generic/templates/objname_model.vtemplate b/lib/code/generator/generic/templates/objname_model.vtemplate new file mode 100644 index 00000000..ea7bf5ff --- /dev/null +++ b/lib/code/generator/generic/templates/objname_model.vtemplate @@ -0,0 +1,154 @@ +module ${args.name} +import freeflowuniverse.herolib.data.paramsparser +import os + +pub const version = '1.14.3' +const singleton = ${args.singleton} +const default = ${args.default} + +@if args.hasconfig +//TODO: THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE TO STRUCT BELOW, IS STRUCTURED AS HEROSCRIPT +pub fn heroscript_default() !string { +@if args.cat == .installer + heroscript:=" + !!${args.name}.configure + name:'${args.name}' + homedir: '{HOME}/hero/var/${args.name}' + configpath: '{HOME}/.config/${args.name}/admin.yaml' + username: 'admin' + password: 'secretpassword' + secret: '' + title: 'My Hero DAG' + host: 'localhost' + port: 8888 + + " +@else + heroscript:=" + !!${args.name}.configure + name:'${args.name}' + mail_from: 'info@@example.com' + mail_password: 'secretpassword' + mail_port: 587 + mail_server: 'smtp-relay.brevo.com' + mail_username: 'kristof@@incubaid.com' + + " + +// mail_from := os.getenv_opt('MAIL_FROM') or {'info@@example.com'} +// mail_password := os.getenv_opt('MAIL_PASSWORD') or {'secretpassword'} +// mail_port := (os.getenv_opt('MAIL_PORT') or {"587"}).int() +// mail_server := os.getenv_opt('MAIL_SERVER') or {'smtp-relay.brevo.com'} +// mail_username := os.getenv_opt('MAIL_USERNAME') or {'kristof@@incubaid.com'} +// +// heroscript:=" +// !!mailclient.configure name:'default' +// mail_from: '??{mail_from}' +// mail_password: '??{mail_password}' +// mail_port: ??{mail_port} +// mail_server: '??{mail_server}' +// mail_username: '??{mail_username}' +// +// " +// + +@end + + return heroscript + +} +@end + +//THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED +@if args.cat == .installer +@[heap] +pub struct ${args.classname} { +pub mut: + name string = 'default' +@if args.hasconfig + homedir string + configpath string + username string + password string @@[secret] + secret string @@[secret] + title string + host string + port int +@end +} +@if args.hasconfig +fn cfg_play(p paramsparser.Params) !${args.classname} { + //THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above + mut mycfg := ${args.classname}{ + name: p.get_default('name', 'default')! + homedir: p.get_default('homedir', '{HOME}/hero/var/${args.name}')! + configpath: p.get_default('configpath', '{HOME}/hero/var/${args.name}/admin.yaml')! + username: p.get_default('username', 'admin')! + password: p.get_default('password', '')! + secret: p.get_default('secret', '')! + title: p.get_default('title', 'HERO DAG')! + host: p.get_default('host', 'localhost')! + port: p.get_int_default('port', 8888)! + } + + if mycfg.password == '' && mycfg.secret == '' { + return error('password or secret needs to be filled in for ${args.name}') + } + return mycfg +} +@end + +@else + +@[heap] +pub struct ${args.classname} { +pub mut: + name string = 'default' + mail_from string + mail_password string @@[secret] + mail_port int + mail_server string + mail_username string +} + +@if args.hasconfig +fn cfg_play(p paramsparser.Params) ! { + //THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above + mut mycfg := ${args.classname}{ + name: p.get_default('name', 'default')! + mail_from: p.get('mail_from')! + mail_password: p.get('mail_password')! + mail_port: p.get_int_default('mail_port', 8888)! + mail_server: p.get('mail_server')! + mail_username: p.get('mail_username')! + } + set(mycfg)! +} +@end + +@end + +fn obj_init(obj_ ${args.classname})!${args.classname}{ + //never call get here, only thing we can do here is work on object itself + mut obj:=obj_ + return obj +} + +@if args.cat == .installer +//called before start if done +fn configure() ! { + @if args.cat == .installer + //mut installer := get()! + @else + //mut client := get()! + @end +@if args.templates + // mut mycode := ??tmpl('templates/atemplate.yaml') + // mut path := pathlib.get_file(path: cfg.configpath, create: true)! + // path.write(mycode)! + // console.print_debug(mycode) +@end +} +@end + + diff --git a/lib/code/generator/generic/templates/readme.md b/lib/code/generator/generic/templates/readme.md new file mode 100644 index 00000000..d7e73a6c --- /dev/null +++ b/lib/code/generator/generic/templates/readme.md @@ -0,0 +1,63 @@ +# ${args.name} + +${args.title} + +To get started + +```vlang + +@if args.cat == .installer + +import freeflowuniverse.herolib.installers.something.${args.name} as ${args.name}_installer + +heroscript:=" +!!${args.name}.configure name:'test' + password: '1234' + port: 7701 + +!!${args.name}.start name:'test' reset:1 +" + +${args.name}_installer.play(heroscript=heroscript)! + +//or we can call the default and do a start with reset +//mut installer:= ${args.name}_installer.get()! +//installer.start(reset:true)! + +@else + +import freeflowuniverse.herolib.clients. ${args.name} + +mut client:= ${args.name}.get()! + +client... + +@end + + + +``` + +## example heroscript + +@if args.cat == .installer +```hero +!!${args.name}.configure + homedir: '/home/user/${args.name}' + username: 'admin' + password: 'secretpassword' + title: 'Some Title' + host: 'localhost' + port: 8888 + +``` +@else +```hero +!!${args.name}.configure + secret: '...' + host: 'localhost' + port: 8888 +``` +@end + + diff --git a/lib/core/vexecutor/execute_large.v b/lib/core/vexecutor/execute_large.v new file mode 100644 index 00000000..bb305bd7 --- /dev/null +++ b/lib/core/vexecutor/execute_large.v @@ -0,0 +1,94 @@ +module vexecutor + +import strings +import os { Result, fileno } +import freeflowuniverse.herolib.ui.console + +fn vpopen(path string) voidptr { + // *C.FILE { + $if windows { + mode := 'rb' + wpath := path.to_wide() + return C._wpopen(wpath, mode.to_wide()) + } $else { + cpath := path.str + return C.popen(&char(cpath), c'r') + } +} + +fn vpclose(f voidptr) int { + $if windows { + return C._pclose(f) + } $else { + ret, _ := posix_wait4_to_exit_status(C.pclose(f)) + return ret + } +} + +fn posix_wait4_to_exit_status(waitret int) (int, bool) { + $if windows { + return waitret, false + } $else { + mut ret := 0 + mut is_signaled := true + // (see man system, man 2 waitpid: C macro WEXITSTATUS section) + if C.WIFEXITED(waitret) { + ret = C.WEXITSTATUS(waitret) + is_signaled = false + } else if C.WIFSIGNALED(waitret) { + ret = C.WTERMSIG(waitret) + is_signaled = true + } + return ret, is_signaled + } +} + +@[manualfree] +pub fn execute_large(cmd string) Result { + // if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') { + // return Result{ exit_code: -1, output: ';, &&, || and \\n are not allowed in shell commands' } + // } + pcmd := if cmd.contains('2>') { cmd.clone() } else { '${cmd} 2>&1' } + defer { + unsafe { pcmd.free() } + } + f := vpopen(pcmd) + if isnil(f) { + return Result{ + exit_code: -1 + output: 'exec("${cmd}") failed' + } + } + fd := fileno(f) + mut res := strings.new_builder(4096) + defer { + unsafe { res.free() } + } + buf := [8192]u8{} + unsafe { + pbuf := &buf[0] + for { + len := C.read(fd, pbuf, 8192) + if len == 0 { + break + } + res.write_ptr(pbuf, len) + } + } + soutput := res.str() + exit_code := vpclose(f) + return Result{ + exit_code: exit_code + output: soutput + } +} + +pub fn execute_large_or_panic(cmd string) Result { + res := execute_large(cmd) + if res.exit_code != 0 { + console.print_debug('failed cmd: ${cmd}') + console.print_debug('failed code: ${res.exit_code}') + panic(res.output) + } + return res +} diff --git a/lib/core/vexecutor/vexecutor.v b/lib/core/vexecutor/vexecutor.v new file mode 100644 index 00000000..d0d14066 --- /dev/null +++ b/lib/core/vexecutor/vexecutor.v @@ -0,0 +1,169 @@ +module vexecutor + +import freeflowuniverse.herolib.core.pathlib +import os +import freeflowuniverse.herolib.ui.console + +// TODO make sure each function opens and closes the executor.vsh file, no file should be open across functions + +struct VExecutor { +mut: + actions []VAction + execution_file os.File + execution_file_path pathlib.Path + final_file_path pathlib.Path +} + +struct VAction { + path pathlib.Path + lines []string +} + +// Creates a new executor object +// ARGS: +// path string - /directory/filename.v +// cat Category - unknown, file, dir, linkdir, linkfile (enum) +// exist UYN - unknown, yes, no (enum) +pub fn new_executor(initial_file_path_ string, execution_file_path_ string, final_file_path_ string) !VExecutor { + // create a execution file at execution_file_path and fill out + mut execution_file_path := pathlib.get(execution_file_path_) + execution_file_path.check() + + mut initial_file_path := pathlib.get(initial_file_path_) + initial_file_path.check() + + mut final_file_path := pathlib.get(final_file_path_) + final_file_path.check() + + // ensure that the execution file is an empty file + if execution_file_path.exist == .yes { + os.rm(execution_file_path.path) or { + return error('Failed to remove execution file: ${err}') + } + } + execution_file := os.create(execution_file_path.path) or { + return error('Failed to create execution file at ' + @FN + ' : ${err}') + } + + if initial_file_path.exist == .no { + return error('Initial file does not exist at path: ${initial_file_path.path}') + } + + // convert the initial file into an action + initial_action := scan_file(mut initial_file_path) or { + return error('Failed to scan initial file path at ' + @FN + ' : ${err}') + } + + // create an VExecutor object + mut v_executor := VExecutor{ + execution_file: execution_file + execution_file_path: execution_file_path + final_file_path: final_file_path + actions: [initial_action] + } + + return v_executor +} + +// Adds all the top level files in a directory to the VExecutor.actions list +// ARGS: +// directory_path string +pub fn (mut v_executor VExecutor) add_dir_to_end(directory_path_ string) ! { + mut directory_path := pathlib.get(directory_path_) + directory_path.check() + + mut fl := directory_path.list() or { + return error('Failed to get file_paths of directory at ' + @FN + ' : ${err}') + } + // mut count := 0 + for mut file_path in fl.paths { + // if count <= 10 { + v_executor.actions << scan_file(mut file_path)! + // count += 1 + // } + } +} + +// Scans a file and converts it into a VAction +// cuts out all content prior to // BEGINNING +// ARGS: +// path string - path to file +fn scan_file(mut path pathlib.Path) !VAction { + if !path.exists() { + return error('cannot find path: ${path.path} to scan for vlang files.') + } + + // read all the lines in the file into an array of lines + mut file := os.open(path.path) or { return error('Failed to open file: ${err}') } + mut lines := os.read_lines(path.path) or { + return error('Failed to readlines of file at ' + @FN + ' : ${err}') + } + file.close() + // remove lines until '// BEGINNING' is reached + mut beginning_found := false + + mut count := 0 + outer: for line in lines { + count += 1 + if line.contains('// BEGINNING') { + beginning_found = true + break outer + } + } + mut trimmed_lines := lines.clone() + if beginning_found == true { + trimmed_lines = lines[count..lines.len] + } + + return VAction{ + lines: trimmed_lines + path: path + } +} + +// combine all actions in VExecutor into one file +pub fn (mut v_executor VExecutor) compile() ! { + v_executor.actions << scan_file(mut v_executor.final_file_path) or { + return error('Failed to scan file at ' + @FN + ' : ${err}') + } + + mut all_lines := []string{} + for action in v_executor.actions { + for line in action.lines { + all_lines << line + } + } + + for line in all_lines { + v_executor.execution_file.writeln(line) or { + return error('Failed to write line to execution file at ' + @FN + ' : ${err}') + } + } + + v_executor.execution_file.close() +} + +// Executes the file +pub fn (mut v_executor VExecutor) do() ! { + console.print_debug('v run ${v_executor.execution_file_path.path}') + result := os.execute('v run ${v_executor.execution_file_path.path}') + console.print_debug('Exit Code: ${result.exit_code}') + if result.exit_code != 0 { + console.print_debug(result) + return error('Failed to run executor file! There are issues with the code.') + } +} + +// prints out all the file paths accessed +pub fn (mut v_executor VExecutor) info() { + for action in v_executor.actions { + console.print_debug('action_path: ${action.path.path}') + } +} + +// Cleans up VExecutors impact on the file system +pub fn (mut v_executor VExecutor) clean() ! { + os.rm(v_executor.execution_file_path.path) or { + return error('Failed to delete execution file at ' + @FN + ' : ${err}') + } +} diff --git a/lib/data/jsonschema/codegen.v b/lib/data/jsonschema/codegen.v index 584d3b20..4f06d4b3 100644 --- a/lib/data/jsonschema/codegen.v +++ b/lib/data/jsonschema/codegen.v @@ -1,6 +1,6 @@ module jsonschema -import freeflowuniverse.herolib.core.codemodel { Alias, Attribute, CodeItem, Struct, StructField, Type } +import freeflowuniverse.herolib.code.codemodel { Alias, Attribute, CodeItem, Struct, StructField, Type } const vtypes = { 'integer': 'int' diff --git a/lib/data/jsonschema/generate.v b/lib/data/jsonschema/generate.v index 69a1a37a..c9cbc291 100644 --- a/lib/data/jsonschema/generate.v +++ b/lib/data/jsonschema/generate.v @@ -1,6 +1,6 @@ module jsonschema -import freeflowuniverse.herolib.core.codemodel { Param, Result, Struct, Type } +import freeflowuniverse.herolib.code.codemodel { Param, Result, Struct, Type } // struct_to_schema generates a json schema or reference from a struct model pub fn sumtype_to_schema(sumtype codemodel.Sumtype) SchemaRef { diff --git a/lib/data/jsonschema/generate_test.v b/lib/data/jsonschema/generate_test.v index 82e64f80..eb01d3aa 100644 --- a/lib/data/jsonschema/generate_test.v +++ b/lib/data/jsonschema/generate_test.v @@ -1,6 +1,6 @@ module jsonschema -import freeflowuniverse.herolib.core.codemodel +import freeflowuniverse.herolib.code.codemodel import freeflowuniverse.herolib.ui.console fn test_struct_to_schema() { diff --git a/test_basic.vsh b/test_basic.vsh index 5f99ab0a..809929e1 100755 --- a/test_basic.vsh +++ b/test_basic.vsh @@ -14,13 +14,15 @@ fn check_redis() bool { return true } +const redis_key_prefix = 'vtests' + // Set Redis key with expiration fn redis_set(key string) ! { mut sock := net.dial_tcp('127.0.0.1:6379')! defer { sock.close() or {} } // SET key value EX seconds - cmd := 'SET vtests.${key} 1 EX 3600\r\n' + cmd := 'SET ${redis_key_prefix}.${key} 1 EX 3600\r\n' sock.write_string(cmd)! } @@ -30,7 +32,7 @@ fn redis_exists(key string) bool { defer { sock.close() or {} } // EXISTS key - cmd := 'EXISTS vtests.${key}\r\n' + cmd := 'EXISTS ${redis_key_prefix}.${key}\r\n' sock.write_string(cmd) or { return false } response := sock.read_line() @@ -38,19 +40,41 @@ fn redis_exists(key string) bool { } // Delete Redis key -fn redis_del(key string) ! { - mut sock := net.dial_tcp('127.0.0.1:6379')! - defer { sock.close() or {} } +// fn redis_del(key string) ! { +// mut sock := net.dial_tcp('127.0.0.1:6379')! +// defer { sock.close() or {} } - // DEL key - cmd := 'DEL vtests.${key}\r\n' - sock.write_string(cmd)! +// // DEL key +// cmd := 'DEL ${redis_key_prefix}.${key}\r\n' +// sock.write_string(cmd)! +// } + +// Normalize a path for consistent handling +fn normalize_path(path string) string { + mut norm_path := os.abs_path(path) + norm_path = norm_path.replace('//', '/') // Remove any double slashes + return norm_path +} + +// Get normalized and relative path +fn get_normalized_paths(path string, base_dir_norm string) (string, string) { + // base_dir_norm is already normalized + norm_path := normalize_path(path) + rel_path := norm_path.replace(base_dir_norm + '/', '') + return norm_path, rel_path +} + +// Generate a Redis key from a path +fn get_redis_key(path string, base_dir string) string { + _, rel_path := get_normalized_paths(path, base_dir) + // Create consistent key format + return rel_path.replace('/', '_').trim('_').to_lower() } // Check if a file should be ignored or marked as error based on its path -fn process_test_file(path string, test_files_ignore []string, test_files_error []string, redis_available bool, mut tests_in_error []string)! { - // Get relative path for more accurate pattern matching - rel_path := path.replace(os.abs_path('.') + '/', '') +fn process_test_file(path string, base_dir string, test_files_ignore []string, test_files_error []string, redis_available bool, mut tests_in_error []string)! { + // Get normalized paths + norm_path, rel_path := get_normalized_paths(path, base_dir) mut should_ignore := false mut is_error := false @@ -72,7 +96,7 @@ fn process_test_file(path string, test_files_ignore []string, test_files_error [ } if !should_ignore && !is_error { - dotest(path, redis_available)! + dotest(norm_path, base_dir, redis_available)! } else { println('Ignoring test: ${rel_path}') if !should_ignore { @@ -81,11 +105,11 @@ fn process_test_file(path string, test_files_ignore []string, test_files_error [ } } -fn dotest(path string, use_redis bool)! { +fn dotest(path string, base_dir string, use_redis bool)! { + norm_path, _ := get_normalized_paths(path, base_dir) + if use_redis { - // Use absolute path as Redis key - abs_path := os.abs_path(path) - redis_key := abs_path.replace('/', '_') + redis_key := get_redis_key(norm_path, base_dir) // Check if test result is cached if redis_exists(redis_key) { @@ -94,7 +118,7 @@ fn dotest(path string, use_redis bool)! { } } - cmd := 'vtest ${path}' + cmd := 'vtest ${norm_path}' println(cmd) result := os.execute(cmd) @@ -105,9 +129,7 @@ fn dotest(path string, use_redis bool)! { } if use_redis { - // Cache successful test result - abs_path := os.abs_path(path) - redis_key := abs_path.replace('/', '_') + redis_key := get_redis_key(norm_path, base_dir) redis_set(redis_key) or { eprintln('Failed to cache test result: ${err}') } @@ -122,12 +144,16 @@ fn dotest(path string, use_redis bool)! { abs_dir_of_script := dir(@FILE) +norm_dir_of_script := normalize_path(abs_dir_of_script) os.chdir(abs_dir_of_script) or { panic(err) } + + +// can use // inside this list as well to ignore temporary certain dirs, useful for testing tests := " +lib/data lib/osal lib/lang -lib/data lib/code lib/clients " @@ -168,6 +194,9 @@ ourdb/db_test.v ourdb/lookup_location_test.v encoderhero/encoder_test.v encoderhero/decoder_test.v +code/codeparser +clients/meilisearch +clients/zdb " @@ -189,7 +218,7 @@ if redis_available { // Run each test with proper v command flags for test in test_files { - if test.trim_space() == '' { + if test.trim_space() == '' || test.trim_space().starts_with("//") || test.trim_space().starts_with("#") { continue } @@ -204,10 +233,10 @@ for test in test_files { // If directory, run tests for each .v file in it recursively files := os.walk_ext(full_path, '.v') for file in files { - process_test_file(file, test_files_ignore, test_files_error, redis_available, mut tests_in_error)! + process_test_file(file, norm_dir_of_script, test_files_ignore, test_files_error, redis_available, mut tests_in_error)! } } else if os.is_file(full_path) { - process_test_file(full_path, test_files_ignore, test_files_error, redis_available, mut tests_in_error)! + process_test_file(full_path, norm_dir_of_script, test_files_ignore, test_files_error, redis_available, mut tests_in_error)! } }