Compare commits

..

22 Commits

Author SHA1 Message Date
ded4a0b102 bump version to 1.0.19 2025-02-25 14:17:31 -07:00
6184441706 Merge branch 'development' into development_bizmodel
* development:
  s
  bump version to 1.0.14
  ...

# Conflicts:
#	cli/hero.v
#	install_hero.sh
#	test_basic.vsh
2025-02-25 14:16:54 -07:00
6492e42358 bump version to 1.0.17 2025-02-25 11:41:01 -07:00
4aaf1bd6db ... 2025-02-25 11:36:01 -07:00
a56a251d7f ... 2025-02-25 11:17:11 -07:00
f306ff728f bump version to 1.0.16 2025-02-25 11:13:07 -07:00
a10a6d6507 bump version to 1.0.15 2025-02-25 10:22:29 -07:00
fff14183a4 .... 2025-02-24 06:34:38 -07:00
6a2e143b98 ... 2025-02-24 02:47:59 -07:00
6820a7e9c8 ... 2025-02-23 10:34:18 +00:00
1c7621f20a docusaurus 2025-02-23 07:42:16 +03:00
5263798b11 s 2025-02-22 19:03:45 +03:00
timurgordon
88fe8f503f rename example output dir 2025-02-20 08:15:49 +03:00
timurgordon
a26bb56b15 create missing dir 2025-02-20 08:12:48 +03:00
timurgordon
2f54f05cfd fix bizmodel docs export 2025-02-20 08:10:49 +03:00
timurgordon
f61d6808e8 fix compilation 2025-02-20 06:59:08 +03:00
timurgordon
4719876feb get example to work with echarts 2025-02-20 06:43:26 +03:00
timurgordon
975cca77b1 decouple charts from wiki representation 2025-02-20 06:41:17 +03:00
timurgordon
eb80294b0a improve echart exporting 2025-02-20 06:40:19 +03:00
timurgordon
0704c9421d make getters immutable 2025-02-20 06:38:42 +03:00
timurgordon
814b61f25e Merge branch 'development_installers' into development_bizmodel 2025-02-19 13:30:34 +03:00
timurgordon
1c264815c3 update example 2025-02-19 13:28:38 +03:00
94 changed files with 2518 additions and 401 deletions

View File

@@ -51,7 +51,7 @@ fn do() ! {
mut cmd := Command{ mut cmd := Command{
name: 'hero' name: 'hero'
description: 'Your HERO toolset.' description: 'Your HERO toolset.'
version: '1.0.14' version: '1.0.19'
} }
// herocmds.cmd_run_add_flags(mut cmd) // herocmds.cmd_run_add_flags(mut cmd)
@@ -102,6 +102,7 @@ fn do() ! {
// herocmds.cmd_juggler(mut cmd) // herocmds.cmd_juggler(mut cmd)
herocmds.cmd_generator(mut cmd) herocmds.cmd_generator(mut cmd)
herocmds.cmd_docusaurus(mut cmd) herocmds.cmd_docusaurus(mut cmd)
herocmds.cmd_starlight(mut cmd)
// herocmds.cmd_docsorter(mut cmd) // herocmds.cmd_docsorter(mut cmd)
// cmd.add_command(publishing.cmd_publisher(pre_func)) // cmd.add_command(publishing.cmd_publisher(pre_func))
cmd.setup() cmd.setup()

25
examples/biztools/bizmodel.vsh Executable file
View File

@@ -0,0 +1,25 @@
#!/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
const playbook_path = os.dir(@FILE) + '/playbook'
const build_path = os.join_path(os.dir(@FILE), '/docusaurus')
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
mut model := bizmodel.getset("example")!
model.workdir = build_path
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")!

View File

@@ -1,2 +0,0 @@
bizmodel
dest

View File

@@ -1,41 +0,0 @@
#!/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.data.doctree
import freeflowuniverse.herolib.biz.bizmodel
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.core.playcmds
import freeflowuniverse.herolib.web.mdbook
import os
const wikipath = os.dir(@FILE) + '/wiki'
const summarypath = os.dir(@FILE) + '/wiki/summary.md'
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
mut m := bizmodel.getset('example')!
m.workdir = wikipath
m.play(mut playbook.new(path: wikipath)!)!
m.export_sheets()!
bizmodel.set(m)
// // execute the actions so we have the info populated
// // playcmds.run(mut plb,false)!
// // just run the doctree & mdbook and it should
// // load the doctree, these are all collections
// mut tree := doctree.new(name: 'bizmodel')!
// tree.scan(path: wikipath)!
// tree.export(dest: buildpath, reset: true)!
// // mut bm:=bizmodel.get("test")!
// // println(bm)
// mut mdbooks := mdbook.get()!
// mdbooks.generate(
// name: 'bizmodel'
// summary_path: summarypath
// doctree_path: buildpath
// title: 'bizmodel example'
// )!
// mdbook.book_open('bizmodel')!

View File

@@ -1,8 +0,0 @@
# Hr Overview
!!!bizmodel.employees_wiki bizname:'test'
> note: Nr People like 0:5,20:5 means, month 0 (start) is 5, month 20 its 5 people

View File

@@ -1,5 +0,0 @@
# CTO
!!!bizmodel.employee_wiki bizname:'test' name:'despiegk'
!!wiki.include page:cto_description.md

View File

@@ -1,3 +0,0 @@
## CTO Description
this is a page to test nested includes

View File

@@ -1,52 +0,0 @@
# This is our business model planner
## P&L Overview
<!-- period is in months, 3 means every quarter -->
!!bizmodel.graph_bar_row rowname:revenue_total unit:million title:'A Title' title_sub:'Sub' sheetname:'bizmodel_test'
Unit is in Million USD.
!!bizmodel.graph_bar_row rowname:revenue_total unit:million sheetname:'bizmodel_test'
!!bizmodel.graph_line_row rowname:revenue_total unit:million sheetname:'bizmodel_test'
!!bizmodel.graph_pie_row rowname:revenue_total unit:million size:'80%' sheetname:'bizmodel_test'
## FUNDING
!!bizmodel.sheet_wiki includefilter:'funding' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'REVENUE' includefilter:rev sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'Revenue Total' includefilter:'revtotal' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'REVENUE' includefilter:'revtotal2' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'COGS' includefilter:'cogs' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'Margin' includefilter:'margin' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'HR Teams' includefilter:'hrnr' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'HR Costs' includefilter:'hrcost' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'COSTS' includefilter:'ocost' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'HR Costs' includefilter:'hrcost' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'P&L Overview' includefilter:'pl' sheetname:'bizmodel_test'
!!bizmodel.sheet_wiki title:'P&L Overview' includefilter:'pl' sheetname:'bizmodel_test'
## Some Details
> show how we can do per month
!!bizmodel.sheet_wiki includefilter:'pl' period_months:1 sheetname:'bizmodel_test'

View File

@@ -0,0 +1,4 @@
bizmodel
dest
wiki
build

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,37 @@
#!/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
//TODO: need to fix wrong location
const playbook_path = os.dir(@FILE) + '/playbook'
const build_path = os.join_path(os.dir(@FILE), '/docusaurus')
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
mut model := bizmodel.getset("example")!
model.workdir = build_path
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")!
report := model.new_report(
name: 'example_report'
title: 'Example Business Model'
)!
report.export(
path: build_path
overwrite: true
format: .docusaurus
)!

View File

@@ -0,0 +1 @@
output dir of example

View File

@@ -0,0 +1,22 @@
#!/bin/bash
set -ex
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${script_dir}"
echo "Docs directory: $script_dir"
cd "${HOME}/hero/var/docusaurus"
export PATH=/tmp/docusaurus_build/node_modules/.bin:${HOME}/.bun/bin/:$PATH
rm -rf /Users/despiegk/hero/var/docusaurus/build/
. ${HOME}/.zprofile
bun docusaurus build
mkdir -p /Users/despiegk/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel/example/docusaurus
echo SYNC TO /Users/despiegk/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel/example/docusaurus
rsync -rv --delete /Users/despiegk/hero/var/docusaurus/build/ /Users/despiegk/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel/example/docusaurus/

View File

@@ -0,0 +1 @@
{"style":"dark","links":[]}

View File

@@ -0,0 +1 @@
{"name":"","title":"Docusaurus","tagline":"","favicon":"img/favicon.png","url":"http://localhost","url_home":"docs/introduction","baseUrl":"/","image":"img/tf_graph.png","metadata":{"description":"Docusaurus","image":"Docusaurus","title":"Docusaurus"},"buildDest":[],"buildDestDev":[]}

View File

@@ -0,0 +1 @@
{"title":"Business Model","items":[{"href":"https://threefold.info/kristof/","label":"ThreeFold Technology","position":"right"},{"href":"https://threefold.io","label":"Operational Plan","position":"left"}]}

View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${script_dir}"
echo "Docs directory: $script_dir"
cd "${HOME}/hero/var/docusaurus"
export PATH=/tmp/docusaurus_build/node_modules/.bin:${HOME}/.bun/bin/:$PATH
. ${HOME}/.zprofile
bun run start -p 3100

View File

@@ -4,11 +4,31 @@
This company is a cloud company ... This company is a cloud company ...
- name, e.g. for a specific project
- descr, description of the revenue line item
- revenue_items: does one of revenue, is not exterpolated
- revenue_growth: is a revenue stream which is being extrapolated
- revenue_setup, revenue for 1 item '1000usd'
- revenue_setup_delay
- revenue_monthly, revenue per month for 1 item
- revenue_monthly_delay, how many months before monthly revenue starts
- maintenance_month_perc, how much percent of revenue_setup will come back over months
- cogs_setup, cost of good for 1 item at setup
- cogs_setup_delay, how many months before setup cogs starts, after sales
- cogs_setup_perc: what is percentage of the cogs (can change over time) for setup e.g. 0:50%
- cogs_monthly, cost of goods for the monthly per 1 item
- cogs_monthly_delay, how many months before monthly cogs starts, after sales
- cogs_monthly_perc: what is percentage of the cogs (can change over time) for monthly e.g. 0:5%,12:10%
- nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200, default is 1)
- nr_months_recurring: how many months is recurring, if 0 then no recurring
```js ```js
!!bizmodel.revenue_define bizname:'test' !!bizmodel.revenue_define bizname:'test'
descr:'OEM Deals' descr:'OEM Deals'
revenue_time:'10:1000000EUR,15:3333,20:1200000' revenue_items:'10:1000000EUR,15:3333,20:1200000'
cogs_perc: '1:5%,20:10%' cogs_setup_perc: '1:5%,20:10%'
!!bizmodel.revenue_define bizname:'test' !!bizmodel.revenue_define bizname:'test'
descr:'License Deals' descr:'License Deals'
@@ -23,6 +43,7 @@ This company is a cloud company ...
revenue_nr:'10:1000,24:2000,60:40000' revenue_nr:'10:1000,24:2000,60:40000'
cogs_perc: '10%' cogs_perc: '10%'
rev_delay_month: 1 rev_delay_month: 1
``` ```
## Revenue Items Recurring ## Revenue Items Recurring

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.web.docusaurus
// import freeflowuniverse.herolib.data.doctree
// Create a new docusaurus factory
mut docs := docusaurus.new(
build_path: '/tmp/docusaurus_build'
)!
// Create a new docusaurus site
mut site := docs.dev(
url: 'https://git.ourworld.tf/despiegk/docs_kristof'
)!
// FOR FUTURE TO ADD CONTENT FROM DOCTREE
// Create a doctree for content
// mut tree := doctree.new(name: 'content')!
// // Add some content from a git repository
// tree.scan(
// git_url: 'https://github.com/yourusername/your-docs-repo'
// git_pull: true
// )!
// // Export the content to the docusaurus site
// tree.export(
// destination: '${site.path_build.path}/docs'
// reset: true
// keep_structure: true
// exclude_errors: false
// )!
// Build the docusaurus site
// site.build()!
// Generate the static site
// site.generate()!
// Optionally open the site in a browser
// site.open()!

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.web.starlight
// import freeflowuniverse.herolib.data.doctree
// Create a new starlight factory
mut docs := starlight.new(
build_path: '/tmp/starlight_build'
)!
// Create a new starlight site
mut site := docs.get(
url: 'https://git.ourworld.tf/tfgrid/docs_aibox'
init:true //init means we put config files if not there
)!
site.dev()!

View File

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

1
lib/biz/bizmodel/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
testdata

View File

@@ -15,7 +15,7 @@ pub fn (mut m BizModel) act(action Action) !Action {
m.funding_define_action(action)! m.funding_define_action(action)!
} }
'revenue_define' { 'revenue_define' {
m.funding_define_action(action)! m.revenue_action(action)!
} }
'costcenter_define' { 'costcenter_define' {
m.costcenter_define_action(action)! m.costcenter_define_action(action)!
@@ -58,7 +58,7 @@ fn (mut m BizModel) export_sheet_action(action Action) !Action {
} }
fn (mut m BizModel) export_graph_title_action(action Action) !Action { fn (mut m BizModel) export_graph_title_action(action Action) !Action {
return m.export_action(m.sheet.wiki_title_chart(row_args_from_params(action.params)!), action) return m.export_action(m.sheet.wiki_title_chart(row_args_from_params(action.params)!)!, action)
} }
fn (mut m BizModel) export_graph_line_action(action Action) !Action { fn (mut m BizModel) export_graph_line_action(action Action) !Action {

View File

@@ -5,18 +5,6 @@ import freeflowuniverse.herolib.web.docusaurus
import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.core.pathlib
pub struct Export {
pub:
path string
overwrite bool
format ExportFormat
}
pub enum ExportFormat {
docusaurus
mdbook
}
pub struct Report { pub struct Report {
pub: pub:
name string name string
@@ -36,7 +24,15 @@ pub fn (b BizModel) new_report(report Report) !Report {
name := if report.name != '' {report.name} else { texttools.snake_case(report.title) } name := if report.name != '' {report.name} else { texttools.snake_case(report.title) }
path := pathlib.get_dir( path := pathlib.get_dir(
path: os.join_path(os.home_dir(), '/hero/var/bizmodel/reports/${name}') path: os.join_path(os.home_dir(), '/hero/var/bizmodel/reports/${name}')
create: true
empty: true
)! )!
b.write_introduction(path.path)!
b.write_operational_plan(path.path)!
b.write_revenue_model(path.path)!
b.write_cost_structure(path.path)!
return Report { return Report {
...report, ...report,
name: name name: name
@@ -52,39 +48,104 @@ pub fn (b BizModel) new_report(report Report) !Report {
// b.export_fundraising(export) // b.export_fundraising(export)
} }
pub struct Export {
pub:
path string
overwrite bool
format ExportFormat
}
pub enum ExportFormat {
docusaurus
mdbook
}
pub fn (r Report) export(export Export) ! { pub fn (r Report) export(export Export) ! {
match export.format { match export.format {
.docusaurus { .docusaurus {
mut dir := pathlib.get_dir(path: r.path)!
dir.copy(dest: '${export.path}/docs', delete: true)!
mut factory := docusaurus.new()! mut factory := docusaurus.new()!
mut site := factory.get( mut site := factory.get(
name: r.name name: r.name
path: r.path path: export.path
publish_path: export.path publish_path: export.path
config: docusaurus.Config {} //TODO: is this needed init: true
config: docusaurus.Config {
navbar: docusaurus.Navbar {
title: "Business Model",
items: [
docusaurus.NavbarItem{
"href": "https://threefold.info/kristof/",
"label": "ThreeFold Technology",
"position": "right"
},
docusaurus.NavbarItem{
"href": "https://threefold.io",
"label": "Operational Plan",
"position": "left"
}
]
}
main: docusaurus.Main {
url_home: 'docs/introduction'
}
} //TODO: is this needed
)! )!
site.build()! site.generate()!
} }
.mdbook {panic('MDBook export not fully implemented')} .mdbook {panic('MDBook export not fully implemented')}
} }
} }
pub fn (b BizModel) export_operational_plan(export Export) ! { pub fn (model BizModel) write_introduction(path string) ! {
mut hr_page := pathlib.get_file(path: '${export.path}/human_resources.md')! mut index_page := pathlib.get_file(path: '${path}/introduction.md')!
hr_page.template_write('./templates/human_resources.md', export.overwrite)! // mut tmpl_index := $tmpl('templates/index.md')
index_page.template_write($tmpl('templates/introduction.md'), true)!
}
for key, employee in b.employees { pub fn (model BizModel) write_operational_plan(path string) ! {
mut employee_page := pathlib.get_file(path: '${export.path}/${texttools.snake_case(employee.name)}.md')! mut dir := pathlib.get_dir(path: '${path}/operational_plan')!
employee_page.template_write('./templates/employee.md', export.overwrite)! mut ops_page := pathlib.get_file(path: '${dir.path}/operational_plan.md')!
ops_page.write('# Operational Plan')!
mut hr_dir := pathlib.get_dir(path: '${dir.path}/human_resources')!
mut hr_page := pathlib.get_file(path: '${hr_dir.path}/human_resources.md')!
hr_page.template_write($tmpl('./templates/human_resources.md'), true)!
for key, employee in model.employees {
mut employee_page := pathlib.get_file(path: '${hr_dir.path}/${texttools.snake_case(employee.name)}.md')!
employee_cost_chart := model.sheet.line_chart(rowname:'hr_cost_${employee.name}', unit: .million)!.mdx()
employee_page.template_write($tmpl('./templates/employee.md'), true)!
}
mut depts_dir := pathlib.get_dir(path: '${dir.path}/departments')!
for key, department in model.departments {
mut dept_page := pathlib.get_file(path: '${depts_dir.path}/${texttools.snake_case(department.name)}.md')!
// dept_cost_chart := model.sheet.line_chart(rowname:'hr_cost_${employee.name}', unit: .million)!.mdx()
// println(employee_cost_chart)
dept_page.template_write($tmpl('./templates/department.md'), true)!
} }
} }
pub fn (b BizModel) export_revenue_model(export Export) ! { pub fn (model BizModel) write_revenue_model(path string) ! {
println('begin') mut dir := pathlib.get_dir(path: '${path}/revenue_model')!
mut overview_page := pathlib.get_file(path: '${export.path}/revenue_overview.md')! mut rm_page := pathlib.get_file(path: '${dir.path}/revenue_model.md')!
overview_page.template_write('./templates/overview.md', export.overwrite)! rm_page.write('# Revenue Model')!
mut products_dir := pathlib.get_dir(path: '${dir.path}/products')!
mut products_page := pathlib.get_file(path: '${products_dir.path}/products.md')!
products_page.template_write('# Products', true)!
for key, product in b.products { name1 := 'example'
mut product_page := pathlib.get_file(path: '${export.path}/${texttools.snake_case(product.name)}.md')! for key, product in model.products {
product_page.template_write('./templates/product.md', export.overwrite)! mut product_page := pathlib.get_file(path: '${products_dir.path}/${texttools.snake_case(product.name)}.md')!
product_page.template_write($tmpl('./templates/product.md'), true)!
} }
}
pub fn (model BizModel) write_cost_structure(path string) ! {
mut dir := pathlib.get_dir(path: '${path}/cost_structure')!
mut cs_page := pathlib.get_file(path: '${dir.path}/cost_structure.md')!
cs_page.write('# Cost Structure')!
} }

View File

@@ -62,7 +62,7 @@ fn employee_wiki(p paramsparser.Params, sim BizModel) !string {
// theme := 'light' // theme := 'light'
// theme := 'dark' // Removed unused variable // theme := 'dark' // Removed unused variable
mut t := $tmpl('./templates/employee.md') mut t := $tmpl('./templates/employee_old.md')
return t return t
} }

View File

@@ -6,6 +6,7 @@ import freeflowuniverse.herolib.biz.spreadsheet
pub struct BizModel { pub struct BizModel {
pub mut: pub mut:
name string name string
description string
workdir string = '${os.home_dir()}/hero/var/bizmodel' workdir string = '${os.home_dir()}/hero/var/bizmodel'
sheet &spreadsheet.Sheet sheet &spreadsheet.Sheet
employees map[string]&Employee employees map[string]&Employee

View File

@@ -13,12 +13,10 @@ import freeflowuniverse.herolib.core.texttools
// - cogs_setup, cost of good for 1 item at setup // - cogs_setup, cost of good for 1 item at setup
// - cogs_setup_delay, how many months before setup cogs starts, after sales // - cogs_setup_delay, how many months before setup cogs starts, after sales
// - cogs_setup_perc: what is percentage of the cogs (can change over time) for setup e.g. 0:50% // - cogs_setup_perc: what is percentage of the cogs (can change over time) for setup e.g. 0:50%
// - cogs_monthly, cost of goods for the monthly per 1 item // - cogs_monthly, cost of goods for the monthly per 1 item
// - cogs_monthly_delay, how many months before monthly cogs starts, after sales // - cogs_monthly_delay, how many months before monthly cogs starts, after sales
// - cogs_monthly_perc: what is percentage of the cogs (can change over time) for monthly e.g. 0:5%,12:10% // - cogs_monthly_perc: what is percentage of the cogs (can change over time) for monthly e.g. 0:5%,12:10%
// - nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200, default is 1)
// - nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200)
// - nr_months_recurring: how many months is recurring, if 0 then no recurring // - nr_months_recurring: how many months is recurring, if 0 then no recurring
// //
fn (mut m BizModel) revenue_action(action Action) !Action { fn (mut m BizModel) revenue_action(action Action) !Action {
@@ -62,6 +60,10 @@ fn (mut m BizModel) revenue_action(action Action) !Action {
extrapolate: false extrapolate: false
)! )!
println(action)
println(revenue)
exit(0)
mut revenue_setup := m.sheet.row_new( mut revenue_setup := m.sheet.row_new(
name: '${name}_revenue_setup' name: '${name}_revenue_setup'
growth: action.params.get_default('revenue_setup', '0:0')! growth: action.params.get_default('revenue_setup', '0:0')!
@@ -139,11 +141,6 @@ fn (mut m BizModel) revenue_action(action Action) !Action {
aggregatetype: .avg aggregatetype: .avg
)! )!
// if true{
// println(cogs_setup_perc)
// println(cogs_monthly_perc)
// panic("sdsd")
// }
mut nr_sold := m.sheet.row_new( mut nr_sold := m.sheet.row_new(
name: '${name}_nr_sold' name: '${name}_nr_sold'
@@ -211,10 +208,6 @@ fn (mut m BizModel) revenue_action(action Action) !Action {
nrmonths: nr_months_recurring nrmonths: nr_months_recurring
aggregatetype: .max aggregatetype: .max
)! )!
// if true{
// println(nr_sold_recurring)
// panic('sd')
// }
} }
// cogs as percentage of revenue // cogs as percentage of revenue
@@ -229,16 +222,17 @@ fn (mut m BizModel) revenue_action(action Action) !Action {
name: '${name}_cogs_monthly_from_perc' name: '${name}_cogs_monthly_from_perc'
)! )!
// if true{ println(action)
// println(revenue_setup_total) println(nr_sold)
// println(cogs_setup_perc) println(revenue)
// println(cogs_setup_from_perc) println(revenue_setup_total)
// println("montlhy") println(revenue_monthly_total)
// println(revenue_monthly_total) println(cogs_setup_perc)
// println(cogs_monthly_perc) println(cogs_setup_from_perc)
// println(cogs_monthly_from_perc) println(cogs_monthly_perc)
// panic("sdsd") println(cogs_monthly_from_perc)
// } exit(0)
// mut cogs_from_perc:=cogs_perc.action(action:.multiply,rows:[revenue],name:"cogs_from_perc")! // mut cogs_from_perc:=cogs_perc.action(action:.multiply,rows:[revenue],name:"cogs_from_perc")!

View File

@@ -0,0 +1,6 @@
# @{department.name}
`@{department.description}`
**Cost To The Company:**

View File

@@ -9,6 +9,7 @@
`@{employee.cost}` `@{employee.cost}`
@{employee_cost_chart}
@if employee.cost_percent_revenue > 0.0 @if employee.cost_percent_revenue > 0.0

View File

@@ -0,0 +1,27 @@
# @{employee.name}
`@{employee.description}`
> department: `@{employee.department}`
**Cost To The Company:**
`@{employee.cost}`
@if employee.cost_percent_revenue > 0.0
**Cost Percent Revenue:**
`@{employee.cost_percent_revenue}%`
@end
@if employee.nrpeople.len > 1
**Number of People in this group**
`@{employee.nrpeople}`
@end

View File

@@ -2,8 +2,6 @@
| Name | Title | Nr People | | Name | Title | Nr People |
|------|-------|-------| |------|-------|-------|
@for employee in model.employees.values().filter(it.department == dept.name) @for employee in model.employees.values()
| @{employee_names[employee.name]} | @{employee.title} | @{employee.nrpeople} | | @{employee.name} | @{employee.title} | @{employee.nrpeople} |
@end
@end @end

View File

@@ -0,0 +1,49 @@
# @{model.name}
@{model.description}
## FUNDING
@{model.sheet.wiki(includefilter:['funding']) or {panic(err)}}
## REVENUE vs COGS
@{model.sheet.wiki(includefilter:['rev']) or {panic(err)}}
#### Revenue Lines
@{model.sheet.wiki(title:'Revenue Total', includefilter:['revtotal']) or {panic(err)}}
#### COGS Lines
@{model.sheet.wiki(title:'COGS', includefilter:['cogstotal']) or {panic(err)}}
## HR
@{model.sheet.wiki(title:'HR Teams', includefilter:['hrnr']) or {panic(err)}}
@{model.sheet.wiki(title:'HR Costs', includefilter:['hrcost']) or {panic(err)}}
## Operational Costs
@{model.sheet.wiki(title:'COSTS', includefilter:['ocost']) or {panic(err)}}
## P&L Overview
<!-- period is in months, 3 means every quarter -->
@{model.sheet.wiki(title:'P&L Overview', includefilter:['pl']) or {panic(err)}}
@{model.sheet.bar_chart(rowname:'revenue_total', unit: .million, title:'A Title', title_sub:'Sub') or {panic(err)}.mdx()}
Unit is in Million USD.
@{model.sheet.line_chart(rowname:'revenue_total', unit: .million) or {panic(err)}.mdx()}
@{model.sheet.pie_chart(rowname:'revenue_total', unit: .million, size:'80%') or {panic(err)}.mdx()}
## Some Details
> show how we can do per month
@{model.sheet.wiki(includefilter:['pl'], period_type:.month) or {panic(err)}}

View File

@@ -9,7 +9,7 @@
Product ${name1} has revenue events (one offs) Product ${name1} has revenue events (one offs)
!!!spreadsheet.sheet_wiki @{model.sheet.wiki() or {''}}
namefilter:'${name1}_revenue,${name1}_cogs,${name1}_cogs_perc,${name1}_maintenance_month_perc' sheetname:'bizmodel_tf9 namefilter:'${name1}_revenue,${name1}_cogs,${name1}_cogs_perc,${name1}_maintenance_month_perc' sheetname:'bizmodel_tf9
- COGS = Cost of Goods Sold (is our cost to deliver the product/service) - COGS = Cost of Goods Sold (is our cost to deliver the product/service)
@@ -21,7 +21,7 @@ Product ${name1} has revenue events (one offs)
Product sold and its revenue/cost of goods Product sold and its revenue/cost of goods
!!!spreadsheet.sheet_wiki @{model.sheet.wiki() or {''}}
namefilter:'${name1}_nr_sold,${name1}_revenue_setup,${name1}_revenue_monthly,${name1}_cogs_setup,${name1}_cogs_setup_perc,${name1}_cogs_monthly,${name1}_cogs_monthly_perc' namefilter:'${name1}_nr_sold,${name1}_revenue_setup,${name1}_revenue_monthly,${name1}_cogs_setup,${name1}_cogs_setup_perc,${name1}_cogs_monthly,${name1}_cogs_monthly_perc'
sheetname:'bizmodel_tf9 sheetname:'bizmodel_tf9
@@ -40,20 +40,19 @@ This product ${name1} is recurring, means customer pays per month ongoing, the p
#### the revenue/cogs calculated #### the revenue/cogs calculated
@{model.sheet.wiki() or {''}}
!!!spreadsheet.sheet_wiki
namefilter:'${name1}_nr_sold_recurring' namefilter:'${name1}_nr_sold_recurring'
sheetname:'bizmodel_tf9 sheetname:'bizmodel_tf9
This results in following revenues and cogs: This results in following revenues and cogs:
!!!spreadsheet.sheet_wiki @{model.sheet.wiki() or {''}}
namefilter:'${name1}_revenue_setup_total,${name1}_revenue_monthly_total,${name1}_cogs_setup_total,${name1}_cogs_monthly_total,${name1}_cogs_setup_from_perc,${name1}_cogs_monthly_from_perc,${name1}_maintenance_month, namefilter:'${name1}_revenue_setup_total,${name1}_revenue_monthly_total,${name1}_cogs_setup_total,${name1}_cogs_monthly_total,${name1}_cogs_setup_from_perc,${name1}_cogs_monthly_from_perc,${name1}_maintenance_month,
${name1}_revenue_monthly_recurring,${name1}_cogs_monthly_recurring' ${name1}_revenue_monthly_recurring,${name1}_cogs_monthly_recurring'
sheetname:'bizmodel_tf9 sheetname:'bizmodel_tf9
resulting revenues: resulting revenues:
!!!spreadsheet.sheet_wiki @{model.sheet.wiki() or {''}}
namefilter:'${name1}_revenue_total,${name1}_cogs_total' namefilter:'${name1}_revenue_total,${name1}_cogs_total'
sheetname:'bizmodel_tf9 sheetname:'bizmodel_tf9
@@ -61,8 +60,3 @@ resulting revenues:
!!!spreadsheet.graph_line_row rowname:'${name1}_cogs_total' unit:million sheetname:'bizmodel_tf9' !!!spreadsheet.graph_line_row rowname:'${name1}_cogs_total' unit:million sheetname:'bizmodel_tf9'
!!!spreadsheet.graph_line_row rowname:'${name1}_revenue_total' unit:million sheetname:'bizmodel_tf9' !!!spreadsheet.graph_line_row rowname:'${name1}_revenue_total' unit:million sheetname:'bizmodel_tf9'
@end //product has_revenue
@end //loop

View File

@@ -0,0 +1,68 @@
# @{product.title}
@{product.description}
#### parameters for the product
@if product.has_oneoffs
Product ${name1} has revenue events (one offs)
!!!spreadsheet.sheet_wiki
namefilter:'${name1}_revenue,${name1}_cogs,${name1}_cogs_perc,${name1}_maintenance_month_perc' sheetname:'bizmodel_tf9
- COGS = Cost of Goods Sold (is our cost to deliver the product/service)
- maintenance is fee we charge to the customer per month in relation to the revenue we charged e.g. 1% of a product which was sold for 1m EUR means we charge 1% of 1 m EUR per month.
@end //one offs
@if product.has_items
Product sold and its revenue/cost of goods
!!!spreadsheet.sheet_wiki
namefilter:'${name1}_nr_sold,${name1}_revenue_setup,${name1}_revenue_monthly,${name1}_cogs_setup,${name1}_cogs_setup_perc,${name1}_cogs_monthly,${name1}_cogs_monthly_perc'
sheetname:'bizmodel_tf9
- nr sold, is the nr sold per month of ${name1}
- revenue setup is setup per item for ${name1}, this is the money we receive. Similar there is a revenue monthly.
- cogs = Cost of Goods Sold (is our cost to deliver the product)
- can we as a setup per item, or per month per item
@if product.nr_months_recurring>1
This product ${name1} is recurring, means customer pays per month ongoing, the period customer is paying for in months is: **${product.nr_months_recurring}**
@end //recurring
@end
#### the revenue/cogs calculated
!!!spreadsheet.sheet_wiki
namefilter:'${name1}_nr_sold_recurring'
sheetname:'bizmodel_tf9
This results in following revenues and cogs:
!!!spreadsheet.sheet_wiki
namefilter:'${name1}_revenue_setup_total,${name1}_revenue_monthly_total,${name1}_cogs_setup_total,${name1}_cogs_monthly_total,${name1}_cogs_setup_from_perc,${name1}_cogs_monthly_from_perc,${name1}_maintenance_month,
${name1}_revenue_monthly_recurring,${name1}_cogs_monthly_recurring'
sheetname:'bizmodel_tf9
resulting revenues:
!!!spreadsheet.sheet_wiki
namefilter:'${name1}_revenue_total,${name1}_cogs_total'
sheetname:'bizmodel_tf9
!!!spreadsheet.graph_line_row rowname:'${name1}_cogs_total' unit:million sheetname:'bizmodel_tf9'
!!!spreadsheet.graph_line_row rowname:'${name1}_revenue_total' unit:million sheetname:'bizmodel_tf9'
@end //product has_revenue
@end //loop

View File

@@ -0,0 +1,138 @@
module spreadsheet
import freeflowuniverse.herolib.data.markdownparser.elements
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.web.echarts
pub fn (s Sheet) title_chart(args RowGetArgs) echarts.EChartsOption {
return echarts.EChartsOption{
title: echarts.Title{
text: args.title
subtext: args.title_sub
left: 'center'
}
}
}
pub fn (s Sheet) line_chart(args_ RowGetArgs) !echarts.EChartsOption {
mut args := args_
rownames := s.rownames_get(args)!
header := s.header_get_as_string(args.period_type)!
mut series := []echarts.Series{}
for rowname in rownames {
data := s.data_get_as_string(RowGetArgs{
...args
rowname: rowname
})!
series << echarts.Series{
name: rowname
type_: 'line'
stack: 'Total'
data: data.split(',')
}
}
return echarts.EChartsOption{
title: s.title_chart(args).title
tooltip: echarts.Tooltip{
trigger: 'axis'
}
legend: echarts.Legend{
data: rownames
}
grid: echarts.Grid{
left: '3%'
right: '4%'
bottom: '3%'
contain_label: true
}
toolbox: echarts.Toolbox{
feature: echarts.ToolboxFeature{
save_as_image: {}
}
}
x_axis: echarts.XAxis{
type_: 'category'
boundary_gap: false
data: header.split(',')
}
y_axis: echarts.YAxis{
type_: 'value'
}
series: series
}
}
pub fn (s Sheet) bar_chart(args_ RowGetArgs) !echarts.EChartsOption {
mut args := args_
args.rowname = s.rowname_get(args)!
header := s.header_get_as_list(args.period_type)!
data := s.data_get_as_list(args)!
return echarts.EChartsOption{
title: s.title_chart(args).title
x_axis: echarts.XAxis{
type_: 'category'
data: header
}
y_axis: echarts.YAxis{
type_: 'value'
}
series: [
echarts.Series{
name: args.rowname
type_: 'bar'
data: data
stack: ''
},
]
}
}
pub fn (s Sheet) pie_chart(args_ RowGetArgs) !echarts.EChartsOption {
mut args := args_
args.rowname = s.rowname_get(args)!
header := s.header_get_as_list(args.period_type)!
data := s.data_get_as_list(args)!
if header.len != data.len {
return error('Data and header lengths must match.')
}
mut pie_data := []map[string]string{}
for i, _ in data {
pie_data << {
'value': data[i].trim_space().trim("'")
'name': header[i].trim_space().trim("'")
}
}
return echarts.EChartsOption{
title: s.title_chart(args).title
tooltip: echarts.Tooltip{
trigger: 'item'
}
legend: echarts.Legend{
data: header
orient: 'vertical'
left: 'left'
}
series: [
echarts.Series{
name: 'Data'
type_: 'pie'
radius: args.size.int()
data: pie_data.map(it.str())
emphasis: echarts.Emphasis{
item_style: echarts.ItemStyle{
shadow_blur: 10
shadow_offset_x: 0
shadow_color: 'rgba(0, 0, 0, 0.5)'
}
}
},
]
}
}

View File

@@ -0,0 +1,77 @@
module spreadsheet
import freeflowuniverse.herolib.data.markdownparser.elements
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.web.echarts
fn test_title_chart() {
mut s := sheet_new() or { panic(err) }
mut nrnodes := s.row_new(
name: 'nrnodes'
growth: '5:100,55:1000'
tags: 'cat:nodes color:yellow urgent'
)!
args := RowGetArgs{
rowname: 'nrnodes'
title: 'Main Title'
title_sub: 'Subtitle'
}
title := s.title_chart(args).title
assert title.text == 'Main Title'
assert title.subtext == 'Subtitle'
assert title.left == 'center'
}
fn test_line_chart() {
mut s := sheet_new() or { panic(err) }
mut nrnodes := s.row_new(
name: 'nrnodes'
growth: '5:100,55:1000'
tags: 'cat:nodes color:yellow urgent'
)!
args := RowGetArgs{
rowname: 'nrnodes'
title: 'Line Chart'
period_type: .month
}
option := s.line_chart(args) or { panic(err) }
assert option.title.text == 'Line Chart'
assert option.tooltip.trigger == 'axis'
assert option.grid.contain_label == true
}
fn test_bar_chart() {
mut s := sheet_new() or { panic(err) }
mut nrnodes := s.row_new(
name: 'nrnodes'
growth: '5:100,55:1000'
tags: 'cat:nodes color:yellow urgent'
)!
args := RowGetArgs{
rowname: 'nrnodes'
title: 'Bar Chart'
period_type: .year
}
option := s.bar_chart(args) or { panic(err) }
assert option.title.text == 'Bar Chart'
assert option.x_axis.type_ == 'category'
assert option.y_axis.type_ == 'value'
}
fn test_pie_chart() {
mut s := sheet_new() or { panic(err) }
mut nrnodes := s.row_new(
name: 'nrnodes'
growth: '5:100,55:1000'
tags: 'cat:nodes color:yellow urgent'
)!
args := RowGetArgs{
rowname: 'nrnodes'
title: 'Pie Chart'
period_type: .quarter
}
option := s.pie_chart(args) or { panic(err) }
assert option.title.text == 'Pie Chart'
assert option.tooltip.trigger == 'item'
assert option.legend.data.len > 0
}

View File

@@ -92,7 +92,7 @@ pub fn playmacro(action Action) !string {
content = sh.wiki(args) or { panic(err) } content = sh.wiki(args) or { panic(err) }
} }
'graph_title_row' { 'graph_title_row' {
content = sh.wiki_title_chart(args) content = sh.wiki_title_chart(args)!
} }
'graph_line_row' { 'graph_line_row' {
content = sh.wiki_line_chart(args)! content = sh.wiki_line_chart(args)!

View File

@@ -202,7 +202,7 @@ pub fn (s Sheet) tosmaller(args_ ToYearQuarterArgs) !&Sheet {
// tagsfilter []string // tagsfilter []string
// tags if set will see that there is at least one corresponding tag per row // tags if set will see that there is at least one corresponding tag per row
// rawsfilter is list of names of rows which will be included // rawsfilter is list of names of rows which will be included
pub fn (mut s Sheet) toyear(args ToYearQuarterArgs) !&Sheet { pub fn (s Sheet) toyear(args ToYearQuarterArgs) !&Sheet {
mut args2 := args mut args2 := args
args2.period_months = 12 args2.period_months = 12
return s.tosmaller(args2) return s.tosmaller(args2)
@@ -215,7 +215,7 @@ pub fn (mut s Sheet) toyear(args ToYearQuarterArgs) !&Sheet {
// tagsfilter []string // tagsfilter []string
// tags if set will see that there is at least one corresponding tag per row // tags if set will see that there is at least one corresponding tag per row
// rawsfilter is list of names of rows which will be included // rawsfilter is list of names of rows which will be included
pub fn (mut s Sheet) toquarter(args ToYearQuarterArgs) !&Sheet { pub fn (s Sheet) toquarter(args ToYearQuarterArgs) !&Sheet {
mut args2 := args mut args2 := args
args2.period_months = 3 args2.period_months = 3
return s.tosmaller(args2) return s.tosmaller(args2)
@@ -260,7 +260,9 @@ pub fn (mut s Sheet) json() string {
// find row, report error if not found // find row, report error if not found
pub fn (s Sheet) row_get(name string) !&Row { pub fn (s Sheet) row_get(name string) !&Row {
row := s.rows[name] or { return error('could not find row with name: ${name}, available rows: ${s.rows.keys()}') } row := s.rows[name] or {
return error('could not find row with name: ${name}, available rows: ${s.rows.keys()}')
}
return row return row
} }

View File

@@ -0,0 +1,49 @@
module spreadsheet
import os
import freeflowuniverse.herolib.core.pathlib
@[params]
pub struct ExportArgs{
pub mut:
path string
}
fn format_number(val f64) string {
if val < 0.001 && val > -0.001 {
return '0'
}
if val >= 1000.0 || val <= -1000.0 {
return int(val).str()
}
// Format small numbers with 3 decimal places to handle floating point precision
return '${val:.3f}'
}
pub fn (mut s Sheet) export(args ExportArgs) !string {
mut result := []string{}
// Add headers
mut header_row := ['Name', 'Description', 'AggregateType', 'Tags', 'Subgroup']
header_row << s.header()!
result << header_row.join('|')
// Add rows
for _, row in s.rows {
mut row_data := [row.name, row.description, row.aggregatetype.str(), row.tags, row.subgroup]
for cell in row.cells {
if cell.empty {
row_data << '-'
} else {
row_data << format_number(cell.val)
}
}
result << row_data.join('|')
}
if args.path.len>0{
mut p:=pathlib.get_file(path:args.path.replace("~",os.home_dir()), create:true, delete:true)!
p.write(result.join('\n'))!
}
return result.join('\n')
}

View File

@@ -71,19 +71,19 @@ pub fn (s Sheet) rowname_get(args RowGetArgs) !string {
} }
// return e.g. "'Y1', 'Y2', 'Y3', 'Y4', 'Y5', 'Y6'" if year, is for header // return e.g. "'Y1', 'Y2', 'Y3', 'Y4', 'Y5', 'Y6'" if year, is for header
pub fn (mut s Sheet) header_get_as_list(period_type PeriodType) ![]string { pub fn (s Sheet) header_get_as_list(period_type PeriodType) ![]string {
str := s.header_get_as_string(period_type)! str := s.header_get_as_string(period_type)!
return str.split(',') return str.split(',')
} }
// return e.g. "'Y1', 'Y2', 'Y3', 'Y4', 'Y5', 'Y6'" if year, is for header // return e.g. "'Y1', 'Y2', 'Y3', 'Y4', 'Y5', 'Y6'" if year, is for header
pub fn (mut s Sheet) data_get_as_list(args RowGetArgs) ![]string { pub fn (s Sheet) data_get_as_list(args RowGetArgs) ![]string {
str := s.data_get_as_string(args)! str := s.data_get_as_string(args)!
return str.split(',') return str.split(',')
} }
// return e.g. "'Y1', 'Y2', 'Y3', 'Y4', 'Y5', 'Y6'" if year, is for header // return e.g. "'Y1', 'Y2', 'Y3', 'Y4', 'Y5', 'Y6'" if year, is for header
pub fn (mut s Sheet) header_get_as_string(period_type PeriodType) !string { pub fn (s Sheet) header_get_as_string(period_type PeriodType) !string {
err_pre := "Can't get header for sheet:${s.name}\n" err_pre := "Can't get header for sheet:${s.name}\n"
nryears := int(s.nrcol / 12) nryears := int(s.nrcol / 12)
mut out := '' mut out := ''
@@ -112,7 +112,7 @@ pub fn (mut s Sheet) header_get_as_string(period_type PeriodType) !string {
} }
// return the values // return the values
pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string { pub fn (s Sheet) data_get_as_string(args RowGetArgs) !string {
if args.rowname == '' { if args.rowname == '' {
return error('rowname needs to be specified') return error('rowname needs to be specified')
} }
@@ -166,7 +166,7 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
} }
// use RowGetArgs to get to smaller version of sheet // use RowGetArgs to get to smaller version of sheet
pub fn (mut s Sheet) filter(args RowGetArgs) !&Sheet { pub fn (s Sheet) filter(args RowGetArgs) !&Sheet {
period_months := match args.period_type { period_months := match args.period_type {
.year { 12 } .year { 12 }
.month { 1 } .month { 1 }

View File

@@ -4,7 +4,7 @@ import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console
// format a sheet properly in wiki format // format a sheet properly in wiki format
pub fn (mut s Sheet) wiki(args_ RowGetArgs) !string { pub fn (s Sheet) wiki(args_ RowGetArgs) !string {
mut args := args_ mut args := args_
_ := match args.period_type { _ := match args.period_type {

View File

@@ -3,22 +3,12 @@ module spreadsheet
import freeflowuniverse.herolib.data.markdownparser.elements import freeflowuniverse.herolib.data.markdownparser.elements
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console
pub fn (mut s Sheet) wiki_title_chart(args RowGetArgs) string { pub fn (s Sheet) wiki_title_chart(args RowGetArgs) !string {
if args.title.len > 0 { return s.title_chart(args).markdown()
titletxt := "
title: {
text: '${args.title}',
subtext: '${args.title_sub}',
left: 'center'
},
"
return titletxt
}
return ''
} }
pub fn (mut s_ Sheet) wiki_row_overview(args RowGetArgs) !string { pub fn (s_ Sheet) wiki_row_overview(args RowGetArgs) !string {
mut s := s_.filter(args)! s := s_.filter(args)!
rows_values := s.rows.values().map([it.name, it.description, it.tags]) rows_values := s.rows.values().map([it.name, it.description, it.tags])
mut rows := []elements.Row{} mut rows := []elements.Row{}
@@ -43,146 +33,18 @@ pub fn (mut s_ Sheet) wiki_row_overview(args RowGetArgs) !string {
// produce a nice looking bar chart see // produce a nice looking bar chart see
// https://echarts.apache.org/examples/en/editor.html?c=line-stack // https://echarts.apache.org/examples/en/editor.html?c=line-stack
pub fn (mut s Sheet) wiki_line_chart(args_ RowGetArgs) !string { pub fn (s Sheet) wiki_line_chart(args_ RowGetArgs) !string {
mut args := args_ return s.line_chart(args_)!.markdown()
rownames := s.rownames_get(args)!
header := s.header_get_as_string(args.period_type)!
mut series_lines := []string{}
for rowname in rownames {
data := s.data_get_as_string(RowGetArgs{
...args
rowname: rowname
})!
series_lines << '{
name: \'${rowname}\',
type: \'line\',
stack: \'Total\',
data: [${data}]
}'
}
// TODO: need to implement the multiple results which can come back from the args, can be more than 1
// header := s.header_get_as_string(args.period_type)!
// data := s.data_get_as_string(args)!
// console.print_debug('HERE! ${header}')
// console.print_debug('HERE!! ${data}')
template := "
${s.wiki_title_chart(args)}
tooltip: {
trigger: 'axis'
},
legend: {
data: ${rownames}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [${header}]
},
yAxis: {
type: 'value'
},
series: [${series_lines.join(',')}]
"
out := remove_empty_line('```echarts\n{${template}\n};\n```\n')
return out
} }
// produce a nice looking bar chart see // produce a nice looking bar chart see
// https://echarts.apache.org/examples/en/index.html#chart-type-bar // https://echarts.apache.org/examples/en/index.html#chart-type-bar
pub fn (mut s Sheet) wiki_bar_chart(args_ RowGetArgs) !string { pub fn (s Sheet) wiki_bar_chart(args_ RowGetArgs) !string {
mut args := args_ return s.bar_chart(args_)!.markdown()
args.rowname = s.rowname_get(args)!
header := s.header_get_as_string(args.period_type)!
data := s.data_get_as_string(args)!
bar1 := "
${s.wiki_title_chart(args)}
xAxis: {
type: 'category',
data: [${header}]
},
yAxis: {
type: 'value'
},
series: [
{
data: [${data}],
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
}
}
]
"
out := remove_empty_line('```echarts\n{${bar1}\n};\n```\n')
return out
} }
// produce a nice looking bar chart see // produce a nice looking bar chart see
// https://echarts.apache.org/examples/en/index.html#chart-type-bar // https://echarts.apache.org/examples/en/index.html#chart-type-bar
pub fn (mut s Sheet) wiki_pie_chart(args_ RowGetArgs) !string { pub fn (s Sheet) wiki_pie_chart(args_ RowGetArgs) !string {
mut args := args_ return s.pie_chart(args_)!.markdown()
args.rowname = s.rowname_get(args)!
header := s.header_get_as_list(args.period_type)!
data := s.data_get_as_list(args)!
mut radius := ''
if args.size.len > 0 {
radius = "radius: '${args.size}',"
}
if header.len != data.len {
return error('data and header lengths must match.\n${header}\n${data}')
}
mut data_lines := []string{}
for i, _ in data {
data_lines << '{ value: ${data[i]}, name: ${header[i]}}'
}
data_str := '[${data_lines.join(',')}]'
bar1 := "
${s.wiki_title_chart(args)}
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: 'Access From',
type: 'pie',
${radius}
data: ${data_str},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
"
out := remove_empty_line('```echarts\n{${bar1}\n};\n```\n')
return out
} }

View File

@@ -0,0 +1,143 @@
module herocmds
import freeflowuniverse.herolib.web.starlight
import os
import cli { Command, Flag }
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()!
}
}

View File

@@ -0,0 +1,94 @@
# DedupeStore
DedupeStore is a content-addressable key-value store with built-in deduplication. It uses blake2b-160 content hashing to identify and deduplicate data, making it ideal for storing files or data blocks where the same content might appear multiple times.
## Features
- Content-based deduplication using blake2b-160 hashing
- Efficient storage using RadixTree for hash lookups
- Persistent storage using OurDB
- Maximum value size limit of 1MB
- Fast retrieval of data using content hash
- Automatic deduplication of identical content
## Usage
```v
import freeflowuniverse.herolib.data.dedupestor
fn main() ! {
// Create a new dedupestore
mut ds := dedupestor.new(
path: 'path/to/store'
reset: false // Set to true to reset existing data
)!
// Store some data
data := 'Hello, World!'.bytes()
hash := ds.store(data)!
println('Stored data with hash: ${hash}')
// Retrieve data using hash
retrieved := ds.get(hash)!
println('Retrieved data: ${retrieved.bytestr()}')
// Check if data exists
exists := ds.exists(hash)
println('Data exists: ${exists}')
// Attempting to store the same data again returns the same hash
same_hash := ds.store(data)!
assert hash == same_hash // True, data was deduplicated
}
```
## Implementation Details
DedupeStore uses two main components for storage:
1. **RadixTree**: Stores mappings from content hashes to data location IDs
2. **OurDB**: Stores the actual data blocks
When storing data:
1. The data is hashed using blake2b-160
2. If the hash exists in the RadixTree, the existing data location is returned
3. If the hash is new:
- Data is stored in OurDB, getting a new location ID
- Hash -> ID mapping is stored in RadixTree
- The hash is returned
When retrieving data:
1. The RadixTree is queried with the hash to get the data location ID
2. The data is retrieved from OurDB using the ID
## Size Limits
- Maximum value size: 1MB
- Attempting to store larger values will result in an error
## Error Handling
The store methods return results that should be handled with V's error handling:
```v
// Handle potential errors
if hash := ds.store(large_data) {
// Success
println('Stored with hash: ${hash}')
} else {
// Error occurred
println('Error: ${err}')
}
```
## Testing
The module includes comprehensive tests covering:
- Basic store/retrieve operations
- Deduplication functionality
- Size limit enforcement
- Edge cases
Run tests with:
```bash
v test lib/data/dedupestor/

View File

@@ -0,0 +1,99 @@
module dedupestor
import crypto.blake2b
import freeflowuniverse.herolib.data.radixtree
import freeflowuniverse.herolib.data.ourdb
pub const max_value_size = 1024 * 1024 // 1MB
// DedupeStore provides a key-value store with deduplication based on content hashing
pub struct DedupeStore {
mut:
radix &radixtree.RadixTree // For storing hash -> id mappings
data &ourdb.OurDB // For storing the actual data
}
@[params]
pub struct NewArgs {
pub mut:
path string // Base path for the store
reset bool // Whether to reset existing data
}
// new creates a new deduplication store
pub fn new(args NewArgs) !&DedupeStore {
// Create the radixtree for hash -> id mapping
mut rt := radixtree.new(
path: '${args.path}/radixtree'
reset: args.reset
)!
// Create the ourdb for actual data storage
mut db := ourdb.new(
path: '${args.path}/data'
record_size_max: max_value_size
incremental_mode: true // We want auto-incrementing IDs
reset: args.reset
)!
return &DedupeStore{
radix: rt
data: db
}
}
// store stores a value and returns its hash
// If the value already exists (same hash), returns the existing hash without storing again
pub fn (mut ds DedupeStore) store(value []u8) !string {
// Check size limit
if value.len > max_value_size {
return error('value size exceeds maximum allowed size of 1MB')
}
// Calculate blake160 hash of the value
hash := blake2b.sum160(value).hex()
// Check if this hash already exists
if _ := ds.radix.search(hash) {
// Value already exists, return the hash
return hash
}
// Store the actual data in ourdb
id := ds.data.set(data: value)!
// Convert id to bytes for storage in radixtree
id_bytes := u32_to_bytes(id)
// Store the mapping of hash -> id in radixtree
ds.radix.insert(hash, id_bytes)!
return hash
}
// get retrieves a value by its hash
pub fn (mut ds DedupeStore) get(hash string) ![]u8 {
// Get the ID from radixtree
id_bytes := ds.radix.search(hash)!
// Convert bytes back to u32 id
id := bytes_to_u32(id_bytes)
// Get the actual data from ourdb
return ds.data.get(id)!
}
// exists checks if a value with the given hash exists
pub fn (mut ds DedupeStore) exists(hash string) bool {
return if _ := ds.radix.search(hash) { true } else { false }
}
// Helper function to convert u32 to []u8
fn u32_to_bytes(n u32) []u8 {
return [u8(n), u8(n >> 8), u8(n >> 16), u8(n >> 24)]
}
// Helper function to convert []u8 to u32
fn bytes_to_u32(b []u8) u32 {
return u32(b[0]) | (u32(b[1]) << 8) | (u32(b[2]) << 16) | (u32(b[3]) << 24)
}

View File

@@ -0,0 +1,108 @@
module dedupestor
import os
fn testsuite_begin() ! {
// Ensure test directories exist and are clean
test_dirs := [
'/tmp/dedupestor_test',
'/tmp/dedupestor_test_size',
'/tmp/dedupestor_test_exists',
'/tmp/dedupestor_test_multiple'
]
for dir in test_dirs {
if os.exists(dir) {
os.rmdir_all(dir) or {}
}
os.mkdir_all(dir) or {}
}
}
fn test_basic_operations() ! {
mut ds := new(
path: '/tmp/dedupestor_test'
reset: true
)!
// Test storing and retrieving data
value1 := 'test data 1'.bytes()
hash1 := ds.store(value1)!
retrieved1 := ds.get(hash1)!
assert retrieved1 == value1
// Test deduplication
hash2 := ds.store(value1)!
assert hash1 == hash2 // Should return same hash for same data
// Test different data gets different hash
value2 := 'test data 2'.bytes()
hash3 := ds.store(value2)!
assert hash1 != hash3 // Should be different hash for different data
retrieved2 := ds.get(hash3)!
assert retrieved2 == value2
}
fn test_size_limit() ! {
mut ds := new(
path: '/tmp/dedupestor_test_size'
reset: true
)!
// Test data under size limit (1KB)
small_data := []u8{len: 1024, init: u8(index)}
small_hash := ds.store(small_data)!
retrieved := ds.get(small_hash)!
assert retrieved == small_data
// Test data over size limit (2MB)
large_data := []u8{len: 2 * 1024 * 1024, init: u8(index)}
if _ := ds.store(large_data) {
assert false, 'Expected error for data exceeding size limit'
}
}
fn test_exists() ! {
mut ds := new(
path: '/tmp/dedupestor_test_exists'
reset: true
)!
value := 'test data'.bytes()
hash := ds.store(value)!
assert ds.exists(hash) == true
assert ds.exists('nonexistent') == false
}
fn test_multiple_operations() ! {
mut ds := new(
path: '/tmp/dedupestor_test_multiple'
reset: true
)!
// Store multiple values
mut values := [][]u8{}
mut hashes := []string{}
for i in 0..5 {
value := 'test data ${i}'.bytes()
values << value
hash := ds.store(value)!
hashes << hash
}
// Verify all values can be retrieved
for i, hash in hashes {
retrieved := ds.get(hash)!
assert retrieved == values[i]
}
// Test deduplication by storing same values again
for i, value in values {
hash := ds.store(value)!
assert hash == hashes[i] // Should get same hash for same data
}
}

View File

@@ -9,15 +9,15 @@ import os
pub const version = '1.14.3' pub const version = '1.14.3'
const singleton = true const singleton = true
const default = true const default = true
const homedir = os.home_dir() pub const homedir = os.home_dir()
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED // THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap] @[heap]
pub struct DaguInstaller { pub struct DaguInstaller {
pub mut: pub mut:
name string = 'default' name string = 'default'
dagsdir string = '${homedir}/.dagu' dagsdir string = '${os.home_dir()}/.dagu'
configpath string = '${homedir}/.config/dagu' configpath string = '${os.home_dir()}/.config/dagu'
username string username string
password string @[secret] password string @[secret]
secret string @[secret] secret string @[secret]

View File

@@ -45,7 +45,8 @@ fn upload() ! {
fn install() ! { fn install() ! {
console.print_header('install bun') console.print_header('install bun')
osal.exec(cmd: 'curl -fsSL https://bun.sh/install | bash')! destroy()!
osal.exec(cmd: 'unset BUN_INSTALL && curl -fsSL https://bun.sh/install | bash')!
} }
fn destroy() ! { fn destroy() ! {

View File

@@ -3,7 +3,7 @@ module bun
import freeflowuniverse.herolib.data.paramsparser import freeflowuniverse.herolib.data.paramsparser
import os import os
pub const version = '1.2.2' pub const version = '1.2.3'
const singleton = true const singleton = true
const default = true const default = true

View File

@@ -10,7 +10,7 @@ import os
// checks if a certain version or above is installed // checks if a certain version or above is installed
fn installed() !bool { fn installed() !bool {
res := os.execute('tailwind -h') res := os.execute('tailwindcss -h')
if res.exit_code == 0 { if res.exit_code == 0 {
r := res.output.split_into_lines().filter(it.contains('tailwindcss v')) r := res.output.split_into_lines().filter(it.contains('tailwindcss v'))
if r.len != 1 { if r.len != 1 {
@@ -22,6 +22,9 @@ fn installed() !bool {
return false return false
} }
return true return true
}else{
println("error in executing tailwindcss")
println(res)
} }
return false return false
} }

View File

@@ -6,22 +6,18 @@ pub const version = '3.4.12'
const singleton = false const singleton = false
const default = true const default = true
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap] @[heap]
pub struct Tailwind { pub struct Tailwind {
pub mut: pub mut:
name string = 'default' name string = 'default'
} }
// your checking & initialization code if needed
fn obj_init(mycfg_ Tailwind) !Tailwind { fn obj_init(mycfg_ Tailwind) !Tailwind {
mut mycfg := mycfg_ mut mycfg := mycfg_
return mycfg return mycfg
} }
// called before start if done
fn configure() ! { fn configure() ! {
// mut installer := get()!
} }
/////////////NORMALLY NO NEED TO TOUCH /////////////NORMALLY NO NEED TO TOUCH

View File

@@ -0,0 +1,13 @@
!!hero_code.generate_installer
name:'tailwind4'
classname:'Tailwind'
singleton:0
templates:0
default:1
title:''
supported_platforms:''
reset:0
startupmanager:0
hasconfig:0
build:0

View File

@@ -0,0 +1,36 @@
# tailwind4
To get started
```vlang
import freeflowuniverse.herolib.installers.something. tailwind4
mut installer:= tailwind4.get()!
installer.start()!
```
## example heroscript
```hero
!!tailwind4.install
homedir: '/home/user/tailwind4'
username: 'admin'
password: 'secretpassword'
title: 'Some Title'
host: 'localhost'
port: 8888
```

View File

@@ -0,0 +1,69 @@
module tailwind4
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.installers.ulist
import os
//////////////////// following actions are not specific to instance of the object
// checks if a certain version or above is installed
fn installed() !bool {
res := os.execute('tailwindcss4 -h')
if res.exit_code == 0 {
r := res.output.split_into_lines().filter(it.contains('tailwindcss v'))
if r.len != 1 {
return error("couldn't parse tailwind4 version, expected 'tailwindcss v' on 1 row.\n${res.output}")
}
v := texttools.version(r[0].all_after(' '))
if v < texttools.version(version) {
return false
}
return true
}else{
println("error in executing tailwindcss")
println(res)
}
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{}
}
fn upload() ! {
}
fn install() ! {
console.print_header('install tailwind4')
mut url := ''
if core.is_linux_arm()! {
url = 'https://github.com/tailwindlabs/tailwindcss/releases/download/v${version}/tailwindcss-linux-arm64'
} else if core.is_linux_intel()! {
url = 'https://github.com/tailwindlabs/tailwindcss/releases/download/v${version}/tailwindcss-linux-x64'
} else if core.is_osx_arm()! {
url = 'https://github.com/tailwindlabs/tailwindcss/releases/download/v${version}/tailwindcss-macos-arm64'
} else if core.is_osx_intel()! {
url = 'https://github.com/tailwindlabs/tailwindcss/releases/download/v${version}/tailwindcss-macos-x64'
} else {
return error('unsported platform')
}
mut dest := osal.download(
url: url
minsize_kb: 40000
// reset: true
)!
osal.cmd_add(
cmdname: 'tailwind4'
source: dest.path
)!
}
fn destroy() ! {}

View File

@@ -0,0 +1,109 @@
module tailwind4
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.sysadmin.startupmanager
import freeflowuniverse.herolib.osal.zinit
__global (
tailwind_global map[string]&Tailwind
tailwind_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string
}
pub fn get(args_ ArgsGet) !&Tailwind {
return &Tailwind{}
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string // if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut args := args_
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
mut other_actions := plbook.find(filter: 'tailwind4.')!
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 tailwind4.destroy')
destroy()!
}
if other_action.name == 'install' {
console.print_debug('install action tailwind4.install')
install()!
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager {
// unknown
// screen
// zinit
// tmux
// systemd
match cat {
.zinit {
console.print_debug('startupmanager: zinit')
return startupmanager.get(cat: .zinit)!
}
.systemd {
console.print_debug('startupmanager: systemd')
return startupmanager.get(cat: .systemd)!
}
else {
console.print_debug('startupmanager: auto')
return startupmanager.get()!
}
}
}
@[params]
pub struct InstallArgs {
pub mut:
reset bool
}
pub fn (mut self Tailwind) install(args InstallArgs) ! {
switch(self.name)
if args.reset || (!installed()!) {
install()!
}
}
pub fn (mut self Tailwind) destroy() ! {
switch(self.name)
destroy()!
}
// switch instance to be used for tailwind4
pub fn switch(name string) {
tailwind_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -0,0 +1,37 @@
module tailwind4
import freeflowuniverse.herolib.data.encoderhero
pub const version = '4.0.8'
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 Tailwind {
pub mut:
name string = 'default'
}
// your checking & initialization code if needed
fn obj_init(mycfg_ Tailwind) !Tailwind {
mut mycfg := mycfg_
return mycfg
}
// called before start if done
fn configure() ! {
// mut installer := get()!
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj Tailwind) !string {
return encoderhero.encode[Tailwind](obj)!
}
pub fn heroscript_loads(heroscript string) !Tailwind {
mut obj := encoderhero.decode[Tailwind](heroscript)!
return obj
}

View File

@@ -78,7 +78,10 @@ pub fn load_config(cfg_dir string) !Config {
// Load and parse footer config // Load and parse footer config
footer_content := os.read_file(os.join_path(cfg_dir, 'footer.json'))! footer_content := os.read_file(os.join_path(cfg_dir, 'footer.json'))!
footer := json.decode(Footer, footer_content)! footer := json.decode(Footer, footer_content) or {
eprintln('footer.json in ${cfg_dir} is not in the right format please fix.\nError: ${err}')
exit(99)
}
// Load and parse main config // Load and parse main config
main_config_path := os.join_path(cfg_dir, 'main.json') main_config_path := os.join_path(cfg_dir, 'main.json')
@@ -115,7 +118,10 @@ pub fn load_config(cfg_dir string) !Config {
// Load and parse navbar config // Load and parse navbar config
navbar_content := os.read_file(os.join_path(cfg_dir, 'navbar.json'))! navbar_content := os.read_file(os.join_path(cfg_dir, 'navbar.json'))!
navbar := json.decode(Navbar, navbar_content)! navbar := json.decode(Navbar, navbar_content) or {
eprintln('navbar.json in ${cfg_dir} is not in the right format please fix.\nError: ${err}')
exit(99)
}
return Config{ return Config{
footer: footer footer: footer

View File

@@ -209,11 +209,13 @@ fn (mut site DocSite) template_install() ! {
mut build_dev_publish_ := site.path_build.file_get_new('build_dev_publish.sh')! mut build_dev_publish_ := site.path_build.file_get_new('build_dev_publish.sh')!
build_dev_publish_.template_write(build_dev_publish, true)! build_dev_publish_.template_write(build_dev_publish, true)!
build_dev_publish_.chmod(0o700)! build_dev_publish_.chmod(0o700)!
develop_templ := $tmpl('templates/develop_src.sh')
mut develop2_ := site.path_src.file_get_new('develop.sh')! mut develop2_ := site.path_src.file_get_new('develop.sh')!
develop2_.template_write(develop, true)! develop2_.template_write(develop_templ, true)!
develop2_.chmod(0o700)! develop2_.chmod(0o700)!
build_templ := $tmpl('templates/build_src.sh')
mut build2_ := site.path_src.file_get_new('build.sh')! mut build2_ := site.path_src.file_get_new('build.sh')!
build2_.template_write(build, true)! build2_.template_write(build, true)!
build2_.chmod(0o700)! build2_.chmod(0o700)!

View File

@@ -52,8 +52,7 @@ pub fn (mut f DocusaurusFactory) get(args_ DSiteGetArgs) !&DocSite {
// First, check if the new site args provides a configuration that can be written instead of template cfg dir // First, check if the new site args provides a configuration that can be written instead of template cfg dir
if cfg := args.config { if cfg := args.config {
panic("not implemented") cfg.write('${args.path}/cfg')!
// cfg.write('${args.path}/cfg')!
} else { } else {
// Then ensure cfg directory exists in src, // Then ensure cfg directory exists in src,
if !os.exists('${args.path}/cfg') { if !os.exists('${args.path}/cfg') {
@@ -71,7 +70,7 @@ pub fn (mut f DocusaurusFactory) get(args_ DSiteGetArgs) !&DocSite {
if args.init{ if args.init{
mut template_cfg := template_path.dir_get('docs')! mut template_cfg := template_path.dir_get('docs')!
template_cfg.copy(dest: '${args.path}/docs')! template_cfg.copy(dest: '${args.path}/docs')!
}else{ } else{
return error("Can't find docs dir in chosen docusaurus location: ${args.path}") return error("Can't find docs dir in chosen docusaurus location: ${args.path}")
} }
} }

View File

@@ -0,0 +1,6 @@
#!/bin/bash -e
script_dir="???cd "???dirname "??{BASH_SOURCE[0]}")" && pwd)"
cd "??{script_dir}"
hero docusaurus -bp

View File

@@ -0,0 +1,6 @@
#!/bin/bash -e
script_dir="???cd "???dirname "??{BASH_SOURCE[0]}")" && pwd)"
cd "??{script_dir}"
hero docusaurus -d

87
lib/web/echarts/echarts.v Normal file
View File

@@ -0,0 +1,87 @@
module echarts
import json
import x.json2
pub struct Title {
pub:
text string @[json: 'text'; omitempty]
subtext string @[json: 'subtext'; omitempty]
left string @[json: 'left'; omitempty]
}
pub struct Tooltip {
pub:
trigger string @[json: 'trigger'; omitempty]
}
pub struct Legend {
pub:
data []string @[json: 'data'; omitempty]
orient string @[omitempty]
left string @[omitempty]
}
pub struct Grid {
pub:
left string @[json: 'left'; omitempty]
right string @[json: 'right'; omitempty]
bottom string @[json: 'bottom'; omitempty]
contain_label bool @[json: 'containLabel'; omitempty]
}
pub struct ToolboxFeature {
pub:
save_as_image map[string]string @[json: 'saveAsImage'; omitempty]
}
pub struct Toolbox {
pub:
feature ToolboxFeature @[json: 'feature'; omitempty]
}
pub struct XAxis {
pub:
type_ string @[json: 'type'; omitempty]
boundary_gap bool @[json: 'boundaryGap'; omitempty]
data []string @[json: 'data'; omitempty]
}
pub struct YAxis {
pub:
type_ string @[json: 'type'; omitempty]
}
pub struct Series {
pub:
name string @[json: 'name'; omitempty]
type_ string @[json: 'type'; omitempty]
stack string @[json: 'stack'; omitempty]
data []string @[json: 'data'; omitempty]
radius int @[omitempty]
emphasis Emphasis @[omitempty]
}
pub struct Emphasis {
pub:
item_style ItemStyle @[json: 'itemStyle'; omitempty]
}
pub struct ItemStyle {
pub:
shadow_blur int @[json: 'shadowBlur'; omitempty]
shadow_offset_x int @[json: 'shadowOffsetX'; omitempty]
shadow_color string @[json: 'shadowColor'; omitempty]
}
pub struct EChartsOption {
pub:
title Title @[json: 'title'; omitempty]
tooltip Tooltip @[json: 'tooltip'; omitempty]
legend Legend @[json: 'legend'; omitempty]
grid Grid @[json: 'grid'; omitempty]
toolbox Toolbox @[json: 'toolbox'; omitempty]
x_axis XAxis @[json: 'xAxis'; omitempty]
y_axis YAxis @[json: 'yAxis'; omitempty]
series []Series @[json: 'series'; omitempty]
}

View File

@@ -0,0 +1,55 @@
module echarts
import json
const option_json = '{"title":{"text":"Main Title","subtext":"Subtitle","left":"center"},"tooltip":{"trigger":"axis"},"legend":{"data":["Example1","Example2"]},"grid":{"left":"3%","right":"4%","bottom":"3%","containLabel":true},"xAxis":{"type":"category","data":["Jan","Feb","Mar"]},"yAxis":{"type":"value"},"series":[{"name":"Example1","type":"line","stack":"Total","data":["10","20","30"]},{"name":"Example2","type":"line","stack":"Total","data":["15","25","35"]}]}'
fn test_echarts() {
option := EChartsOption{
title: Title{
text: 'Main Title'
subtext: 'Subtitle'
left: 'center'
}
tooltip: Tooltip{
trigger: 'axis'
}
legend: Legend{
data: ['Example1', 'Example2']
}
grid: Grid{
left: '3%'
right: '4%'
bottom: '3%'
contain_label: true
}
toolbox: Toolbox{
feature: ToolboxFeature{
save_as_image: {}
}
}
x_axis: XAxis{
type_: 'category'
boundary_gap: false
data: ['Jan', 'Feb', 'Mar']
}
y_axis: YAxis{
type_: 'value'
}
series: [
Series{
name: 'Example1'
type_: 'line'
stack: 'Total'
data: ['10', '20', '30']
},
Series{
name: 'Example2'
type_: 'line'
stack: 'Total'
data: ['15', '25', '35']
},
]
}
assert json.encode(option) == option_json
}

120
lib/web/echarts/encode.v Normal file
View File

@@ -0,0 +1,120 @@
module echarts
import x.json2 as json
pub fn (o EChartsOption) json() string {
return json.encode(o)
}
pub fn (o EChartsOption) mdx() string {
option := format_js_object(o, true)
return '<EChart option={${option}} />'
}
pub fn (o EChartsOption) markdown() string {
option := format_js_object(o, true)
return '```echarts\n{${option}\n};\n```\n'
}
// Generic function to format JavaScript-like objects
fn format_js_object[T](obj T, omitempty bool) string {
mut result := ''
result += '{'
$for field in T.fields {
field_name := if field.attrs.any(it.starts_with('json:')) {
field.attrs.filter(it.starts_with('json'))[0].all_after('json:').trim_space()
} else {
field.name
}
value := obj.$(field.name)
formatted_value := format_js_value(value, field.attrs.contains('omitempty'))
if formatted_value.trim_space() != '' || !omitempty {
result += '${field_name}: ${formatted_value.trim_space()}, '
}
}
result += '}'
if result == '{}' && omitempty {
return ''
}
return result.str().replace(', }', '}') // Remove trailing comma
}
// Fully generic function to format any JS value
// TODO: improve code below, far from cleanest implementation
// currently is sufficient since only used in echart mdx export
fn format_js_value[T](value T, omitempty bool) string {
return $if T is string {
// is actually map
if value.str().starts_with('{') && value.str().ends_with('}') {
value
// map_any := json2.raw_decode(value.str()) or {'{}'}.as_map()
// println('debugzo21 ${map_any}')
// mut val := '{'
// for k, v in map_any {
// val += '${k}: ${format_js_value(v.str(), false)}'
// }
// val += '}'
// if val == '{}' && omitempty {
// return ''
// }
// val
} else {
val := '"${value}"'
if val == '""' && omitempty {
return ''
}
val
}
} $else $if T is int {
if '${value}' == '0' && omitempty {
''
} else {
'${value}'
}
} $else $if T is f64 {
if '${value}' == '0.0' && omitempty {
''
} else {
'${value}'
}
} $else $if T is bool {
if '${value}' == 'false' && omitempty {
''
} else {
'${value}'
}
} $else $if T is $struct {
val := format_js_object(value, omitempty)
if val == '' && omitempty {
return ''
}
val
} $else $if T is $array {
mut arr := '['
for i in 0 .. value.len {
if i != 0 {
arr += ', '
}
val := format_js_value(value[i], omitempty)
if val.starts_with('"{') && val.ends_with('}"') {
arr += val.trim('"')
} else if val.starts_with('"\'') && val.ends_with('\'"') {
arr += val.trim('"')
} else if val.trim('"').trim_space().f64() != 0 {
arr += val.trim('"').trim_space()
} else if val.trim('"').trim_space() == '0' || val.trim('"').trim_space() == '0.0' {
arr += '0'
} else {
arr += val
}
}
arr += ']'
if omitempty && arr == '[]' {
return ''
}
arr
} $else {
'null'
}
}

51
lib/web/starlight/clean.v Normal file
View File

@@ -0,0 +1,51 @@
module starlight
import os
import strings
pub fn (mut site DocSite) clean(args ErrorArgs) ! {
toclean := '
/node_modules
babel.config.js
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
bun.lockb
bun.lock
yarn.lock
build.sh
build_dev.sh
build-dev.sh
develop.sh
install.sh
package.json
package-lock.json
pnpm-lock.yaml
sidebars.ts
tsconfig.json
'
//TODO: need better way how to deal with this
}

126
lib/web/starlight/config.v Normal file
View File

@@ -0,0 +1,126 @@
module starlight
import freeflowuniverse.herolib.core.pathlib
import json
import os
// Footer config structures
pub struct FooterItem {
pub mut:
label string
to string
href string
}
pub struct FooterLink {
pub mut:
title string
items []FooterItem
}
pub struct Footer {
pub mut:
style string = 'dark'
links []FooterLink
}
pub struct Main {
pub mut:
name string
title string = 'A Test Site'
// tagline string
url string = 'http://localhost/testsite'
// url_home string
// base_url string = '/' @[json: 'baseUrl']
// image string = 'img/tf_graph.png' @[required]
build_dest []string @[json: 'buildDest']
build_dest_dev []string @[json: 'buildDestDev']
content []ContentItem
}
// Navbar config structures
pub struct NavbarItem {
pub mut:
href string
label string
position string
}
pub struct Navbar {
pub mut:
title string
items []NavbarItem
}
// Combined config structure
pub struct Config {
pub mut:
footer Footer
main Main
navbar Navbar
}
//pulled from e.g. git and linked to a destination in the astro build location
pub struct ContentItem {
pub mut:
url string
dest string
replacer map[string]string //items we want to replace
}
// load_config loads all configuration from the specified directory
pub fn load_config(cfg_dir string) !Config {
// Ensure the config directory exists
if !os.exists(cfg_dir) {
return error('Config directory ${cfg_dir} does not exist')
}
// Load and parse footer config
footer_content := os.read_file(os.join_path(cfg_dir, 'footer.json'))!
footer := json.decode(Footer, footer_content)!
// Load and parse main config
main_config_path := os.join_path(cfg_dir, 'main.json')
main_content := os.read_file(main_config_path)!
main := json.decode(Main, main_content) or {
eprintln('main.json in ${cfg_dir} is not in the right format please fix.\nError: ${err}')
println('
## EXAMPLE OF A GOOD ONE:
- note the list for buildDest and buildDestDev
- note its the full path where the html is pushed too
{
"title": "ThreeFold Web4",
"tagline": "ThreeFold Web4",
"url": "https://docs.threefold.io",
"url_home": "docs/introduction",
"image": "img/tf_graph.png",
"buildDest":["root@info.ourworld.tf:/root/hero/www/info/tfgrid4"],
"buildDestDev":["root@info.ourworld.tf:/root/hero/www/infodev/tfgrid4"]
}
')
exit(99)
}
// Load and parse navbar config
navbar_content := os.read_file(os.join_path(cfg_dir, 'navbar.json'))!
navbar := json.decode(Navbar, navbar_content)!
return Config{
footer: footer
main: main
navbar: navbar
}
}
pub fn (c Config) write(path string) ! {
mut footer_file := pathlib.get_file(path: '${path}/footer.json', create: true)!
footer_file.write(json.encode(c.footer))!
mut main_file := pathlib.get_file(path: '${path}/main.json', create: true)!
main_file.write(json.encode(c.main))!
mut navbar_file := pathlib.get_file(path: '${path}/navbar.json', create: true)!
navbar_file.write(json.encode(c.navbar))!
}

View File

@@ -0,0 +1,42 @@
module starlight
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.develop.gittools
@[heap]
pub struct StarlightFactory {
pub mut:
sites []&DocSite @[skip; str: skip]
path_build pathlib.Path
// path_publish pathlib.Path
args StarlightArgs
}
@[params]
pub struct StarlightArgs {
pub mut:
// publish_path string
build_path string
production bool
update bool
}
pub fn new(args_ StarlightArgs) !&StarlightFactory {
mut args := args_
if args.build_path == '' {
args.build_path = '${os.home_dir()}/hero/var/starlight'
}
// if args.publish_path == ""{
// args.publish_path = "${os.home_dir()}/hero/var/starlight/publish"
// }
mut ds := &StarlightFactory{
args: args_
path_build: pathlib.get_dir(path: args.build_path, create: true)!
// path_publish: pathlib.get_dir(path: args_.publish_path, create: true)!
}
ds.template_install(install:true,template_update:args.update,delete:true)!
return ds
}

24
lib/web/starlight/model.v Normal file
View File

@@ -0,0 +1,24 @@
module starlight
pub struct SiteError {
Error
pub mut:
path string
msg string
cat ErrorCat
}
pub enum ErrorCat {
unknown
image_double
file_double
file_not_found
image_not_found
page_double
page_not_found
sidebar
circular_import
def
summary
include
}

214
lib/web/starlight/site.v Normal file
View File

@@ -0,0 +1,214 @@
module starlight
import freeflowuniverse.herolib.osal.screen
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.develop.gittools
import json
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.ui.console
@[heap]
pub struct DocSite {
pub mut:
name string
url string
path_src pathlib.Path
path_build pathlib.Path
// path_publish pathlib.Path
args SiteGetArgs
errors []SiteError
config Config
factory &StarlightFactory @[skip; str: skip] // Reference to the parent
}
pub fn (mut s DocSite) build() ! {
s.generate()!
osal.exec(
cmd: '
cd ${s.path_build.path}
bash build.sh
'
retry: 0
)!
}
pub fn (mut s DocSite) build_dev_publish() ! {
s.generate()!
osal.exec(
cmd: '
cd ${s.path_build.path}
bash build_dev_publish.sh
'
retry: 0
)!
}
pub fn (mut s DocSite) build_publish()! {
s.generate()!
osal.exec(
cmd: '
cd ${s.path_build.path}
bash build_publish.sh
'
retry: 0
)!
}
pub fn (mut s DocSite) dev()! {
s.clean()!
s.generate()!
// Create screen session for starlight development server
mut screen_name := 'starlight'
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
scr.cmd_send('cd ${s.path_build.path}')!
scr.cmd_send('bash develop.sh')!
// Print instructions for user
console.print_header(' Starlight Development Server')
console.print_item('Development server is running in a screen session.')
console.print_item('To view the server output:')
console.print_item(' 1. Attach to screen: screen -r ${screen_name}')
console.print_item(' 2. To detach from screen: Press Ctrl+A then D')
console.print_item(' 3. To list all screens: screen -ls')
console.print_item('The site content is on::')
console.print_item(' 1. location of documents: ${s.path_src.path}/src')
if osal.cmd_exists('code') {
console.print_item(' 2. We opened above dir in vscode.')
osal.exec(cmd: 'code ${s.path_src.path}/src')!
}
// 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 s.args.watch_changes {
docs_path := '${s.path_src.path}/src'
watch_docs(docs_path, s.path_src.path, s.path_build.path)!
}
}
@[params]
pub struct ErrorArgs {
pub mut:
path string
msg string
cat ErrorCat
}
pub fn (mut site DocSite) error(args ErrorArgs) {
// path2 := pathlib.get(args.path)
e := SiteError{
path: args.path
msg: args.msg
cat: args.cat
}
site.errors << e
console.print_stderr(args.msg)
}
fn check_item(item string)!{
item2:=item.trim_space().trim("/").trim_space().all_after_last("/")
if ["internal","infodev","info","dev","friends","dd","web"].contains(item2){
return error("destination path is wrong, cannot be: ${item}")
}
}
fn (mut site DocSite) check() ! {
for item in site.config.main.build_dest{
check_item(item)!
}
for item in site.config.main.build_dest_dev{
check_item(item)!
}
}
pub fn (mut site DocSite) generate() ! {
console.print_header(' site generate: ${site.name} on ${site.path_build.path}')
console.print_header(' site source on ${site.path_src.path}')
site.check()!
site.template_install()!
// Now copy all directories that exist in src to build
for item in ['src', 'static', 'cfg', 'public'] {
if os.exists('${site.path_src.path}/${item}') {
mut aa := site.path_src.dir_get(item)!
aa.copy(dest: '${site.path_build.path}/${item}')!
}
}
for item in ['src'] {
if os.exists('${site.path_src.path}/${item}') {
mut aa := site.path_src.dir_get(item)!
aa.copy(dest: '${site.path_build.path}/${item}', delete: true)!
}
}
}
fn (mut site DocSite) template_install() ! {
mut gs := gittools.new()!
site.factory.template_install(template_update:false, install:false, delete:false)!
cfg := site.config
mut myhome:="\$\{HOME\}" //for usage in bash
profile_include := osal.profile_path_source()!.replace(os.home_dir(),myhome)
mydir:=site.path_build.path.replace(os.home_dir(),myhome)
for item in ['src', 'static'] {
mut aa := site.path_src.dir_get(item) or {continue}
aa.copy(dest: '${site.factory.path_build.path}/${item}', delete:false)!
}
develop := $tmpl('templates/develop.sh')
build := $tmpl('templates/build.sh')
build_dev_publish := $tmpl('templates/build_dev_publish.sh')
build_publish := $tmpl('templates/build_publish.sh')
mut develop_ := site.path_build.file_get_new('develop.sh')!
develop_.template_write(develop, true)!
develop_.chmod(0o700)!
mut build_ := site.path_build.file_get_new('build.sh')!
build_.template_write(build, true)!
build_.chmod(0o700)!
mut build_publish_ := site.path_build.file_get_new('build_publish.sh')!
build_publish_.template_write(build_publish, true)!
build_publish_.chmod(0o700)!
mut build_dev_publish_ := site.path_build.file_get_new('build_dev_publish.sh')!
build_dev_publish_.template_write(build_dev_publish, true)!
build_dev_publish_.chmod(0o700)!
mut develop2_ := site.path_src.file_get_new('develop.sh')!
develop2_.template_write(develop, true)!
develop2_.chmod(0o700)!
mut build2_ := site.path_src.file_get_new('build.sh')!
build2_.template_write(build, true)!
build2_.chmod(0o700)!
}

View File

@@ -0,0 +1,109 @@
module starlight
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.ui.console
@[params]
pub struct SiteGetArgs {
pub mut:
name string
nameshort string
path string
url string
publish_path string
build_path string
production bool
watch_changes bool = true
update bool
init bool //means create new one if needed
deploykey string
config ?Config
}
pub fn (mut f StarlightFactory) get(args_ SiteGetArgs) !&DocSite {
console.print_header(' Starlight: ${args_.name}')
mut args := args_
if args.build_path.len == 0 {
args.build_path = '${f.path_build.path}'
}
// if args.publish_path.len == 0 {
// args.publish_path = '${f.path_publish.path}/${args.name}'
// coderoot:"${os.home_dir()}/hero/var/publishcode"
mut gs := gittools.new(ssh_key_path: args.deploykey)!
if args.url.len > 0 {
args.path = gs.get_path(url: args.url)!
}
if args.path.trim_space() == "" {
args.path = os.getwd()
}
args.path = args.path.replace('~', os.home_dir())
mut r := gs.get_repo(
url: 'https://github.com/freeflowuniverse/starlight_template.git'
)!
mut template_path := r.patho()!
// First, check if the new site args provides a configuration that can be written instead of template cfg dir
if cfg := args.config {
cfg.write('${args.path}/cfg')!
} else {
// Then ensure cfg directory exists in src,
if !os.exists('${args.path}/cfg') {
if args.init{
// else copy config from template
mut template_cfg := template_path.dir_get('cfg')!
template_cfg.copy(dest: '${args.path}/cfg')!
}else{
return error("Can't find cfg dir in chosen starlight location: ${args.path}")
}
}
}
if !os.exists('${args.path}/src') {
if args.init{
mut template_cfg := template_path.dir_get('src')!
template_cfg.copy(dest: '${args.path}/src')!
} else{
return error("Can't find src dir in chosen starlight location: ${args.path}")
}
}
mut myconfig := load_config('${args.path}/cfg')!
if args.name == '' {
args.name = myconfig.main.name
}
if args.name.len==0{
return error("name for a site cannot be empty")
}
if args.nameshort.len == 0 {
args.nameshort = args.name
}
args.nameshort = texttools.name_fix(args.nameshort)
mut ds := DocSite{
name: args.name
url: args.url
path_src: pathlib.get_dir(path: args.path, create: false)!
path_build: f.path_build
// path_publish: pathlib.get_dir(path: args.publish_path, create: true)!
args: args
config: myconfig
factory: &f
}
ds.check()!
f.sites << &ds
return &ds
}

View File

@@ -0,0 +1,58 @@
module starlight
import freeflowuniverse.herolib.develop.gittools
import freeflowuniverse.herolib.osal
import freeflowuniverse.herolib.installers.web.bun
import freeflowuniverse.herolib.installers.web.tailwind
import os
@[params]
struct TemplateInstallArgs{
template_update bool = true
install bool
delete bool = true
}
fn (mut self StarlightFactory) template_install(args TemplateInstallArgs) ! {
mut gs := gittools.new()!
mut r := gs.get_repo(
url: 'https://github.com/freeflowuniverse/starlight_template.git'
pull: args.template_update
)!
mut template_path := r.patho()!
for item in ['public', 'src'] {
mut aa := template_path.dir_get(item) or {continue} //skip if not exist
aa.copy(dest: '${self.path_build.path}/${item}', delete: args.delete)!
}
for item in ['package.json', 'tsconfig.json', 'astro.config.mjs'] {
src_path := os.join_path(template_path.path, item)
dest_path := os.join_path(self.path_build.path, item)
os.cp(src_path, dest_path) or {
return error('Failed to copy ${item} to build path: ${err}')
}
}
if args.install{
// install bun
mut installer := bun.get()!
installer.install()!
mut installer2 := tailwind.get()!
installer2.install()!
osal.exec(
cmd: '
${osal.profile_path_source_and()!}
export PATH=/tmp/starlight_build/node_modules/.bin:${os.home_dir()}/.bun/bin/:??PATH
cd ${self.path_build.path}
bun install
'
)!
}
}

View File

@@ -0,0 +1,22 @@
#!/bin/bash
set -ex
script_dir="???cd "???dirname "??{BASH_SOURCE[0]}")" && pwd)"
cd "??{script_dir}"
echo "Docs directory: ??script_dir"
cd "${mydir}"
export PATH=${site.path_build.path}/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
rm -rf ${site.path_build.path}/build/
${profile_include}
bun run build
mkdir -p ${site.args.publish_path.trim_right("/")}
echo SYNC TO ${site.args.publish_path.trim_right("/")}
rsync -rv --delete ${site.path_build.path}/build/ ${site.args.publish_path.trim_right("/")}/

View File

@@ -0,0 +1,23 @@
#!/bin/bash
set -e
script_dir="???cd "???dirname "??{BASH_SOURCE[0]}")" && pwd)"
cd "??{script_dir}"
echo "Docs directory: ??script_dir"
cd "${mydir}"
export PATH=${site.path_build.path}/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
rm -rf ${site.path_build.path}/build/
${profile_include}
bun run build
@for dest in cfg.main.build_dest_dev
rsync -rv --delete ${site.path_build.path}/build/ ${dest.trim_right("/")}/
@end

View File

@@ -0,0 +1,22 @@
#!/bin/bash
set -ex
script_dir="???cd "???dirname "??{BASH_SOURCE[0]}")" && pwd)"
cd "??{script_dir}"
echo "Docs directory: ??script_dir"
cd "${mydir}"
export PATH=${site.path_build.path}/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
rm -rf ${site.path_build.path}/build/
${profile_include}
bun run build
@for dest in cfg.main.build_dest
rsync -rv --delete ${site.path_build.path}/build/ ${dest.trim_right("/")}/
@end

View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
script_dir="???cd "???dirname "??{BASH_SOURCE[0]}")" && pwd)"
cd "??{script_dir}"
echo "Docs directory: ??script_dir"
cd "${mydir}"
export PATH=${site.path_build.path}/node_modules/.bin:??{HOME}/.bun/bin/:??PATH
${profile_include}
bun dev

View File

@@ -0,0 +1,96 @@
module starlight
import freeflowuniverse.herolib.osal.notifier
import os
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
}
n.args['path_src'] = path_src
n.args['path_build'] = path_build
// 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.start()!
}
// 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)
// Skip files starting with #
if file_base.starts_with('#') {
return
}
// For files (not directories), check extensions
if !is_dir {
ext := os.file_ext(path).to_lower()
if ext !in ['.md', '.png', '.jpeg', '.jpg'] {
return
}
}
// Get relative path from docs directory
rel_path := path.replace('${args['path_src']}/src/', '')
dest_path := '${args['path_build']}/src/${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}')
}
}
}
}

View File

@@ -181,7 +181,10 @@ systemd_process_test.v
data/graphdb data/graphdb
data/radixtree data/radixtree
clients/livekit clients/livekit
data/radixtree
data/dedupestor
core/playcmds core/playcmds
' '