Compare commits

...

44 Commits

Author SHA1 Message Date
55f0621983 ... 2025-08-13 09:35:58 +02:00
75363d7aeb ... 2025-08-13 08:55:38 +02:00
1f9bc11a2e ... 2025-08-13 08:49:44 +02:00
aab018925d ... 2025-08-13 07:23:14 +02:00
5fa361256a ... 2025-08-13 07:18:02 +02:00
42fe7b0a0d Merge branch 'development_fix_docusaurus' of github.com:freeflowuniverse/herolib into development_fix_docusaurus
* 'development_fix_docusaurus' of github.com:freeflowuniverse/herolib:
  refactor: Improve docusaurus import and site handling
2025-08-13 05:59:46 +02:00
011e5b039e ... 2025-08-13 05:59:40 +02:00
Mahmoud-Emad
e9bcf6ef69 refactor: Improve docusaurus import and site handling
- Simplify command logic to use a single defined site
- Enhance git import to resolve paths relative to project root
- Add `docusaurus.export` action to trigger `build_publish`
- Change asset import destination from `docs` to `static`
- Add `dsite_get_only` helper for simplified site access
2025-08-13 04:19:32 +03:00
f885563982 Co-authored-by: Omdanii <mahmmoud.hassanein@gmail.com> 2025-08-12 16:01:27 +02:00
ffff44f347 .. 2025-08-12 15:52:13 +02:00
Mahmoud-Emad
0e1450b5db chore: add debug prints and perform code cleanup
- Add extensive debug prints for troubleshooting
- Comment out docusaurus build/dev action logic
- Rename gittools parameters for clarity (reset/pull)
- Apply consistent formatting to function calls
- Remove unused imports in playbook include module
2025-08-12 13:37:01 +03:00
f8734a7e9f .. 2025-08-12 11:07:09 +02:00
c05ec6be7f ... 2025-08-12 11:03:49 +02:00
8677d177cb ... 2025-08-12 11:02:21 +02:00
dd37eeaa29 ... 2025-08-12 10:58:53 +02:00
d5753ee794 replace 2025-08-12 10:39:18 +02:00
6b46b3dbaa ... 2025-08-12 10:36:26 +02:00
4cd5b51085 ... 2025-08-12 10:33:29 +02:00
0a7851b920 ... 2025-08-12 09:41:50 +02:00
a0fdaf395e ... 2025-08-12 09:33:53 +02:00
2c5a2ace17 ... 2025-08-11 22:34:23 +02:00
965a2bebb7 ... 2025-08-11 22:12:44 +02:00
2c08ee8687 to compare 2025-08-11 21:41:38 +02:00
e105dd73b5 Merge branch 'development_fix_docusaurus_include' into development
* development_fix_docusaurus_include:
  refactor: Rework playbook include and site import logic
2025-08-11 21:26:49 +02:00
f5dfe8c0af Merge branch 'fix-issue-104' into development
* fix-issue-104:
  Fix issue #104: Fix all biztools examples to use playbook.new()
  Fix issue #104: Add fixed examples and solution documentation
  Fix issue #104: Add notworking.md documenting biztools examples test results

# Conflicts:
#	examples/biztools/notworking.md
2025-08-11 21:26:18 +02:00
ac97e9e7bc Merge branch 'copilot/fix-104' into development
* copilot/fix-104:
  Complete testing of all biztools examples - all 11 scripts are non-functional
  Initial plan
2025-08-11 21:25:57 +02:00
Mahmoud-Emad
beae2cef82 refactor: Rework playbook include and site import logic
- Replace manual script concatenation with playbook include handling
- Preserve site configuration (imports, menu) during generation
- Add support for copying static files from imported content
- Handle static assets from sibling `ebooksall` directories
- Fix import copy logic to not delete destination before copying
2025-08-11 21:53:24 +03:00
2b23771056 ... 2025-08-11 16:38:54 +02:00
19632b4b4b ... 2025-08-11 15:32:12 +02:00
31c033300a Merge branch 'development' of github.com:freeflowuniverse/herolib into development 2025-08-11 11:51:54 +02:00
ca4127319d ... 2025-08-11 11:51:51 +02:00
openhands
d3d8f0d0f1 ... 2025-08-10 03:50:00 +00:00
openhands
2968e4dddc Fix issue #104: Fix all biztools examples to use playbook.new() 2025-08-09 20:08:51 +00:00
copilot-swe-agent[bot]
30c7951058 Complete testing of all biztools examples - all 11 scripts are non-functional
Co-authored-by: despiegk <6021844+despiegk@users.noreply.github.com>
2025-08-09 20:01:58 +00:00
openhands
af63e266d8 Fix issue #104: Add fixed examples and solution documentation 2025-08-09 20:01:37 +00:00
openhands
5d1e3d416e Fix issue #104: Add notworking.md documenting biztools examples test results 2025-08-09 19:57:37 +00:00
copilot-swe-agent[bot]
dd9dc59485 Initial plan 2025-08-09 19:51:51 +00:00
b473630ceb ... 2025-08-09 06:16:49 +02:00
a34b8b70ba ... 2025-08-08 21:36:46 +02:00
fd195f0824 ... 2025-08-08 17:51:57 +02:00
a727d19281 ... 2025-08-08 17:13:33 +02:00
52c88bccb5 bump version to 1.0.28 2025-08-08 17:02:05 +02:00
85cb868bff ... 2025-08-08 17:01:27 +02:00
4339220b42 bump version to 1.0.28 2025-08-08 17:01:11 +02:00
115 changed files with 7460 additions and 3753 deletions

View File

@@ -24,9 +24,9 @@ jobs:
- target: aarch64-apple-darwin
os: macos-latest
short-name: macos-arm64
- target: x86_64-apple-darwin
os: macos-13
short-name: macos-i64
# - target: x86_64-apple-darwin
# os: macos-13
# short-name: macos-i64
runs-on: ${{ matrix.os }}
steps:
@@ -42,10 +42,9 @@ jobs:
run: ./install_v.sh --herolib
timeout-minutes: 10
- name: Do all the basic tests
timeout-minutes: 25
run: ./test_basic.vsh
# - name: Do all the basic tests
# timeout-minutes: 25
# run: ./test_basic.vsh
- name: Build Hero
timeout-minutes: 15

View File

@@ -14,21 +14,17 @@ Herolib is an opinionated library primarily used by ThreeFold to automate cloud
The Hero tool can be installed with a single command:
```bash
curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_hero.sh > /tmp/install_hero.sh
bash /tmp/install_hero.sh
#do not forget to do the following this makes sure vtest and vrun exists
bash install_herolib.vsh
curl https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_hero.sh | bash
```
Hero will be installed in:
- `/usr/local/bin` for Linux
- `~/hero/bin` for macOS
After installation on macOS, you may need to:
After installation on macOS, you may need to do source see below or restart your terminal to ensure the `hero` command is available:
```bash
source ~/.zprofile
# Or copy to system bin directory
cp ~/hero/bin/hero /usr/local/bin
```
The Hero tool can be used to work with git, build documentation, interact with Hero AI, and more.
@@ -40,7 +36,13 @@ For development purposes, use the automated installation script:
```bash
curl 'https://raw.githubusercontent.com/freeflowuniverse/herolib/refs/heads/development/install_v.sh' > /tmp/install_v.sh
bash /tmp/install_v.sh --analyzer --herolib
#do not forget to do the following this makes sure vtest and vrun exists
cd ~/code/github/freeflowuniverse/herolib
bash install_herolib.vsh
# IMPORTANT: Start a new shell after installation for paths to be set correctly
```
#### Installation Options

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Herolib Web Server Installation Script
# This script sets up the necessary environment for the Flask web server.
set -e # Exit on any error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
/workspace/herolib/install_v.sh

View File

@@ -13,12 +13,12 @@ prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)')
help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested {
println(fp.usage())
exit(0)
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
exit(1)
}
eprintln(err)
println(fp.usage())
exit(1)
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env -S v -n -cg -w -parallel-cc -enable-globals run
// #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
#!/usr/bin/env -S v -n -g -cg -w -parallel-cc -showcc -enable-globals run
// #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import os
import flag
@@ -14,20 +14,20 @@ prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)')
help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested {
println(fp.usage())
exit(0)
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
exit(1)
eprintln(err)
println(fp.usage())
exit(1)
}
if additional_args.len > 0 {
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
println(fp.usage())
exit(1)
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
println(fp.usage())
exit(1)
}
// Change to the hero directory
@@ -37,35 +37,38 @@ os.chdir(hero_dir) or { panic('Failed to change directory to ${hero_dir}: ${err}
// Set HEROPATH based on OS
mut heropath := '/usr/local/bin/hero'
if os.user_os() == 'macos' {
heropath = os.join_path(os.home_dir(), 'hero/bin/hero')
heropath = os.join_path(os.home_dir(), 'hero/bin/hero')
}
// Set compilation command based on OS and mode
compile_cmd := if os.user_os() == 'macos' {
if prod_mode {
'v -enable-globals -w -n -prod hero.v'
} else {
'v -w -cg -gc none -cc tcc -d use_openssl -enable-globals hero.v'
}
if prod_mode {
'v -enable-globals -g -w -n -prod hero.v'
} else {
'v -n -g -w -cg -gc none -cc tcc -d use_openssl -enable-globals hero.v'
}
} else {
if prod_mode {
'v -cg -enable-globals -parallel-cc -w -n hero.v'
} else {
'v -cg -enable-globals -w -n hero.v'
}
if prod_mode {
'v -cg -enable-globals -parallel-cc -w -n hero.v'
} else {
'v -cg -enable-globals -w -n hero.v'
}
}
println('Building in ${if prod_mode { 'production' } else { 'debug' }} mode...')
// eprintln(compile_cmd)
if os.system(compile_cmd) != 0 {
panic('Failed to compile hero.v with command: ${compile_cmd}')
panic('Failed to compile hero.v with command: ${compile_cmd}')
}
// Make executable
os.chmod('hero', 0o755) or { panic('Failed to make hero binary executable: ${err}') }
// Ensure destination directory exists
os.mkdir_all(os.dir(heropath)) or { panic('Failed to create directory ${os.dir(heropath)}: ${err}') }
os.mkdir_all(os.dir(heropath)) or {
panic('Failed to create directory ${os.dir(heropath)}: ${err}')
}
println(heropath)
// Copy to destination paths
os.cp('hero', heropath) or { panic('Failed to copy hero binary to ${heropath}: ${err}') }

View File

@@ -64,7 +64,9 @@ account = ${s3keyid}
key = ${s3appid}
hard_delete = true'
os.write_file(rclone_conf, config_content) or { return error('Failed to write rclone config: ${err}') }
os.write_file(rclone_conf, config_content) or {
return error('Failed to write rclone config: ${err}')
}
println('made S3 config on: ${rclone_conf}')
content := os.read_file(rclone_conf) or { return error('Failed to read rclone config: ${err}') }
@@ -72,8 +74,10 @@ hard_delete = true'
}
fn hero_upload() ! {
hero_path := os.find_abs_path_of_executable('hero') or { return error("Error: 'hero' command not found in PATH") }
hero_path := os.find_abs_path_of_executable('hero') or {
return error("Error: 'hero' command not found in PATH")
}
s3_configure()!
platform_id := get_platform_id()
@@ -83,15 +87,18 @@ fn hero_upload() ! {
// List contents
os.execute_or_panic('rclone --config="${rclone_conf}" lsl b2:threefold/${platform_id}/')
// Copy hero binary
os.execute_or_panic('rclone --config="${rclone_conf}" copy "${hero_path}" b2:threefold/${platform_id}/')
}
fn main() {
//os.execute_or_panic('${os.home_dir()}/code/github/freeflowuniverse/herolib/cli/compile.vsh -p')
println("compile hero can take 60 sec+ on osx.")
// os.execute_or_panic('${os.home_dir()}/code/github/freeflowuniverse/herolib/cli/compile.vsh -p')
println('compile hero can take 60 sec+ on osx.')
os.execute_or_panic('${os.home_dir()}/code/github/freeflowuniverse/herolib/cli/compile.vsh -p')
println( "upload:")
hero_upload() or { eprintln(err) exit(1) }
println('upload:')
hero_upload() or {
eprintln(err)
exit(1)
}
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env -S v -n -cg -w -parallel-cc -enable-globals run
// #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
// #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import os
import flag
@@ -14,20 +14,20 @@ prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)')
help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested {
println(fp.usage())
exit(0)
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
exit(1)
eprintln(err)
println(fp.usage())
exit(1)
}
if additional_args.len > 0 {
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
println(fp.usage())
exit(1)
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
println(fp.usage())
exit(1)
}
// Change to the vdo directory
@@ -37,35 +37,37 @@ os.chdir(hero_dir) or { panic('Failed to change directory to ${hero_dir}: ${err}
// Set HEROPATH based on OS
mut heropath := '/usr/local/bin/vdo'
if os.user_os() == 'macos' {
heropath = os.join_path(os.home_dir(), 'hero/bin/vdo')
heropath = os.join_path(os.home_dir(), 'hero/bin/vdo')
}
// Set compilation command based on OS and mode
compile_cmd := if os.user_os() == 'macos' {
if prod_mode {
'v -enable-globals -w -n -prod vdo.v'
} else {
'v -w -cg -gc none -cc tcc -d use_openssl -enable-globals vdo.v'
}
if prod_mode {
'v -enable-globals -w -n -prod vdo.v'
} else {
'v -w -cg -gc none -cc tcc -d use_openssl -enable-globals vdo.v'
}
} else {
if prod_mode {
'v -cg -enable-globals -parallel-cc -w -n vdo.v'
} else {
'v -cg -enable-globals -w -n vdo.v'
}
if prod_mode {
'v -cg -enable-globals -parallel-cc -w -n vdo.v'
} else {
'v -cg -enable-globals -w -n vdo.v'
}
}
println('Building in ${if prod_mode { 'production' } else { 'debug' }} mode...')
if os.system(compile_cmd) != 0 {
panic('Failed to compile vdo.v with command: ${compile_cmd}')
panic('Failed to compile vdo.v with command: ${compile_cmd}')
}
// Make executable
os.chmod('vdo', 0o755) or { panic('Failed to make vdo binary executable: ${err}') }
// Ensure destination directory exists
os.mkdir_all(os.dir(heropath)) or { panic('Failed to create directory ${os.dir(heropath)}: ${err}') }
os.mkdir_all(os.dir(heropath)) or {
panic('Failed to create directory ${os.dir(heropath)}: ${err}')
}
println(heropath)
// Copy to destination paths
os.cp('vdo', heropath) or { panic('Failed to copy vdo binary to ${heropath}: ${err}') }

View File

@@ -3,8 +3,6 @@ module main
import os
import cli { Command }
import freeflowuniverse.herolib.core.herocmds
// import freeflowuniverse.herolib.hero.cmds
// import freeflowuniverse.herolib.hero.publishing
import freeflowuniverse.herolib.installers.base
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.ui
@@ -15,7 +13,7 @@ import freeflowuniverse.herolib.core.playcmds
fn playcmds_do(path string) ! {
mut plbook := playbook.new(path: path)!
playcmds.run(plbook:plbook)!
playcmds.run(plbook: plbook)!
}
fn do() ! {
@@ -50,7 +48,7 @@ fn do() ! {
mut cmd := Command{
name: 'hero'
description: 'Your HERO toolset.'
version: '1.0.27'
version: '1.0.28'
}
// herocmds.cmd_run_add_flags(mut cmd)
@@ -83,9 +81,14 @@ fn do() ! {
base.redis_install()!
// herocmds.cmd_bootstrap(mut cmd)
herocmds.cmd_run(mut cmd)
herocmds.cmd_git(mut cmd)
herocmds.cmd_generator(mut cmd)
herocmds.cmd_docusaurus(mut cmd)
// herocmds.cmd_bootstrap(mut cmd)
// herocmds.cmd_init(mut cmd)
// herocmds.cmd_imagedownsize(mut cmd)
// herocmds.cmd_biztools(mut cmd)
@@ -94,13 +97,15 @@ fn do() ! {
// herocmds.cmd_installers(mut cmd)
// herocmds.cmd_configure(mut cmd)
// herocmds.cmd_postgres(mut cmd)
herocmds.cmd_mdbook(mut cmd)
// Ensure the herocmds module is imported so the remaining commands are visible
// `cmd_mdbook` is not part of the current code base it has been removed.
// If you need markdownbook support, reintroduce the command implementation
// and uncomment the line below.
// herocmds.cmd_mdbook(mut cmd)
// herocmds.cmd_luadns(mut cmd)
// herocmds.cmd_caddy(mut cmd)
// herocmds.cmd_zola(mut cmd)
// herocmds.cmd_juggler(mut cmd)
herocmds.cmd_generator(mut cmd)
herocmds.cmd_docusaurus(mut cmd)
// herocmds.cmd_starlight(mut cmd)
// herocmds.cmd_docsorter(mut cmd)
// cmd.add_command(publishing.cmd_publisher(pre_func))
@@ -109,9 +114,15 @@ fn do() ! {
}
fn main() {
do() or { panic(err) }
do() or {
$dbg;
eprintln('Error: ${err}')
print_backtrace()
exit(1)
}
}
fn pre_func(cmd Command) ! {
herocmds.plbook_run(cmd)!
}
// fn pre_func(cmd Command) ! {
// herocmds.plbook_run(cmd)!
// }

View File

@@ -6,7 +6,7 @@ fn main() {
// Create and start the MCP server
mut server := v_do.new_server()
server.start() or {
eprintln('Error starting server: $err')
eprintln('Error starting server: ${err}')
exit(1)
}
}

35
doc.vsh
View File

@@ -7,13 +7,13 @@ abs_dir_of_script := dir(@FILE)
// Format code
println('Formatting code...')
if os.system('v fmt -w ${abs_dir_of_script}/examples') != 0 {
eprintln('Warning: Failed to format examples')
exit(1)
eprintln('Warning: Failed to format examples')
exit(1)
}
if os.system('v fmt -w ${abs_dir_of_script}/lib') != 0 {
eprintln('Warning: Failed to format herolib')
exit(1)
eprintln('Warning: Failed to format herolib')
exit(1)
}
// Clean existing docs
@@ -24,9 +24,7 @@ os.rmdir_all('docs') or {}
os.rmdir_all('vdocs') or {}
herolib_path := os.join_path(abs_dir_of_script, 'lib')
os.chdir(herolib_path) or {
panic('Failed to change directory to herolib: ${err}')
}
os.chdir(herolib_path) or { panic('Failed to change directory to herolib: ${err}') }
os.mkdir_all('_docs') or {}
os.mkdir_all('docs') or {}
@@ -35,17 +33,14 @@ os.mkdir_all('vdocs') or {}
// Generate HTML documentation
println('Generating HTML documentation...')
if os.system('v doc -m -f html . -readme -comments -no-timestamp -o ../docs') != 0 {
panic('Failed to generate HTML documentation')
panic('Failed to generate HTML documentation')
}
if os.system('v doc -m -f md . -no-color -o ../vdocs/') != 0 {
panic('Failed to generate Hero markdown documentation')
panic('Failed to generate Hero markdown documentation')
}
os.chdir(abs_dir_of_script) or {
panic('Failed to change directory to abs_dir_of_script: ${err}')
}
os.chdir(abs_dir_of_script) or { panic('Failed to change directory to abs_dir_of_script: ${err}') }
// Generate Markdown documentation
println('Generating Markdown documentation...')
@@ -60,12 +55,10 @@ println('Generating Markdown documentation...')
// Open documentation in browser on non-Linux systems
$if !linux {
os.chdir(abs_dir_of_script) or {
panic('Failed to change directory: ${err}')
}
if os.system('open docs/index.html') != 0 {
eprintln('Warning: Failed to open documentation in browser')
}
os.chdir(abs_dir_of_script) or { panic('Failed to change directory: ${err}') }
if os.system('open docs/index.html') != 0 {
eprintln('Warning: Failed to open documentation in browser')
}
}
// Create Jekyll required files
@@ -75,7 +68,7 @@ os.mkdir_all('docs/assets/css') or {}
// Create style.scss
style_content := '---\n---\n\n@import "{{ site.theme }}";'
os.write_file('docs/assets/css/style.scss', style_content) or {
panic('Failed to create style.scss: ${err}')
panic('Failed to create style.scss: ${err}')
}
// Create _config.yml
@@ -94,7 +87,7 @@ exclude:
- vendor/ruby/'
os.write_file('docs/_config.yml', config_content) or {
panic('Failed to create _config.yml: ${err}')
panic('Failed to create _config.yml: ${err}')
}
println('Documentation generation completed successfully!')

View File

@@ -4,46 +4,45 @@ import os
import flag
fn addtoscript(tofind string, toadd string) ! {
home_dir := os.home_dir()
mut rc_file := '${home_dir}/.zshrc'
if !os.exists(rc_file) {
rc_file = '${home_dir}/.bashrc'
if !os.exists(rc_file) {
return error('No .zshrc or .bashrc found in home directory')
}
}
home_dir := os.home_dir()
mut rc_file := '${home_dir}/.zshrc'
if !os.exists(rc_file) {
rc_file = '${home_dir}/.bashrc'
if !os.exists(rc_file) {
return error('No .zshrc or .bashrc found in home directory')
}
}
// Read current content
mut content := os.read_file(rc_file)!
// Remove existing alias if present
lines := content.split('\n')
mut new_lines := []string{}
mut prev_is_emtpy := false
for line in lines {
if prev_is_emtpy {
if line.trim_space() == ""{
continue
}else{
prev_is_emtpy = false
}
}
if line.trim_space() == ""{
prev_is_emtpy = true
}
// Read current content
mut content := os.read_file(rc_file)!
if !line.contains(tofind) {
new_lines << line
}
}
new_lines << toadd
new_lines << ""
// Write back to file
new_content := new_lines.join('\n')
os.write_file(rc_file, new_content)!
// Remove existing alias if present
lines := content.split('\n')
mut new_lines := []string{}
mut prev_is_emtpy := false
for line in lines {
if prev_is_emtpy {
if line.trim_space() == '' {
continue
} else {
prev_is_emtpy = false
}
}
if line.trim_space() == '' {
prev_is_emtpy = true
}
if !line.contains(tofind) {
new_lines << line
}
}
new_lines << toadd
new_lines << ''
// Write back to file
new_content := new_lines.join('\n')
os.write_file(rc_file, new_content)!
}
vroot := @VROOT
abs_dir_of_script := dir(@FILE)
@@ -52,20 +51,20 @@ println('Resetting all symlinks...')
os.rm('${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {}
// Create necessary directories
os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or {
panic('Failed to create directory ~/.vmodules/freeflowuniverse: ${err}')
os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or {
panic('Failed to create directory ~/.vmodules/freeflowuniverse: ${err}')
}
// Create new symlinks
os.symlink('${abs_dir_of_script}/lib', '${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {
panic('Failed to create herolib symlink: ${err}')
panic('Failed to create herolib symlink: ${err}')
}
println('Herolib installation completed successfully!')
// Add vtest alias
addtoscript('alias vtest=', 'alias vtest=\'v -stats -enable-globals -n -w -cg -gc none -cc tcc test\' ') or {
eprintln('Failed to add vtest alias: ${err}')
addtoscript('alias vtest=', "alias vtest='v -stats -enable-globals -n -w -cg -gc none -cc tcc test' ") or {
eprintln('Failed to add vtest alias: ${err}')
}
println('Added vtest alias to shell configuration')

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := "
@@ -21,8 +22,13 @@ This time we have the cogs defined in fixed manner, the default currency is USD
cogs: '10:100000,15:1000,20:120000'
"
bizmodel.play(heroscript: heroscript)!
// Create a new playbook with the heroscript text
mut pb := playbook.new(text: heroscript)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('test')!
bm.sheet.pprint(nr_columns: 30)!

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := "
@@ -16,8 +17,13 @@ heroscript := "
//revenue_item_monthly_perc:'3%'
"
bizmodel.play(heroscript: heroscript)!
// Create a new playbook with the heroscript text
mut pb := playbook.new(text: heroscript)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('test')!
bm.sheet.pprint(nr_columns: 30)!

View File

@@ -1,11 +1,17 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := os.join_path(os.dir(@FILE), 'examples/complete.heroscript')
heroscript_path := os.join_path(os.dir(@FILE), 'examples/complete.heroscript')
// Execute the script and print results
bizmodel.play(heroscript_path: heroscript)!
// Create a new playbook with the heroscript path
mut pb := playbook.new(path: heroscript_path)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('threefold')!
bm.sheet.pprint(nr_columns: 10)!

View File

@@ -1,33 +1,23 @@
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
//#!/usr/bin/env -S v -cg -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.core.playcmds
import os
// heroscript := os.join_path(os.dir(@FILE), 'examples/full')
// // Execute the script and print results
// bizmodel.play(heroscript_path:heroscript)!
heroscript_path := os.join_path(os.dir(@FILE), 'examples/complete.heroscript')
heroscript := os.join_path(os.dir(@FILE), 'examples/complete.heroscript')
// Execute the script and print results
bizmodel.play(heroscript_path: heroscript)!
// Create a new playbook with the heroscript path
mut pb := playbook.new(path: heroscript_path)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('threefold')!
bm.sheet.pprint(nr_columns: 10)!
// buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
// println("buildpath: ${buildpath}")
// model.play(mut playbook.new(path: playbook_path)!)!
// println(model.sheet)
// println(model.sheet.export()!)
// model.sheet.export(path:"~/Downloads/test.csv")!
// model.sheet.export(path:"~/code/github/freeflowuniverse/starlight_template/src/content/test.csv")!
// Export the business model to a report
bm.export(
name: 'example_report'
title: 'Example Business Model'

View File

@@ -1,11 +1,17 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := os.join_path(os.dir(@FILE), 'examples/full')
heroscript_path := os.join_path(os.dir(@FILE), 'examples/full')
// Execute the script and print results
bizmodel.play(heroscript_path: heroscript)!
// Create a new playbook with the heroscript path
mut pb := playbook.new(path: heroscript_path)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('threefold')!
bm.sheet.pprint(nr_columns: 25)!

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := "
@@ -45,8 +46,13 @@ heroscript := "
"
bizmodel.play(heroscript: heroscript)!
// Create a new playbook with the heroscript text
mut pb := playbook.new(text: heroscript)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('test')!
bm.sheet.pprint(nr_columns: 20)!

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := "
@@ -17,8 +18,13 @@ heroscript := "
"
bizmodel.play(heroscript: heroscript)!
// Create a new playbook with the heroscript text
mut pb := playbook.new(text: heroscript)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('test')!
bm.sheet.pprint(nr_columns: 20)!

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import os
heroscript := "
@@ -36,8 +37,13 @@ heroscript := "
"
bizmodel.play(heroscript: heroscript)!
// Create a new playbook with the heroscript text
mut pb := playbook.new(text: heroscript)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('test')!
bm.sheet.pprint(nr_columns: 20)!

View File

@@ -0,0 +1,50 @@
# BizTools Examples Test Results
## Working Examples
All examples have been fixed and now work correctly:
- `bizmodel.vsh` - This example was already working correctly.
- `bizmodel1.vsh` - Fixed to use `playbook.new()` with text parameter.
- `bizmodel2.vsh` - Fixed to use `playbook.new()` with text parameter.
- `bizmodel_complete.vsh` - Fixed to use `playbook.new()` with path parameter.
- `bizmodel_export.vsh` - Fixed to use `playbook.new()` with path parameter.
- `bizmodel_full.vsh` - Fixed to use `playbook.new()` with path parameter.
- `costs.vsh` - Fixed to use `playbook.new()` with text parameter.
- `funding.vsh` - Fixed to use `playbook.new()` with text parameter.
- `hr.vsh` - Fixed to use `playbook.new()` with text parameter.
## Previous Issues
All examples had issues with the `bizmodel.play()` function:
1. Unknown field (`heroscript` or `heroscript_path`) in struct literal of type `PlayBook`.
2. Reference field `PlayBook.session` must be initialized.
3. Function `bizmodel.play` parameter `plbook` is `mut`, so it requires `mut PlayBook{...}` instead.
## Solution Applied
All examples have been fixed by using the `playbook.new()` function to create a properly initialized PlayBook:
For examples with heroscript text:
```v
// Create a new playbook with the heroscript text
mut pb := playbook.new(text: heroscript)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
// Get the bizmodel and print it
mut bm := bizmodel.get('test')!
```
For examples with heroscript path:
```v
// Create a new playbook with the heroscript path
mut pb := playbook.new(path: heroscript_path)!
// Play the bizmodel actions
bizmodel.play(mut pb)!
```
## Environment Setup
- Tests were performed with V language version 0.4.11 a11de72
- Redis server was running during tests
- All tests were executed from the `/workspace/project/herolib/examples/biztools` directory

View File

@@ -6,3 +6,4 @@ markdown_example0
doctree_example
tree_scan
*.dSYM
ui_demo

View File

@@ -11,15 +11,15 @@ playcmds.run(
// install: 1
// template_update: 1
!!docusaurus.add sitename:"tfgrid_tech"
git_url:"https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/ebooks/tech"
!!docusaurus.add sitename:"owh_intro"
git_url:"https://git.ourworld.tf/ourworld_holding/docs_owh/src/branch/main/ebooks/owh_intro"
git_root:"/tmp/code"
git_reset:1
git_pull:1
play:true
!!docusaurus.build
// !!docusaurus.build
// !!docusaurus.dev site:"tfgrid_tech" open:true
!!docusaurus.dev site:"owh_intro" open:true
'
)!

13
examples/web/ui_demo.vsh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.web.ui
fn main() {
println('Starting UI test server on port 8080...')
println('Visit http://localhost:8080 to see the admin interface')
ui.start(
title: 'Test Admin Panel'
port: 8080
)!
}

View File

@@ -10,41 +10,39 @@ fp.version('v0.1.0')
fp.description('Generate code')
fp.skip_executable()
mut path := fp.string('path', `p`, "", 'Path where to generate a module, if not mentioned will scan over all installers & clients.\nif . then will be path we are on.')
mut path := fp.string('path', `p`, '', 'Path where to generate a module, if not mentioned will scan over all installers & clients.\nif . then will be path we are on.')
reset := fp.bool('reset', `r`, false, 'If we want to reset')
interactive := fp.bool('interactive', `i`, false, 'If we want to work interactive')
scan := fp.bool('scan', `s`, false, 'If we want to scan')
help_requested := fp.bool('help', `h`, false, 'Show help message')
if help_requested {
println(fp.usage())
exit(0)
println(fp.usage())
exit(0)
}
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
exit(1)
eprintln(err)
println(fp.usage())
exit(1)
}
if additional_args.len > 0 {
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
println(fp.usage())
exit(1)
eprintln('Unexpected arguments: ${additional_args.join(' ')}')
println(fp.usage())
exit(1)
}
// reset bool // regenerate all, dangerous !!!
// interactive bool //if we want to ask
// path string
if path.trim_space() == "." {
if path.trim_space() == '.' {
path = os.getwd()
}
if ! scan {
generator.do(path:path, reset:reset, interactive:interactive)!
}else{
generator.scan(path:path, reset:reset, interactive:interactive)!
if !scan {
generator.do(path: path, reset: reset, interactive: interactive)!
} else {
generator.scan(path: path, reset: reset, interactive: interactive)!
}

View File

@@ -4,7 +4,7 @@ set -e
os_name="$(uname -s)"
arch_name="$(uname -m)"
version='1.0.27'
version='1.0.28'
# Base URL for GitHub releases

View File

@@ -4,46 +4,45 @@ import os
import flag
fn addtoscript(tofind string, toadd string) ! {
home_dir := os.home_dir()
mut rc_file := '${home_dir}/.zshrc'
if !os.exists(rc_file) {
rc_file = '${home_dir}/.bashrc'
if !os.exists(rc_file) {
return error('No .zshrc or .bashrc found in home directory')
}
}
home_dir := os.home_dir()
mut rc_file := '${home_dir}/.zshrc'
if !os.exists(rc_file) {
rc_file = '${home_dir}/.bashrc'
if !os.exists(rc_file) {
return error('No .zshrc or .bashrc found in home directory')
}
}
// Read current content
mut content := os.read_file(rc_file)!
// Remove existing alias if present
lines := content.split('\n')
mut new_lines := []string{}
mut prev_is_emtpy := false
for line in lines {
if prev_is_emtpy {
if line.trim_space() == ""{
continue
}else{
prev_is_emtpy = false
}
}
if line.trim_space() == ""{
prev_is_emtpy = true
}
// Read current content
mut content := os.read_file(rc_file)!
if !line.contains(tofind) {
new_lines << line
}
}
new_lines << toadd
new_lines << ""
// Write back to file
new_content := new_lines.join('\n')
os.write_file(rc_file, new_content)!
// Remove existing alias if present
lines := content.split('\n')
mut new_lines := []string{}
mut prev_is_emtpy := false
for line in lines {
if prev_is_emtpy {
if line.trim_space() == '' {
continue
} else {
prev_is_emtpy = false
}
}
if line.trim_space() == '' {
prev_is_emtpy = true
}
if !line.contains(tofind) {
new_lines << line
}
}
new_lines << toadd
new_lines << ''
// Write back to file
new_content := new_lines.join('\n')
os.write_file(rc_file, new_content)!
}
vroot := @VROOT
abs_dir_of_script := dir(@FILE)
@@ -52,29 +51,29 @@ println('Resetting all symlinks...')
os.rm('${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {}
// Create necessary directories
os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or {
panic('Failed to create directory ~/.vmodules/freeflowuniverse: ${err}')
os.mkdir_all('${os.home_dir()}/.vmodules/freeflowuniverse') or {
panic('Failed to create directory ~/.vmodules/freeflowuniverse: ${err}')
}
// Create new symlinks
os.symlink('${abs_dir_of_script}/lib', '${os.home_dir()}/.vmodules/freeflowuniverse/herolib') or {
panic('Failed to create herolib symlink: ${err}')
panic('Failed to create herolib symlink: ${err}')
}
println('Herolib installation completed successfully!')
// Add vtest alias
addtoscript('alias vtest=', 'alias vtest=\'v -stats -enable-globals -show-c-output -n -w -cg -gc none -cc tcc test\' ') or {
eprintln('Failed to add vtest alias: ${err}')
addtoscript('alias vtest=', "alias vtest='v -stats -enable-globals -show-c-output -n -w -cg -gc none -cc tcc test' ") or {
eprintln('Failed to add vtest alias: ${err}')
}
// Add vrun alias
addtoscript('alias vrun=', 'alias vrun=\'v -stats -enable-globals -show-c-output -n -w -cg -gc none -cc tcc run\' ') or {
eprintln('Failed to add vrun alias: ${err}')
addtoscript('alias vrun=', "alias vrun='v -stats -enable-globals -show-c-output -n -w -cg -gc none -cc tcc run' ") or {
eprintln('Failed to add vrun alias: ${err}')
}
addtoscript('HOME/hero/bin', 'export PATH="\$PATH:\$HOME/hero/bin"') or {
eprintln('Failed to add path to hero, ${err}')
eprintln('Failed to add path to hero, ${err}')
}
// ulimit -n 32000

View File

@@ -40,7 +40,7 @@ pub fn jina_model_from_string(s string) !JinaModel {
'jina-embeddings-v2-base-zh' { JinaModel.jina_embeddings_v2_base_zh }
'jina-embeddings-v2-base-code' { JinaModel.jina_embeddings_v2_base_code }
'jina-embeddings-v3' { JinaModel.jina_embeddings_v3 }
else { error('Invalid Jina model string: ${s}') }
else { return error('Invalid Jina model string: ${s}') }
}
}
@@ -66,7 +66,7 @@ pub fn truncate_type_from_string(s string) !TruncateType {
'NONE' { TruncateType.none_ }
'START' { TruncateType.start }
'END' { TruncateType.end }
else { error('Invalid truncate type string: ${s}') }
else { return error('Invalid truncate type string: ${s}') }
}
}
@@ -95,7 +95,7 @@ pub fn embedding_type_from_string(s string) !EmbeddingType {
'base64' { EmbeddingType.base64 }
'binary' { EmbeddingType.binary }
'ubinary' { EmbeddingType.ubinary }
else { error('Invalid embedding type string: ${s}') }
else { return error('Invalid embedding type string: ${s}') }
}
}
@@ -116,7 +116,7 @@ pub fn task_type_from_string(s string) !TaskType {
'text-matching' { TaskType.text_matching }
'classification' { TaskType.classification }
'separation' { TaskType.separation }
else { error('Invalid task type string: ${s}') }
else { return error('Invalid task type string: ${s}') }
}
}

View File

@@ -63,7 +63,7 @@ pub fn jina_rerank_model_from_string(s string) !JinaRerankModel {
'jina-reranker-v1-tiny-en' { JinaRerankModel.reranker_v1_tiny_en }
'jina-reranker-v1-turbo-en' { JinaRerankModel.reranker_v1_turbo_en }
'jina-colbert-v1-en' { JinaRerankModel.colbert_v1_en }
else { error('Invalid JinaRerankModel string: ${s}') }
else { return error('Invalid JinaRerankModel string: ${s}') }
}
}

View File

@@ -1,69 +0,0 @@
module installer_client
import freeflowuniverse.herolib.ui.console
import os
import freeflowuniverse.herolib.core.pathlib
// will ask questions & create the .heroscript
pub fn ask(path string) ! {
mut myconsole := console.new()
mut model := gen_model_get(path, false)!
console.clear()
console.print_header('Configure generation of code for a module on path:')
console.print_green('Path: ${path}')
console.lf()
model.classname = myconsole.ask_question(
description: 'Class name of the ${model.cat}'
question: 'What is the class name of the generator e.g. MyClass ?'
warning: 'Please provide a valid class name for the generator'
default: model.classname
minlen: 4
)!
model.title = myconsole.ask_question(
description: 'Title of the ${model.cat} (optional)'
default: model.title
)!
model.hasconfig = !myconsole.ask_yesno(
description: 'Is there a config (normally yes)?'
default: model.hasconfig
)!
if model.hasconfig {
model.singleton = !myconsole.ask_yesno(
description: 'Can there be multiple instances (normally yes)?'
default: !model.singleton
)!
if model.cat == .installer {
model.templates = myconsole.ask_yesno(
description: 'Will there be templates available for your installer?'
default: model.templates
)!
}
} else {
model.singleton = true
}
if model.cat == .installer {
model.startupmanager = myconsole.ask_yesno(
description: 'Is this an installer which will be managed by a startup mananger?'
default: model.startupmanager
)!
model.build = myconsole.ask_yesno(
description: 'Are there builders for the installers (compilation)'
default: model.build
)!
}
// if true{
// println(model)
// panic("Sdsd")
// }
gen_model_set(GenerateArgs{ model: model, path: path })!
}

View File

@@ -1,85 +0,0 @@
module installer_client
import freeflowuniverse.herolib.ui.console
import os
@[params]
pub struct GenerateArgs {
pub mut:
reset bool // regenerate all, dangerous !!!
interactive bool // if we want to ask
path string
playonly bool
model ?GenModel
cat ?Cat
}
pub struct PlayArgs {
pub mut:
name string
modulepath string
}
// the default to start with
//
// reset bool // regenerate all, dangerous !!!
// interactive bool //if we want to ask
// path string
// model ?GenModel
// cat ?Cat
//
// will return the module path where we need to execute a play command as well as the name of
pub fn do(args_ GenerateArgs) !PlayArgs {
mut args := args_
console.print_header('Generate code for path: ${args.path} (reset:${args.reset}, interactive:${args.interactive})')
mut create := true // to create .heroscript
mut model := args.model or {
create = false // we cannot create because model not given
if args.path == '' {
args.path = os.getwd()
}
mut m := gen_model_get(args.path, false)!
m
}
if model.classname == '' {
args.interactive = true
}
if create {
if args.path == '' {
return error('need to specify path fo ${args_} because we asked to create .heroscript ')
}
gen_model_set(args)! // persist it on disk
} else {
if args.path == '' {
args.path = os.getwd()
}
}
// if model.cat == .unknown {
// model.cat = args.cat or { return error('cat needs to be specified for generator.') }
// }
if args.interactive {
ask(args.path)!
args.model = gen_model_get(args.path, false)!
} else {
args.model = model
}
console.print_debug(args)
// only generate if playonly is false and there is a classname
if !args.playonly && model.classname.len > 0 {
generate(args)!
}
return PlayArgs{
name: model.play_name
modulepath: model.module_path
}
}

View File

@@ -1,77 +0,0 @@
module installer_client
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.pathlib
// generate based on filled in args, ask has to be done before
fn generate(args GenerateArgs) ! {
console.print_debug('generate code for path: ${args.path}')
// as used in the templates
model := args.model or { panic('bug no model specified in generate') }
mut path_actions := pathlib.get(args.path + '/${model.name}_actions.v')
if args.reset {
path_actions.delete()!
}
if !path_actions.exists() && model.cat == .installer {
console.print_debug('write installer actions')
mut templ_1 := $tmpl('templates/objname_actions.vtemplate')
pathlib.template_write(templ_1, '${args.path}/${model.name}_actions.v', true)!
}
mut templ_2 := $tmpl('templates/objname_factory_.vtemplate')
pathlib.template_write(templ_2, '${args.path}/${model.name}_factory_.v', true)!
mut path_model := pathlib.get(args.path + '/${model.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}/${model.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.model or { panic('bug') }).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 GenModel) ! {
// 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 GenModel) 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
// }

View File

@@ -1,136 +0,0 @@
module installer_client
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
pub struct GenModel {
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 !!!
interactive bool // if we want to ask
startupmanager bool = true
build bool = true
hasconfig bool = true
cat Cat // dont' set default
play_name string // e.g. docusaurus is what we look for
module_path string // e.g.freeflowuniverse.herolib.web.docusaurus
}
pub enum Cat {
unknown
client
installer
}
// creates the heroscript from the GenModel as part of GenerateArgs
pub fn gen_model_set(args GenerateArgs) ! {
console.print_debug('Code generator set: ${args}')
model := args.model or { return error('model is none') }
heroscript_templ := match model.cat {
.client { $tmpl('templates/heroscript_client') }
.installer { $tmpl('templates/heroscript_installer') }
else { return error('Invalid category: ${model.cat}') }
}
pathlib.template_write(heroscript_templ, '${args.path}/.heroscript', true)!
}
// loads the heroscript and return the model
pub fn gen_model_get(path string, create bool) !GenModel {
console.print_debug('play installer code for path: ${path}')
mut config_path := pathlib.get_file(path: '${path}/.heroscript', create: create)!
mut plbook := playbook.new(text: config_path.read()!)!
mut model := GenModel{}
mut found := false
mut install_actions := plbook.find(filter: 'hero_code.generate_installer')!
if install_actions.len > 0 {
for install_action in install_actions {
if found {
return error('cannot find more than one her_code.generate_installer ... in ${path}')
}
found = true
mut p := install_action.params
model = GenModel{
name: p.get_default('name', '')!
classname: p.get_default('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')
startupmanager: p.get_default_true('startupmanager')
build: p.get_default_true('build')
hasconfig: p.get_default_true('hasconfig')
cat: .installer
}
}
}
mut client_actions := plbook.find(filter: 'hero_code.generate_client')!
if client_actions.len > 0 {
for client_action in client_actions {
if found {
return error('cannot find more than one her_code.generate_client ... in ${path}')
}
found = true
mut p := client_action.params
model = GenModel{
name: p.get_default('name', '')!
classname: p.get_default('classname', '')!
title: p.get_default('title', '')!
default: p.get_default_true('default')
singleton: p.get_default_false('singleton')
hasconfig: p.get_default_true('hasconfig')
cat: .client
}
}
}
if model.cat == .unknown {
if path.contains('clients') {
model.cat = .client
} else {
model.cat = .installer
}
}
if model.name == '' {
model.name = os.base(path).to_lower()
}
model.play_name = model.name
pathsub := path.replace('${os.home_dir()}/code/github/', '')
model.module_path = pathsub.replace('/', '.').replace('.lib.', '.')
// !!hero_code.play
// name:'docusaurus'
mut play_actions := plbook.find(filter: 'hero_code.play')!
if play_actions.len > 1 {
return error('should have max 1 hero_code.play action in ${config_path.path}')
}
if play_actions.len == 1 {
mut p := play_actions[0].params
model.play_name = p.get_default('name', model.name)!
}
if model.module_path.contains('docusaurus') {
println(model)
println('4567ujhjk')
exit(0)
}
return model
}

View File

@@ -1,71 +0,0 @@
# generation framework for clients & installers
```bash
#generate all play commands
hero generate -playonly
#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
title:''
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
```
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 -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
```

View File

@@ -1,49 +0,0 @@
module installer_client
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.ui.console
@[params]
pub struct ScannerArgs {
pub mut:
reset bool // regenerate all, dangerous !!!
interactive bool // if we want to ask
path string
playonly bool
}
// scan over a set of directories call the play where
pub fn scan(args ScannerArgs) ! {
console.print_debug('Code generator scan: ${args.path}')
if args.path == '' {
scan(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/installers')!
scan(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/clients')!
scan(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/web')!
return
}
console.print_header('Scan for generation of code for ${args.path}')
// 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()!
path_module := pparent.path
if os.exists('${path_module}/.heroscript') {
do(
interactive: args.interactive
path: path_module
reset: args.reset
playonly: args.playonly
)!
}
}
}

View File

@@ -1,5 +0,0 @@
name: ??{model.name}

View File

@@ -1,7 +0,0 @@
!!hero_code.generate_client
name: "${model.name}"
classname: "${model.classname}"
hasconfig: ${model.hasconfig}
singleton: ${model.singleton}
default: ${model.default}
title: "${model.title}"

View File

@@ -1,11 +0,0 @@
!!hero_code.generate_installer
name: "${model.name}"
classname: "${model.classname}"
hasconfig: ${model.hasconfig}
singleton: ${model.singleton}
default: ${model.default}
title: "${model.title}"
templates: ${model.templates}
build: ${model.build}
startupmanager: ${model.startupmanager}

View File

@@ -1,219 +0,0 @@
module ${model.name}
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.installers.ulist
import freeflowuniverse.herolib.installers.base
@if model.startupmanager
import freeflowuniverse.herolib.osal.systemd
import freeflowuniverse.herolib.osal.zinit
@end
@if model.build
import freeflowuniverse.herolib.installers.lang.golang
import freeflowuniverse.herolib.installers.lang.rust
import freeflowuniverse.herolib.installers.lang.python
@end
import os
@if model.startupmanager
fn startupcmd () ![]zinit.ZProcessNewArgs{
mut installer := get()!
mut res := []zinit.ZProcessNewArgs{}
//THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// res << zinit.ZProcessNewArgs{
// name: '${model.name}'
// cmd: '${model.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 ${model.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: '${model.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('${model.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 model.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()!} ${model.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 ${model.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: '${model.name}'
// source: '??{gitpath}/target/x86_64-unknown-linux-musl/release/${model.name}'
// )!
}
fn install_() ! {
console.print_header('install ${model.name}')
//THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED
// mut url := ''
// if core.is_linux_arm()! {
// url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_linux_arm64.tar.gz'
// } else if core.is_linux_intel()! {
// url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_linux_amd64.tar.gz'
// } else if core.is_osx_arm()! {
// url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_darwin_arm64.tar.gz'
// } else if core.is_osx_intel()! {
// url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_darwin_amd64.tar.gz'
// } else {
// return error('unsported platform')
// }
// mut dest := osal.download(
// url: url
// minsize_kb: 9000
// expand_dir: '/tmp/${model.name}'
// )!
// //dest.moveup_single_subdir()!
// mut binpath := dest.file_get('${model.name}')!
// osal.cmd_add(
// cmdname: '${model.name}'
// source: binpath.path
// )!
}
@if model.build
fn build_() ! {
//url := 'https://github.com/threefoldtech/${model.name}'
// make sure we install base on the node
// if core.platform()!= .ubuntu {
// return error('only support ubuntu for now')
// }
//mut g:=golang.get()!
//g.install()!
//console.print_header('build coredns')
//mut gs := gittools.new(coderoot: '~/code')!
// console.print_header('build ${model.name}')
// gitpath := gittools.get_repo(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

View File

@@ -1,335 +0,0 @@
module ${model.name}
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core
@if model.hasconfig
import freeflowuniverse.herolib.data.encoderhero
@end
@if model.cat == .installer
import freeflowuniverse.herolib.osal.startupmanager
import freeflowuniverse.herolib.osal.zinit
import time
@end
__global (
${model.name}_global map[string]&${model.classname}
${model.name}_default string
)
/////////FACTORY
@if model.singleton == false
^^[params]
pub struct ArgsGet{
pub mut:
name string
}
fn args_get (args_ ArgsGet) ArgsGet {
mut model:=args_
if model.name == ""{
model.name = ${model.name}_default
}
if model.name == ""{
model.name = "default"
}
return model
}
pub fn get(args_ ArgsGet) !&${model.classname} {
mut args := args_get(args_)
if !(args.name in ${model.name}_global) {
if args.name=="default"{
if ! exists(args)!{
if default{
mut context:=base.context() or { panic("bug") }
context.hero_config_set("${model.name}",args.name,heroscript_default()!)!
}
}
load(args)!
}
}
return ${model.name}_global[args.name] or {
println(${model.name}_global)
panic("could not get config for ??{args.name}.")
}
}
@end
@if model.hasconfig
//set the model in mem and the config on the filesystem
pub fn set(o ${model.classname})! {
mut o2:=obj_init(o)!
${model.name}_global[o.name] = &o2
${model.name}_default = o.name
}
//check we find the config on the filesystem
pub fn exists(args_ ArgsGet)!bool {
mut model := args_get(args_)
mut context:=base.context()!
return context.hero_config_exists("${model.name}",model.name)
}
//load the config error if it doesn't exist
pub fn load(args_ ArgsGet) ! {
mut model := args_get(args_)
mut context:=base.context()!
mut heroscript := context.hero_config_get("${model.name}",model.name)!
play(heroscript:heroscript)!
}
//save the config to the filesystem in the context
pub fn save(o ${model.classname})! {
mut context:=base.context()!
heroscript := encoderhero.encode[${model.classname}](o)!
context.hero_config_set("${model.name}",o.name,heroscript)!
}
pub fn play(mut plbook PlayBook) ! {
@if model.hasconfig
mut configure_actions := plbook.find(filter: '${model.name}.configure')!
if configure_actions.len > 0 {
for config_action in configure_actions {
mut p := config_action.params
mycfg:=cfg_play(p)!
console.print_debug("install action ${model.name}.configure\n??{mycfg}")
set(mycfg)!
save(mycfg)!
}
}
@end
@if model.cat == .installer
mut other_actions := plbook.find(filter: '${model.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 ${model.name}.destroy")
destroy_()!
}
if other_action.name == "install"{
console.print_debug("install action ${model.name}.install")
install_()!
}
}
@if model.startupmanager
if other_action.name in ["start","stop","restart"]{
mut p := other_action.params
name := p.get('name')!
mut ${model.name}_obj:=get(name:name)!
console.print_debug("action object:\n??{${model.name}_obj}")
if other_action.name == "start"{
console.print_debug("install action ${model.name}.??{other_action.name}")
${model.name}_obj.start()!
}
if other_action.name == "stop"{
console.print_debug("install action ${model.name}.??{other_action.name}")
${model.name}_obj.stop()!
}
if other_action.name == "restart"{
console.print_debug("install action ${model.name}.??{other_action.name}")
${model.name}_obj.restart()!
}
}
@end
}
@end
}
@end
@if model.cat == .installer
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
@if model.hasconfig
//load from disk and make sure is properly intialized
pub fn (mut self ${model.classname}) reload() ! {
switch(self.name)
self=obj_init(self)!
}
@end
@if model.startupmanager
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()!
}
}
}
pub fn (mut self ${model.classname}) start() ! {
switch(self.name)
if self.running()!{
return
}
console.print_header('${model.name} start')
if ! installed_()!{
install_()!
}
configure()!
start_pre()!
for zprocess in startupcmd()!{
mut sm:=startupmanager_get(zprocess.startuptype)!
console.print_debug('starting ${model.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('${model.name} did not install properly.')
}
pub fn (mut self ${model.classname}) install_start(model InstallArgs) ! {
switch(self.name)
self.install(model)!
self.start()!
}
pub fn (mut self ${model.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 ${model.classname}) restart() ! {
switch(self.name)
self.stop()!
self.start()!
}
pub fn (mut self ${model.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
}
@if model.singleton
pub fn install(args InstallArgs) ! {
if args.reset {
destroy()!
}
if ! (installed_()!){
install_()!
}
}
pub fn destroy() ! {
destroy_()!
}
@if model.build
pub fn build() ! {
build_()!
}
@end
@else
//switch instance to be used for ${model.name}
pub fn switch(name string) {
${model.name}_default = name
}
pub fn (mut self ${model.classname}) install(args InstallArgs) ! {
switch(self.name)
if args.reset {
destroy_()!
}
if ! (installed_()!){
install_()!
}
}
@if model.build
pub fn (mut self ${model.classname}) build() ! {
switch(self.name)
build_()!
}
@end
pub fn (mut self ${model.classname}) destroy() ! {
switch(self.name)
@if model.startupmanager
self.stop() or {}
@end
destroy_()!
}
@end
@end

View File

@@ -1,155 +0,0 @@
module ${model.name}
import freeflowuniverse.herolib.data.paramsparser
import os
pub const version = '0.0.0'
const singleton = ${model.singleton}
const default = ${model.default}
@if model.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 model.cat == .installer
heroscript:="
!!${model.name}.configure
name:'${model.name}'
homedir: '{HOME}/hero/var/${model.name}'
configpath: '{HOME}/.config/${model.name}/admin.yaml'
username: 'admin'
password: 'secretpassword'
secret: ''
title: 'My Hero DAG'
host: 'localhost'
port: 8888
"
@else
heroscript:="
!!${model.name}.configure
name:'${model.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 model.cat == .installer
^^[heap]
pub struct ${model.classname} {
pub mut:
name string = 'default'
@if model.hasconfig
homedir string
configpath string
username string
password string @@[secret]
secret string @@[secret]
title string
host string
port int
@end
}
@if model.hasconfig
fn cfg_play(p paramsparser.Params) !${model.classname} {
//THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
mut mycfg := ${model.classname}{
name: p.get_default('name', 'default')!
homedir: p.get_default('homedir', '{HOME}/hero/var/${model.name}')!
configpath: p.get_default('configpath', '{HOME}/hero/var/${model.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 ${model.name}')
}
return mycfg
}
@end
@else
^^[heap]
pub struct ${model.classname} {
pub mut:
name string = 'default'
mail_from string
mail_password string @@[secret]
mail_port int
mail_server string
mail_username string
}
@if model.hasconfig
fn cfg_play(p paramsparser.Params) !${model.classname} {
//THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above
mut mycfg := ${model.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)!
return mycfg
}
@end
@end
fn obj_init(obj_ ${model.classname})!${model.classname}{
//never call get here, only thing we can do here is work on object itself
mut obj:=obj_
return obj
}
@if model.cat == .installer
//called before start if done
fn configure() ! {
@if model.cat == .installer
//mut installer := get()!
@else
//mut client := get()!
@end
@if model.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

View File

@@ -1,63 +0,0 @@
# ${model.name}
${model.title}
To get started
```vlang
@if model.cat == .installer
import freeflowuniverse.herolib.installers.something.${model.name} as ${model.name}_installer
heroscript:="
!!${model.name}.configure name:'test'
password: '1234'
port: 7701
!!${model.name}.start name:'test' reset:1
"
${model.name}_installer.play(heroscript=heroscript)!
//or we can call the default and do a start with reset
//mut installer:= ${model.name}_installer.get()!
//installer.start(reset:true)!
@else
import freeflowuniverse.herolib.clients. ${model.name}
mut client:= ${model.name}.get()!
client...
@end
```
## example heroscript
@if model.cat == .installer
```hero
!!${model.name}.configure
homedir: '/home/user/${model.name}'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
```
@else
```hero
!!${model.name}.configure
secret: '...'
host: 'localhost'
port: 8888
```
@end

View File

@@ -1,8 +1,9 @@
module herocmds
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.web.docusaurus
import freeflowuniverse.herolib.core.playcmds
import freeflowuniverse.herolib.develop.gittools
import os
import cli { Command, Flag }
@@ -14,13 +15,13 @@ pub fn cmd_docusaurus(mut cmdroot Command) Command {
execute: cmd_docusaurus_execute
}
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'reset'
// abbrev: 'r'
// description: 'will reset.'
// })
cmd_run.add_flag(Flag{
flag: .bool
required: false
name: 'reset'
abbrev: 'r'
description: 'will reset.'
})
cmd_run.add_flag(Flag{
flag: .string
@@ -40,31 +41,31 @@ pub fn cmd_docusaurus(mut cmdroot Command) Command {
description: 'Path where docusaurus configuration is.'
})
cmd_run.add_flag(Flag{
flag: .string
required: false
name: 'buildpath'
abbrev: 'b'
// default: ''
description: 'Path where docusaurus build is.'
})
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'buildpath'
// abbrev: 'b'
// // default: ''
// description: 'Path where docusaurus build is.'
// })
cmd_run.add_flag(Flag{
flag: .string
required: false
name: 'deploykey'
abbrev: 'dk'
// default: ''
description: 'Path of SSH Key used to deploy.'
})
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'deploykey'
// abbrev: 'dk'
// // default: ''
// description: 'Path of SSH Key used to deploy.'
// })
cmd_run.add_flag(Flag{
flag: .string
required: false
name: 'publish'
// default: ''
description: 'Path where to publish.'
})
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'publish'
// // default: ''
// description: 'Path where to publish.'
// })
cmd_run.add_flag(Flag{
flag: .bool
@@ -74,13 +75,13 @@ pub fn cmd_docusaurus(mut cmdroot Command) Command {
description: 'build and publish.'
})
cmd_run.add_flag(Flag{
flag: .bool
required: false
name: 'builddevpublish'
abbrev: 'bpd'
description: 'build dev version and publish.'
})
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'builddevpublish'
// abbrev: 'bpd'
// description: 'build dev version and publish.'
// })
cmd_run.add_flag(Flag{
flag: .bool
@@ -105,102 +106,57 @@ pub fn cmd_docusaurus(mut cmdroot Command) Command {
description: 'Run your dev environment on local browser.'
})
cmd_run.add_flag(Flag{
flag: .bool
required: false
name: 'new'
abbrev: 'n'
description: 'create a new docusaurus site.'
})
cmd_run.add_flag(Flag{
flag: .bool
required: false
name: 'reset'
abbrev: 'r'
description: 'reset the docusaurus building process, reinstall all.'
})
cmdroot.add_command(cmd_run)
return cmdroot
}
fn cmd_docusaurus_execute(cmd Command) ! {
mut open := cmd.flags.get_bool('open') or { false }
// ---------- FLAGS ----------
mut open_ := cmd.flags.get_bool('open') or { false }
mut buildpublish := cmd.flags.get_bool('buildpublish') or { false }
mut builddevpublish := cmd.flags.get_bool('builddevpublish') or { false }
mut dev := cmd.flags.get_bool('dev') or { false }
mut new := cmd.flags.get_bool('new') or { false }
mut reset := cmd.flags.get_bool('reset') or { false }
mut update := cmd.flags.get_bool('update') or { false }
// --- Build Path Logic ---
mut build_path := cmd.flags.get_string('buildpath') or { '' }
if build_path == '' {
build_path = '${os.home_dir()}/hero/var/docusaurus'
// ---------- PATH LOGIC ----------
// Resolve the source directory that contains a cfg subdirectory.
mut path := cmd.flags.get_string('path') or { '' }
mut url := cmd.flags.get_string('url') or { '' }
if path == '' && url == '' {
path = os.getwd()
}
// --- Path Logic ---
mut provided_path := cmd.flags.get_string('path') or { '' }
mut source_path := ''
if provided_path != '' {
if !os.exists(provided_path) || !os.is_dir(provided_path) {
return error('Provided path "${provided_path}" does not exist or is not a directory.')
}
// Check if the provided path contains a cfg subdirectory (ebook directory structure)
cfg_subdir := os.join_path(provided_path, 'cfg')
if os.exists(cfg_subdir) && os.is_dir(cfg_subdir) {
source_path = provided_path
} else {
if provided_path.ends_with('cfg') {
// If path ends with cfg, use parent directory as source
source_path = os.dir(provided_path)
} else {
return error('Provided path "${provided_path}" does not contain a "cfg" subdirectory.')
}
}
} else {
mut cwd := os.getwd()
cfg_dir := os.join_path(cwd, 'cfg')
if !os.exists(cfg_dir) || !os.is_dir(cfg_dir) {
return error('Flag -path not provided and directory "./cfg" not found in the current working directory.')
}
source_path = cwd
}
console.print_header('Running Docusaurus for: ${source_path}')
// Use the centralized site processing function from docusaurus module
mysite := docusaurus.process_site_from_path(source_path, '')!
site_name := mysite.siteconfig.name
// Set up the docusaurus factory
docusaurus.factory_set(
path_build: build_path
reset: reset
install: reset
template_update: reset
docusaurus_path := gittools.path(
git_url: url
path: path
git_reset: reset
git_pull: update
)!
// Add the docusaurus site
mut dsite := docusaurus.dsite_add(
sitename: site_name
path: source_path
play: false // Site already processed
console.print_header('Running Docusaurus for: ${docusaurus_path.path}')
playcmds.run(
heroscript_path: docusaurus_path.path
reset: false
)!
// Execute the requested action directly
// TODO: We need to load the sitename instead, or maybe remove it
mut dsite := docusaurus.dsite_get("")!
if buildpublish {
// Build and publish production-ready artifacts
dsite.build_publish()!
} else if builddevpublish {
dsite.build()!
} else if dev {
// Run local development server
dsite.dev(
open: open
open: open_
watch_changes: false
)!
} else {
// Default: just build the static site
dsite.build()!
}
}

View File

@@ -170,38 +170,8 @@ pub fn cmd_git(mut cmdroot Command) {
abbrev: 'f'
description: 'Filter is part of path of repo e.g. threefoldtech/info_'
})
// c.add_flag(Flag{
// flag: .string
// required: false
// name: 'repo'
// abbrev: 'r'
// description: 'name of repo'
// })
// c.add_flag(Flag{
// flag: .string
// required: false
// name: 'branch'
// abbrev: 'b'
// description: 'branch of repo (optional)'
// })
// c.add_flag(Flag{
// flag: .string
// required: false
// name: 'account'
// abbrev: 'a'
// description: 'name of account e.g. threefoldtech'
// })
// c.add_flag(Flag{
// flag: .string
// required: false
// name: 'provider'
// abbrev: 'p'
// description: 'name of provider e.g. github'
// })
}
for mut c_ in allcmdsref {
mut c := *c_
c.add_flag(Flag{
@@ -245,21 +215,6 @@ fn cmd_git_execute(cmd Command) ! {
// create the filter for doing group actions, or action on 1 repo
mut filter := cmd.flags.get_string('filter') or { '' }
// mut branch := cmd.flags.get_string('branch') or { '' }
// mut repo := cmd.flags.get_string('repo') or { '' }
// mut account := cmd.flags.get_string('account') or { '' }
// mut provider := cmd.flags.get_string('provider') or { '' }
// if cmd.name != 'cd' {
// // check if we are in a git repo
// if repo == '' && account == '' && provider == '' && filter == '' {
// if r0 := gs.get_working_repo() {
// repo = r0.name
// account = r0.account
// provider = r0.provider
// }
// }
// }
if cmd.name in gittools.gitcmds.split(',') {
mut pull := cmd.flags.get_bool('pull') or { false }

View File

@@ -1,121 +0,0 @@
module herocmds
// import freeflowuniverse.herolib.web.mdbook
import freeflowuniverse.herolib.core.pathlib
import cli { Command, Flag }
import os
import freeflowuniverse.herolib.ui.console
// path string //if location on filessytem, if exists, this has prio on git_url
// git_url string // location of where the hero scripts are
// git_pull bool // means when getting new repo will pull even when repo is already there
// git_pullreset bool // means we will force a pull and reset old content
// coderoot string //the location of coderoot if its another one
pub fn cmd_mdbook(mut cmdroot Command) {
mut cmd_mdbook := Command{
name: 'mdbook'
usage: '
## Manage your MDBooks
example:
hero mdbook -u https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/heroscript
If you do -gp it will pull newest book content from git and give error if there are local changes.
If you do -gr it will pull newest book content from git and overwrite local changes (careful).
'
description: 'create, edit, show mdbooks'
required_args: 0
execute: cmd_mdbook_execute
}
cmd_run_add_flags(mut cmd_mdbook)
cmd_mdbook.add_flag(Flag{
flag: .string
name: 'name'
abbrev: 'n'
description: 'name of the mdbook.'
})
// cmd_mdbook.add_flag(Flag{
// flag: .bool
// required: false
// name: 'edit'
// description: 'will open vscode for collections & summary.'
// })
cmd_mdbook.add_flag(Flag{
flag: .bool
required: false
name: 'open'
abbrev: 'o'
description: 'will open the generated book.'
})
mut cmd_list := Command{
sort_flags: true
name: 'list'
execute: cmd_mdbook_list
description: 'will list existing mdbooks'
}
cmd_mdbook.add_command(cmd_list)
cmdroot.add_command(cmd_mdbook)
}
fn cmd_mdbook_list(cmd Command) ! {
console.print_header('MDBooks:')
build_path := os.join_path(os.home_dir(), 'hero/var/mdbuild')
mut build_dir := pathlib.get_dir(path: build_path)!
list := build_dir.list(
recursive: false
dirs_only: true
)!
for path in list.paths {
console.print_stdout(path.name())
}
}
fn cmd_mdbook_execute(cmd Command) ! {
mut name := cmd.flags.get_string('name') or { '' }
mut url := cmd.flags.get_string('url') or { '' }
mut path := cmd.flags.get_string('path') or { '' }
if path.len > 0 || url.len > 0 {
// execute the attached plbook
mut plbook, _ := plbook_run(cmd)!
// get name from the book.generate action
if name == '' {
mut a := plbook.get(filter: 'book.define')!
name = a.params.get('name') or { '' }
}
} else {
mdbook_help(cmd)
}
if name == '' {
console.print_debug('did not find name of book to generate, check in heroscript or specify with --name')
mdbook_help(cmd)
exit(1)
}
edit := cmd.flags.get_bool('edit') or { false }
open := cmd.flags.get_bool('open') or { false }
if edit || open {
// mdbook.book_open(name)!
}
if edit {
// mdbook.book_edit(name)!
}
}
fn mdbook_help(cmd Command) {
console.clear()
console.print_header('Instructions for mdbook:')
console.print_lf(1)
console.print_stdout(cmd.help_message())
console.print_lf(5)
}

View File

@@ -1,143 +0,0 @@
module herocmds
// import freeflowuniverse.herolib.web.starlight
import os
import cli
// pub fn cmd_starlight(mut cmdroot Command) {
// mut cmd_run := Command{
// name: 'starlight'
// description: 'Generate, build, run starlight sites.'
// required_args: 0
// execute: cmd_starlight_execute
// }
// // cmd_run.add_flag(Flag{
// // flag: .bool
// // required: false
// // name: 'reset'
// // abbrev: 'r'
// // description: 'will reset.'
// // })
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'url'
// abbrev: 'u'
// // default: ''
// description: 'Url where starlight source is.'
// })
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'path'
// abbrev: 'p'
// // default: ''
// description: 'Path where starlight source is.'
// })
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'deploykey'
// abbrev: 'dk'
// // default: ''
// description: 'Path of SSH Key used to deploy.'
// })
// cmd_run.add_flag(Flag{
// flag: .string
// required: false
// name: 'publish'
// // default: ''
// description: 'Path where to publish.'
// })
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'buildpublish'
// abbrev: 'bp'
// description: 'build and publish.'
// })
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'builddevpublish'
// abbrev: 'bpd'
// description: 'build dev version and publish.'
// })
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'update'
// description: 'update your environment the template and the repo you are working on (git pull).'
// })
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'dev'
// abbrev: 'd'
// description: 'Run your dev environment on local browser.'
// })
// cmd_run.add_flag(Flag{
// flag: .bool
// required: false
// name: 'new'
// abbrev: 'n'
// description: 'create a new starlight site.'
// })
// cmdroot.add_command(cmd_run)
// }
// fn cmd_starlight_execute(cmd Command) ! {
// mut update := cmd.flags.get_bool('update') or { false }
// mut init := cmd.flags.get_bool('new') or { false }
// mut url := cmd.flags.get_string('url') or { '' }
// mut publish_path := cmd.flags.get_string('publish') or { '' }
// mut deploykey := cmd.flags.get_string('deploykey') or { '' }
// mut path := cmd.flags.get_string('path') or { '' }
// mut buildpublish := cmd.flags.get_bool('buildpublish') or { false }
// mut builddevpublish := cmd.flags.get_bool('builddevpublish') or { false }
// mut dev := cmd.flags.get_bool('dev') or { false }
// // if build== false && build== false && build== false {
// // eprintln("specify build, builddev or dev")
// // exit(1)
// // }
// mut docs := starlight.new(update: update)!
// mut site := docs.get(
// url: url
// path: path
// update: update
// publish_path: publish_path
// deploykey: deploykey
// init: init
// )!
// if publish_path.len > 0 {
// site.build()!
// }
// if buildpublish {
// site.build_publish()!
// }
// if builddevpublish {
// site.build_dev_publish()!
// }
// if dev {
// site.dev(host: 'localhost', port: 3000)!
// }
// }

326
lib/core/herocmds/tofix.md Normal file
View File

@@ -0,0 +1,326 @@
## Overview
The **`lib/core/playcmds`** directory contains the core “playcommands” that are used by the `playcmds.run()` entrypoint. The bulk of the code works, but there are several **logic, naming and deadcode problems** that make the package harder to maintain, cause potential compiletime collisions, and generate confusing TODO comments in the source.
Below youll find a **concise, actionable “TODO list”** grouped by file, together with a **short description of the problem** and **exact code changes that should be applied**. The aim is to get the module to compile cleanly, make the public API consistent, and remove dead / misleading code.
---
## 1⃣ `lib/core/playcmds/play_docusaurus.v`
| Problem | What to do |
|---|---|
| **Name clash** the file defines a **`fn play(mut plbook PlayBook) !`** in the `playcmds` module. This collides with other possible `play` functions (e.g. `play_core`, `play_git`), and the function is never used by the factory. | 1. **Rename** the function to `pub fn play_docusaurus(mut plbook PlayBook) !`. <br>2. Update the `factory.v` to call the renamed function (or simply remove this file and call `docusaurus.play` directly). <br>3. Remove the import of `PlayBook` from the file header if it is no longer needed. |
| **Unused import** `import freeflowuniverse.herolib.core.playbook { PlayBook }` is only needed for the renamed function. If we decide to keep the wrapper, keep it; otherwise, delete the whole file. | Delete or comment out the file if you prefer to call `docusaurus.play` directly from `factory.v`. |
| **Missing documentation** The file has no comment describing why the wrapper exists. | Add a short comment: `// Wrapper used by legacy scripts forwards to the docusaurus module.` |
### Concrete Patch (rename function, update factory)
```v
// lib/core/playcmds/play_docusaurus.v
module playcmds
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.web.docusaurus
// -------------------------------------------------------------------
// Legacy wrapper: older scripts expected a “play” function in the
// playcmds package. We keep it for backwardscompatibility and
// forward the call to the real docusaurus implementation.
// -------------------------------------------------------------------
pub fn play_docusaurus(mut plbook PlayBook) ! {
docusaurus.play(mut plbook)!
}
```
**Update in `lib/core/playcmds/factory.v`**
```v
// Replace the old call (if any) with the new wrapper:
play_docusaurus.play(mut plbook)! // <-- new line, optional
// OR simply delete the import of the wrapper if you decide to
// use the docusaurus module directly.
```
---
## 2⃣ `lib/core/playcmds/play_git.v`
| Problem | What to do |
|---|---|
| **Wrong API name** the code uses **`gittools.get(gittools.GitStructureArgGet{})`** there is no `GitStructureArgGet` struct in the gittools package. The correct type is **`gittools.GitStructureArgs`** (or the default `gittools.GitStructure` argument). | Replace `GitStructureArgGet` with the correct type (`gittools.GitStructureArgs`). |
| **Missing import alias** the file uses `gittools.get` and `gittools.new` but the import is just `import freeflowuniverse.herolib.develop.gittools`. That is fine, but for clarity rename the import to **`gittools`** (it already is) and use the same alias everywhere. |
| **Potential nil `gs`** after a `git.clone` we do `gs = gittools.new(coderoot: coderoot)!`. This shadows the previous `gs` and loses the original configuration (e.g. `light`, `log`). The intent is to **reinitialise** the `GitStructure` **only** when a `coderoot` is explicitly given. Keep the current flow but **document** the intention. |
| **Unused variable `action_`** the variable `action_` is used only for iteration. No problem. |
| **Missing `gittools.GitCloneArgs`** check that the struct is actually named `GitCloneArgs` in the gittools package. If not, change to the proper name. | Verify and, if needed, replace with the correct struct name (`gittools.GitCloneArgs`). |
| **Missing error handling for unknown actions** the code already prints an error and continues when `error_ignore` is true. That part is OK. |
| **Redundant import** the file imports `freeflowuniverse.herolib.ui.console` but only uses `console.print_stderr`. Keep it, but add a comment that it is for verbose error reporting. |
| **Formatting** add a header comment explaining what this file does (process git actions). | Add a comment block at the top of the file. |
### Concrete Patch (partial)
```v
// lib/core/playcmds/play_git.v
module playcmds
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
// ---------------------------------------------------------------
// Git actions interpreter for HeroScript. This file
// parses `!!git.*` actions and forwards them to the
// gittools package.
// ---------------------------------------------------------------
fn play_git(mut plbook PlayBook) ! {
// -----------------------------------------------------------
// !!git.define configure the GitStructure
// -----------------------------------------------------------
define_actions := plbook.find(filter: 'git.define')!
mut gs := if define_actions.len > 0 {
// ... (same as before)
} else {
// Default GitStructure (no args)
gittools.get(gittools.GitStructureArgs{})!
}
// -----------------------------------------------------------
// !!git.clone clone repositories
// -----------------------------------------------------------
// (unchanged)
// -----------------------------------------------------------
// !!git.repo_action pull, commit, push, …
// -----------------------------------------------------------
// (unchanged)
// -----------------------------------------------------------
// !!git.list print repo status
// -----------------------------------------------------------
// (unchanged)
// -----------------------------------------------------------
// !!git.reload_cache reload git cache
// -----------------------------------------------------------
// (unchanged)
}
```
**Fix the `GitStructureArgs`** (if the actual struct name differs, adjust accordingly).
---
## 3⃣ `lib/core/playcmds/play_core.v`
| Problem | What to do |
|---|---|
| **`env_set_once`** the code uses `session.env_set(key, val)` for both `env_set` and `env_set_once`. The **`env_set_once`** method exists on `Session` and prevents overwriting a previously set key. If the intention is to set a variable *only* when it has not been set, use `session.env_set_once(key, val)`. | Change the `env_set_once` case to call `session.env_set_once`. |
| **Unused comment** the comment “// Use env_set instead of env_set_once to avoid duplicate errors” is contradictory. Replace the comment with a clear explanation. |
| **Missing import** `console` is already imported. No changes needed. |
| **Potential missing `session.env` nil check** if `plbook.session` is optional, a nil check should be added. This is defensive but not strictly required because a PlayBook always creates a Session. Still, add a guard. | Add a guard: `if plbook.session == none { return error('No session attached to PlayBook') }` before first use of `session`. |
| **Unused variable** `sitename := session.env_get('SITENAME') or { '' }` is never used. Remove it (or use it for templating if needed). |
| **Formatting** add a comment block at the top explaining purpose of the module. |
### Patch
```v
// lib/core/playcmds/play_core.v
module playcmds
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
// -------------------------------------------------------------------
// Core playcommand processing (context, session, envsubst, etc)
// -------------------------------------------------------------------
fn play_core(mut plbook PlayBook) ! {
// ----------------------------------------------------------------
// 1. Include handling (play include / echo)
// ----------------------------------------------------------------
// ... (unchanged)
// ----------------------------------------------------------------
// 2. Session environment handling
// ----------------------------------------------------------------
// Guard make sure a session exists
mut session := plbook.session or {
return error('PlayBook has no attached Session')
}
// !!session.env_set / env_set_once
for mut action in plbook.find(filter: 'session.')! {
mut p := action.params
match action.name {
'env_set' {
key := p.get('key')!
val := p.get('val') or { p.get('value')! }
session.env_set(key, val)!
}
'env_set_once' {
key := p.get('key')!
val := p.get('val') or { p.get('value')! }
// Use the dedicated “setonce” method
session.env_set_once(key, val)!
}
else { /* ignore unknown subaction */ }
}
action.done = true
}
// ----------------------------------------------------------------
// 3. Template replacement in action parameters
// ----------------------------------------------------------------
// (unchanged)
}
```
---
## 4⃣ `lib/core/playcmds/factory.v`
| Problem | What to do |
|---|---|
| **Unused imports** several imports are commented out. Keep them commented or delete if not needed. |
| **Missing call to the renamed `play_docusaurus`** after renaming, the factory should either call `play_docusaurus` or rely on `docusaurus.play` directly. | Either **(a)** remove the wrapper import entirely (since `docusaurus.play` is already called) **or** add a call to `play_docusaurus` if you want to keep the wrapper. |
| **Potential deadcode** the commented-out sections for `play_ssh`, `play_publisher`, etc., are dead and can be removed to keep the file concise. |
| **Consistency** rename the `run` function to `run_playcmds` (optional) for clarity; not mandatory, but improves readability. |
| **Add documentation** a short header comment describing the purpose of the factory function. | Add comment at top of file. |
### Patch (cleanup)
```v
module playcmds
import freeflowuniverse.herolib.core.playbook { PlayBook, PlayArgs }
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.core.playcmds
import freeflowuniverse.herolib.core.playcmds.{play_core, play_git, play_docusaurus}
import freeflowuniverse.herolib.web.docusaurus as docusaurus_mod // optional alias
// -------------------------------------------------------------------
// run entry point for all HeroScript playcommands
// -------------------------------------------------------------------
pub fn run(args_ PlayArgs) ! {
mut args := args_
mut plbook := args.plbook or {
playbook.new(text: args.heroscript, path: args.heroscript_path)!
}
// Core actions
play_core(mut plbook)!
// Git actions
play_git(mut plbook)!
// Business model (e.g. currency, bizmodel)
bizmodel.play(mut plbook)! // <-- ensure that bizmodel.play exists
// OpenAI client
openai.play(mut plbook)!
// Website / docs
site.play(mut plbook)!
doctree.play(mut plbook)!
// Docusaurus either call the wrapper or the module directly
// play_docusaurus(mut plbook)! // < optional wrapper
docusaurus.play(mut plbook)! // direct call (preferred)
// (optional) other playcommands can be added here
// Ensure we did not leave any actions unprocessed
plbook.empty_check()!
}
```
---
## 5⃣ `_archive` Directory (All Files)
The `_archive` folder contains **dead, commentedout, or placeholder code** (e.g., `bizmodel.v`, `currency.v`, `dagu.v`, etc.). They are compiled into the `playcmds` module but serve no purpose. Keeping them clutters the module namespace and may hide future compile errors.
**Action Items**
| File | Action |
|------|-------|
| `*_archive/*.v` | **Delete the whole `_archive` directory** (or move it outside of `lib/core/playcmds` to a noncompiled location). |
| If any of the archived files contain code that you still need (e.g., `play_juggler`), **move them** to a dedicated `playcmds/juggler.v` file or an appropriate submodule. |
| After removal, run `v .` to verify the package builds without “duplicate module” errors. |
---
## 6⃣ `lib/core/playcmds/play_ssh.v` Minor Cleanup
| Problem | What to do |
|---|---|
| **Unused import** `import freeflowuniverse.herolib.osal.sshagent` is used, fine. |
| **No `pub` on `play_ssh`** the function is private but is referenced from `factory.v`. It is already called as `play_ssh(mut plbook)`. The function is **public** (no `pub` needed because it's called inside the same module). No change needed. |
| **Comment about missing actions** Keep a short comment stating “Currently only `key_add` is supported”. |
| **Add error handling** The `else` branch returns an error. This is fine. No changes needed. |
---
## 7⃣ `lib/core/playcmds/play_luadns.v`
| Problem | What to do |
|---|---|
| **Unused import** `os` is commented out (good). |
| **Unused variable `mut buildroot` etc.** These variables are **commented out** already. No change needed. |
| **Naming** function `play_luadns` is `pub` (good). |
| **Potential missing `luadns` import** The import is correct. No changes. |
---
## 8⃣ `lib/core/playcmds/play_zola.v` **All code is commented out**
No current code. Keep the file as a placeholder for future implementation. No action needed unless you want to **delete** it to keep the repo tidy.
---
## 9⃣ `lib/core/playcmds/readme.md`
| Problem | What to do |
|---|---|
| **Outofdate example** It still refers to `playcmds.run(..., heroscript_path:'')`. The `run` signature now uses `PlayArgs`. Update the README to reflect the new `PlayArgs` struct (heroscript_path is now `heroscript_path` but `heroscript` is a string argument, not a map). |
| **Add example** Provide a tiny example that shows how to run from a `.hero` file using `run(PlayArgs{...})`. | Update README accordingly. |
### Example README update
```md
# Using the playcmds package
```v
import freeflowuniverse.herolib.core.playcmds
mut args := playcmds.PlayArgs{
heroscript: my_hero_script,
heroscript_path: '/tmp/hero',
reset: false,
}
playcmds.run(args)!
```
```
---
## ✅ Summary of Files to Modify
| File | Change(s) |
|------|----------|
| `lib/core/playcmds/play_docusaurus.v` | Rename exported function to `play_docusaurus`, update wrapper or remove. |
| `lib/core/playcmds/play_git.v` | Correct `GitStructure` API, add header comment, ensure correct struct names (`GitStructureArgs`, `GitCloneArgs`). |
| `lib/playcmds/play_core.v` | Use `env_set_once`, add sessionnil guard, remove unused `sitename`, add documentation comment. |
| `lib/playcmds/factory.v` | Clean up imports, call `docusaurus.play` directly (or use renamed wrapper), remove dead/ commented-out sections, add file header comment. |
| `lib/core/playcmds/_archive/*` | **Delete** entire `_archive` folder (or move needed code elsewhere). |
| `lib/core/playcmds/readme.md` | Update example to match current `PlayArgs` struct. |
| (optional) `lib/playcmds/play_docusaurus.v` (if wrapper kept) add wrapper comment. |
| (optional) `lib/playcmds/_archive/...` move any needed code to proper modules. |
---
## 🚀 Next Steps
1. **Apply the patches** listed above (or copypaste the suggested code changes).
2. **Run** `v .` from the repository root to verify that the package builds.
3. **Run** the test suite (`./test_runner.vsh` or the GitHub actions) to ensure no regressions.
4. **Commit** the changes with a clear commit message, e.g. `"fix: rename play_docusaurus, fix gittools API, cleanup playcmds module"`.
After these changes the **`lib/core/playcmds`** module will compile cleanly, the public API will be consistent, and the dead code will no longer pollute the package namespace. Happy coding! 🚀

View File

@@ -30,7 +30,7 @@ pub mut:
// if overwrite this means will overwrite the last one in the directory
pub fn (mut path Path) backup_path(args BackupArgs) !Path {
if !path.exists() && args.restore == false {
error('cannot find path, so cannot create backup for ${path}')
return error('cannot find path, so cannot create backup for ${path}')
}
mut dest := ''
mut rel := ''

View File

@@ -13,6 +13,7 @@ pub mut:
git_reset bool
prio int = 50
priorities map[int]string // filter and give priority, see filtersort method to know how to use
replace map[string]string
// session ?&base.Session
}
@@ -25,10 +26,12 @@ pub mut:
// git_branch string
// git_reset bool
// session &base.Session
// replace map[string]string
// ```
pub fn new(args_ PlayBookNewArgs) !PlayBook {
mut args := args_
mut c := base.context() or { return error('failed to get context: ${err}') }
mut s := c.session_new()!

View File

@@ -92,6 +92,23 @@ pub fn (mut plbook PlayBook) find(args FindArgs) ![]&Action {
return res
}
// use this in play function to make sure we only have one of those actions, the one action is then returned
pub fn (mut plbook PlayBook) ensure_once(args FindArgs) !&Action {
// println('DEBUG: In the error')
mut res := plbook.find(args) or { [] }
// println('res: ${res}')
if res.len == 0 {
return error('No actions found based on filter: ${args.filter}')
}
if res.len > 1 {
return error('More than 1 action found based on filter: ${args.filter}, this Playbook only supports 1.')
}
return res[0] or { panic('bug') }
}
// check its once, if not say that playbook only can have one action
pub fn (mut plbook PlayBook) exists_once(args FindArgs) bool {
mut res := plbook.find(args) or { [] }
return res.len == 1
@@ -101,6 +118,9 @@ pub fn (mut plbook PlayBook) exists_once(args FindArgs) bool {
pub fn (mut plbook PlayBook) max_once(args FindArgs) !bool {
mut res := plbook.find(args) or { [] }
if res.len > 1 {
$if debug {
print_backtrace()
}
return error("found more than one action: '${args.filter}'")
}
return res.len == 1
@@ -126,6 +146,9 @@ pub fn (mut plbook PlayBook) get(args FindArgs) !&Action {
if res.len == 0 {
return error("can't find action: '${args.filter}'")
} else if res.len > 1 {
$if debug {
print_backtrace()
}
return error("found more than one action: '${args.filter}'")
}
return res[0] or { panic('bug') }

View File

@@ -14,6 +14,7 @@ pub mut:
result string // if any result
nractions int
done []int // which actions did we already find/run?
path string
session &base.Session @[skip; str: skip]
}
@@ -83,7 +84,7 @@ pub fn (mut plbook PlayBook) actions_sorted(args SortArgs) ![]&Action {
@[params]
pub struct HeroScriptArgs {
pub mut:
show_done bool = true
show_done bool
}
// serialize to heroscript

View File

@@ -12,6 +12,18 @@ enum State {
othertext
}
// pub struct PlayBookNewArgs {
// path string
// text string
// git_url string
// git_pull bool
// git_branch string
// git_reset bool
// prio int = 50
// priorities map[int]string // filter and give priority, see filtersort method to know how to use
// replace map[string]string
// }
pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
mut args := args_
@@ -21,9 +33,27 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
git_pull: args.git_pull
git_reset: args.git_reset
}
args.path = gittools.path(git_path_args)!.path
newpath := gittools.path(git_path_args)!
args.path = newpath.path
}
if plbook.path=="" && args.path!="" {
plbook.path = args.path
}
if args.text.len>0 && args.replace.len>0{
//now we need to replace any placeholders in the text
for key, value in args.replace {
if key.starts_with('@') || key.starts_with('$') || key.starts_with('[') || key.starts_with('{') {
args.text = args.text.replace(key, value)
}else{
args.text = args.text.replace("@${key}", value)
args.text = args.text.replace("$\{${key}\}", value)
args.text = args.text.replace("\{${key}\}", value)
}
}
}
// walk over directory
if args.path.len > 0 {
// console.print_header("PLBOOK add path:'${args.path}'")
@@ -33,7 +63,7 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
}
if p.is_file() {
c := p.read()!
plbook.add(text: c, prio: args.prio)!
plbook.add(text: c, prio: args.prio, replace: args.replace)!
return
} else if p.is_dir() {
// get .md and .hero files from dir
@@ -45,7 +75,7 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
paths << ol2.paths
for mut p2 in paths {
c2 := p2.read()!
plbook.add(text: c2, prio: args.prio)!
plbook.add(text: c2, prio: args.prio, replace: args.replace)!
}
return
}
@@ -122,7 +152,7 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
paramsdata << line_strip.all_after_first(' ').trim_space()
}
if actionname.starts_with('!!!!!') {
error('there is no action starting with 5 x !')
return error('there is no action starting with 5 x !')
} else if actionname.starts_with('!!!!') {
action.actiontype = .wal
} else if actionname.starts_with('!!!') {

View File

@@ -0,0 +1,60 @@
module playbook
import freeflowuniverse.herolib.develop.gittools // Added import for gittools
//REMARK: include is done in play_core
// // Include external playbook actions (from git repo or local path)
// // based on actions defined as `!!play.include`.
// // Parameters:
// // git_url git repository URL (optional)
// // git_pull pull latest changes (bool, default false)
// // git_reset reset local copy (bool, default false)
// // path local path to include (optional)
// pub fn (mut plbook PlayBook) include() ! {
// // Find all include actions in the playbook
// println(plbook)
// if true{panic("568")}
// mut inc_actions := plbook.find(filter: 'play.include')!
// if inc_actions.len == 0 {
// return
// }
// println(plbook)
// if true{panic("56")}
// for mut inc in inc_actions {
// mut p := inc.params
// // Extract parameters with sensible defaults
// git_url := p.get_default('git_url', '')!
// git_pull := p.get_default_false('git_pull')
// git_reset := p.get_default_false('git_reset')
// path := p.get_default('path', '')!
// // Resolve the path to include
// mut includepath := ''
// if git_url != '' {
// // Resolve a git repository path (may clone / pull)
// includepath = gittools.path(
// git_url: git_url
// path: path
// git_pull: git_pull
// git_reset: git_reset
// )!.path
// } else {
// includepath = path
// }
// // Add the found content (files / directories) to the current playbook.
// // `add` will handle reading files, recursing into directories, etc.
// if includepath != '' {
// plbook.add(path: includepath)!
// }
// // Mark this include action as processed
// inc.done = true
// }
// }

View File

@@ -1,19 +0,0 @@
module playcmds
// import freeflowuniverse.herolib.core.playbook
// fn git(mut actions playbook.Actions, action playbook.Action) ! {
// if action.name == 'init' {
// // means we support initialization afterwards
// c.bizmodel_init(mut actions, action)!
// }
// // if action.name == 'get' {
// // mut gs := gittools.get()!
// // url := action.params.get('url')!
// // branch := action.params.get_default('branch', '')!
// // reset := action.params.get_default_false('reset')!
// // pull := action.params.get_default_false('pull')!
// // mut gr := gs.repo_get_from_url(url: url, branch: branch, pull: pull, reset: reset)!
// // }
// }

View File

@@ -1,21 +0,0 @@
module playcmds
// fn currency_actions(actions_ []playbook.Action) ! {
// mut actions2 := actions.filtersort(actions: actions_, actor: 'currency', book: '*')!
// if actions2.len == 0 {
// return
// }
// mut cs := currency.new()!
// for action in actions2 {
// // TODO: set the currencies
// if action.name == 'default_set' {
// cur := action.params.get('cur')!
// usdval := action.params.get_int('usdval')!
// cs.default_set(cur, usdval)!
// }
// }
// // TODO: add the currency metainfo, do a test
// }

View File

@@ -1,9 +0,0 @@
module playcmds
// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
// pub fn scheduler(heroscript string) ! {
// daguserver.play(
// heroscript: heroscript
// )!
// }

View File

@@ -1,59 +0,0 @@
module playcmds
// import freeflowuniverse.herolib.core.playbook
// import freeflowuniverse.herolib.sysadmin.downloader
// can start with sal, dal, ... the 2nd name is typicall the actor (or topic)
// do this function public and then it breaches out to detail functionality
// pub fn sal_downloader(action playbook.Action) ! {
// match action.actor {
// 'downloader' {
// match action.name {
// 'get' {
// downloader_get(action: action)!
// }
// else {
// return error('actions not supported yet')
// }
// }
// }
// else {
// return error('actor not supported yet')
// }
// }
// }
// fn downloader_get(args ActionExecArgs) ! {
// action := args.action
// // session:=args.action or {panic("no context")} //if we need it here
// mut name := action.params.get_default('name', '')!
// mut downloadpath := action.params.get_default('downloadpath', '')!
// mut url := action.params.get_default('url', '')!
// mut reset := action.params.get_default_false('reset')
// mut gitpull := action.params.get_default_false('gitpull')
// mut minsize_kb := action.params.get_u32_default('minsize_kb', 0)!
// mut maxsize_kb := action.params.get_u32_default('maxsize_kb', 0)!
// mut destlink := action.params.get_default_false('destlink')
// mut dest := action.params.get_default('dest', '')!
// mut hash := action.params.get_default('hash', '')!
// mut metapath := action.params.get_default('metapath', '')!
// mut meta := downloader.download(
// name: name
// downloadpath: downloadpath
// url: url
// reset: reset
// gitpull: gitpull
// minsize_kb: minsize_kb
// maxsize_kb: maxsize_kb
// destlink: destlink
// dest: dest
// hash: hash
// metapath: metapath
// // session:session // TODO IMPLEMENT (also optional)
// )!
// }

View File

@@ -1,143 +0,0 @@
module playcmds
// import freeflowuniverse.herolib.installers.web.caddy as caddy_installer
// import freeflowuniverse.herolib.servers.caddy { CaddyFile }
// import freeflowuniverse.herolib.core.playbook
// import os
// // import net.urllib
// pub fn play_caddy(mut plbook playbook.PlayBook) ! {
// play_caddy_basic(mut plbook)!
// play_caddy_configure(mut plbook)!
// }
// pub fn play_caddy_configure(mut plbook playbook.PlayBook) ! {
// mut caddy_actions := plbook.find(filter: 'caddy_configure')!
// if caddy_actions.len == 0 {
// return
// }
// }
// pub fn play_caddy_basic(mut plbook playbook.PlayBook) ! {
// caddy_actions := plbook.find(filter: 'caddy.')!
// if caddy_actions.len == 0 {
// return
// }
// mut install_actions := plbook.find(filter: 'caddy.install')!
// if install_actions.len > 0 {
// for install_action in install_actions {
// mut p := install_action.params
// xcaddy := p.get_default_false('xcaddy')
// file_path := p.get_default('file_path', '/etc/caddy')!
// file_url := p.get_default('file_url', '')!
// reset := p.get_default_false('reset')
// start := p.get_default_false('start')
// restart := p.get_default_false('restart')
// stop := p.get_default_false('stop')
// homedir := p.get_default('file_url', '')!
// plugins := p.get_list_default('plugins', []string{})!
// caddy_installer.install(
// xcaddy: xcaddy
// file_path: file_path
// file_url: file_url
// reset: reset
// start: start
// restart: restart
// stop: stop
// homedir: homedir
// plugins: plugins
// )!
// }
// }
// mut config_actions := plbook.find(filter: 'caddy.configure')!
// if config_actions.len > 0 {
// mut coderoot := ''
// mut reset := false
// mut pull := false
// mut public_ip := ''
// mut c := caddy.get('')!
// // that to me seems to be wrong, not generic enough
// if config_actions.len > 1 {
// return error('can only have 1 config action for books')
// } else if config_actions.len == 1 {
// mut p := config_actions[0].params
// path := p.get_default('path', '/etc/caddy')!
// url := p.get_default('url', '')!
// public_ip = p.get_default('public_ip', '')!
// c = caddy.configure('', homedir: path)!
// config_actions[0].done = true
// }
// mut caddyfile := CaddyFile{}
// for mut action in plbook.find(filter: 'caddy.add_reverse_proxy')! {
// mut p := action.params
// mut from := p.get_default('from', '')!
// mut to := p.get_default('to', '')!
// if from == '' || to == '' {
// return error('from & to cannot be empty')
// }
// caddyfile.add_reverse_proxy(
// from: from
// to: to
// )!
// action.done = true
// }
// for mut action in plbook.find(filter: 'caddy.add_file_server')! {
// mut p := action.params
// mut domain := p.get_default('domain', '')!
// mut root := p.get_default('root', '')!
// if root.starts_with('~') {
// root = '${os.home_dir()}${root.trim_string_left('~')}'
// }
// if domain == '' || root == '' {
// return error('domain & root cannot be empty')
// }
// caddyfile.add_file_server(
// domain: domain
// root: root
// )!
// action.done = true
// }
// for mut action in plbook.find(filter: 'caddy.add_basic_auth')! {
// mut p := action.params
// mut domain := p.get_default('domain', '')!
// mut username := p.get_default('username', '')!
// mut password := p.get_default('password', '')!
// if domain == '' || username == '' || password == '' {
// return error('domain & root cannot be empty')
// }
// caddyfile.add_basic_auth(
// domain: domain
// username: username
// password: password
// )!
// action.done = true
// }
// for mut action in plbook.find(filter: 'caddy.generate')! {
// c.set_caddyfile(caddyfile)!
// action.done = true
// }
// for mut action in plbook.find(filter: 'caddy.start')! {
// c.start()!
// action.done = true
// }
// c.reload()!
// }
// }

View File

@@ -1,95 +0,0 @@
module playcmds
// import freeflowuniverse.herolib.clients.daguclient
// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import os
// pub fn play_dagu(mut plbook playbook.PlayBook) ! {
// // dagu_actions := plbook.find(filter: 'dagu.')!
// // if dagu_actions.len == 0 {
// // return
// // }
// // play_dagu_basic(mut plbook)!
// // play_dagu_configure(mut plbook)!
// }
// // play_dagu plays the dagu play commands
// pub fn play_dagu_basic(mut plbook playbook.PlayBook) ! {
// // mut install_actions := plbook.find(filter: 'daguserver.configure')!
// // if install_actions.len > 0 {
// // for install_action in install_actions {
// // mut p := install_action.params
// // panic("daguinstall play")
// // }
// // }
// // dagu_actions := plbook.find(filter: 'daguserver.install')!
// // if dagu_actions.len > 0 {
// // panic("daguinstall play")
// // return
// // }
// // mut config_actions := plbook.find(filter: 'dagu.configure')!
// // mut d := if config_actions.len > 1 {
// // return error('can only have 1 config action for dagu')
// // } else if config_actions.len == 1 {
// // mut p := config_actions[0].params
// // instance := p.get_default('instance', 'default')!
// // port := p.get_int_default('port', 8888)!
// // username := p.get_default('username', '')!
// // password := p.get_default('password', '')!
// // config_actions[0].done = true
// // mut server := daguserver.configure(instance,
// // port: port
// // username: username
// // password: password
// // )!
// // server.start()!
// // console.print_debug('Dagu server is running at http://localhost:${port}')
// // console.print_debug('Username: ${username} password: ${password}')
// // // configure dagu client with server url and api secret
// // server_cfg := server.config()!
// // daguclient.get(instance,
// // url: 'http://localhost:${port}'
// // apisecret: server_cfg.secret
// // )!
// // } else {
// // mut server := daguserver.get('')!
// // server.start()!
// // daguclient.get('')!
// // }
// // mut dags := map[string]DAG{}
// // for mut action in plbook.find(filter: 'dagu.new_dag')! {
// // mut p := action.params
// // name := p.get_default('name', '')!
// // dags[name] = DAG{}
// // action.done = true
// // }
// // for mut action in plbook.find(filter: 'dagu.add_step')! {
// // mut p := action.params
// // dag := p.get_default('dag', 'default')!
// // name := p.get_default('name', 'default')!
// // command := p.get_default('command', '')!
// // dags[dag].step_add(
// // nr: dags.len
// // name: name
// // command: command
// // )!
// // }
// // for mut action in plbook.find(filter: 'dagu.run')! {
// // mut p := action.params
// // dag := p.get_default('dag', 'default')!
// // // d.new_dag(dags[dag])!
// // panic('to implement')
// // }
// }

View File

@@ -1,31 +0,0 @@
module playcmds
import freeflowuniverse.herolib.core.playbook
const dagu_script = "
!!dagu.configure
instance: 'test'
username: 'admin'
password: 'testpassword'
!!dagu.new_dag
name: 'test_dag'
!!dagu.add_step
dag: 'test_dag'
name: 'hello_world'
command: 'echo hello world'
!!dagu.add_step
dag: 'test_dag'
name: 'last_step'
command: 'echo last step'
"
fn test_play_dagu() ! {
mut plbook := playbook.new(text: dagu_script)!
play_dagu(mut plbook)!
// panic('s')
}

View File

@@ -1,47 +0,0 @@
module playcmds
import freeflowuniverse.herolib.data.doctree
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.develop.juggler
import os
pub fn play_juggler(mut plbook playbook.PlayBook) ! {
mut coderoot := ''
// mut install := false
mut reset := false
mut pull := false
mut config_actions := plbook.find(filter: 'juggler.configure')!
mut j := juggler.Juggler{}
if config_actions.len > 1 {
return error('can only have 1 config action for juggler')
} else if config_actions.len == 1 {
mut p := config_actions[0].params
path := p.get_default('path', '/etc/juggler')!
url := p.get_default('url', '')!
username := p.get_default('username', '')!
password := p.get_default('password', '')!
port := p.get_int_default('port', 8000)!
j = juggler.configure(
url: 'https://git.threefold.info/projectmycelium/itenv'
username: username
password: password
reset: true
)!
config_actions[0].done = true
}
for mut action in plbook.find(filter: 'juggler.start')! {
j.start()!
action.done = true
}
for mut action in plbook.find(filter: 'juggler.restart')! {
j.restart()!
action.done = true
}
}

View File

@@ -1,8 +0,0 @@
module playcmds
import freeflowuniverse.herolib.core.playbook
// import freeflowuniverse.herolib.hero.publishing
// pub fn play_publisher(mut plbook playbook.PlayBook) ! {
// publishing.play(mut plbook)!
// }

View File

@@ -1,34 +0,0 @@
module playcmds
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.core.playcmds
import freeflowuniverse.herolib.core.pathlib
import os
fn test_play_publisher() {
mut p := pathlib.get_file(path: '/tmp/heroscript/do.hero', create: true)!
s2 := "
!!publisher.new_collection
url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/collections'
reset: false
pull: true
!!book.define
name:'info_tfgrid'
summary_url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md'
title:'ThreeFold Technology'
collections: 'about,dashboard,farmers,library,partners_utilization,tech,p2p'
!!book.publish
name:'tech'
production: false
"
p.write(s2)!
mut plbook := playbook.new(path: '/tmp/heroscript')!
playcmds.play_publisher(mut plbook)!
}

View File

@@ -1,64 +0,0 @@
module playcmds
import freeflowuniverse.herolib.core.playbook
// import freeflowuniverse.herolib.threefold.grid
// import freeflowuniverse.herolib.threefold.tfrobot
// import os
// pub fn play_threefold(mut plbook playbook.PlayBook) ! {
// panic('fix tfrobot module')
// // mut config_actions := plbook.find(filter: 'threefold.configure')!
// // mnemonics_ := os.getenv_opt('TFGRID_MNEMONIC') or { '' }
// // mut ssh_key := os.getenv_opt('SSH_KEY') or { '' }
// // tfrobot.configure('play', network: 'main', mnemonics: mnemonics_)!
// // mut robot := tfrobot.get('play')!
// // if config_actions.len > 1 {
// // return error('can only have 1 config action for threefold')
// // } else if config_actions.len == 1 {
// // mut a := config_actions[0]
// // mut p := a.params
// // mut network := p.get_default('network', 'main')!
// // mnemonics := p.get_default('mnemonics', '')!
// // ssh_key = p.get_default('ssh_key', '')!
// // network = network.to_lower()
// // // mnemonics string
// // // network string = 'main'
// // tfrobot.configure('play', network: network, mnemonics: mnemonics)!
// // robot = tfrobot.get('play')!
// // config_actions[0].done = true
// // }
// // cfg := robot.config()!
// // if cfg.mnemonics == '' {
// // return error('TFGRID_MNEMONIC should be specified as env variable')
// // }
// // if ssh_key == '' {
// // return error('SSHKey should be specified as env variable')
// // }
// // panic('implement')
// // for mut action in plbook.find(filter: 'threefold.deploy_vm')! {
// // mut p := action.params
// // deployment_name := p.get_default('deployment_name', 'deployment')!
// // name := p.get_default('name', 'vm')!
// // ssh_key := p.get_default('ssh_key', '')!
// // cores := p.get_int_default('cores', 1)!
// // memory := p.get_int_default('memory', 20)!
// // panic("implement")
// // action.done = true
// // }
// // for mut action in plbook.find(filter: 'threefold.deploy_zdb')! {
// // panic("implement")
// // action.done = true
// // }
// }

View File

@@ -1,246 +0,0 @@
module playcmds
// import freeflowuniverse.herolib.ui.console
// import freeflowuniverse.herolib.web.zola
// import freeflowuniverse.herolib.core.playbook
// struct WebsiteItem {
// mut:
// name string
// site ?&zola.ZolaSite
// }
// pub fn play_zola(mut plbook playbook.PlayBook) ! {
// // mut coderoot := ''
// mut buildroot := ''
// mut publishroot := ''
// mut install := true
// mut reset := false
// wsactions := plbook.find(filter: 'website.')!
// if wsactions.len == 0 {
// return
// }
// mut config_actions := plbook.find(filter: 'websites:configure')!
// if config_actions.len > 1 {
// return error('can only have 1 config action for websites')
// } else if config_actions.len == 1 {
// mut p := config_actions[0].params
// buildroot = p.get_default('buildroot', '')!
// publishroot = p.get_default('publishroot', '')!
// // coderoot = p.get_default('coderoot', '')!
// install = p.get_default_true('install')
// reset = p.get_default_false('reset')
// config_actions[0].done = true
// }
// mut websites := zola.new(
// path_build: buildroot
// path_publish: publishroot
// install: install
// reset: reset
// )!
// mut ws := WebsiteItem{}
// for mut action in plbook.find(filter: 'website.')! {
// if action.name == 'define' {
// console.print_debug('website.define')
// mut p := action.params
// ws.name = p.get('name')!
// title := p.get_default('title', '')!
// description := p.get_default('description', '')!
// ws.site = websites.new(name: ws.name, title: title, description: description)!
// } else if action.name == 'template_add' {
// console.print_debug('website.template_add')
// mut p := action.params
// url := p.get_default('url', '')!
// mut site_ := ws.site or {
// return error("can't find website for template_add, should have been defined before with !!website.define")
// }
// site_.template_add(url: url)!
// } else if action.name == 'content_add' {
// console.print_debug('website.content_add')
// mut p := action.params
// url := p.get_default('url', '')!
// mut site_ := ws.site or {
// return error("can't find website for content_add, should have been defined before with !!website.define")
// }
// site_.content_add(url: url)!
// } else if action.name == 'doctree_add' {
// console.print_debug('website.doctree_add')
// mut p := action.params
// url := p.get_default('url', '')!
// pull := p.get_default_false('pull')
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.doctree_add(url: url, pull: pull)!
// } else if action.name == 'post_add' {
// console.print_debug('website.post_add')
// mut p := action.params
// name := p.get_default('name', '')!
// collection := p.get_default('collection', '')!
// file := p.get_default('file', '')!
// page := p.get_default('page', '')!
// pointer := p.get_default('pointer', '')!
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.post_add(name: name, collection: collection, file: file, pointer: pointer)!
// } else if action.name == 'blog_add' {
// console.print_debug('website.blog_add')
// mut p := action.params
// name := p.get_default('name', '')!
// collection := p.get_default('collection', '')!
// file := p.get_default('file', '')!
// page := p.get_default('page', '')!
// pointer := p.get_default('pointer', '')!
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.blog_add(name: name)!
// } else if action.name == 'person_add' {
// console.print_debug('website.person_add')
// mut p := action.params
// name := p.get_default('name', '')!
// page := p.get_default('page', '')!
// collection := p.get_default('collection', '')!
// file := p.get_default('file', '')!
// pointer := p.get_default('pointer', '')!
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.person_add(
// name: name
// collection: collection
// file: file
// page: page
// pointer: pointer
// )!
// } else if action.name == 'people_add' {
// console.print_debug('website.people_add')
// mut p := action.params
// name := p.get_default('name', '')!
// description := p.get_default('description', '')!
// sort_by_ := p.get_default('sort_by', '')!
// mut site_ := ws.site or {
// return error("can't find website for people_add, should have been defined before with !!website.define")
// }
// sort_by := zola.SortBy.from(sort_by_)!
// site_.people_add(
// name: name
// title: p.get_default('title', '')!
// sort_by: sort_by
// description: description
// )!
// } else if action.name == 'blog_add' {
// console.print_debug('website.blog_add')
// mut p := action.params
// name := p.get_default('name', '')!
// description := p.get_default('description', '')!
// sort_by_ := p.get_default('sort_by', '')!
// mut site_ := ws.site or {
// return error("can't find website for people_add, should have been defined before with !!website.define")
// }
// sort_by := zola.SortBy.from(sort_by_)!
// site_.blog_add(
// name: name
// title: p.get_default('title', '')!
// sort_by: sort_by
// description: description
// )!
// } else if action.name == 'news_add' {
// console.print_debug('website.news_add')
// mut p := action.params
// name := p.get_default('name', '')!
// collection := p.get_default('collection', '')!
// pointer := p.get_default('pointer', '')!
// file := p.get_default('file', '')!
// mut site_ := ws.site or {
// return error("can't find website for news_add, should have been defined before with !!website.define")
// }
// site_.article_add(name: name, collection: collection, file: file, pointer: pointer)!
// } else if action.name == 'header_add' {
// console.print_debug('website.header_add')
// mut p := action.params
// template := p.get_default('template', '')!
// logo := p.get_default('logo', '')!
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.header_add(template: template, logo: logo)!
// } else if action.name == 'header_link_add' {
// console.print_debug('website.header_link_add')
// mut p := action.params
// page := p.get_default('page', '')!
// label := p.get_default('label', '')!
// mut site_ := ws.site or {
// return error("can't find website for header_link_add, should have been defined before with !!website.define")
// }
// site_.header_link_add(page: page, label: label)!
// } else if action.name == 'footer_add' {
// console.print_debug('website.footer_add')
// mut p := action.params
// template := p.get_default('template', '')!
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.footer_add(template: template)!
// } else if action.name == 'page_add' {
// console.print_debug('website.page_add')
// mut p := action.params
// name := p.get_default('name', '')!
// collection := p.get_default('collection', '')!
// file := p.get_default('file', '')!
// homepage := p.get_default_false('homepage')
// mut site_ := ws.site or {
// return error("can't find website for doctree_add, should have been defined before with !!website.define")
// }
// site_.page_add(name: name, collection: collection, file: file, homepage: homepage)!
// // }else if action.name=="pull"{
// // mut site_:=ws.site or { return error("can't find website for pull, should have been defined before with !!website.define")}
// // site_.pull()!
// } else if action.name == 'section_add' {
// console.print_debug('website.section_add')
// // mut p := action.params
// // name := p.get_default('name', '')!
// // // collection := p.get_default('collection', '')!
// // // file := p.get_default('file', '')!
// // // homepage := p.get_default_false('homepage')
// // mut site_ := ws.site or {
// // return error("can't find website for doctree_add, should have been defined before with !!website.define")
// // }
// // site_.add_section(name: name)!
// // }else if action.name=="pull"{
// // mut site_:=ws.site or { return error("can't find website for pull, should have been defined before with !!website.define")}
// // site_.pull()!
// } else if action.name == 'generate' {
// mut site_ := ws.site or {
// return error("can't find website for generate, should have been defined before with !!website.define")
// }
// site_.generate()!
// // site_.serve()!
// } else {
// return error("Cannot find right action for website. Found '${action.name}' which is a non understood action for !!website.")
// }
// action.done = true
// }
// }

View File

@@ -1,21 +1,15 @@
module playcmds
// import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.data.doctree
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.web.site
import freeflowuniverse.herolib.web.docusaurus
import freeflowuniverse.herolib.clients.openai
// import freeflowuniverse.herolib.hero.publishing
// import freeflowuniverse.herolib.threefold.grid4.gridsimulator
// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
// import freeflowuniverse.herolib.threefold.grid4.farmingsimulator
// import freeflowuniverse.herolib.web.components.slides
// import freeflowuniverse.herolib.installers.base as base_install
// import freeflowuniverse.herolib.installers.infra.coredns
// import freeflowuniverse.herolib.virt.hetzner
// import freeflowuniverse.herolib.clients.b2
// -------------------------------------------------------------------
// run entry point for all HeroScript playcommands
// -------------------------------------------------------------------
@[params]
pub struct PlayArgs {
@@ -28,33 +22,29 @@ pub mut:
pub fn run(args_ PlayArgs) ! {
mut args := args_
// println('DEBUG: the args is: ${args}')
mut plbook := args.plbook or {
playbook.new(text: args.heroscript, path: args.heroscript_path)!
}
// Core actions
play_core(mut plbook)!
// Git actions
play_git(mut plbook)!
// play_ssh(mut plbook)!
// play_publisher(mut plbook)!
// play_zola(mut plbook)!
// play_caddy(mut plbook)!
// play_juggler(mut plbook)!
// play_luadns(mut plbook)!
// hetzner.heroplay(mut plbook)!
// b2.heroplay(mut plbook)!
// plbook = farmingsimulator.play(mut plbook)!
// plbook = gridsimulator.play(mut plbook)!
// Business model (e.g. currency, bizmodel)
bizmodel.play(mut plbook)!
doctree.play(mut plbook)!
docusaurus.play(mut plbook)!
// OpenAI client
openai.play(mut plbook)!
// slides.play(mut plbook)!
// base_install(play(mut plbook)!
// coredns.play(mut plbook)!
// Website / docs
site.play(mut plbook)!
doctree.play(mut plbook)!
// plbook.empty_check()!
docusaurus.play(mut plbook)!
// Ensure we did not leave any actions unprocessed
plbook.empty_check()!
}

View File

@@ -4,41 +4,31 @@ import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import os
// !!context.configure
// name:'test'
// coderoot:...
// interactive:true
// -------------------------------------------------------------------
// Core playcommand processing (context, session, envsubst, etc)
// -------------------------------------------------------------------
fn play_core(mut plbook PlayBook) ! {
// for mut action in plbook.find(filter: 'context.configure')! {
// mut p := action.params
// mut session := plbook.session
// if p.exists('interactive') {
// session.interactive = p.get_default_false('interactive')
// }
// if p.exists('coderoot') {
// panic('implement')
// // mut coderoot := p.get_path_create('coderoot')!
// // mut gs := gittools.get()!
// }
// action.done = true
// }
// ----------------------------------------------------------------
// 1. Include handling (play include / echo)
// ----------------------------------------------------------------
// Track included paths to prevent infinite recursion
mut included_paths := map[string]bool{}
for action_ in plbook.find(filter: 'play.*')! {
for mut action_ in plbook.find(filter: 'play.*')! {
if action_.name == 'include' {
console.print_debug('play run:${action_}')
mut action := *action_
mut toreplace := action.params.get_default('replace', '')!
mut playrunpath := action.params.get_default('path', '')!
if playrunpath.len == 0 {
action.name = 'pull'
playrunpath = gittools.get_repo_path(
path: action.params.get_default('path', '')!
path: playrunpath
git_url: action.params.get_default('git_url', '')!
git_reset: action.params.get_default_false('git_reset')
git_pull: action.params.get_default_false('git_pull')
@@ -48,15 +38,24 @@ fn play_core(mut plbook PlayBook) ! {
return error("can't run a heroscript didn't find url or path.")
}
// console.print_debug('play run:\n${action_}')
if ! playrunpath.starts_with('/') {
playrunpath=os.abs_path("${plbook.path}/${playrunpath}")
}
console.print_debug('play run include path:${playrunpath}')
// Check for cycle detection
if playrunpath in included_paths {
console.print_debug('Skipping already included path: ${playrunpath}')
continue
}
console.print_debug('play run path:${playrunpath}')
toreplacedict:=texttools.to_map(toreplace)
included_paths[playrunpath] = true
plbook.add(path: playrunpath)!
plbook.add(path: playrunpath,replace:toreplacedict)!
action.done = true
}
if action_.name == 'echo' {
content := action_.params.get_default('content', "didn't find content")!
@@ -64,30 +63,38 @@ fn play_core(mut plbook PlayBook) ! {
}
}
for mut action in plbook.find(filter: 'session.')! {
mut p := action.params
mut session := plbook.session
// ----------------------------------------------------------------
// 2. Session environment handling
// ----------------------------------------------------------------
// Guard make sure a session exists
mut session := plbook.session
// !!session.env_set / env_set_once
for mut action in plbook.find(filter: 'session.')! {
//!!session.env_set key:'JWT_SHARED_KEY' val:'...'
if action.name == 'env_set' {
mut key := p.get('key')!
mut val := p.get('val') or { p.get('value')! }
session.env_set(key, val)!
}
if action.name == 'env_set_once' {
mut key := p.get('key')!
mut val := p.get('val') or { p.get('value')! }
// Use env_set instead of env_set_once to avoid duplicate errors
session.env_set(key, val)!
}
action.done = true
}
mut session := plbook.session
sitename := session.env_get('SITENAME') or { '' }
mut p := action.params
match action.name {
'env_set' {
key := p.get('key')!
val := p.get('val') or { p.get('value')! }
session.env_set(key, val)!
}
'env_set_once' {
key := p.get('key')!
val := p.get('val') or { p.get('value')! }
// Use the dedicated setonce method
session.env_set_once(key, val)!
}
else { /* ignore unknown subaction */ }
}
action.done = true
}
// ----------------------------------------------------------------
// 3. Template replacement in action parameters
// ----------------------------------------------------------------
// Apply template replacement from session environment variables
if session.env.len > 0 {
// Create a map with name_fix applied to keys for template replacement
@@ -135,4 +142,5 @@ fn play_core(mut plbook PlayBook) ! {
session.save()!
action.done = true
}
}

View File

@@ -1,10 +0,0 @@
module playcmds
import freeflowuniverse.herolib.core.playbook { PlayBook }
// import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.web.docusaurus
fn play(mut plbook PlayBook) ! {
// Use the new docusaurus.play() function which handles the new API structure
docusaurus.play(mut plbook)!
}

View File

@@ -2,10 +2,18 @@ module playcmds
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.ui.console // For verbose error reporting
// ---------------------------------------------------------------
// Git actions interpreter for HeroScript. This file
// parses `!!git.*` actions and forwards them to the
// gittools package.
// ---------------------------------------------------------------
fn play_git(mut plbook PlayBook) ! {
// Handle !!git.define action first to configure GitStructure
// -----------------------------------------------------------
// !!git.define configure the GitStructure
// -----------------------------------------------------------
define_actions := plbook.find(filter: 'git.define')!
mut gs := if define_actions.len > 0 {
mut p := define_actions[0].params
@@ -17,7 +25,7 @@ fn play_git(mut plbook PlayBook) ! {
ssh_key_path := p.get_default('ssh_key_path', '')!
reload := p.get_default_false('reload')
gittools.new( // Changed to gittools.new
gittools.new(
coderoot: coderoot
light: light
log: log
@@ -27,11 +35,13 @@ fn play_git(mut plbook PlayBook) ! {
reload: reload
)!
} else {
// Initialize GitStructure with defaults
gittools.get(gittools.GitStructureArgGet{})! // Changed to gittools.get with default args
// Default GitStructure (no args)
gittools.get()!
}
// Handle !!git.clone action
// -----------------------------------------------------------
// !!git.clone clone repositories
// -----------------------------------------------------------
clone_actions := plbook.find(filter: 'git.clone')!
for action in clone_actions {
mut p := action.params
@@ -41,14 +51,16 @@ fn play_git(mut plbook PlayBook) ! {
light := p.get_default_true('light')
recursive := p.get_default_false('recursive')
mut clone_args := gittools.GitCloneArgs{ // Changed to gittools.GitCloneArgs
mut clone_args := gittools.GitCloneArgs{
url: url
sshkey: sshkey
recursive: recursive
light: light
}
if coderoot.len > 0 {
gs = gittools.new(coderoot: coderoot)! // Changed to gittools.new
// Re-initialize GitStructure only when a coderoot is explicitly given.
// This shadows the previous `gs` and loses the original configuration (e.g. `light`, `log`).
gs = gittools.new(coderoot: coderoot)!
}
gs.clone(clone_args)!
}

View File

@@ -14,6 +14,7 @@ fn play_ssh(mut plbook PlayBook) ! {
agent.add(name, privkey)!
}
else {
// Currently only `key_add` is supported
return error('action name ${action.name} not supported')
}
}

View File

@@ -23,6 +23,26 @@ pub fn to_array_int(r string) []int {
return r2
}
//convert a:b ,c:d,e:f to dict with keys a,c,e and corresponding values b,d,f
pub fn to_map(mapstring string) map[string]string {
mut result := map[string]string{}
mut mapstring_array := to_array(mapstring)
for item in mapstring_array {
if item.contains(':') {
parts := item.split(':')
if parts.len == 2 {
result[parts[0].trim_space()] = parts[1].trim_space().trim("'\"").trim_space()
} else {
panic('to_map: expected key:value pairs, got: ${item}')
}
} else {
panic('to_map: expected key:value pairs, got: ${item}')
}
}
return result
}
// intelligent way how to map a line to a map
//```
// r:=texttools.to_map("name,-,-,-,-,pid,-,-,-,-,path",
@@ -37,7 +57,7 @@ pub fn to_array_int(r string) []int {
// "root 304 0.0 0.0 408185328 1360 ?? S 16Dec23 0:34.06 \n \n")
// assert {'name': 'root', 'pid': '1360', 'path': ''} == r3
//```
pub fn to_map(mapstring string, line string, delimiter_ string) map[string]string {
pub fn to_map_special(mapstring string, line string, delimiter_ string) map[string]string {
mapstring_array := split_smart(mapstring, '')
mut line_array := split_smart(line, '')
mut result := map[string]string{}
@@ -71,7 +91,7 @@ pub fn to_list_map(mapstring string, txt_ string, delimiter_ string) []map[strin
mut txt := remove_empty_lines(txt_)
txt = dedent(txt)
for line in txt.split_into_lines() {
result << to_map(mapstring, line, delimiter_)
result << to_map_special(mapstring, line, delimiter_)
}
return result
}

View File

@@ -6,8 +6,8 @@ import freeflowuniverse.herolib.core.playbook { PlayBook }
pub fn play(mut plbook PlayBook) ! {
mut doctrees := map[string]&Tree{}
collection_actions := plbook.find(filter: 'doctree.scan')!
for action in collection_actions {
mut collection_actions := plbook.find(filter: 'doctree.scan')!
for mut action in collection_actions {
mut p := action.params
name := p.get_default('name', 'main')!
mut doctree := doctrees[name] or {
@@ -20,11 +20,11 @@ pub fn play(mut plbook PlayBook) ! {
git_reset := p.get_default_false('git_reset')
git_pull := p.get_default_false('git_pull')
doctree.scan(path: path, git_url: git_url, git_reset: git_reset, git_pull: git_pull)!
action.done = true
tree_set(doctree)
}
export_actions := plbook.find(filter: 'doctree.export')!
mut export_actions := plbook.find(filter: 'doctree.export')!
if export_actions.len == 0 && collection_actions.len > 0 {
// Only auto-export if we have collections to export
name0 := 'main'
@@ -38,7 +38,7 @@ pub fn play(mut plbook PlayBook) ! {
}
}
for action in export_actions {
for mut action in export_actions {
mut p := action.params
name := p.get_default('name', 'main')!
destination := p.get('destination')!
@@ -50,6 +50,7 @@ pub fn play(mut plbook PlayBook) ! {
reset: reset
exclude_errors: exclude_errors
)!
action.done = true
}
// println(tree_list())

View File

@@ -12,7 +12,9 @@ pub fn (params &Params) get(key_ string) !string {
return p.value.trim(' ')
}
}
// print_backtrace()
$if debug {
print_backtrace()
}
return error('Did not find key:${key} in ${params}')
}
@@ -156,7 +158,10 @@ pub fn (params &Params) get_int_default(key string, defval int) !int {
}
pub fn (params &Params) get_default_true(key string) bool {
mut r := params.get(key) or { '' }
mut r := ""
if params.exists(key) {
r = params.get(key) or { panic("bug") }
}
r = texttools.name_fix_no_underscore(r)
if r == '' || r == '1' || r == 'true' || r == 'y' || r == 'yes' {
return true
@@ -165,8 +170,10 @@ pub fn (params &Params) get_default_true(key string) bool {
}
pub fn (params &Params) get_default_false(key string) bool {
mut r := params.get(key) or { '' }
r = texttools.name_fix_no_underscore(r)
mut r := ""
if params.exists(key) {
r = params.get(key) or { panic("bug") }
} r = texttools.name_fix_no_underscore(r)
if r == '' || r == '0' || r == 'false' || r == 'n' || r == 'no' {
return false
}

View File

@@ -15,14 +15,22 @@ pub mut:
git_url string
git_pull bool
git_reset bool
git_root string
}
// get_repo_path implements the GitUrlResolver interface
pub fn get_repo_path(args GetRepoArgs) !string {
if os.exists(args.path) {
return args.path
if args.path!=""{
if os.exists(args.path) {
return args.path
}else{
if args.git_url == "" {
return error("can't resolve git repo path without url or existing path, ${args.path} does not exist.")
}
}
}
mut gs := get()!
mut gs := get(coderoot:args.git_root)!
mut repo := gs.get_repo(
url: args.git_url
pull: args.git_pull

View File

@@ -116,8 +116,11 @@ pub fn (mut gitstructure GitStructure) get_repo(args_ ReposGetArgs) !&GitRepo {
}
if repositories.len > 1 {
repos := repositories.map('- ${it} ${it.account}.${it.name}').join_lines()
return error('Found more than one repository for \n${args}\n${repos}')
// repos := repositories.map('- ${it.account}.${it.name}').join_lines()
$if debug {
print_backtrace()
}
return error('Found more than one repository for \n${args}')
}
// the pull & reset was not used, now re-inserted

View File

@@ -0,0 +1,8 @@
!!hero_code.generate_client
name:'reprompt'
classname:'RepromptWorkspace'
singleton:0
default:1
hasconfig:1
reset:0

View File

@@ -0,0 +1,30 @@
# reprompt
To get started
```vlang
import freeflowuniverse.herolib.clients. reprompt
mut client:= reprompt.get()!
client...
```
## example heroscript
```hero
!!reprompt.configure
secret: '...'
host: 'localhost'
port: 8888
```

View File

@@ -0,0 +1,12 @@
module reprompt
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.core.pathlib
import os
// your checking & initialization code if needed
fn (mut ws RepromptWorkspace) reprompt() !string {
// TODO: fill in template based on selection
return ''
}

View File

@@ -0,0 +1,102 @@
module reprompt
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.ui.console
__global (
reprompt_global map[string]&RepromptWorkspace
reprompt_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string
}
fn args_get(args_ ArgsGet) ArgsGet {
mut args := args_
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&RepromptWorkspace {
mut context := base.context()!
mut args := args_get(args_)
mut obj := RepromptWorkspace{
name: args.name
}
if args.name !in reprompt_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('reprompt', args.name)!
mut obj_ := heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return reprompt_global[args.name] or {
println(reprompt_global)
// bug if we get here because should be in globals
panic('could not get config for reprompt with name, is bug:${args.name}')
}
}
// register the config for the future
pub fn set(o RepromptWorkspace) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('reprompt', o.name, heroscript)!
}
// does the config exists?
pub fn exists(args_ ArgsGet) !bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists('reprompt', args.name)
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('reprompt', args.name)!
if args.name in reprompt_global {
// del reprompt_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o RepromptWorkspace) ! {
mut o2 := obj_init(o)!
reprompt_global[o.name] = &o2
reprompt_default = o.name
}
pub fn play(mut plbook PlayBook) ! {
mut install_actions := plbook.find(filter: 'reprompt.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
}
}
}
// switch instance to be used for reprompt
pub fn switch(name string) {
reprompt_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -0,0 +1,48 @@
module reprompt
import freeflowuniverse.herolib.data.paramsparser
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.core.pathlib
import os
pub const version = '0.0.0'
const singleton = false
const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct RepromptWorkspace {
pub mut:
name string = 'default'
dirs []RepromptDir
}
pub struct RepromptDir {
pub mut:
path pathlib.Path
selections []string // paths selected in the RepromptDir
}
// your checking & initialization code if needed
fn obj_init(mycfg_ RepromptWorkspace) !RepromptWorkspace {
mut mycfg := mycfg_
if mycfg.password == '' && mycfg.secret == '' {
return error('password or secret needs to be filled in for ${mycfg.name}')
}
return mycfg
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj RepromptWorkspace) !string {
// create heroscript following template
// check for our homedir on our machine and replace in the heroscript to @HOME in path
return encoderhero.encode[RepromptWorkspace](obj)!
}
pub fn heroscript_loads(heroscript string) !RepromptWorkspace {
// TODO: parse heroscript populate RepromptWorkspace
mut obj := encoderhero.decode[RepromptWorkspace](heroscript)!
return obj
}

View File

@@ -0,0 +1,10 @@
!!reprompt.configure name:"default"
!!reprompt.workspace_dir name:"default"
path:"@HOME/code/github/freeflowuniverse/herolib/lib/builder"
selection:"path1,path2" //paths are relative in the path of workspace
filter_exclude:","
filter_include:","

View File

@@ -0,0 +1 @@
TODO:...

File diff suppressed because it is too large Load Diff

View File

@@ -53,7 +53,7 @@ pub fn ping(args PingArgs) !PingResult {
}
else {
// println("${err} ${err.code()}")
error("can't ping on osx (${err.code()})\n${err}")
return error("can't ping on osx (${err.code()})\n${err}")
}
}
} else if platform_ == .ubuntu {

View File

@@ -388,11 +388,11 @@ fn (mut self TFDeployment) save() ! {
}
fn (self TFDeployment) compress(data []u8) ![]u8 {
return zlib.compress(data) or { error('Cannot compress the data due to: ${err}') }
return zlib.compress(data) or { return error('Cannot compress the data due to: ${err}') }
}
fn (self TFDeployment) decompress(data []u8) ![]u8 {
return zlib.decompress(data) or { error('Cannot decompress the data due to: ${err}') }
return zlib.decompress(data) or { return error('Cannot decompress the data due to: ${err}') }
}
fn (self TFDeployment) encrypt(compressed []u8) ![]u8 {

View File

@@ -24,12 +24,6 @@ hero docusaurus -b -path /path/to/your/site
hero docusaurus -bp -path /path/to/your/site
```
The hero command automatically:
- Processes all heroscript files in `cfg/` directory and subdirectories
- Filters out global includes (`!!play.include`) while preserving page definitions
- Creates and configures the docusaurus site
- Handles site name resolution from configuration
### Example HeroScript

View File

@@ -0,0 +1,66 @@
module docusaurus
import os
import freeflowuniverse.herolib.core.pathlib
__global (
docusaurus_sites map[string]&DocSite
docusaurus_config []DocusaurusConfigParams
docusaurus_last string //the last one we worked with
)
pub struct DocusaurusConfig {
pub mut:
path_build pathlib.Path
path_publish pathlib.Path
install bool
reset bool
template_update bool
coderoot string
}
@[params]
pub struct DocusaurusConfigParams {
pub mut:
path_build string
path_publish string
install bool
reset bool
template_update bool
coderoot string
}
//return the last know config
pub fn config() !DocusaurusConfig {
if docusaurus_config.len == 0 {
docusaurus_config << DocusaurusConfigParams{}
}
mut args:= docusaurus_config[0] or { panic("bug in docusaurus config") }
if args.path_build == '' {
args.path_build = '${os.home_dir()}/hero/var/docusaurus/build'
}
if args.path_publish == '' {
args.path_publish = '${os.home_dir()}/hero/var/docusaurus/publish'
}
if !os.exists('${args.path_build}/node_modules') {
args.install = true
}
mut c := DocusaurusConfig{
path_publish: pathlib.get_dir(path: args.path_publish, create: true)!
path_build: pathlib.get_dir(path: args.path_build, create: true)!
coderoot: args.coderoot
install: args.install
reset: args.reset
template_update: args.template_update
}
if c.install {
install(c)!
c.install=true
}
return c
}
pub fn config_set(args_ DocusaurusConfigParams) ! {
docusaurus_config = [args_]
}

View File

@@ -1,25 +1,23 @@
module docusaurus
import freeflowuniverse.herolib.osal.screen
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.web.site as sitemodule
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.ui.console
import time
@[heap]
pub struct DocSite {
pub mut:
name string
url string
path_src pathlib.Path
// path_src pathlib.Path
path_publish pathlib.Path
path_build pathlib.Path
errors []SiteError
config Configuration
website sitemodule.Site
}
generated bool
}
pub fn (mut s DocSite) build() ! {
s.generate()!
@@ -52,6 +50,21 @@ pub fn (mut s DocSite) build_publish() ! {
'
retry: 0
)!
for item in s.website.siteconfig.build_dest {
if item.path.trim_space().trim("/ ") == "" {
$if debug{
print_backtrace()
}
return error("build destination path is empty for docusaurus.")
}
osal.exec(
cmd: '
cd ${s.path_build.path}
rsync -avz --delete -e "ssh -p 22 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" build/ ${item.path}
'
)!
}
}
@[params]
@@ -81,104 +94,6 @@ pub fn (mut s DocSite) dev(args DevArgs) ! {
s.open()!
}
pub fn (mut s DocSite) dev_watch(args DevArgs) ! {
s.generate()!
// Create screen session for docusaurus development server
mut screen_name := 'docusaurus'
mut sf := screen.new()!
// Add and start a new screen session
mut scr := sf.add(
name: screen_name
cmd: '/bin/bash'
start: true
attach: false
reset: true
)!
// Send commands to the screen session
console.print_item('To view the server output:: cd ${s.path_build.path}')
scr.cmd_send('cd ${s.path_build.path}')!
// Start script recording in the screen session for log streaming
log_file := '/tmp/docusaurus_${screen_name}.log'
script_cmd := 'script -f ${log_file}'
scr.cmd_send(script_cmd)!
// Small delay to ensure script is ready
time.sleep(500 * time.millisecond)
// Start bun in the scripted session
bun_cmd := 'bun start -p ${args.port} -h ${args.host}'
scr.cmd_send(bun_cmd)!
// Stream the log output to current terminal
console.print_header(' Docusaurus Development Server')
console.print_item('Streaming server output... Press Ctrl+C to detach and leave server running')
console.print_item('Server will be available at: http://${args.host}:${args.port}')
console.print_item('To reattach later: screen -r ${screen_name}')
println('')
// Stream logs until user interrupts
s.stream_logs(log_file, screen_name)!
// After user interrupts, show final instructions
console.print_header(' Server Running in Background')
console.print_item(' Development server is running in background')
console.print_item('Server URL: http://${args.host}:${args.port}')
console.print_item('To reattach: screen -r ${screen_name}')
console.print_item('To stop server: screen -S ${screen_name} -X kill')
console.print_item('The site content is on: ${s.path_src.path}/docs')
// Start the watcher in a separate thread
// mut tf:=spawn watch_docs(docs_path, s.path_src.path, s.path_build.path)
// tf.wait()!
println('\n')
if args.open {
s.open()!
}
if args.watch_changes {
docs_path := '${s.path_src.path}/docs'
watch_docs(docs_path, s.path_src.path, s.path_build.path)!
}
}
// Stream logs from script file to current terminal until user interrupts
fn (mut s DocSite) stream_logs(log_file string, screen_name string) ! {
// Wait a moment for the log file to be created
mut attempts := 0
for !os.exists(log_file) && attempts < 10 {
time.sleep(200 * time.millisecond)
attempts++
}
if !os.exists(log_file) {
console.print_stderr('Warning: Log file not created, falling back to screen attach')
console.print_item('Attaching to screen session... Press Ctrl+A then D to detach')
// Fallback to direct screen attach
osal.execute_interactive('screen -r ${screen_name}')!
return
}
// Use tail -f to stream the log file
// The -f flag follows the file as it grows
tail_cmd := 'tail -f ${log_file}'
// Execute tail in interactive mode - this will stream until Ctrl+C
osal.execute_interactive(tail_cmd) or {
// If tail fails, try alternative approach
console.print_stderr('Log streaming failed, attaching to screen session...')
osal.execute_interactive('screen -r ${screen_name}')!
return
}
// Clean up the log file after streaming
os.rm(log_file) or {}
}
@[params]
pub struct ErrorArgs {
pub mut:

View File

@@ -1,178 +0,0 @@
module docusaurus
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.web.site
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.core.playbook
// import freeflowuniverse.herolib.data.doctree
// Recursively process heroscript files in a directory
fn process_heroscript_files_recursive(dir_path string) !string {
mut combined_heroscript := ''
files := os.ls(dir_path) or { return combined_heroscript }
for file in files {
file_path := os.join_path(dir_path, file)
if os.is_dir(file_path) {
// Recursively process subdirectories
subdir_content := process_heroscript_files_recursive(file_path)!
combined_heroscript += subdir_content
} else if file.ends_with('.heroscript') {
content := os.read_file(file_path) or { continue }
// Filter out only the play.include lines while keeping all other content
lines := content.split('\n')
mut filtered_lines := []string{}
for line in lines {
trimmed := line.trim_space()
if !trimmed.starts_with('!!play.include') {
filtered_lines << line
}
}
filtered_content := filtered_lines.join('\n')
// Only add if there's meaningful content after filtering
if filtered_content.trim_space().len > 0 {
combined_heroscript += filtered_content + '\n\n'
}
}
}
return combined_heroscript
}
// Central function to process site configuration from a path
// This is the single point of use for all site processing logic
// If sitename is empty, it will return the first available site
pub fn process_site_from_path(path string, sitename string) !&site.Site {
console.print_debug('Processing site configuration from: ${path}')
// Process the site configuration recursively (excluding global includes)
combined_heroscript := process_heroscript_files_recursive('${path}/cfg')!
console.print_debug('Combined heroscript length: ${combined_heroscript.len} characters')
if combined_heroscript.trim_space().len == 0 {
return error('No valid heroscript files found in ${path}/cfg')
}
// Create playbook and process site configuration
mut plbook := playbook.new(text: combined_heroscript)!
console.print_debug('Created playbook with ${plbook.actions.len} actions')
site.play(mut plbook)!
// Check what sites were created
available_sites := site.list()
console.print_debug('Available sites after site.play(): ${available_sites}')
if available_sites.len == 0 {
return error('No sites were created from the configuration')
}
// Determine which site to return
target_sitename := if sitename.len == 0 {
console.print_debug('No specific site requested, using first available: ${available_sites[0]}')
available_sites[0] // Use the first (and likely only) site
} else {
console.print_debug('Looking for specific site: ${sitename}')
sitename
}
mysite := site.get(name: target_sitename) or {
return error('Failed to get site after playing playbook: ${target_sitename}. Available sites: ${available_sites}')
}
console.print_debug('Site processed successfully: ${mysite.siteconfig.name} with ${mysite.pages.len} pages')
return mysite
}
@[params]
pub struct AddArgs {
pub mut:
sitename string // needs to exist in web.site module
path string // site of the docusaurus site with the config as is needed to populate the docusaurus site
git_url string
git_reset bool
git_root string
git_pull bool
path_publish string
play bool = true
}
pub fn dsite_add(args_ AddArgs) !&DocSite {
mut args := args_
args.sitename = texttools.name_fix(args_.sitename)
console.print_header('Add Docusaurus Site: ${args.sitename}')
if args.sitename in docusaurus_sites {
return error('Docusaurus site ${args.sitename} already exists, returning existing.')
}
mut path := gittools.path(
path: args.path
git_url: args.git_url
git_reset: args.git_reset
git_root: args.git_root
git_pull: args.git_pull
currentdir: false
)!
args.path = path.path
if !path.is_dir() {
return error('path is not a directory')
}
if !os.exists('${args.path}/cfg') {
return error('config directory for docusaurus does not exist in ${args.path}/cfg.\n${args}')
}
configpath := '${args.path}/cfg'
if !os.exists(configpath) {
return error("can't find config file for docusaurus in ${configpath}")
}
osal.rm('${args.path}/cfg/main.json')!
osal.rm('${args.path}/cfg/footer.json')!
osal.rm('${args.path}/cfg/navbar.json')!
osal.rm('${args.path}/build.sh')!
osal.rm('${args.path}/develop.sh')!
osal.rm('${args.path}/sync.sh')!
osal.rm('${args.path}/.DS_Store')!
mut f := factory_get()!
if args.path_publish == '' {
args.path_publish = '${f.path_publish.path}/${args.sitename}'
}
path_build_ := '${f.path_build.path}/${args.sitename}'
// get our website
mut mysite := &site.Site{}
if site.exists(name: args.sitename) {
// Site already exists (likely processed by hero command), use existing site
mysite = site.get(name: args.sitename)!
} else {
if !args.play {
return error('Docusaurus site ${args.sitename} does not exist, please set play to true to create it.')
}
// Use the centralized site processing function
mysite = process_site_from_path(args.path, args.sitename)!
}
// Create the DocSite instance
mut dsite := &DocSite{
name: args.sitename
path_src: pathlib.get_dir(path: args.path, create: false)!
path_publish: pathlib.get_dir(path: args.path_publish, create: true)!
path_build: pathlib.get_dir(path: path_build_, create: true)!
config: new_configuration(mysite.siteconfig)!
website: mysite
}
docusaurus_sites[args.sitename] = dsite
return dsite
}

View File

@@ -1,199 +1,36 @@
module docusaurus
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.playbook
import json
import os
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.data.doctree
import freeflowuniverse.herolib.web.site as sitegen
import freeflowuniverse.herolib.core.texttools.regext
pub fn (mut site DocSite) generate() ! {
mut f := factory_get()!
pub fn (mut docsite DocSite) generate() ! {
if docsite.generated {
return
}
mut c := config()!
console.print_header(' site generate: ${site.name} on ${f.path_build.path}')
console.print_header(' site source on ${site.path_src.path}')
console.print_header(' docsite generate: ${docsite.name} on ${c.path_build.path}')
// lets make sure we remove the cfg dir so we rebuild
cfg_path := os.join_path(f.path_build.path, 'cfg')
osal.rm('${c.path_build.path}/docs')!
cfg_path:="${c.path_build.path}/cfg"
osal.rm(cfg_path)!
mut gs := gittools.new()!
template_path := gs.get_path(
pull: false
reset: false
url: 'https://github.com/freeflowuniverse/docusaurus_template/src/branch/main/template/'
)!
// we need to copy the template each time for these 2 items, otherwise there can be leftovers from other run
for item in ['src', 'static'] {
mut template_src_path := pathlib.get_dir(path: '${template_path}/${item}', create: true)!
template_src_path.copy(dest: '${f.path_build.path}/${item}', delete: true)!
// now copy the info which can be overruled from source in relation to the template
if os.exists('${site.path_src.path}/${item}') {
mut src_path := pathlib.get_dir(path: '${site.path_src.path}/${item}', create: false)!
src_path.copy(dest: '${f.path_build.path}/${item}', delete: false)!
}
}
// We'll generate the configuration files after processing the site
// This is moved to after sitegen.play() so we can use the processed site configuration
osal.rm('${f.path_build.path}/docs')!
if os.exists('${site.path_src.path}/docs') {
mut aa := site.path_src.dir_get('docs')!
aa.copy(dest: '${f.path_build.path}/docs', delete: true)!
}
// now we need to process the pages, call the sitegen module, which will look for statements like
// !!site.page sitename:'atest'
// path:"crazy/sub.md" position:1
// src:"marketplace_specs:tft_tfp_marketplace"
// title:"Just a Page"
// description:"A description not filled in"
// draft:1 hide_title:1
configpath := '${site.path_src.path}/cfg'
// Create a playbook from the config path and run site processing
mut plbook := playbook.new(path: configpath)!
sitegen.play(mut plbook)!
// Get the updated site object after processing
// The site name in the config might be different from the docusaurus site name
// Find the site with the most pages (should contain the processed page definitions)
available_sites := sitegen.list()
mut best_site := &sitegen.Site(unsafe { nil })
mut max_pages := 0
for site_name in available_sites {
mut test_site := sitegen.get(name: site_name) or { continue }
if test_site.pages.len > max_pages {
max_pages = test_site.pages.len
best_site = test_site
}
}
if best_site == unsafe { nil } || max_pages == 0 {
return error('No sites with pages found after processing playbook. Available sites: ${available_sites}')
}
mut updated_site := best_site
// Generate the configuration files using the processed site configuration
mut updated_config := new_configuration(updated_site.siteconfig)!
mut main_file := pathlib.get_file(path: '${cfg_path}/main.json', create: true)!
main_file.write(json.encode_pretty(updated_config.main))!
main_file.write(json.encode_pretty(docsite.config.main))!
mut navbar_file := pathlib.get_file(path: '${cfg_path}/navbar.json', create: true)!
navbar_file.write(json.encode_pretty(updated_config.navbar))!
navbar_file.write(json.encode_pretty(docsite.config.navbar))!
mut footer_file := pathlib.get_file(path: '${cfg_path}/footer.json', create: true)!
footer_file.write(json.encode_pretty(updated_config.footer))!
footer_file.write(json.encode_pretty(docsite.config.footer))!
// Fix the index.tsx redirect to handle baseUrl properly
// When baseUrl is not '/', we need to use an absolute redirect path
if updated_config.main.base_url != '/' {
index_tsx_path := '${f.path_build.path}/src/pages/index.tsx'
if os.exists(index_tsx_path) {
// Create the corrected index.tsx content
fixed_index_content := "import React from 'react';
import { Redirect } from '@docusaurus/router';
import main from '../../cfg/main.json';
docsite.generate_docs()!
export default function Home() {
// Use absolute redirect path when baseUrl is not root
const redirectPath = main.baseUrl + main.url_home;
return <Redirect to={redirectPath} />;
}"
docsite.import()!
mut index_file := pathlib.get_file(path: index_tsx_path, create: false)!
index_file.write(fixed_index_content)!
}
}
// Scan and export doctree collections to Redis before generating docs
// This ensures the doctreeclient can access the collections when generating pages
console.print_header(' scanning doctree collections for site: ${site.name}')
// Find the collections directory relative to the source path
// The collections should be in the parent directory of the ebooks
mut collections_path := ''
// Try to find collections directory by going up from the source path
mut current_path := pathlib.get_dir(path: site.path_src.path)!
for _ in 0 .. 5 { // Search up to 5 levels up
collections_candidate := '${current_path.path}/collections'
if os.exists(collections_candidate) {
collections_path = collections_candidate
break
}
parent := current_path.parent() or { break } // reached root or error
if parent.path == current_path.path {
break // reached root
}
current_path = parent
}
if collections_path != '' {
// Create a doctree and scan the collections
mut tree := doctree.new(name: site.name)!
tree.scan(path: collections_path)!
// Export to Redis and temporary location for doctreeclient access
tree.export(
destination: '/tmp/doctree_export_${site.name}'
reset: true
exclude_errors: false
)!
}
// Generate the actual docs content from the processed site configuration
docs_path := '${f.path_build.path}/docs'
console.print_header(' generating docs from site pages to: ${docs_path}')
generate_docs(
path: docs_path
site: updated_site
)!
site.process_imports()!
}
pub fn (mut site DocSite) process_imports() ! {
mut gs := gittools.new()!
mut f:=factory_get()!
for item in site.website.siteconfig.imports {
mypath := gs.get_path(
pull: false
reset: false
url: item.url
)!
mut mypatho := pathlib.get(mypath)
mypatho.copy(dest: '${f.path_build.path}/docs/${item.dest}', delete: true)!
// println(item)
// replace: {'NAME': 'MyName', 'URGENCY': 'red'}
mut ri := regext.regex_instructions_new()
for key, val in item.replace {
ri.add_item('\{${key}\}', val)!
}
mypatho.copy(dest: '${f.path_build.path}/docs/${item.dest}', delete: true)!
ri.replace_in_dir(
path: '${f.path_build.path}/docs/${item.dest}'
extensions: [
'md',
]
)!
}
}

View File

@@ -15,29 +15,22 @@ mut:
client &doctreeclient.DocTreeClient
flat bool // if flat then won't use sitenames as subdir's
site Site
errors []string // collect errors here
}
@[params]
struct SiteGeneratorArgs {
mut:
path string
flat bool // if flat then won't use sitenames as subdir's
site Site
errors []string // collect errors here
}
// Generate docs from site configuration
pub fn generate_docs(args SiteGeneratorArgs) ! {
mut path := args.path
if args.path == '' {
return error('Path must be provided to generate site')
}
pub fn (mut docsite DocSite) generate_docs() ! {
c := config()!
//we generate the docs in the build path
docs_path := '${c.path_build.path}/docs'
mut gen := SiteGenerator{
path: pathlib.get_dir(path: path, create: true)!
path: pathlib.get_dir(path: docs_path, create: true)!
client: doctreeclient.new()!
flat: args.flat
site: args.site
flat: true
site: docsite.website
}
for section in gen.site.sections {
@@ -51,31 +44,28 @@ pub fn generate_docs(args SiteGeneratorArgs) ! {
if gen.errors.len > 0 {
return error('Errors occurred during site generation:\n${gen.errors.join('\n\n')}\nPlease fix the errors and try again.\nPage List: is header collection and page name per collection.\nAvailable pages:\n${gen.client.list_markdown()!}')
}
}
fn (mut mysite SiteGenerator) error( msg string) ! {
fn (mut generator SiteGenerator) error(msg string) ! {
console.print_stderr('Error: ${msg}')
mysite.errors << msg
generator.errors << msg
}
fn (mut mysite SiteGenerator) page_generate(args_ Page) ! {
fn (mut generator SiteGenerator) page_generate(args_ Page) ! {
mut args := args_
mut content := ['---']
mut parts := args.src.split(':')
if parts.len != 2 {
mysite.error("Invalid src format for page '${args.src}', expected format: collection:page_name, TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist")!
return
generator.error("Invalid src format for page '${args.src}', expected format: collection:page_name, TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist")!
return
}
collection_name := parts[0]
page_name := parts[1]
mut page_content := mysite.client.get_page_content(collection_name, page_name) or {
mysite.error("Couldn't find page '${collection_name}:${page_name}' is formatted as collectionname:pagename. TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist. ")!
mut page_content := generator.client.get_page_content(collection_name, page_name) or {
generator.error("Couldn't find page '${collection_name}:${page_name}' is formatted as collectionname:pagename. TODO: fix in ${args.path}, check the collection & page_name exists in the pagelist. ")!
return
}
@@ -129,7 +119,7 @@ fn (mut mysite SiteGenerator) page_generate(args_ Page) ! {
c += '\n${page_content}\n'
if args.path.ends_with('/') {
if args.path.ends_with('/') || args.path.trim_space() == '' {
// means is dir
args.path += page_name
}
@@ -138,18 +128,18 @@ fn (mut mysite SiteGenerator) page_generate(args_ Page) ! {
args.path += '.md'
}
mut pagepath := '${mysite.path.path}/${args.path}'
mut pagepath := '${generator.path.path}/${args.path}'
mut pagefile := pathlib.get_file(path: pagepath, create: true)!
pagefile.write(c)!
mysite.client.copy_images(collection_name, page_name, pagefile.path_dir()) or {
mysite.error("Couldn't copy image ${pagefile} for '${page_name}' in collection '${collection_name}', try to find the image and fix the path is in ${args.path}.}\nError: ${err}")!
generator.client.copy_images(collection_name, page_name, pagefile.path_dir()) or {
generator.error("Couldn't copy image ${pagefile} for '${page_name}' in collection '${collection_name}', try to find the image and fix the path is in ${args.path}.}\nError: ${err}")!
return
}
}
fn (mut mysite SiteGenerator) section_generate(args_ Section) ! {
fn (mut generator SiteGenerator) section_generate(args_ Section) ! {
mut args := args_
mut c := '{
@@ -160,7 +150,7 @@ fn (mut mysite SiteGenerator) section_generate(args_ Section) ! {
}
}'
mut category_path := '${mysite.path.path}/${args.path}/_category_.json'
mut category_path := '${generator.path.path}/${args.path}/_category_.json'
mut catfile := pathlib.get_file(path: category_path, create: true)!
catfile.write(c)!

View File

@@ -0,0 +1,61 @@
module docusaurus
import freeflowuniverse.herolib.develop.gittools
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools.regext
pub fn (mut docsite DocSite) import() ! {
for importparams in docsite.website.siteconfig.imports {
console.print_header('Importing: path:${importparams.path} or url:${importparams.url}')
// pub struct ImportItem {
// name string // will normally be empty
// url string // http git url can be to specific path
// path string
// dest string // location in the docs folder of the place where we will build the documentation site e.g. docusaurus
// replace map[string]string // will replace ${NAME} in the imported content
// visible bool = true
// }
c:=config()!
if importparams.path == "" && importparams.url != "" {
return error("in import for docusaurus need to specify url or path")
}
// Use gittools to get path of what we want to import
import_path := gittools.get_repo_path(
git_pull: c.reset
git_reset: c.reset
git_url: importparams.url
git_root: c.coderoot
path: importparams.path
)!
mut import_patho := pathlib.get(import_path)
if importparams.dest.starts_with("/") {
return error("Import path ${importparams.dest} must be relative, will be relative in relation to the build dir.")
}
import_patho.copy(dest: '${c.path_build.path}/${importparams.dest}', delete: false)!
// println(importparams)
// replace: {'NAME': 'MyName', 'URGENCY': 'red'}
mut ri := regext.regex_instructions_new()
for key, val in importparams.replace {
ri.add_item('\{${key}\}', val)!
}
ri.replace_in_dir(
path: '${c.path_build.path}/docs/${importparams.dest}'
extensions: [
'md',
]
)!
}
}

View File

@@ -1,79 +1,63 @@
module docusaurus
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.installers.web.bun
__global (
docusaurus_sites map[string]&DocSite
docusaurus_factory []DocSiteFactory
)
pub struct DocSiteFactory {
pub mut:
path_publish pathlib.Path
path_build pathlib.Path
}
import freeflowuniverse.herolib.web.site
import freeflowuniverse.herolib.ui.console
@[params]
pub struct DocSiteFactoryArgs {
pub struct AddArgs {
pub mut:
path_build string
path_publish string
install bool
reset bool
template_update bool
sitename string // needs to exist in web.site module
}
pub fn factory_get(args_ DocSiteFactoryArgs) !DocSiteFactory {
mut args := args_
if docusaurus_factory.len > 1 {
panic('multiple docusaurus factories found, please specify which one to use')
}
if docusaurus_factory.len > 0 {
return docusaurus_factory[0]
}
return factory_set(args)!
}
pub fn dsite_define(sitename string) ! {
console.print_header('Add Docusaurus Site: ${sitename}')
mut c := config()!
pub fn factory_set(args_ DocSiteFactoryArgs) !DocSiteFactory {
mut args := args_
if args.path_build == '' {
args.path_build = '${os.home_dir()}/hero/var/docusaurus/build'
}
if args.path_publish == '' {
args.path_publish = '${os.home_dir()}/hero/var/docusaurus/publish'
}
mut factory := DocSiteFactory{
path_publish: pathlib.get_dir(path: args.path_publish, create: true)!
path_build: pathlib.get_dir(path: args.path_build, create: true)!
path_publish := '${c.path_publish.path}/${sitename}'
path_build_ := '${c.path_build.path}'
// Get the site object after processing, this is the website which is a generic definition of a site
mut website := site.get(name: sitename)!
// Create the DocSite instance
mut dsite := &DocSite{
name: sitename
path_publish: pathlib.get_dir(path: "${path_build_}/build", create: true)!
path_build: pathlib.get_dir(path: path_build_, create: true)!
config: new_configuration(website.siteconfig)!
website: website
}
if !os.exists('${args.path_build}/node_modules') {
args.install = true
}
if args.install {
factory.install(args.reset, args.template_update)!
}
docusaurus_factory << factory
return factory
docusaurus_sites[sitename] = dsite
docusaurus_last = sitename
}
pub fn dsite_get(name_ string) !&DocSite {
name := texttools.name_fix(name_)
mut name := texttools.name_fix(name_)
if name=="" {
name = docusaurus_last
}
return docusaurus_sites[name] or {
return error('docusaurus site with name "${name}" does not exist')
}
}
pub fn dsite_exists(name_ string) !bool {
name := texttools.name_fix(name_)
d := docusaurus_sites[name] or { return false }
mut name := texttools.name_fix(name_)
if name=="" {
name = docusaurus_last
}
_ := docusaurus_sites[name] or { return false }
return true
}
// dsite_names returns the list of defined docusaurus site names.
pub fn dsite_names() []string {
mut names := []string{}
for k, _ in docusaurus_sites {
names << k
}
return names
}

View File

@@ -6,23 +6,23 @@ import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.installers.web.bun
fn (mut f DocSiteFactory) install(reset bool, template_update bool) ! {
fn install( c DocusaurusConfig) ! {
mut gs := gittools.new()!
if reset {
osal.rm(f.path_build.path)!
osal.dir_ensure(f.path_build.path)!
if c.reset {
osal.rm(c.path_build.path)!
osal.dir_ensure(c.path_build.path)!
}
template_path := gs.get_path(
pull: template_update
reset: reset // Changed args.delete to args.reset
pull: c.template_update
reset: c.reset
url: 'https://github.com/freeflowuniverse/docusaurus_template/src/branch/main/template'
)!
mut template_path0 := pathlib.get_dir(path: template_path, create: false)!
template_path0.copy(dest: f.path_build.path, delete: reset)! // Changed args.delete to args.reset
template_path0.copy(dest: c.path_build.path, delete: false)! //the dir has already been deleted so no point to delete again
// install bun
mut installer := bun.get()!
@@ -30,10 +30,11 @@ fn (mut f DocSiteFactory) install(reset bool, template_update bool) ! {
osal.exec(
// always stay in the context of the build directory
cmd: '
${osal.profile_path_source_and()!}
export PATH=${f.path_build.path}/node_modules/.bin::${os.home_dir()}/.bun/bin/:\$PATH
cd ${f.path_build.path}
bun install
'
${osal.profile_path_source_and()!}
export PATH=${c.path_build.path}/node_modules/.bin::${os.home_dir()}/.bun/bin/:\$PATH
cd ${c.path_build.path}
bun install
'
)!
}

View File

@@ -2,91 +2,69 @@ module docusaurus
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.web.site
import os
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'docusaurus.') {
return
}
// 1. Process generic site configuration first.
// This populates the global `site.websites` map.
site.play(mut plbook)!
//there should be 1 define section
mut action_define := plbook.ensure_once(filter: 'docusaurus.define')!
mut param_define := action_define.params
// check if docusaurus.define exists, if not, we create a default factory
mut f := DocSiteFactory{}
if plbook.max_once(filter: 'docusaurus.define')! {
mut a := plbook.get(filter: 'docusaurus.define') or {
panic('docusaurus.define action not found, this should not happen.')
}
mut p := a.params
f = factory_set(
path_build: p.get_default('path_build', '')!
path_publish: p.get_default('path_publish', '')!
reset: p.get_default_false('reset')
template_update: p.get_default_false('template_update')
install: p.get_default_false('install')
)!
a.done = true
} else {
f = factory_get()!
config_set(
path_build: param_define.get_default('path_build', '')!
path_publish: param_define.get_default('path_publish', '')!
reset: param_define.get_default_false('reset')
template_update: param_define.get_default_false('template_update')
install: param_define.get_default_false('install')
)!
site_name := param_define.get('name') or {
return error('In docusaurus.define, param "name" is required.')
}
dsite_define(site_name)!
// 3. Process `docusaurus.add` actions to create sites.
for mut action in plbook.find(filter: 'docusaurus.add')! {
mut p := action.params
site_name := p.get('sitename') or {
return error('In docusaurus.add, param "sitename" is required.')
}
action_define.done = true
mut dsite := dsite_get(site_name)!
dsite_add(
sitename: site_name
path: p.get_default('path', '')! // Make path optional
git_url: p.get_default('git_url', '')! // Make git_url optional too
git_reset: p.get_default_false('git_reset')
git_root: p.get_default('git_root', '')! // Make git_root optional
git_pull: p.get_default_false('git_pull')
path_publish: p.get_default('path_publish', f.path_publish.path)!
play: p.get_default_false('play') // Respect the play parameter from heroscript
)!
action.done = true
}
dsite.generate()!
mut actions_dev := plbook.find(filter: 'docusaurus.dev')!
if actions_dev.len > 1 {
return error('Multiple "docusaurus.dev" actions found. Only one is allowed.')
}
for mut action in actions_dev {
mut p := action.params
site_name := p.get('site')!
mut dsite := dsite_get(site_name)!
dsite.dev(
host: p.get_default('host', 'localhost')!
port: p.get_int_default('port', 3000)!
open: p.get_default_false('open')
watch_changes: p.get_default_false('watch_changes')
host: p.get_default('host', 'localhost')!
port: p.get_int_default('port', 3000)!
open: p.get_default_false('open')
)!
action.done = true
}
mut actions_build := plbook.find(filter: 'docusaurus.build')!
if actions_build.len > 1 {
return error('Multiple "docusaurus.build" actions found. Only one is allowed.')
}
for mut action in actions_build {
mut p := action.params
site_name := p.get('site') or {
// If no site specified, use the first available site
if docusaurus_sites.len == 0 {
return error('No docusaurus sites available to build. Use docusaurus.add to create a site first.')
}
// Get the first site name
docusaurus_sites.keys()[0]
}
mut dsite := dsite_get(site_name)!
dsite.build()!
action.done = true
}
mut actions_export := plbook.find(filter: 'docusaurus.publish')!
if actions_export.len > 1 {
return error('Multiple "docusaurus.publish" actions found. Only one is allowed.')
}
for mut action in actions_export {
dsite.build_publish()!
action.done = true
}
plbook.ensure_processed(filter: 'docusaurus.')!
}

View File

@@ -1,96 +1,195 @@
module docusaurus
import freeflowuniverse.herolib.osal.notifier
import os
//not longer working because is coming from doctree
fn watch_docs(docs_path string, path_src string, path_build string) ! {
mut n := notifier.new('docsite_watcher') or {
eprintln('Failed to create watcher: ${err}')
return
}
// import freeflowuniverse.herolib.osal.notifier
// import os
n.args['path_src'] = path_src
n.args['path_build'] = path_build
// fn watch_docs(docs_path string, path_src string, path_build string) ! {
// mut n := notifier.new('docsite_watcher') or {
// eprintln('Failed to create watcher: ${err}')
// return
// }
// Add watch with captured args
n.add_watch(docs_path, fn (event notifier.NotifyEvent, path string, args map[string]string) {
handle_file_change(event, path, args) or { eprintln('Error handling file change: ${err}') }
})!
// n.args['path_src'] = path_src
// n.args['path_build'] = path_build
n.start()!
}
// // Add watch with captured args
// n.add_watch(docs_path, fn (event notifier.NotifyEvent, path string, args map[string]string) {
// handle_file_change(event, path, args) or { eprintln('Error handling file change: ${err}') }
// })!
// handle_file_change processes file system events
fn handle_file_change(event notifier.NotifyEvent, path string, args map[string]string) ! {
file_base := os.base(path)
is_dir := os.is_dir(path)
// n.start()!
// }
// Skip files starting with #
if file_base.starts_with('#') {
return
}
// // handle_file_change processes file system events
// fn handle_file_change(event notifier.NotifyEvent, path string, args map[string]string) ! {
// file_base := os.base(path)
// is_dir := os.is_dir(path)
// For files (not directories), check extensions
if !is_dir {
ext := os.file_ext(path).to_lower()
if ext !in ['.md', '.png', '.jpeg', '.jpg'] {
return
}
}
// // Skip files starting with #
// if file_base.starts_with('#') {
// return
// }
// Get relative path from docs directory
rel_path := path.replace('${args['path_src']}/docs/', '')
dest_path := '${args['path_build']}/docs/${rel_path}'
// // For files (not directories), check extensions
// if !is_dir {
// ext := os.file_ext(path).to_lower()
// if ext !in ['.md', '.png', '.jpeg', '.jpg'] {
// return
// }
// }
match event {
.create, .modify {
if is_dir {
// For directories, just ensure they exist
os.mkdir_all(dest_path) or {
return error('Failed to create directory ${dest_path}: ${err}')
}
println('Created directory: ${rel_path}')
} else {
// For files, ensure parent directory exists and copy
os.mkdir_all(os.dir(dest_path)) or {
return error('Failed to create directory ${os.dir(dest_path)}: ${err}')
}
os.cp(path, dest_path) or {
return error('Failed to copy ${path} to ${dest_path}: ${err}')
}
println('Updated: ${rel_path}')
}
}
.delete {
if os.exists(dest_path) {
if is_dir {
os.rmdir_all(dest_path) or {
return error('Failed to delete directory ${dest_path}: ${err}')
}
println('Deleted directory: ${rel_path}')
} else {
os.rm(dest_path) or { return error('Failed to delete ${dest_path}: ${err}') }
println('Deleted: ${rel_path}')
}
}
}
.rename {
// For rename events, fswatch provides the new path in the event
// The old path is already removed, so we just need to handle the new path
if is_dir {
os.mkdir_all(dest_path) or {
return error('Failed to create directory ${dest_path}: ${err}')
}
println('Renamed directory to: ${rel_path}')
} else {
os.mkdir_all(os.dir(dest_path)) or {
return error('Failed to create directory ${os.dir(dest_path)}: ${err}')
}
os.cp(path, dest_path) or {
return error('Failed to copy ${path} to ${dest_path}: ${err}')
}
println('Renamed to: ${rel_path}')
}
}
}
}
// // Get relative path from docs directory
// rel_path := path.replace('${args['path_src']}/docs/', '')
// dest_path := '${args['path_build']}/docs/${rel_path}'
// match event {
// .create, .modify {
// if is_dir {
// // For directories, just ensure they exist
// os.mkdir_all(dest_path) or {
// return error('Failed to create directory ${dest_path}: ${err}')
// }
// println('Created directory: ${rel_path}')
// } else {
// // For files, ensure parent directory exists and copy
// os.mkdir_all(os.dir(dest_path)) or {
// return error('Failed to create directory ${os.dir(dest_path)}: ${err}')
// }
// os.cp(path, dest_path) or {
// return error('Failed to copy ${path} to ${dest_path}: ${err}')
// }
// println('Updated: ${rel_path}')
// }
// }
// .delete {
// if os.exists(dest_path) {
// if is_dir {
// os.rmdir_all(dest_path) or {
// return error('Failed to delete directory ${dest_path}: ${err}')
// }
// println('Deleted directory: ${rel_path}')
// } else {
// os.rm(dest_path) or { return error('Failed to delete ${dest_path}: ${err}') }
// println('Deleted: ${rel_path}')
// }
// }
// }
// .rename {
// // For rename events, fswatch provides the new path in the event
// // The old path is already removed, so we just need to handle the new path
// if is_dir {
// os.mkdir_all(dest_path) or {
// return error('Failed to create directory ${dest_path}: ${err}')
// }
// println('Renamed directory to: ${rel_path}')
// } else {
// os.mkdir_all(os.dir(dest_path)) or {
// return error('Failed to create directory ${os.dir(dest_path)}: ${err}')
// }
// os.cp(path, dest_path) or {
// return error('Failed to copy ${path} to ${dest_path}: ${err}')
// }
// println('Renamed to: ${rel_path}')
// }
// }
// }
// }
// pub fn (mut s DocSite) dev_watch(args DevArgs) ! {
// s.generate()!
// // Create screen session for docusaurus development server
// mut screen_name := 'docusaurus'
// mut sf := screen.new()!
// // Add and start a new screen session
// mut scr := sf.add(
// name: screen_name
// cmd: '/bin/bash'
// start: true
// attach: false
// reset: true
// )!
// // Send commands to the screen session
// console.print_item('To view the server output:: cd ${s.path_build.path}')
// scr.cmd_send('cd ${s.path_build.path}')!
// // Start script recording in the screen session for log streaming
// log_file := '/tmp/docusaurus_${screen_name}.log'
// script_cmd := 'script -f ${log_file}'
// scr.cmd_send(script_cmd)!
// // Small delay to ensure script is ready
// time.sleep(500 * time.millisecond)
// // Start bun in the scripted session
// bun_cmd := 'bun start -p ${args.port} -h ${args.host}'
// scr.cmd_send(bun_cmd)!
// // Stream the log output to current terminal
// console.print_header(' Docusaurus Development Server')
// console.print_item('Streaming server output... Press Ctrl+C to detach and leave server running')
// console.print_item('Server will be available at: http://${args.host}:${args.port}')
// console.print_item('To reattach later: screen -r ${screen_name}')
// println('')
// // Stream logs until user interrupts
// s.stream_logs(log_file, screen_name)!
// // After user interrupts, show final instructions
// console.print_header(' Server Running in Background')
// console.print_item(' Development server is running in background')
// console.print_item('Server URL: http://${args.host}:${args.port}')
// console.print_item('To reattach: screen -r ${screen_name}')
// console.print_item('To stop server: screen -S ${screen_name} -X kill')
// // console.print_item('The site content is on: ${s.path_src.path}/docs')
// // Start the watcher in a separate thread
// // mut tf:=spawn watch_docs(docs_path, s.path_src.path, s.path_build.path)
// // tf.wait()!
// println('\n')
// if args.open {
// s.open()!
// }
// }
// // Stream logs from script file to current terminal until user interrupts
// fn (mut s DocSite) stream_logs(log_file string, screen_name string) ! {
// // Wait a moment for the log file to be created
// mut attempts := 0
// for !os.exists(log_file) && attempts < 10 {
// time.sleep(200 * time.millisecond)
// attempts++
// }
// if !os.exists(log_file) {
// console.print_stderr('Warning: Log file not created, falling back to screen attach')
// console.print_item('Attaching to screen session... Press Ctrl+A then D to detach')
// // Fallback to direct screen attach
// osal.execute_interactive('screen -r ${screen_name}')!
// return
// }
// // Use tail -f to stream the log file
// // The -f flag follows the file as it grows
// tail_cmd := 'tail -f ${log_file}'
// // Execute tail in interactive mode - this will stream until Ctrl+C
// osal.execute_interactive(tail_cmd) or {
// // If tail fails, try alternative approach
// console.print_stderr('Log streaming failed, attaching to screen session...')
// osal.execute_interactive('screen -r ${screen_name}')!
// return
// }
// // Clean up the log file after streaming
// os.rm(log_file) or {}
// }

View File

@@ -1,77 +1,20 @@
module site
import freeflowuniverse.herolib.core.playbook { Action, PlayBook }
import os
import freeflowuniverse.herolib.core.playbook { PlayBook }
import freeflowuniverse.herolib.core.texttools
import time
pub fn play(mut plbook PlayBook) ! {
if !plbook.exists(filter: 'site.') && !plbook.exists(filter: 'docusaurus.config') {
if !plbook.exists(filter: 'site.') {
return
}
// Handle multiple site configurations - look for both site.config and docusaurus.config
mut config_actions := plbook.find(filter: 'site.config')!
if config_actions.len == 0 {
// Fallback to docusaurus.config for backward compatibility
config_actions = plbook.find(filter: 'docusaurus.config')!
}
mut config_action := plbook.ensure_once(filter: 'site.config')!
if config_actions.len == 0 {
return error('No site.config or docusaurus.config actions found')
}
// For now, just process the first site configuration to avoid memory issues
// TODO: Fix the underlying memory corruption issue with multiple site configs
if config_actions.len > 0 {
mut config_action := config_actions[0]
// Work around memory corruption by accessing params directly here
mut p := config_action.params
name := p.get_default('name', 'default')! // Use 'default' as fallback name
mut website := play_config_single_safe(name, mut config_action)!
mut config := &website.siteconfig
play_import(mut plbook, mut config)!
play_menu(mut plbook, mut config)!
play_footer(mut plbook, mut config)!
play_build_dest(mut plbook, mut config)!
play_build_dest_dev(mut plbook, mut config)!
play_pages(mut plbook, mut website)!
// Mark all other config actions as done to avoid processing them
for i in 1 .. config_actions.len {
config_actions[i].done = true
}
}
}
fn play_config_single_safe(name string, mut action Action) !&Site {
mut p := action.params
mut website := new(name: name)!
mut config := &website.siteconfig
config.title = p.get_default('title', config.title)!
config.description = p.get_default('description', config.description)!
config.tagline = p.get_default('tagline', config.tagline)!
config.favicon = p.get_default('favicon', config.favicon)!
config.image = p.get_default('image', config.image)!
config.copyright = p.get_default('copyright', config.copyright)!
config.url = p.get_default('url', config.url)!
config.base_url = p.get_default('base_url', config.base_url)!
config.url_home = p.get_default('url_home', config.url_home)!
config.name = name
return website
}
fn play_config(mut plbook PlayBook) !&Site {
mut action := plbook.get(filter: 'site.config')!
mut p := action.params
name := p.get('name') or { return error('need to specify name in site.config') }
mut p := config_action.params
name := p.get_default('name', 'default')! // Use 'default' as fallback name
// configure the website
mut website := new(name: name)!
mut config := &website.siteconfig
@@ -88,9 +31,9 @@ fn play_config(mut plbook PlayBook) !&Site {
config.url_home = p.get_default('url_home', '')!
// Process !!site.config_meta for specific metadata overrides
mut meta_action := plbook.get(filter: 'site.config_meta')!
mut meta_action := plbook.ensure_once(filter: 'site.config_meta')!
mut p_meta := meta_action.params
// If 'title' is present in site.config_meta, it overrides. Otherwise, meta_title remains empty or uses site.config.title logic in docusaurus model.
config.meta_title = p_meta.get_default('title', config.title)!
// If 'image' is present in site.config_meta, it overrides. Otherwise, meta_image remains empty or uses site.config.image logic.
@@ -100,8 +43,15 @@ fn play_config(mut plbook PlayBook) !&Site {
config.description = p_meta.get('description')!
}
action.done = true // Mark the action as done
return website
config_action.done = true // Mark the action as done
meta_action.done = true
play_import(mut plbook, mut config)!
play_menu(mut plbook, mut config)!
play_footer(mut plbook, mut config)!
play_publish(mut plbook, mut config)!
play_publish_dev(mut plbook, mut config)!
play_pages(mut plbook, mut website)!
}
fn play_import(mut plbook PlayBook, mut config SiteConfig) ! {
@@ -120,10 +70,18 @@ fn play_import(mut plbook PlayBook, mut config SiteConfig) ! {
}
}
}
mut importpath := p.get_default('path', '')!
if importpath != '' {
if ! importpath.starts_with('/') {
importpath = os.abs_path('${plbook.path}/${importpath}')
}
}
mut import_ := ImportItem{
name: p.get_default('name', '')!
url: p.get('url')!
path: p.get_default('path', '')!
url: p.get_default('url', '')!
path: importpath
dest: p.get_default('dest', '')!
replace: replace_map
visible: p.get_default_false('visible')
@@ -219,12 +177,12 @@ fn play_footer(mut plbook PlayBook, mut config SiteConfig) ! {
}
}
fn play_build_dest(mut plbook PlayBook, mut config SiteConfig) ! {
mut build_dest_actions := plbook.find(filter: 'site.build_dest')!
fn play_publish(mut plbook PlayBook, mut config SiteConfig) ! {
mut build_dest_actions := plbook.find(filter: 'site.publish')!
for mut action in build_dest_actions {
mut p := action.params
mut dest := BuildDest{
path: p.get('path')!
path: p.get_default('path', '')! //can be url
ssh_name: p.get_default('ssh_name', '')!
}
config.build_dest << dest
@@ -232,15 +190,16 @@ fn play_build_dest(mut plbook PlayBook, mut config SiteConfig) ! {
}
}
fn play_build_dest_dev(mut plbook PlayBook, mut config SiteConfig) ! {
mut build_dest_dev_actions := plbook.find(filter: 'site.build_dest_dev')!
for mut action in build_dest_dev_actions {
fn play_publish_dev(mut plbook PlayBook, mut config SiteConfig) ! {
mut build_dest_actions := plbook.find(filter: 'site.publish_dev')!
for mut action in build_dest_actions {
mut p := action.params
mut dest_dev := BuildDest{
path: p.get('path')!
mut dest := BuildDest{
path: p.get_default('path', '')! //can be url
ssh_name: p.get_default('ssh_name', '')!
}
config.build_dest_dev << dest_dev
config.build_dest_dev << dest
action.done = true // Mark the action as done
}
}

View File

@@ -19,9 +19,9 @@ fn play_pages(mut plbook PlayBook, mut site Site) ! {
}
// LETS FIRST DO THE CATEGORIES
category_actions := plbook.find(filter: 'site.page_category')!
mut category_actions := plbook.find(filter: 'site.page_category')!
mut section := Section{}
for action in category_actions {
for mut action in category_actions {
// println(action)
mut p := action.params
section.position = p.get_int_default('position', 20)!
@@ -32,9 +32,10 @@ fn play_pages(mut plbook PlayBook, mut site Site) ! {
return error('need to specify path in site.page_category')
}
site.sections << section
action.done = true // Mark the action as done
}
page_actions := plbook.find(filter: 'site.page')!
mut page_actions := plbook.find(filter: 'site.page')!
mut mypage := Page{
src: ''
path: ''
@@ -42,7 +43,7 @@ fn play_pages(mut plbook PlayBook, mut site Site) ! {
mut position_next := 1
mut position := 0
mut path := ''
for action in page_actions {
for mut action in page_actions {
// println(action)
mut p := action.params
pathnew := p.get_default('path', '')!
@@ -82,5 +83,7 @@ fn play_pages(mut plbook PlayBook, mut site Site) ! {
mypage.title_nr = p.get_int_default('title_nr', 0)!
site.pages << mypage
action.done = true // Mark the action as done
}
}

502
lib/web/ui/factory.v Normal file
View File

@@ -0,0 +1,502 @@
module ui
import veb
import os
import net.http
// Public Context type for veb
pub struct Context {
veb.Context
}
// Simple tree menu structure
pub struct MenuItem {
pub:
title string
href string
children []MenuItem
}
// Factory args
@[params]
pub struct FactoryArgs {
pub mut:
name string = 'default'
port int = 8080
title string = 'Admin'
menu []MenuItem
}
// The App holds server state and config
pub struct App {
veb.StaticHandler
pub mut:
title string
menu []MenuItem
port int
}
// Global registry (multi-instance support by name)
__global (
uireg map[string]&App
)
// Create a new app (does not start the server)
pub fn new(args FactoryArgs) !&App {
name := if args.name.len == 0 { 'default' } else { args.name }
if app := uireg[name] {
return app
}
mut app := &App{
title: args.title
menu: if args.menu.len > 0 { args.menu } else { default_menu() }
port: args.port
}
uireg[name] = app
return app
}
// Get a named app
pub fn get(name string) !&App {
mut app := uireg[name] or {
return error('ui: app "${name}" not found, call ui.new(...) first')
}
return app
}
// Get default app (creates if not existing)
pub fn default() !&App {
if uireg.len == 0 {
return new(port: 8080)!
}
return get('default')!
}
// Start the webserver (blocking)
pub fn start(args FactoryArgs) ! {
mut app := new(args)!
veb.run[App, Context](mut app, app.port)
}
// Routes
// Redirect root to /admin
@['/'; get]
pub fn (app &App) root(mut ctx Context) veb.Result {
return ctx.redirect('/admin')
}
// Admin home page
@['/admin'; get]
pub fn (app &App) admin_index(mut ctx Context) veb.Result {
return ctx.html(app.render_admin('/', 'Welcome'))
}
// HeroScript editor page
@['/admin/heroscript'; get]
pub fn (app &App) admin_heroscript(mut ctx Context) veb.Result {
return ctx.html(app.render_heroscript())
}
// Chat page
@['/admin/chat'; get]
pub fn (app &App) admin_chat(mut ctx Context) veb.Result {
return ctx.html(app.render_chat())
}
// Static CSS files
@['/static/css/colors.css'; get]
pub fn (app &App) serve_colors_css(mut ctx Context) veb.Result {
css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'colors.css')
css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
ctx.set_content_type('text/css')
return ctx.text(css_content)
}
@['/static/css/main.css'; get]
pub fn (app &App) serve_main_css(mut ctx Context) veb.Result {
css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'main.css')
css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
ctx.set_content_type('text/css')
return ctx.text(css_content)
}
// Static JS files
@['/static/js/theme.js'; get]
pub fn (app &App) serve_theme_js(mut ctx Context) veb.Result {
js_path := os.join_path(os.dir(@FILE), 'templates', 'js', 'theme.js')
js_content := os.read_file(js_path) or { return ctx.text('/* JS file not found */') }
ctx.set_content_type('application/javascript')
return ctx.text(js_content)
}
@['/static/js/heroscript.js'; get]
pub fn (app &App) serve_heroscript_js(mut ctx Context) veb.Result {
js_path := os.join_path(os.dir(@FILE), 'templates', 'js', 'heroscript.js')
js_content := os.read_file(js_path) or { return ctx.text('/* JS file not found */') }
ctx.set_content_type('application/javascript')
return ctx.text(js_content)
}
@['/static/js/chat.js'; get]
pub fn (app &App) serve_chat_js(mut ctx Context) veb.Result {
js_path := os.join_path(os.dir(@FILE), 'templates', 'js', 'chat.js')
js_content := os.read_file(js_path) or { return ctx.text('/* JS file not found */') }
ctx.set_content_type('application/javascript')
return ctx.text(js_content)
}
@['/static/css/heroscript.css'; get]
pub fn (app &App) serve_heroscript_css(mut ctx Context) veb.Result {
css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'heroscript.css')
css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
ctx.set_content_type('text/css')
return ctx.text(css_content)
}
@['/static/css/chat.css'; get]
pub fn (app &App) serve_chat_css(mut ctx Context) veb.Result {
css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'chat.css')
css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
ctx.set_content_type('text/css')
return ctx.text(css_content)
}
// Catch-all content under /admin/*
@['/admin/:path...'; get]
pub fn (app &App) admin_section(mut ctx Context, path string) veb.Result {
// Render current path in the main content
return ctx.html(app.render_admin(path, 'Content'))
}
// View rendering using external template
fn (app &App) render_admin(path string, heading string) string {
// Get the template file path relative to the module
template_path := os.join_path(os.dir(@FILE), 'templates', 'admin_layout.html')
// Read the template file
template_content := os.read_file(template_path) or {
// Fallback to inline template if file not found
return app.render_admin_fallback(path, heading)
}
// Generate menu HTML
menu_content := menu_html(app.menu, 0, 'm')
// Simple template variable replacement
mut result := template_content
result = result.replace('{{.title}}', app.title)
result = result.replace('{{.heading}}', heading)
result = result.replace('{{.path}}', path)
result = result.replace('{{.menu_html}}', menu_content)
result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
result = result.replace('{{.css_main_url}}', '/static/css/main.css')
result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
return result
}
// HeroScript editor rendering using external template
fn (app &App) render_heroscript() string {
// Get the template file path relative to the module
template_path := os.join_path(os.dir(@FILE), 'templates', 'heroscript_editor.html')
// Read the template file
template_content := os.read_file(template_path) or {
// Fallback to basic template if file not found
return app.render_heroscript_fallback()
}
// Generate menu HTML
menu_content := menu_html(app.menu, 0, 'm')
// Simple template variable replacement
mut result := template_content
result = result.replace('{{.title}}', app.title)
result = result.replace('{{.menu_html}}', menu_content)
result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
result = result.replace('{{.css_main_url}}', '/static/css/main.css')
result = result.replace('{{.css_heroscript_url}}', '/static/css/heroscript.css')
result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
result = result.replace('{{.js_heroscript_url}}', '/static/js/heroscript.js')
return result
}
// Chat rendering using external template
fn (app &App) render_chat() string {
// Get the template file path relative to the module
template_path := os.join_path(os.dir(@FILE), 'templates', 'chat.html')
// Read the template file
template_content := os.read_file(template_path) or {
// Fallback to basic template if file not found
return app.render_chat_fallback()
}
// Generate menu HTML
menu_content := menu_html(app.menu, 0, 'm')
// Simple template variable replacement
mut result := template_content
result = result.replace('{{.title}}', app.title)
result = result.replace('{{.menu_html}}', menu_content)
result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
result = result.replace('{{.css_main_url}}', '/static/css/main.css')
result = result.replace('{{.css_chat_url}}', '/static/css/chat.css')
result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
result = result.replace('{{.js_chat_url}}', '/static/js/chat.js')
return result
}
// Fallback HeroScript rendering method
fn (app &App) render_heroscript_fallback() string {
return '
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${app.title} - HeroScript Editor</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>HeroScript Editor</h1>
<p>HeroScript editor template not found. Please check the template files.</p>
<a href="/admin" class="btn btn-primary">Back to Admin</a>
</div>
</body>
</html>
'
}
// Fallback Chat rendering method
fn (app &App) render_chat_fallback() string {
return '
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${app.title} - Chat</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1>Chat Assistant</h1>
<p>Chat template not found. Please check the template files.</p>
<a href="/admin" class="btn btn-primary">Back to Admin</a>
</div>
</body>
</html>
'
}
// Fallback rendering method (inline template)
fn (app &App) render_admin_fallback(path string, heading string) string {
return '
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${app.title}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
body { padding-top: 44px; }
.header {
height: 44px;
line-height: 44px;
font-size: 14px;
}
.sidebar {
position: fixed;
top: 44px;
bottom: 0;
left: 0;
width: 260px;
overflow-y: auto;
background: #f8f9fa;
border-right: 1px solid #e0e0e0;
}
.main {
margin-left: 260px;
padding: 16px;
}
.list-group-item {
border: 0;
padding: .35rem .75rem;
background: transparent;
}
.menu-leaf a {
color: #212529;
text-decoration: none;
}
.menu-toggle {
text-decoration: none;
color: #212529;
}
.menu-toggle .chev {
font-size: 10px;
opacity: .6;
}
.menu-section {
font-weight: 600;
color: #6c757d;
padding: .5rem .75rem;
}
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark fixed-top header px-2">
<div class="d-flex w-100 align-items-center justify-content-between">
<div class="text-white fw-bold">${app.title}</div>
<div class="text-white-50">Admin</div>
</div>
</nav>
<aside class="sidebar">
<div class="p-2">
<div class="menu-section">Navigation</div>
<div class="list-group list-group-flush">
${menu_html(app.menu,
0, 'm')}
</div>
</div>
</aside>
<main class="main">
<div class="container-fluid">
<div class="d-flex align-items-center mb-3">
<h5 class="mb-0">${heading}</h5>
<span class="ms-2 text-muted small">/admin/${path}</span>
</div>
<div class="card">
<div class="card-body">
<p class="text-muted">This is a placeholder admin content area for: <code>/admin/${path}</code>.</p>
<p class="mb-0">Use the treeview on the left to navigate.</p>
</div>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
'
}
// Recursive menu renderer
fn menu_html(items []MenuItem, depth int, prefix string) string {
mut out := []string{}
for i, it in items {
id := '${prefix}_${depth}_${i}'
if it.children.len > 0 {
// expandable group
out << '<div class="list-group-item">'
out << '<a class="menu-toggle d-flex align-items-center justify-content-between" data-bs-toggle="collapse" href="#${id}" role="button" aria-expanded="${if depth == 0 {
'true'
} else {
'false'
}}" aria-controls="${id}">'
out << '<span>${it.title}</span><span class="chev">&rsaquo;</span>'
out << '</a>'
out << '<div class="collapse ${if depth == 0 { 'show' } else { '' }}" id="${id}">'
out << '<div class="ms-2 mt-1">'
out << menu_html(it.children, depth + 1, id)
out << '</div>'
out << '</div>'
out << '</div>'
} else {
// leaf
out << '<div class="list-group-item menu-leaf"><a href="${if it.href.len > 0 {
it.href
} else {
'/admin'
}}">${it.title}</a></div>'
}
}
return out.join('\n')
}
// Default sample menu
fn default_menu() []MenuItem {
return [
MenuItem{
title: 'Dashboard'
href: '/admin'
},
MenuItem{
title: 'HeroScript'
href: '/admin/heroscript'
},
MenuItem{
title: 'Chat'
href: '/admin/chat'
},
MenuItem{
title: 'Users'
children: [
MenuItem{
title: 'Overview'
href: '/admin/users/overview'
},
MenuItem{
title: 'Create'
href: '/admin/users/create'
},
MenuItem{
title: 'Roles'
href: '/admin/users/roles'
},
]
},
MenuItem{
title: 'Content'
children: [
MenuItem{
title: 'Pages'
href: '/admin/content/pages'
},
MenuItem{
title: 'Media'
href: '/admin/content/media'
},
MenuItem{
title: 'Settings'
children: [
MenuItem{
title: 'SEO'
href: '/admin/content/settings/seo'
},
MenuItem{
title: 'Themes'
href: '/admin/content/settings/themes'
},
]
},
]
},
MenuItem{
title: 'System'
children: [
MenuItem{
title: 'Status'
href: '/admin/system/status'
},
MenuItem{
title: 'Logs'
href: '/admin/system/logs'
},
MenuItem{
title: 'Backups'
href: '/admin/system/backups'
},
]
},
]
}

View File

@@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.title}}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="{{.css_colors_url}}">
<link rel="stylesheet" href="{{.css_main_url}}">
<meta name="color-scheme" content="light dark">
</head>
<body>
<nav class="navbar navbar-dark bg-dark fixed-top header px-2">
<div class="d-flex w-100 align-items-center justify-content-between">
<div class="text-white fw-bold">{{.title}}</div>
<div class="text-white-50">Admin</div>
</div>
</nav>
<aside class="sidebar">
<div class="p-2">
<div class="menu-section">Navigation</div>
<div class="list-group list-group-flush">
{{.menu_html}}
</div>
</div>
</aside>
<main class="main">
<div class="container-fluid">
<div class="d-flex align-items-center mb-3">
<h5 class="mb-0">{{.heading}}</h5>
<span class="ms-2 text-muted small">/admin/{{.path}}</span>
</div>
<div class="card">
<div class="card-body">
<p class="text-muted">This is a placeholder admin content area for: <code>/admin/{{.path}}</code>.</p>
<p class="mb-0">Use the treeview on the left to navigate.</p>
</div>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="{{.js_theme_url}}"></script>
</body>
</html>

View File

@@ -0,0 +1,200 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{.title}} - Chat</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href="{{.css_colors_url}}">
<link rel="stylesheet" href="{{.css_main_url}}">
<link rel="stylesheet" href="{{.css_chat_url}}">
<meta name="color-scheme" content="light dark">
</head>
<body>
<nav class="navbar navbar-dark bg-dark fixed-top header px-2">
<div class="d-flex w-100 align-items-center justify-content-between">
<div class="text-white fw-bold">{{.title}}</div>
<div class="text-white-50">Chat</div>
</div>
</nav>
<aside class="sidebar">
<div class="p-2">
<div class="menu-section">Navigation</div>
<div class="list-group list-group-flush">
{{.menu_html}}
</div>
</div>
</aside>
<main class="main">
<div class="container-fluid h-100">
<!-- Chat Section -->
<div class="chat-container">
<div class="chat-header">
<h5 class="mb-0">AI Chat Assistant</h5>
<div class="chat-controls">
<button class="btn btn-sm btn-outline-secondary" id="clearChat">
<i class="bi bi-trash"></i> Clear
</button>
<button class="btn btn-sm btn-outline-secondary" id="voiceToggle">
<i class="bi bi-mic"></i> Voice
</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message assistant">
<div class="message-avatar">
<i class="bi bi-robot"></i>
</div>
<div class="message-content">
<div class="message-text">Hello! I'm your AI assistant. How can I help you today?</div>
<div class="message-time">Just now</div>
</div>
</div>
</div>
<div class="chat-input-container">
<div class="chat-input-wrapper">
<textarea class="form-control chat-input" id="chatInput" placeholder="Type your message here..." rows="1"></textarea>
<div class="chat-input-actions">
<button class="btn btn-outline-secondary btn-sm" id="attachFile" title="Attach file">
<i class="bi bi-paperclip"></i>
</button>
<button class="btn btn-outline-secondary btn-sm" id="voiceInput" title="Voice input">
<i class="bi bi-mic"></i>
</button>
<button class="btn btn-primary btn-sm" id="sendMessage" title="Send message">
<i class="bi bi-send"></i>
</button>
</div>
</div>
<div class="chat-status" id="chatStatus"></div>
</div>
</div>
<!-- Recorder Section -->
<div class="recorder-container">
<div class="recorder-header">
<h6 class="mb-0">Voice Recorder</h6>
<div class="recorder-controls">
<button class="btn btn-sm btn-danger" id="recordBtn">
<i class="bi bi-record-circle"></i> Record
</button>
<button class="btn btn-sm btn-secondary" id="stopBtn" disabled>
<i class="bi bi-stop-circle"></i> Stop
</button>
<button class="btn btn-sm btn-outline-secondary" id="playBtn" disabled>
<i class="bi bi-play-circle"></i> Play
</button>
</div>
</div>
<div class="recording-status" id="recordingStatus">
<div class="recording-indicator">
<span class="recording-dot"></span>
<span class="recording-time">00:00</span>
</div>
<div class="recording-level">
<div class="level-bar" id="levelBar"></div>
</div>
</div>
<div class="recordings-explorer">
<div class="explorer-header">
<h6 class="mb-2">Recordings</h6>
<div class="explorer-actions">
<button class="btn btn-sm btn-outline-secondary" id="newFolderBtn">
<i class="bi bi-folder-plus"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" id="refreshBtn">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="explorer-tree" id="explorerTree">
<div class="tree-item folder expanded" data-path="/">
<div class="tree-item-content">
<i class="bi bi-folder-open"></i>
<span class="tree-item-name">Recordings</span>
</div>
<div class="tree-item-children">
<div class="tree-item file" data-path="/sample1.mp3">
<div class="tree-item-content">
<i class="bi bi-file-earmark-music"></i>
<span class="tree-item-name">sample1.mp3</span>
<span class="tree-item-size">2.1 MB</span>
</div>
</div>
<div class="tree-item file" data-path="/sample2.wav">
<div class="tree-item-content">
<i class="bi bi-file-earmark-music"></i>
<span class="tree-item-name">sample2.wav</span>
<span class="tree-item-size">5.3 MB</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" data-action="transcribe">
<i class="bi bi-file-text"></i> Transcribe
</div>
<div class="context-menu-item" data-action="translate">
<i class="bi bi-translate"></i> Translate
</div>
<div class="context-menu-item" data-action="open">
<i class="bi bi-folder-open"></i> Open
</div>
<div class="context-menu-item" data-action="move">
<i class="bi bi-arrow-right"></i> Move
</div>
<div class="context-menu-item" data-action="rename">
<i class="bi bi-pencil"></i> Rename
</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" data-action="export">
<i class="bi bi-download"></i> Export
</div>
</div>
<!-- File Upload Modal -->
<div class="modal fade" id="fileUploadModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Upload File</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="fileInput" class="form-label">Choose file</label>
<input class="form-control" type="file" id="fileInput" accept=".txt,.pdf,.doc,.docx,.md">
</div>
<div class="upload-progress" id="uploadProgress" style="display: none;">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="uploadBtn">Upload</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="{{.js_theme_url}}"></script>
<script src="{{.js_chat_url}}"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More