Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29c2fccbe5 | |||
| 975c07fc2e | |||
| ff45beac09 | |||
| 24eb709293 | |||
| 49e2146152 | |||
| cdaf64b3cf | |||
| 49af31776e | |||
| 0880823576 | |||
| 1fa1ecb8ec | |||
| d6108c9836 | |||
|
|
cc344fa60e | ||
|
|
5e468359a1 | ||
|
|
2317dd2d4c | ||
|
|
b92647c52e | ||
|
|
54024ee222 | ||
| 60d6474a42 | |||
| 8c52326550 | |||
| 52aba347a8 | |||
|
|
61a0fd2aa6 | ||
|
|
d604d739e3 | ||
|
|
a57f53fdb4 | ||
|
|
f276cdf697 | ||
|
|
7fb46a4c0b | ||
|
|
fc993a95d7 | ||
| c6ff7e7ba5 | |||
| 3f9a3fb1cd | |||
| 7ae4f7dbd0 | |||
| dbb44ec30a | |||
| 7f4fc42a7a | |||
| 01db4540b1 | |||
| 5c0c096a79 | |||
| c1719a057a | |||
| e969eacd06 | |||
| 8ed3439cdc | |||
|
|
5241dfddd4 | ||
|
|
6e0572b48a | ||
|
|
9a4a39b19a | ||
|
|
8abf113715 | ||
| 25c997fec5 | |||
|
|
1546bf7f87 | ||
|
|
02d4adcff0 | ||
|
|
147c889b53 | ||
|
|
f6e7644284 | ||
|
|
582da6d7f0 | ||
|
|
3a337b7b0a | ||
|
|
2953dd0172 | ||
|
|
08f0620305 | ||
|
|
ec22a8e0ec | ||
|
|
be4d2547e4 | ||
|
|
a7f8893399 | ||
|
|
d0b52f40b7 | ||
| 3117d288b1 | |||
|
|
6ecd190de8 | ||
|
|
dacd6d5afb | ||
|
|
de0e66a94a | ||
|
|
e86504ecd5 | ||
|
|
1b192328b2 | ||
|
|
77a77ff87e | ||
|
|
439dff4a64 | ||
|
|
f8cb6f25f7 | ||
|
|
c157c86600 | ||
| cb0110ed20 | |||
| 2bbf814003 | |||
|
86e3fdb910
|
@@ -51,7 +51,7 @@ fn do() ! {
|
||||
mut cmd := Command{
|
||||
name: 'hero'
|
||||
description: 'Your HERO toolset.'
|
||||
version: '1.0.13'
|
||||
version: '1.0.14'
|
||||
}
|
||||
|
||||
// herocmds.cmd_run_add_flags(mut cmd)
|
||||
|
||||
2
examples/biztools/bizmodel/example/.gitignore
vendored
Normal file
2
examples/biztools/bizmodel/example/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bizmodel
|
||||
dest
|
||||
41
examples/biztools/bizmodel/example/bizmodel.vsh
Executable file
41
examples/biztools/bizmodel/example/bizmodel.vsh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/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')!
|
||||
10
examples/biztools/bizmodel/example/load.md
Normal file
10
examples/biztools/bizmodel/example/load.md
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
## Loader instructions
|
||||
|
||||
this will make sure we load the appropriate biz model
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.load name:'default' url:'https://github.com/freeflowuniverse/herolib/tree/development/bizmodel/example/data'
|
||||
```
|
||||
|
||||
1
examples/biztools/bizmodel/example/wiki/.collection
Normal file
1
examples/biztools/bizmodel/example/wiki/.collection
Normal file
@@ -0,0 +1 @@
|
||||
name:bizmodel_example
|
||||
10
examples/biztools/bizmodel/example/wiki/bizmodel.md
Normal file
10
examples/biztools/bizmodel/example/wiki/bizmodel.md
Normal file
@@ -0,0 +1,10 @@
|
||||

|
||||
|
||||
# bizmodel
|
||||
|
||||
OurWorld has developed a tool to generate and keep business models up to date.
|
||||
|
||||
Our aim is to make it easy for ourworld to track changes in planning over the multiple projects and even be able to aggregated them. Because the input for such a plan is text (as you can see in this ebook) its easy to see how the modelling and parameters change over time.
|
||||
|
||||
This is a very flexible tool which will be extended for budgetting, cashflow management, shareholder tables, ...
|
||||
|
||||
4
examples/biztools/bizmodel/example/wiki/debug.md
Normal file
4
examples/biztools/bizmodel/example/wiki/debug.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Debug
|
||||
|
||||
Some tools and info to help debug the bizmodel simulator.
|
||||
|
||||
8
examples/biztools/bizmodel/example/wiki/employees.md
Normal file
8
examples/biztools/bizmodel/example/wiki/employees.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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
|
||||
5
examples/biztools/bizmodel/example/wiki/hr/cto.md
Normal file
5
examples/biztools/bizmodel/example/wiki/hr/cto.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# CTO
|
||||
|
||||
!!!bizmodel.employee_wiki bizname:'test' name:'despiegk'
|
||||
|
||||
!!wiki.include page:cto_description.md
|
||||
@@ -0,0 +1,3 @@
|
||||
## CTO Description
|
||||
|
||||
this is a page to test nested includes
|
||||
1
examples/biztools/bizmodel/example/wiki/img/.done
Normal file
1
examples/biztools/bizmodel/example/wiki/img/.done
Normal file
@@ -0,0 +1 @@
|
||||
ms1bmodel.png
|
||||
BIN
examples/biztools/bizmodel/example/wiki/img/ms1bmodel.png
Normal file
BIN
examples/biztools/bizmodel/example/wiki/img/ms1bmodel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
52
examples/biztools/bizmodel/example/wiki/overview.md
Normal file
52
examples/biztools/bizmodel/example/wiki/overview.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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'
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# HR Params
|
||||
|
||||
## Engineering
|
||||
|
||||
Costs can be grouped in cost centers which can then be used to futher process e.g. transcactions between companies.
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.costcenter_define bizname:'test'
|
||||
name:'tfdmcc'
|
||||
descr:'TFDMCC executes on near source agreement for TFTech'
|
||||
min_month:'10000USD'
|
||||
max_month:'100000USD'
|
||||
end_date:'1/1/2026' //when does agreement stop
|
||||
|
||||
!!bizmodel.costcenter_define bizname:'test'
|
||||
name:'cs_tftech'
|
||||
descr:'Nearsource agreement for TFTech towards Codescalers'
|
||||
min_month:'10000USD'
|
||||
max_month:'100000USD'
|
||||
end_date:'1/1/2026'
|
||||
|
||||
!!bizmodel.costcenter_define bizname:'test'
|
||||
name:'cs_tfcloud'
|
||||
descr:'Nearsource agreement for TFCloud towards Codescalers'
|
||||
min_month:'10000USD'
|
||||
max_month:'100000USD'
|
||||
end_date:'1/1/2026'
|
||||
|
||||
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generic Overhead Costs
|
||||
|
||||
possible parameters
|
||||
|
||||
- name
|
||||
- descr: description of the cost
|
||||
- cost: is 'month:amount,month:amount, ...', no extrapolation
|
||||
- cost_growth: is 'month:amount,month:amount, ..., or just a nr', will extrapolate
|
||||
- type: travel, admin, legal, varia, office
|
||||
- cost_percent_revenue e.g. 4%, will make sure the cost will be at least 4% of revenue
|
||||
- indexation, e.g. 2%
|
||||
|
||||
Other financial flows can be mentioned here as well.
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.cost_define bizname:'test'
|
||||
name:'rental'
|
||||
descr:'Office Rental in BE.'
|
||||
cost:'5000'
|
||||
indexation:'2%'
|
||||
type:'office'
|
||||
|
||||
!!bizmodel.cost_define bizname:'test'
|
||||
name:'oneoff'
|
||||
descr:'Event in Z.'
|
||||
cost_one:'3:50000'
|
||||
type:'event'
|
||||
|
||||
!!bizmodel.cost_define bizname:'test'
|
||||
name:'cloud'
|
||||
descr:'Datacenter and Cloud Costs'
|
||||
cost:'2000eur'
|
||||
cost_percent_revenue:'2%'
|
||||
type:'cloud'
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Department Params
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.department_define bizname:'test'
|
||||
name:'ops'
|
||||
title:'Operations'
|
||||
order:5
|
||||
|
||||
!!bizmodel.department_define bizname:'test'
|
||||
name:'coordination'
|
||||
title:'Coordination'
|
||||
order:1
|
||||
|
||||
!!bizmodel.department_define bizname:'test'
|
||||
name:'engineering'
|
||||
title:'Engineering'
|
||||
order:4
|
||||
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
# Funding Params
|
||||
|
||||
possible parameters
|
||||
|
||||
- name, e.g. for a specific person
|
||||
- descr: description of the funding
|
||||
- investment is month:amount,month:amount, ...
|
||||
- type: loan or capital
|
||||
|
||||
Other financial flows can be mentioned here as well.
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.funding_define bizname:'test'
|
||||
name:'our_investor'
|
||||
descr:'A fantastic super investor.'
|
||||
investment:'3:1000000EUR'
|
||||
type:'capital'
|
||||
|
||||
!!bizmodel.funding_define bizname:'test'
|
||||
name:'a_founder'
|
||||
descr:'Together Are Strong'
|
||||
investment:'2000000'
|
||||
type:'loan'
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
73
examples/biztools/bizmodel/example/wiki/params/hr_params.md
Normal file
73
examples/biztools/bizmodel/example/wiki/params/hr_params.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# HR Params
|
||||
|
||||
## Engineering
|
||||
|
||||
possible parameters
|
||||
|
||||
- descr, description of the function (e.g. master architect)
|
||||
- cost, any currency eg. 1000usd
|
||||
- in case cost changes over time e.g. 1:10000USD,20:20000USD,60:30000USD
|
||||
- indexation, e.g. 2%
|
||||
- department
|
||||
- name, e.g. for a specific person
|
||||
- nrpeople: how many people per month, growth over time notation e.g. 1:10,60:20 means 10 in month 1 growing to 20 month 60
|
||||
- cost_percent_revenue e.g. 4%, will make sure the cost will be at least 4% of revenue
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
sid:2
|
||||
descr:'Senior Engineer'
|
||||
cost:'1:12000,12:14000' //cost is always per person
|
||||
department:'engineering'
|
||||
nrpeople:'0:5,20:5'
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
name:'despiegk'
|
||||
title: 'CTO and crazy inventor.'
|
||||
sid:3
|
||||
descr:'CTO'
|
||||
cost:'12000EUR' //the salary is the cost independent of the fulltime status
|
||||
indexation:'10%'
|
||||
department:'coordination'
|
||||
page:'cto.md'
|
||||
fulltime: "50%" //100% means yes
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Senior Architect'
|
||||
cost:'10000USD' indexation:'5%'
|
||||
department:'engineering'
|
||||
nrpeople:'0:5,20:10'
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Junior Engineer'
|
||||
cost:'4000USD' indexation:'5%'
|
||||
department:'engineering'
|
||||
nrpeople:'0:5,20:10'
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Operations
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Ops Manager'
|
||||
cost:'1:8000,12:14000'
|
||||
department:'ops'
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Support Junior'
|
||||
cost:'2000EUR' indexation:'5%'
|
||||
department:'ops'
|
||||
nrpeople:'7:5,18:10'
|
||||
cost_percent_revenue:'1%'
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Support Senior'
|
||||
cost:'5000EUR' indexation:'5%'
|
||||
department:'ops'
|
||||
nrpeople:'3:5,20:10'
|
||||
cost_percent_revenue:'1%'
|
||||
costcenter:'tfdmcc:25,cs_tfcloud:75'
|
||||
generate_page:'../employees/support_senior.md'
|
||||
```
|
||||
14
examples/biztools/bizmodel/example/wiki/params/params.md
Normal file
14
examples/biztools/bizmodel/example/wiki/params/params.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Bizmodel Params
|
||||
|
||||
In this section we can find all the parameters for the bizmodel.
|
||||
|
||||
## how to use and read
|
||||
|
||||
The params are defined in the different instruction files e.g. revenue_params.md
|
||||
|
||||
Often you will see something like `revenue_growth:'10:1000,20:1100'` this can be read as month 10 it 1000, month 20 its 1100.
|
||||
|
||||
The software will extrapolate.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# HR Params
|
||||
|
||||
## Revenue Items (non recurring)
|
||||
|
||||
This company is a cloud company ...
|
||||
|
||||
```js
|
||||
!!bizmodel.revenue_define bizname:'test'
|
||||
descr:'OEM Deals'
|
||||
revenue_time:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_perc: '1:5%,20:10%'
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test'
|
||||
descr:'License Deals'
|
||||
revenue_growth:'10:1000,20:1100'
|
||||
cogs_perc: '10%'
|
||||
rev_delay_month: 1
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test'
|
||||
descr:'3NODE License Sales 1 Time'
|
||||
//means revenue is 100 month 1, 200 month 60
|
||||
revenue_item:'1:100,60:200'
|
||||
revenue_nr:'10:1000,24:2000,60:40000'
|
||||
cogs_perc: '10%'
|
||||
rev_delay_month: 1
|
||||
```
|
||||
|
||||
## Revenue Items Recurring
|
||||
|
||||
possible parameters
|
||||
|
||||
- name, e.g. for a specific project
|
||||
- descr, description of the revenue line item
|
||||
- revenue_setup, revenue for 1 item '1000usd'
|
||||
- revenue_monthly, revenue per month for 1 item
|
||||
- revenue_setup_delay, how many months before revenue comes in after sales
|
||||
- revenue_monthly_delay, how many months before monthly revenue starts
|
||||
- cogs_setup, cost of good for 1 item at setup
|
||||
- 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_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)
|
||||
- nr_months: how many months is recurring
|
||||
|
||||
if currency not specified then is always in USD
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.revenue_recurring_define bizname:'test'
|
||||
name: '3node_lic'
|
||||
descr:'3NODE License Sales Recurring Basic'
|
||||
revenue_setup:'1:100,60:50'
|
||||
// revenue_setup:'5'
|
||||
revenue_monthly_delay:3
|
||||
revenue_monthly:'1:1,60:1'
|
||||
// cogs_setup:'1:0'
|
||||
cogs_setup_perc:'50%'
|
||||
revenue_setup_delay:1
|
||||
cogs_monthly_perc:'50%'
|
||||
nr_sold:'10:1000,24:2000,60:40000'
|
||||
60 is the default
|
||||
nr_months:60
|
||||
```
|
||||
|
||||
13
examples/biztools/bizmodel/example/wiki/revenue.md
Normal file
13
examples/biztools/bizmodel/example/wiki/revenue.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Revenue
|
||||
|
||||
Overview of achieved revenue.
|
||||
|
||||
Unit is in Million USD.
|
||||
|
||||
!!bizmodel.sheet_wiki title:'REVENUE' includefilter:rev sheetname:'bizmodel_test'
|
||||
|
||||
!!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'
|
||||
13
examples/biztools/bizmodel/example/wiki/summary.md
Normal file
13
examples/biztools/bizmodel/example/wiki/summary.md
Normal file
@@ -0,0 +1,13 @@
|
||||
- [bizmodel](bizmodel_example/bizmodel.md)
|
||||
- [Revenue](bizmodel_example/revenue.md)
|
||||
- [Result](bizmodel_example/overview.md)
|
||||
- [parameters](bizmodel_example/params.md)
|
||||
- [revenue_params](bizmodel_example/params/revenue_params.md)
|
||||
- [funding_params](bizmodel_example/params/funding_params.md)
|
||||
- [hr_params](bizmodel_example/params/hr_params.md)
|
||||
- [costs_params](bizmodel_example/params/costs_params.md)
|
||||
- [rows overview](bizmodel_example/rows_overview.md)
|
||||
- [employees](bizmodel_example/employees.md)
|
||||
- [debug](bizmodel_example/debug.md)
|
||||
- [worksheet](bizmodel_example/worksheet.md)
|
||||
|
||||
4
examples/biztools/bizmodel/example/wiki/worksheet.md
Normal file
4
examples/biztools/bizmodel/example/wiki/worksheet.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Overview of the rows in the biz model sheet
|
||||
|
||||
|
||||
!!bizmodel.sheet_wiki sheetname:'bizmodel_test'
|
||||
48
examples/biztools/bizmodel/tf9_biz.vsh
Executable file
48
examples/biztools/bizmodel/tf9_biz.vsh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env -S v -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.ui.console
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.core.playcmds
|
||||
import freeflowuniverse.herolib.web.mdbook
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
import os
|
||||
|
||||
const name = 'tf9_budget'
|
||||
|
||||
const wikipath = '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/info_ourworld/collections/${name}'
|
||||
const summarypath = '${wikipath}/summary.md'
|
||||
|
||||
// mut sh := spreadsheet.sheet_new(name: 'test2') or { panic(err) }
|
||||
// println(sh)
|
||||
// sh.row_new(descr: 'this is a description', name: 'something', growth: '0:100aed,55:1000eur')!
|
||||
// println(sh)
|
||||
// println(sh.wiki()!)
|
||||
|
||||
// exit(0)
|
||||
|
||||
// execute the actions so we have the info populated
|
||||
// mut plb:=playbook.new(path: wikipath)!
|
||||
// playcmds.run(mut plb,false)!
|
||||
|
||||
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
|
||||
|
||||
// just run the doctree & mdbook and it should
|
||||
// load the doctree, these are all collections
|
||||
mut tree := doctree.new(name: name)!
|
||||
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 ${name}'
|
||||
)!
|
||||
mdbook.book_open('bizmodel')!
|
||||
12
examples/biztools/bizmodel/todo.md
Normal file
12
examples/biztools/bizmodel/todo.md
Normal file
@@ -0,0 +1,12 @@
|
||||
need to find where the manual is
|
||||
|
||||
- [manual](bizmodel_example/configuration.md)
|
||||
- [widgets](bizmodel_example/widgets.md)
|
||||
- [graph_bar_row](bizmodel_example/graph_bar_row.md)
|
||||
- [sheet_tables](bizmodel_example/sheet_tables.md)
|
||||
- [widget_args](bizmodel_example/widget_args.md)
|
||||
- [params](bizmodel_example/configuration.md)
|
||||
- [revenue params](bizmodel_example/revenue_params.md)
|
||||
- [funding params](bizmodel_example/funding_params.md)
|
||||
- [hr params](bizmodel_example/hr_params.md)
|
||||
- [costs params](bizmodel_example/costs_params.md)
|
||||
11
examples/biztools/investor_tool.vsh
Executable file
11
examples/biztools/investor_tool.vsh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env -S v -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.biz.investortool
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import os
|
||||
|
||||
mut plbook := playbook.new(
|
||||
path: '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/investorstool/output'
|
||||
)!
|
||||
mut it := investortool.play(mut plbook)!
|
||||
it.check()!
|
||||
@@ -1 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
@@ -1 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.daguserver
|
||||
import freeflowuniverse.herolib.installers.infra.zinit
|
||||
|
||||
// make sure zinit is there and running, will restart it if needed
|
||||
mut z := zinit.get()!
|
||||
z.destroy()!
|
||||
z.start()!
|
||||
|
||||
// mut ds := daguserver.get()!
|
||||
// ds.destroy()!
|
||||
// ds.start()!
|
||||
|
||||
// println(ds)
|
||||
8
examples/installers/db/meilisearch.vsh
Executable file
8
examples/installers/db/meilisearch.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.db.meilisearch_installer
|
||||
|
||||
mut meilisearch := meilisearch_installer.get()!
|
||||
meilisearch.install()!
|
||||
meilisearch.start()!
|
||||
meilisearch.destroy()!
|
||||
9
examples/installers/db/postgresql.vsh
Executable file
9
examples/installers/db/postgresql.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.db.postgresql as postgresql_installer
|
||||
|
||||
mut db := postgresql_installer.get()!
|
||||
|
||||
db.install()!
|
||||
db.start()!
|
||||
db.destroy()!
|
||||
9
examples/installers/db/zerodb.vsh
Executable file
9
examples/installers/db/zerodb.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.db.zerodb as zerodb_installer
|
||||
|
||||
mut db := zerodb_installer.get()!
|
||||
|
||||
db.install()!
|
||||
db.start()!
|
||||
db.destroy()!
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.gitea as gitea_installer
|
||||
|
||||
mut installer := gitea_installer.get(name: 'test')!
|
||||
|
||||
// if you want to configure using heroscript
|
||||
gitea_installer.play(
|
||||
heroscript: "
|
||||
!!gitea.configure name:test
|
||||
passwd:'something'
|
||||
domain: 'docs.info.com'
|
||||
"
|
||||
)!
|
||||
|
||||
installer.start()!
|
||||
8
examples/installers/infra/gitea.vsh
Executable file
8
examples/installers/infra/gitea.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.gitea as gitea_installer
|
||||
|
||||
mut gitea := gitea_installer.get()!
|
||||
gitea.install()!
|
||||
gitea.start()!
|
||||
gitea.destroy()!
|
||||
8
examples/installers/infra/livekit.vsh
Executable file
8
examples/installers/infra/livekit.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.livekit as livekit_installer
|
||||
|
||||
mut livekit := livekit_installer.get()!
|
||||
livekit.install()!
|
||||
livekit.start()!
|
||||
livekit.destroy()!
|
||||
8
examples/installers/infra/screen.vsh
Executable file
8
examples/installers/infra/screen.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.screen as screen_installer
|
||||
|
||||
mut screen := screen_installer.get()!
|
||||
|
||||
screen.install()!
|
||||
screen.destroy()!
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.zinit as zinit_installer
|
||||
import freeflowuniverse.herolib.installers.infra.zinit_installer
|
||||
|
||||
mut installer := zinit_installer.get()!
|
||||
installer.install()!
|
||||
installer.start()!
|
||||
// installer.destroy()!
|
||||
6
examples/installers/lang/golang.vsh
Executable file
6
examples/installers/lang/golang.vsh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.golang
|
||||
|
||||
mut golang_installer := golang.get()!
|
||||
golang_installer.install()!
|
||||
7
examples/installers/lang/nodejs.vsh
Executable file
7
examples/installers/lang/nodejs.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.nodejs
|
||||
|
||||
mut nodejs_installer := nodejs.get()!
|
||||
// nodejs_installer.install()!
|
||||
nodejs_installer.destroy()!
|
||||
7
examples/installers/lang/python.vsh
Executable file
7
examples/installers/lang/python.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.python as python_module
|
||||
|
||||
mut python_installer := python_module.get()!
|
||||
// python_installer.install()!
|
||||
python_installer.destroy()!
|
||||
7
examples/installers/lang/rust.vsh
Executable file
7
examples/installers/lang/rust.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.rust as rust_module
|
||||
|
||||
mut rust_installer := rust_module.get()!
|
||||
// rust_installer.install()!
|
||||
rust_installer.destroy()!
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
|
||||
import freeflowuniverse.herolib.installers.net.mycelium_installer
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
mut installer := mycelium_installer.get()!
|
||||
@@ -13,25 +13,27 @@ mut client := mycelium.get()!
|
||||
|
||||
// Send a message to a node by public key
|
||||
// Parameters: public_key, payload, topic, wait_for_reply
|
||||
msg := client.send_msg('abc123...', // destination public key
|
||||
'Hello World', // message payload
|
||||
'greetings', // optional topic
|
||||
true // wait for reply
|
||||
)!
|
||||
msg := client.send_msg(
|
||||
public_key: 'abc123...' // destination public key
|
||||
payload: 'Hello World' // message payload
|
||||
topic: 'greetings' // optional topic
|
||||
wait: true // wait for reply
|
||||
)!
|
||||
println('Sent message ID: ${msg.id}')
|
||||
|
||||
// Receive messages
|
||||
// Parameters: wait_for_message, peek_only, topic_filter
|
||||
received := client.receive_msg(true, false, 'greetings')!
|
||||
received := client.receive_msg(wait: true, peek: false, topic: 'greetings')!
|
||||
println('Received message from: ${received.src_pk}')
|
||||
println('Message payload: ${received.payload}')
|
||||
|
||||
// Reply to a message
|
||||
client.reply_msg(received.id, // original message ID
|
||||
received.src_pk, // sender's public key
|
||||
'Got your message!', // reply payload
|
||||
'greetings' // topic
|
||||
)!
|
||||
client.reply_msg(
|
||||
id: received.id // original message ID
|
||||
public_key: received.src_pk // sender's public key
|
||||
payload: 'Got your message!' // reply payload
|
||||
topic: 'greetings' // topic
|
||||
)!
|
||||
|
||||
// Check message status
|
||||
status := client.get_msg_status(msg.id)!
|
||||
7
examples/installers/net/wireguard.vsh
Executable file
7
examples/installers/net/wireguard.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.net.wireguard_installer as wireguard
|
||||
|
||||
mut wireguard_installer := wireguard.get()!
|
||||
wireguard_installer.install()!
|
||||
wireguard_installer.destroy()!
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import time
|
||||
import freeflowuniverse.herolib.installers.db.postgresql
|
||||
|
||||
mut db := postgresql.get()!
|
||||
|
||||
// db.destroy()!
|
||||
db.start()!
|
||||
|
||||
// db.db_create('my_new_db')!
|
||||
// db.stop()!
|
||||
// db.start()!
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.actrunner
|
||||
import freeflowuniverse.herolib.installers.virt.herocontainers
|
||||
// import freeflowuniverse.herolib.installers.virt.herocontainers
|
||||
|
||||
actrunner.install()!
|
||||
mut actrunner_ := actrunner.get()!
|
||||
actrunner_.install()!
|
||||
// herocontainers.start()!
|
||||
8
examples/installers/sysadmintools/garage_s3.vsh
Executable file
8
examples/installers/sysadmintools/garage_s3.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.garage_s3 as garage_s3_installer
|
||||
|
||||
mut garage_s3 := garage_s3_installer.get()!
|
||||
garage_s3.install()!
|
||||
garage_s3.start()!
|
||||
garage_s3.destroy()!
|
||||
7
examples/installers/sysadmintools/rclone.vsh
Executable file
7
examples/installers/sysadmintools/rclone.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.rclone as rclone_installer
|
||||
|
||||
mut rclone := rclone_installer.get()!
|
||||
rclone.install()!
|
||||
rclone.destroy()!
|
||||
@@ -4,3 +4,4 @@ import freeflowuniverse.herolib.installers.threefold.griddriver
|
||||
|
||||
mut griddriver_installer := griddriver.get()!
|
||||
griddriver_installer.install()!
|
||||
griddriver_installer.destroy()!
|
||||
9
examples/installers/virt/dagu.vsh
Executable file
9
examples/installers/virt/dagu.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.daguserver
|
||||
import freeflowuniverse.herolib.installers.infra.zinit_installer
|
||||
|
||||
mut ds := daguserver.get()!
|
||||
ds.install()!
|
||||
ds.start()!
|
||||
ds.destroy()!
|
||||
11
examples/installers/virt/pacman.vsh
Executable file
11
examples/installers/virt/pacman.vsh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.virt.pacman as pacman_installer
|
||||
|
||||
mut pacman := pacman_installer.get()!
|
||||
|
||||
// To install
|
||||
pacman.install()!
|
||||
|
||||
// To remove
|
||||
pacman.destroy()!
|
||||
60
examples/osal/coredns/example.vsh
Executable file
60
examples/osal/coredns/example.vsh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
|
||||
import freeflowuniverse.herolib.osal.coredns
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
|
||||
// coredns_installer.delete()!
|
||||
mut installer := coredns_installer.get()!
|
||||
// coredns_installer.fix()!
|
||||
installer.start()!
|
||||
|
||||
mut script := "
|
||||
!!dns.a_record
|
||||
sub_domain: 'host1'
|
||||
ip: '1.2.3.4'
|
||||
ttl: 300
|
||||
|
||||
!!dns.aaaa_record
|
||||
sub_domain: 'host1'
|
||||
ip: '2001:db8::1'
|
||||
ttl: 300
|
||||
|
||||
!!dns.mx_record
|
||||
sub_domain: '*'
|
||||
host: 'mail.example.com'
|
||||
preference: 10
|
||||
ttl: 300
|
||||
|
||||
!!dns.txt_record
|
||||
sub_domain: '*'
|
||||
text: 'v=spf1 mx ~all'
|
||||
ttl: 300
|
||||
|
||||
!!dns.srv_record
|
||||
service: 'ssh'
|
||||
protocol: 'tcp'
|
||||
host: 'host1'
|
||||
target: 'sip.example.com'
|
||||
port: 5060
|
||||
priority: 10
|
||||
weight: 100
|
||||
ttl: 300
|
||||
|
||||
!!dns.ns_record
|
||||
host: 'ns1.example.com'
|
||||
ttl: 300
|
||||
|
||||
!!dns.soa_record
|
||||
mbox: 'hostmaster.example.com'
|
||||
ns: 'ns1.example.com'
|
||||
refresh: 44
|
||||
retry: 55
|
||||
expire: 66
|
||||
minttl: 100
|
||||
ttl: 300
|
||||
"
|
||||
|
||||
mut plbook := playbook.new(text: script)!
|
||||
mut set := coredns.play_dns(mut plbook)!
|
||||
set.set(key_prefix: 'dns:', domain: 'heroexample.com')!
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
|
||||
os_name="$(uname -s)"
|
||||
arch_name="$(uname -m)"
|
||||
version='1.0.13'
|
||||
version='1.0.14'
|
||||
|
||||
|
||||
# Base URL for GitHub releases
|
||||
|
||||
@@ -181,7 +181,7 @@ function os_update {
|
||||
fi
|
||||
#apt install apt-transport-https ca-certificates curl software-properties-common -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes
|
||||
package_install "apt-transport-https ca-certificates curl wget software-properties-common tmux"
|
||||
package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config"
|
||||
package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config libssl-dev iproute2"
|
||||
|
||||
elif [[ "${OSNAME}" == "darwin"* ]]; then
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
|
||||
155
lib/biz/bizmodel/act.v
Normal file
155
lib/biz/bizmodel/act.v
Normal file
@@ -0,0 +1,155 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook, Action }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.data.paramsparser {Params}
|
||||
import freeflowuniverse.herolib.biz.spreadsheet {RowGetArgs, UnitType, PeriodType}
|
||||
|
||||
pub fn (mut m BizModel) act(action Action) !Action {
|
||||
return match texttools.snake_case(action.name) {
|
||||
'funding_define' {
|
||||
m.funding_define_action(action)!
|
||||
}
|
||||
'revenue_define' {
|
||||
m.funding_define_action(action)!
|
||||
}
|
||||
'costcenter_define' {
|
||||
m.costcenter_define_action(action)!
|
||||
}
|
||||
'cost_define' {
|
||||
m.cost_define_action(action)!
|
||||
}
|
||||
'department_define' {
|
||||
m.department_define_action(action)!
|
||||
}
|
||||
'employee_define' {
|
||||
m.employee_define_action(action)!
|
||||
}
|
||||
'export_report' {
|
||||
m.new_report_action(action)!
|
||||
}
|
||||
'sheet_wiki' {
|
||||
m.export_sheet_action(action)!
|
||||
}
|
||||
'graph_bar_row' {
|
||||
m.export_graph_bar_action(action)!
|
||||
}
|
||||
'graph_pie_row' {
|
||||
m.export_graph_pie_action(action)!
|
||||
}
|
||||
'graph_line_row' {
|
||||
m.export_graph_line_action(action)!
|
||||
}
|
||||
'row_overview' {
|
||||
m.export_overview_action(action)!
|
||||
}
|
||||
else {
|
||||
return error('Unknown operation: ${action.name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_sheet_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki(row_args_from_params(action.params)!)!, 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)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_graph_line_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_line_chart(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_graph_bar_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_bar_chart(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
pub fn (mut m BizModel) export_graph_pie_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_pie_chart(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
pub fn (mut m BizModel) export_overview_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_row_overview(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) new_report_action(action Action) !Action {
|
||||
m.new_report(action.params.decode[Report]()!)!
|
||||
return action
|
||||
}
|
||||
|
||||
// fetches args for getting row from params
|
||||
pub fn row_args_from_params(p Params) !RowGetArgs {
|
||||
rowname := p.get_default('rowname', '')!
|
||||
namefilter := p.get_list_default('namefilter', [])!
|
||||
includefilter := p.get_list_default('includefilter', [])!
|
||||
excludefilter := p.get_list_default('excludefilter', [])!
|
||||
size := p.get_default('size', '')!
|
||||
title_sub := p.get_default('title_sub', '')!
|
||||
title := p.get_default('title', '')!
|
||||
unit := p.get_default('unit', 'normal')!
|
||||
unit_e := match unit {
|
||||
'thousand' { UnitType.thousand }
|
||||
'million' { UnitType.million }
|
||||
'billion' { UnitType.billion }
|
||||
else { UnitType.normal }
|
||||
}
|
||||
period_type := p.get_default('period_type', 'year')!
|
||||
if period_type !in ['year', 'month', 'quarter'] {
|
||||
return error('period type needs to be in year,month,quarter')
|
||||
}
|
||||
period_type_e := match period_type {
|
||||
'year' { PeriodType.year }
|
||||
'month' { PeriodType.month }
|
||||
'quarter' { PeriodType.quarter }
|
||||
else { PeriodType.error }
|
||||
}
|
||||
if period_type_e == .error {
|
||||
return error('period type needs to be in year,month,quarter')
|
||||
}
|
||||
|
||||
rowname_show := p.get_default_true('rowname_show')
|
||||
descr_show := p.get_default_true('descr_show')
|
||||
|
||||
return RowGetArgs{
|
||||
rowname: rowname
|
||||
namefilter: namefilter
|
||||
includefilter: includefilter
|
||||
excludefilter: excludefilter
|
||||
period_type: period_type_e
|
||||
unit: unit_e
|
||||
title_sub: title_sub
|
||||
title: title
|
||||
size: size
|
||||
rowname_show: rowname_show
|
||||
descr_show: descr_show
|
||||
}
|
||||
}
|
||||
|
||||
// creates the name for a file being exported given the params of the export action
|
||||
fn (m BizModel) export_action(content string, action Action) !Action {
|
||||
// determine name of file being exported
|
||||
name := if action.params.exists('name') { action.params.get('name')! } else {
|
||||
if action.params.exists('title') { action.params.get('title')! } else {
|
||||
// if no name or title, name is ex: revenue_total_graph_bar_row
|
||||
rowname := action.params.get_default('rowname', '')!
|
||||
'${rowname}_${action.name}'
|
||||
}
|
||||
}
|
||||
|
||||
// by default exports to working dir of bizmodel
|
||||
destination := action.params.get_default('destination', m.workdir)!
|
||||
|
||||
mut path := pathlib.get_file(
|
||||
path: os.join_path(destination, name)
|
||||
increment: true
|
||||
empty: action.params.get_default_false('overwrite')
|
||||
)!
|
||||
|
||||
path.write(content)!
|
||||
return action
|
||||
}
|
||||
90
lib/biz/bizmodel/export.v
Normal file
90
lib/biz/bizmodel/export.v
Normal file
@@ -0,0 +1,90 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
pub struct Export {
|
||||
pub:
|
||||
path string
|
||||
overwrite bool
|
||||
format ExportFormat
|
||||
}
|
||||
|
||||
pub enum ExportFormat {
|
||||
docusaurus
|
||||
mdbook
|
||||
}
|
||||
|
||||
pub struct Report {
|
||||
pub:
|
||||
name string
|
||||
title string
|
||||
description string
|
||||
path string
|
||||
sections []ReportSection
|
||||
}
|
||||
|
||||
pub enum ReportSection {
|
||||
revenue_model
|
||||
cost_structure
|
||||
human_resources
|
||||
}
|
||||
|
||||
pub fn (b BizModel) new_report(report Report) !Report {
|
||||
name := if report.name != '' {report.name} else { texttools.snake_case(report.title) }
|
||||
path := pathlib.get_dir(
|
||||
path: os.join_path(os.home_dir(), '/hero/var/bizmodel/reports/${name}')
|
||||
)!
|
||||
return Report {
|
||||
...report,
|
||||
name: name
|
||||
path: path.path
|
||||
}
|
||||
// b.export_summary()
|
||||
// b.export_business_description()
|
||||
// b.export_market_analysis()
|
||||
// b.export_business_model()
|
||||
// b.export_revenue_model(export)!
|
||||
// b.export_cost_structure(export)
|
||||
// b.export_operational_plan(export)!
|
||||
// b.export_fundraising(export)
|
||||
}
|
||||
|
||||
pub fn (r Report) export(export Export) ! {
|
||||
match export.format {
|
||||
.docusaurus {
|
||||
mut factory := docusaurus.new()!
|
||||
mut site := factory.get(
|
||||
name: r.name
|
||||
path: r.path
|
||||
publish_path: export.path
|
||||
config: docusaurus.Config {} //TODO: is this needed
|
||||
)!
|
||||
site.build()!
|
||||
}
|
||||
.mdbook {panic('MDBook export not fully implemented')}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (b BizModel) export_operational_plan(export Export) ! {
|
||||
mut hr_page := pathlib.get_file(path: '${export.path}/human_resources.md')!
|
||||
hr_page.template_write('./templates/human_resources.md', export.overwrite)!
|
||||
|
||||
for key, employee in b.employees {
|
||||
mut employee_page := pathlib.get_file(path: '${export.path}/${texttools.snake_case(employee.name)}.md')!
|
||||
employee_page.template_write('./templates/employee.md', export.overwrite)!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (b BizModel) export_revenue_model(export Export) ! {
|
||||
println('begin')
|
||||
mut overview_page := pathlib.get_file(path: '${export.path}/revenue_overview.md')!
|
||||
overview_page.template_write('./templates/overview.md', export.overwrite)!
|
||||
|
||||
for key, product in b.products {
|
||||
mut product_page := pathlib.get_file(path: '${export.path}/${texttools.snake_case(product.name)}.md')!
|
||||
product_page.template_write('./templates/product.md', export.overwrite)!
|
||||
}
|
||||
}
|
||||
14
lib/biz/bizmodel/export_test.v
Normal file
14
lib/biz/bizmodel/export_test.v
Normal file
@@ -0,0 +1,14 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
const bizmodel_name = 'test'
|
||||
const export_path = os.join_path(os.dir(@FILE), 'testdata')
|
||||
|
||||
pub fn test_export_report() ! {
|
||||
model := getset(bizmodel_name)!
|
||||
model.export_report(Report{
|
||||
title: 'My Business Model'
|
||||
}, path: export_path)!
|
||||
}
|
||||
@@ -11,8 +11,8 @@ pub fn get(name string) !&BizModel {
|
||||
if name in bizmodels {
|
||||
return bizmodels[name] or { panic('bug') }
|
||||
}
|
||||
return error("cann't find biz model:'${name}' in global bizmodels ${bizmodels.keys()}")
|
||||
}
|
||||
return error("cann't find biz model:'${name}' in global bizmodels")
|
||||
}
|
||||
|
||||
// get bizmodel from global
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
pub struct BizModel {
|
||||
pub mut:
|
||||
name string
|
||||
workdir string = '${os.home_dir()}/hero/var/bizmodel'
|
||||
sheet &spreadsheet.Sheet
|
||||
employees map[string]&Employee
|
||||
departments map[string]&Department
|
||||
|
||||
@@ -1,89 +1,51 @@
|
||||
module bizmodel
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||
import arrays
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook, Action }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.core.texttools
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
// first make sure we find a run action to know the name
|
||||
mut actions4 := plbook.actions_find(actor: 'bizmodel')!
|
||||
|
||||
if actions4.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
knownactions := ['revenue_define', 'employee_define', 'department_define', 'funding_define',
|
||||
'costcenter_define', 'cost_define']
|
||||
|
||||
for action in actions4 {
|
||||
// biz name needs to be specified in the the bizmodel hero actions
|
||||
bizname := action.params.get('bizname') or {
|
||||
return error("Can't find param: 'bizname' for ${action.actor}.${action.name} macro, is a requirement argument.")
|
||||
}
|
||||
mut sim := getset(bizname)!
|
||||
|
||||
if action.name !in knownactions {
|
||||
return error("Can't find macro with name: ${action.name} for macro's for bizmodel.")
|
||||
}
|
||||
|
||||
console.print_debug(action.name)
|
||||
match action.name {
|
||||
'revenue_define' {
|
||||
sim.revenue_action(action)!
|
||||
}
|
||||
'funding_define' {
|
||||
sim.funding_define_action(action)!
|
||||
}
|
||||
'costcenter_define' {
|
||||
sim.costcenter_define_action(action)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
console.print_debug('TOTALS for bizmodel play')
|
||||
// now we have processed the macro's, we can calculate the totals
|
||||
rlock bizmodels {
|
||||
for _, mut sim in bizmodels {
|
||||
// sim.hr_total()!
|
||||
sim.cost_total()!
|
||||
sim.revenue_total()!
|
||||
sim.funding_total()!
|
||||
}
|
||||
}
|
||||
|
||||
for action in actions4 {
|
||||
console.print_debug(action.name)
|
||||
// biz name needs to be specified in the the bizmodel hero actions
|
||||
bizname := action.params.get('bizname') or {
|
||||
return error("Can't find param: 'bizname' for bizmodel macro, is a requirement argument.")
|
||||
}
|
||||
|
||||
mut sim := get(bizname)!
|
||||
|
||||
if action.name !in knownactions {
|
||||
return error("Can't find macro with name: ${action.name} for macro's for bizmodel.")
|
||||
}
|
||||
|
||||
match action.name {
|
||||
'cost_define' {
|
||||
sim.cost_define_action(action)!
|
||||
}
|
||||
'department_define' {
|
||||
sim.department_define_action(action)!
|
||||
}
|
||||
'employee_define' {
|
||||
sim.employee_define_action(action)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
// mut sim:=get("test")!
|
||||
// //println(sim.sheet.rows.keys())
|
||||
// //println(spreadsheet.sheets_keys())
|
||||
// println(spreadsheet.sheet_get('bizmodel_test')!)
|
||||
// if true{panic("sss")}
|
||||
const action_priorities = {
|
||||
0: ['revenue_define', 'costcenter_define', 'funding_define']
|
||||
1: ['cost_define', 'department_define', 'employee_define']
|
||||
2: ['sheet_wiki', 'graph_bar_row', 'graph_pie_row', 'graph_line_row', 'row_overview']
|
||||
}
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
// group actions by which bizmodel they belong to
|
||||
actions_by_biz := arrays.group_by[string, &Action](
|
||||
plbook.actions_find(actor: 'bizmodel')!,
|
||||
fn (a &Action) string {
|
||||
return a.params.get('bizname') or {'default'}
|
||||
}
|
||||
)
|
||||
|
||||
// play actions for each biz in playbook
|
||||
for biz, actions in actions_by_biz {
|
||||
mut model := getset(biz)!
|
||||
model.play(mut plbook)!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut m BizModel) play(mut plbook PlayBook) ! {
|
||||
mut actions := plbook.actions_find(actor: 'bizmodel')!
|
||||
|
||||
for action in actions.filter(it.name in action_priorities[0]) {
|
||||
m.act(*action)!
|
||||
}
|
||||
|
||||
m.cost_total()!
|
||||
m.revenue_total()!
|
||||
m.funding_total()!
|
||||
|
||||
for action in actions.filter(it.name in action_priorities[1]) {
|
||||
m.act(*action)!
|
||||
}
|
||||
|
||||
for action in actions.filter(it.name in action_priorities[2]) {
|
||||
m.act(*action)!
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
fn (mut m BizModel) cost_define_action(action Action) ! {
|
||||
fn (mut m BizModel) cost_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -73,6 +73,7 @@ fn (mut m BizModel) cost_define_action(action Action) ! {
|
||||
)!
|
||||
m.sheet.row_delete('tmp3')
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
fn (mut sim BizModel) cost_total() ! {
|
||||
|
||||
@@ -3,7 +3,7 @@ module bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
fn (mut m BizModel) costcenter_define_action(action Action) ! {
|
||||
fn (mut m BizModel) costcenter_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -20,4 +20,5 @@ fn (mut m BizModel) costcenter_define_action(action Action) ! {
|
||||
department: department
|
||||
}
|
||||
m.costcenters[name] = &cc
|
||||
return action
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
// - descr: description of the funding .
|
||||
// - investment is month:amount,month:amount, ... .
|
||||
// - type: loan or capital .
|
||||
fn (mut m BizModel) funding_define_action(action Action) ! {
|
||||
fn (mut m BizModel) funding_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -29,6 +29,7 @@ fn (mut m BizModel) funding_define_action(action Action) ! {
|
||||
descr: descr
|
||||
extrapolate: false
|
||||
)!
|
||||
return action
|
||||
}
|
||||
|
||||
fn (mut sim BizModel) funding_total() ! {
|
||||
|
||||
@@ -15,7 +15,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
// department:'engineering'
|
||||
// cost_percent_revenue e.g. 4%, will make sure the cost will be at least 4% of revenue
|
||||
|
||||
fn (mut m BizModel) employee_define_action(action Action) ! {
|
||||
fn (mut m BizModel) employee_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -107,10 +107,7 @@ fn (mut m BizModel) employee_define_action(action Action) ! {
|
||||
fulltime_perc: action.params.get_percentage_default('fulltime', '100%')!
|
||||
}
|
||||
|
||||
// println(employee)
|
||||
|
||||
// todo: use existing id gen
|
||||
|
||||
if name != '' {
|
||||
// sid = smartid.sid_new('')!
|
||||
// // TODO: this isn't necessary if sid_new works correctly
|
||||
@@ -120,9 +117,10 @@ fn (mut m BizModel) employee_define_action(action Action) ! {
|
||||
// }
|
||||
m.employees[name] = &employee
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
fn (mut m BizModel) department_define_action(action Action) ! {
|
||||
fn (mut m BizModel) department_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -141,6 +139,8 @@ fn (mut m BizModel) department_define_action(action Action) ! {
|
||||
if name != '' {
|
||||
m.departments[name] = &department
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
// fn (mut sim BizModel) hr_total() ! {
|
||||
|
||||
@@ -21,7 +21,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
// - 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
|
||||
//
|
||||
fn (mut m BizModel) revenue_action(action Action) ! {
|
||||
fn (mut m BizModel) revenue_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -312,6 +312,7 @@ fn (mut m BizModel) revenue_action(action Action) ! {
|
||||
// panic("sdsd")
|
||||
|
||||
// }
|
||||
return action
|
||||
}
|
||||
|
||||
// revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model
|
||||
|
||||
9
lib/biz/bizmodel/templates/human_resources.md
Normal file
9
lib/biz/bizmodel/templates/human_resources.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Human Resources
|
||||
|
||||
| Name | Title | Nr People |
|
||||
|------|-------|-------|
|
||||
@for employee in model.employees.values().filter(it.department == dept.name)
|
||||
| @{employee_names[employee.name]} | @{employee.title} | @{employee.nrpeople} |
|
||||
@end
|
||||
|
||||
@end
|
||||
@@ -2,53 +2,48 @@
|
||||
|
||||
## FUNDING
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:'funding'
|
||||
@{bizmodel.sheet.wiki(includefilter:'funding')!}
|
||||
|
||||
## REVENUE vs COGS
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:rev
|
||||
@{bizmodel.sheet.wiki(includefilter:'rev')!}
|
||||
|
||||
#### Revenue Lines
|
||||
|
||||
!!bizmodel.sheet_wiki title:'Revenue Total' includefilter:'revtotal'
|
||||
@{bizmodel.sheet.wiki(title:'Revenue Total', includefilter:'revtotal')!}
|
||||
|
||||
#### COGS Lines
|
||||
|
||||
!!bizmodel.sheet_wiki title:'COGS' includefilter:'cogstotal'
|
||||
@{bizmodel.sheet.wiki(title:'COGS', includefilter:'cogstotal')!}
|
||||
|
||||
## HR
|
||||
!!bizmodel.sheet_wiki title:'HR Teams' includefilter:'hrnr'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'HR Costs' includefilter:'hrcost'
|
||||
@{bizmodel.sheet.wiki(title:'HR Teams', includefilter:'hrnr')!}
|
||||
|
||||
@{bizmodel.sheet.wiki(title:'HR Costs', includefilter:'hrcost')!}
|
||||
|
||||
## Operational Costs
|
||||
|
||||
!!bizmodel.sheet_wiki title:'COSTS' includefilter:'ocost'
|
||||
|
||||
@{bizmodel.sheet.wiki(title:'COSTS', includefilter:'ocost')!}
|
||||
|
||||
## P&L Overview
|
||||
|
||||
<!-- period is in months, 3 means every quarter -->
|
||||
|
||||
!!bizmodel.sheet_wiki title:'P&L Overview' includefilter:'pl'
|
||||
@{bizmodel.sheet.wiki(title:'P&L Overview', includefilter:'pl')!}
|
||||
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million title:'A Title' title_sub:'Sub'
|
||||
@{bizmodel.graph_bar_row(rowname:'revenue_total', unit:'million', title:'A Title', title_sub:'Sub')!}
|
||||
|
||||
Unit is in Million USD.
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million
|
||||
@{bizmodel.graph_bar_row(rowname:'revenue_total', unit:'million')!}
|
||||
|
||||
!!bizmodel.graph_line_row rowname:revenue_total unit:million
|
||||
|
||||
!!bizmodel.graph_pie_row rowname:revenue_total unit:million size:'80%'
|
||||
@{bizmodel.graph_line_row(rowname:'revenue_total', unit:'million')!}
|
||||
|
||||
@{bizmodel.graph_pie_row(rowname:'revenue_total', unit:'million', size:'80%')!}
|
||||
|
||||
## Some Details
|
||||
|
||||
> show how we can do per month
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:'pl' period_months:1
|
||||
|
||||
|
||||
|
||||
@{bizmodel.sheet_wiki(includefilter:'pl', period_months:1)!}
|
||||
68
lib/biz/bizmodel/templates/product.md
Normal file
68
lib/biz/bizmodel/templates/product.md
Normal 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
|
||||
@@ -124,9 +124,4 @@ fn test_curr() {
|
||||
console.print_debug(sh.rows['something'].cells[0])
|
||||
assert sh.rows['something']!.cells[0].val == 25.0
|
||||
assert sh.rows['something']!.cells[60 - 1].val == 900.0
|
||||
|
||||
// TODO: we need to create tests for it
|
||||
|
||||
console.print_debug(sh)
|
||||
panic('test1')
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ pub fn (mut r Row) cell_get(colnr int) !&Cell {
|
||||
return &r.cells[colnr]
|
||||
}
|
||||
|
||||
pub fn (mut r Row) values_get() []f64 {
|
||||
pub fn (r Row) values_get() []f64 {
|
||||
mut out := []f64{}
|
||||
for cell in r.cells {
|
||||
out << cell.val
|
||||
|
||||
@@ -259,13 +259,13 @@ pub fn (mut s Sheet) json() string {
|
||||
}
|
||||
|
||||
// find row, report error if not found
|
||||
pub fn (mut s Sheet) row_get(name string) !&Row {
|
||||
mut row := s.rows[name] or { return error('could not find row with name: ${name}') }
|
||||
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()}') }
|
||||
return row
|
||||
}
|
||||
|
||||
pub fn (mut s Sheet) values_get(name string) ![]f64 {
|
||||
mut r := s.row_get(name)!
|
||||
pub fn (s Sheet) values_get(name string) ![]f64 {
|
||||
r := s.row_get(name)!
|
||||
vs := r.values_get()
|
||||
return vs
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
|
||||
mut s2 := s
|
||||
|
||||
if args.period_type == .year {
|
||||
s.toyear(
|
||||
s2 = s.toyear(
|
||||
name: args.rowname
|
||||
namefilter: args.namefilter
|
||||
includefilter: args.includefilter
|
||||
@@ -129,7 +129,7 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
|
||||
)!
|
||||
}
|
||||
if args.period_type == .quarter {
|
||||
s.toquarter(
|
||||
s2 = s.toquarter(
|
||||
name: args.rowname
|
||||
namefilter: args.namefilter
|
||||
includefilter: args.includefilter
|
||||
@@ -141,13 +141,13 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
|
||||
// console.print_debug(s2.row_get(args.rowname)!)
|
||||
mut vals := s2.values_get(args.rowname)!
|
||||
if args.period_type == .year && vals.len != nryears {
|
||||
return error('${err_pre}Vals.len need to be 6, for year.\nhere:\n${vals}')
|
||||
return error('${err_pre}Vals.len need to be ${nryears}, for year.\nhere:\n${vals}')
|
||||
}
|
||||
if args.period_type == .quarter && vals.len != nryears * 4 {
|
||||
return error('${err_pre}vals.len need to be 6*4, for quarter.\nhere:\n${vals}')
|
||||
return error('${err_pre}vals.len need to be ${nryears}*4, for quarter.\nhere:\n${vals}')
|
||||
}
|
||||
if args.period_type == .month && vals.len != nryears * 12 {
|
||||
return error('${err_pre}vals.len need to be 6*12, for month.\nhere:\n${vals}')
|
||||
return error('${err_pre}vals.len need to be ${nryears}*12, for month.\nhere:\n${vals}')
|
||||
}
|
||||
|
||||
for mut val in vals {
|
||||
|
||||
@@ -77,7 +77,6 @@ module livekit
|
||||
// token.grants.video = grant
|
||||
// }
|
||||
|
||||
|
||||
// // Method to generate a JWT token
|
||||
// pub fn (token AccessToken) to_jwt() !string {
|
||||
// // Create JWT payload
|
||||
@@ -150,4 +149,4 @@ module livekit
|
||||
|
||||
// // Parse and return the claims as ClaimGrants
|
||||
// return json.decode(ClaimGrants, payload_json)
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module zdb
|
||||
module zerodb_client
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
@@ -14,11 +14,12 @@ pub mut:
|
||||
// /tmp/redis-default.sock
|
||||
pub fn get(addr string, auth string, namespace string) !ZDB {
|
||||
console.print_header(' ZDB get: addr:${addr} namespace:${namespace}')
|
||||
mut redis := redisclient.get(addr)!
|
||||
mut redis := redisclient.new(addr)!
|
||||
mut zdb := ZDB{
|
||||
redis: redis
|
||||
}
|
||||
|
||||
println('Here..')
|
||||
if auth != '' {
|
||||
zdb.redis.send_expect_ok(['AUTH', auth])!
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ module generic
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.osal
|
||||
|
||||
fn generate_exec(path string, reset bool) ! {
|
||||
mut args := args_get(path)!
|
||||
@@ -43,12 +44,15 @@ fn generate_exec(path string, reset bool) ! {
|
||||
if args.reset {
|
||||
path_templ_dir.delete()!
|
||||
}
|
||||
|
||||
if args.templates {
|
||||
if !path_templ_dir.exists() {
|
||||
mut templ_6 := $tmpl('templates/atemplate.yaml')
|
||||
pathlib.template_write(templ_6, '${args.path}/templates/atemplate.yaml', true)!
|
||||
}
|
||||
}
|
||||
console.print_debug('formating dir ${args.path}')
|
||||
osal.execute_silent('v fmt -w ${args.path}')!
|
||||
}
|
||||
|
||||
fn platform_check(args GeneratorArgs) ! {
|
||||
|
||||
@@ -47,7 +47,7 @@ fn args_get(path string) !GeneratorArgs {
|
||||
classname: p.get('classname')!
|
||||
title: p.get_default('title', '')!
|
||||
default: p.get_default_true('default')
|
||||
supported_platforms: p.get_list('supported_platforms')!
|
||||
supported_platforms: p.get_list_default('supported_platforms', [])!
|
||||
singleton: p.get_default_false('singleton')
|
||||
templates: p.get_default_false('templates')
|
||||
reset: p.get_default_false('reset')
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
module ${args.name}
|
||||
|
||||
@if args.hasconfig
|
||||
import freeflowuniverse.herolib.core.base
|
||||
@end
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
|
||||
@if args.cat == .installer
|
||||
import freeflowuniverse.herolib.sysadmin.startupmanager
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
@if args.startupmanager
|
||||
import time
|
||||
@end
|
||||
@end
|
||||
|
||||
__global (
|
||||
${args.name}_global map[string]&${args.classname}
|
||||
|
||||
@@ -29,6 +29,15 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
description: 'Url where docusaurus source is.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
name: 'path'
|
||||
abbrev: 'p'
|
||||
// default: ''
|
||||
description: 'Path where docusaurus source is.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
@@ -36,7 +45,7 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
abbrev: 'dk'
|
||||
// default: ''
|
||||
description: 'Path of SSH Key used to deploy.'
|
||||
})
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .string
|
||||
@@ -46,7 +55,6 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
description: 'Path where to publish.'
|
||||
})
|
||||
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .bool
|
||||
required: false
|
||||
@@ -78,20 +86,25 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
|
||||
description: 'Run your dev environment on local browser.'
|
||||
})
|
||||
|
||||
cmd_run.add_flag(Flag{
|
||||
flag: .bool
|
||||
required: false
|
||||
name: 'new'
|
||||
abbrev: 'n'
|
||||
description: 'create a new docusaurus site.'
|
||||
})
|
||||
|
||||
cmdroot.add_command(cmd_run)
|
||||
}
|
||||
|
||||
fn cmd_docusaurus_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 { '' }
|
||||
// if path == '' {
|
||||
// path = os.getwd()
|
||||
// }
|
||||
// path = path.replace('~', os.home_dir())
|
||||
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 }
|
||||
@@ -101,43 +114,30 @@ fn cmd_docusaurus_execute(cmd Command) ! {
|
||||
// eprintln("specify build, builddev or dev")
|
||||
// exit(1)
|
||||
// }
|
||||
|
||||
mut docs := docusaurus.new(update: update)!
|
||||
|
||||
if publish_path.len>0 {
|
||||
_ := docs.build(
|
||||
url: url
|
||||
update: update
|
||||
publish_path: publish_path
|
||||
deploykey:deploykey
|
||||
)!
|
||||
mut docs := docusaurus.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 {
|
||||
// Create a new docusaurus site
|
||||
_ := docs.build_publish(
|
||||
url: url
|
||||
update: update
|
||||
deploykey:deploykey
|
||||
)!
|
||||
site.build_publish()!
|
||||
}
|
||||
|
||||
if builddevpublish {
|
||||
// Create a new docusaurus site
|
||||
_ := docs.build_dev_publish(
|
||||
url: url
|
||||
update: update
|
||||
deploykey:deploykey
|
||||
)!
|
||||
site.build_dev_publish()!
|
||||
}
|
||||
|
||||
if dev {
|
||||
// Create a new docusaurus site
|
||||
_ := docs.dev(
|
||||
url: url
|
||||
update: update
|
||||
deploykey:deploykey
|
||||
)!
|
||||
site.dev()!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,36 +171,36 @@ pub fn cmd_git(mut cmdroot Command) {
|
||||
description: 'Filter is part of path of repo e.g. threefoldtech/info_'
|
||||
})
|
||||
|
||||
c.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
name: 'repo'
|
||||
abbrev: 'r'
|
||||
description: 'name of repo'
|
||||
})
|
||||
c.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
name: 'branch'
|
||||
abbrev: 'b'
|
||||
description: 'branch of repo (optional)'
|
||||
})
|
||||
// c.add_flag(Flag{
|
||||
// flag: .string
|
||||
// required: false
|
||||
// name: 'repo'
|
||||
// abbrev: 'r'
|
||||
// description: 'name of repo'
|
||||
// })
|
||||
// c.add_flag(Flag{
|
||||
// flag: .string
|
||||
// required: false
|
||||
// name: 'branch'
|
||||
// abbrev: 'b'
|
||||
// description: 'branch of repo (optional)'
|
||||
// })
|
||||
|
||||
c.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
name: 'account'
|
||||
abbrev: 'a'
|
||||
description: 'name of account e.g. threefoldtech'
|
||||
})
|
||||
// c.add_flag(Flag{
|
||||
// flag: .string
|
||||
// required: false
|
||||
// name: 'account'
|
||||
// abbrev: 'a'
|
||||
// description: 'name of account e.g. threefoldtech'
|
||||
// })
|
||||
|
||||
c.add_flag(Flag{
|
||||
flag: .string
|
||||
required: false
|
||||
name: 'provider'
|
||||
abbrev: 'p'
|
||||
description: 'name of provider e.g. github'
|
||||
})
|
||||
// c.add_flag(Flag{
|
||||
// flag: .string
|
||||
// required: false
|
||||
// name: 'provider'
|
||||
// abbrev: 'p'
|
||||
// description: 'name of provider e.g. github'
|
||||
// })
|
||||
}
|
||||
for mut c_ in allcmdsref {
|
||||
mut c := *c_
|
||||
@@ -245,21 +245,21 @@ fn cmd_git_execute(cmd Command) ! {
|
||||
|
||||
// create the filter for doing group actions, or action on 1 repo
|
||||
mut filter := cmd.flags.get_string('filter') or { '' }
|
||||
mut branch := cmd.flags.get_string('branch') or { '' }
|
||||
mut repo := cmd.flags.get_string('repo') or { '' }
|
||||
mut account := cmd.flags.get_string('account') or { '' }
|
||||
mut provider := cmd.flags.get_string('provider') or { '' }
|
||||
// mut branch := cmd.flags.get_string('branch') or { '' }
|
||||
// mut repo := cmd.flags.get_string('repo') or { '' }
|
||||
// mut account := cmd.flags.get_string('account') or { '' }
|
||||
// mut provider := cmd.flags.get_string('provider') or { '' }
|
||||
|
||||
if cmd.name != 'cd' {
|
||||
// check if we are in a git repo
|
||||
if repo == '' && account == '' && provider == '' && filter == '' {
|
||||
if r0 := gs.get_working_repo() {
|
||||
repo = r0.name
|
||||
account = r0.account
|
||||
provider = r0.provider
|
||||
}
|
||||
}
|
||||
}
|
||||
// if cmd.name != 'cd' {
|
||||
// // check if we are in a git repo
|
||||
// if repo == '' && account == '' && provider == '' && filter == '' {
|
||||
// if r0 := gs.get_working_repo() {
|
||||
// repo = r0.name
|
||||
// account = r0.account
|
||||
// provider = r0.provider
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if cmd.name in gittools.gitcmds.split(',') {
|
||||
mut pull := cmd.flags.get_bool('pull') or { false }
|
||||
@@ -271,11 +271,7 @@ fn cmd_git_execute(cmd Command) ! {
|
||||
}
|
||||
mypath := gs.do(
|
||||
filter: filter
|
||||
repo: repo
|
||||
reload: reload
|
||||
account: account
|
||||
provider: provider
|
||||
branch: branch
|
||||
recursive: recursive
|
||||
cmd: cmd.name
|
||||
script: cmd.flags.get_bool('script') or { false }
|
||||
|
||||
@@ -189,7 +189,6 @@ pub fn (mut h HTTPConnection) get(req_ Request) !string {
|
||||
req.debug = true
|
||||
req.method = .get
|
||||
result := h.send(req)!
|
||||
println(result)
|
||||
return result.data
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,12 @@ pub fn get_no_check(path_ string) Path {
|
||||
@[params]
|
||||
pub struct GetArgs {
|
||||
pub mut:
|
||||
path string
|
||||
create bool
|
||||
check bool = true // means will check the dir, link or file exists
|
||||
empty bool // will empty the dir or the file
|
||||
delete bool
|
||||
path string
|
||||
create bool
|
||||
check bool = true // means will check the dir, link or file exists
|
||||
empty bool // will empty the dir or the file
|
||||
delete bool
|
||||
increment bool // will increment filename until free name available (filename1...)
|
||||
}
|
||||
|
||||
// get a directory, or needs to be created
|
||||
@@ -81,6 +82,17 @@ pub fn get_file(args_ GetArgs) !Path {
|
||||
mut p2 := get_no_check(args.path)
|
||||
if args.check {
|
||||
p2.check()
|
||||
|
||||
if args.increment {
|
||||
if p2.exists() {
|
||||
incr := if args.path[args.path.len - 1].is_digit() {
|
||||
args.path[args.path.len - 1].ascii_str().int()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
return get_file(GetArgs{ ...args, path: '${args.path}${incr}' })
|
||||
}
|
||||
}
|
||||
if args.create {
|
||||
mut parent_ := p2.parent()!
|
||||
parent_.check()
|
||||
|
||||
@@ -13,7 +13,7 @@ pub mut:
|
||||
delete bool // do we want to delete the destination
|
||||
ignore []string // arguments to ignore e.g. ['*.pyc','*.bak']
|
||||
ignore_default bool = true // if set will ignore a common set
|
||||
debug bool = true
|
||||
debug bool
|
||||
fast_rsync bool
|
||||
sshkey string
|
||||
}
|
||||
@@ -37,8 +37,8 @@ pub fn rsync(args_ RsyncArgs) ! {
|
||||
get(args.source)
|
||||
}
|
||||
cmdoptions := rsync_cmd_options(args)!
|
||||
$if debug {
|
||||
console.print_debug(' rsync command:\nrsync ${cmdoptions}')
|
||||
if args.debug {
|
||||
console.print_debug('rsync ${cmdoptions}')
|
||||
}
|
||||
r := os.execute('which rsync')
|
||||
if r.exit_code > 0 {
|
||||
|
||||
@@ -10,7 +10,7 @@ pub fn template_write(template_ string, dest string, overwrite bool) ! {
|
||||
if overwrite || !(os.exists(dest)) {
|
||||
mut p := get_file(path: dest, create: true)!
|
||||
$if debug {
|
||||
console.print_header(" write template to '${dest}'")
|
||||
console.print_debug(" write template to '${dest}'")
|
||||
}
|
||||
p.write(template)!
|
||||
}
|
||||
|
||||
38
lib/core/texttools/casing.v
Normal file
38
lib/core/texttools/casing.v
Normal file
@@ -0,0 +1,38 @@
|
||||
module texttools
|
||||
|
||||
pub fn snake_case(s string) string {
|
||||
return separate_words(s).join('_')
|
||||
}
|
||||
|
||||
pub fn title_case(s string) string {
|
||||
return separate_words(s).join(' ').title()
|
||||
}
|
||||
|
||||
pub fn pascal_case(s string) string {
|
||||
mut pascal := s.replace('_', ' ')
|
||||
return pascal.title().replace(' ', '')
|
||||
}
|
||||
|
||||
pub fn camel_case(s string) string {
|
||||
return pascal_case(s).uncapitalize()
|
||||
}
|
||||
|
||||
const separators = ['.', '_', '-', '/', ' ', ':', ',', ';']
|
||||
|
||||
fn separate_words(s string) []string {
|
||||
mut words := []string{}
|
||||
mut word := ''
|
||||
for i, c in s {
|
||||
if (c.is_capital() || c.ascii_str() in separators) && word != '' {
|
||||
words << word.to_lower()
|
||||
word = ''
|
||||
}
|
||||
if c.ascii_str() !in separators {
|
||||
word += c.ascii_str().to_lower()
|
||||
}
|
||||
}
|
||||
if word != '' {
|
||||
words << word.to_lower()
|
||||
}
|
||||
return words
|
||||
}
|
||||
@@ -37,7 +37,6 @@ pub fn new(args_ GitStructureArgsNew) !&GitStructure {
|
||||
debug: args.debug
|
||||
ssh_key_name: args.ssh_key_name
|
||||
ssh_key_path: args.ssh_key_path
|
||||
|
||||
}
|
||||
|
||||
return get(coderoot: args.coderoot, reload: args.reload, cfg: cfg)
|
||||
@@ -81,18 +80,20 @@ pub fn get(args_ GitStructureArgGet) !&GitStructure {
|
||||
}
|
||||
|
||||
mut cfg := args.cfg or {
|
||||
mut cfg_:=GitStructureConfig{coderoot:"SKIP"}
|
||||
mut cfg_ := GitStructureConfig{
|
||||
coderoot: 'SKIP'
|
||||
}
|
||||
cfg_
|
||||
}
|
||||
|
||||
if cfg.coderoot != "SKIP"{
|
||||
if cfg.coderoot != 'SKIP' {
|
||||
gs.config_ = cfg
|
||||
gs.config_save()!
|
||||
//println(gs.config()!)
|
||||
// println(gs.config()!)
|
||||
}
|
||||
|
||||
gs.config()! // will load the config, don't remove
|
||||
|
||||
|
||||
gs.load(false)!
|
||||
|
||||
if gs.repos.keys().len == 0 || args.reload {
|
||||
|
||||
@@ -17,7 +17,6 @@ pub mut:
|
||||
ssh_key_path string
|
||||
}
|
||||
|
||||
|
||||
// GitStructure holds information about repositories within a specific code root.
|
||||
// This structure keeps track of loaded repositories, their configurations, and their status.
|
||||
@[heap]
|
||||
|
||||
@@ -11,7 +11,11 @@ fn get_repo_status(gr GitRepo) !string {
|
||||
statuses << 'COMMIT'
|
||||
}
|
||||
|
||||
if repo.need_push_or_pull()! {
|
||||
if repo.need_push()! {
|
||||
statuses << 'PUSH'
|
||||
}
|
||||
|
||||
if repo.need_pull()! {
|
||||
statuses << 'PULL'
|
||||
}
|
||||
|
||||
@@ -28,7 +32,7 @@ fn format_repo_info(repo GitRepo) ![]string {
|
||||
'[${repo.status_local.branch}]' // Otherwise, display branch
|
||||
}
|
||||
|
||||
relative_path := repo.get_relative_path()!
|
||||
relative_path := repo.get_human_path()!
|
||||
return [' - ${relative_path}', tag_or_branch, status]
|
||||
}
|
||||
|
||||
|
||||
@@ -34,16 +34,16 @@ pub fn (mut gitstructure GitStructure) clone(args GitCloneArgs) !&GitRepo {
|
||||
extra = '--depth 1 --no-single-branch '
|
||||
}
|
||||
|
||||
cfg:=gitstructure.config()!
|
||||
cfg := gitstructure.config()!
|
||||
|
||||
mut cmd := 'cd ${parent_dir} && git clone ${extra} ${repo.get_http_url()!} ${repo.name}'
|
||||
|
||||
mut sshkey_include := ""
|
||||
if cfg.ssh_key_path.len>0{
|
||||
sshkey_include="GIT_SSH_COMMAND=\"ssh -i ${cfg.ssh_key_path}\" "
|
||||
mut sshkey_include := ''
|
||||
if cfg.ssh_key_path.len > 0 {
|
||||
sshkey_include = "GIT_SSH_COMMAND=\"ssh -i ${cfg.ssh_key_path}\" "
|
||||
cmd = 'cd ${parent_dir} && ${sshkey_include}git clone ${extra} ${repo.get_ssh_url()!} ${repo.name}'
|
||||
}
|
||||
|
||||
|
||||
console.print_debug(cmd)
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
|
||||
@@ -48,8 +48,8 @@ pub fn (mut repo GitRepo) need_commit() !bool {
|
||||
return repo.has_changes
|
||||
}
|
||||
|
||||
// Check if the repository has changes that need to be pushed (is against the cached info).
|
||||
pub fn (mut repo GitRepo) need_push_or_pull() !bool {
|
||||
// Check if the repository has local changes that need to be pushed to remote
|
||||
pub fn (mut repo GitRepo) need_push() !bool {
|
||||
repo.status_update()!
|
||||
last_remote_commit := repo.get_last_remote_commit() or {
|
||||
return error('Failed to get last remote commit: ${err}')
|
||||
@@ -57,10 +57,44 @@ pub fn (mut repo GitRepo) need_push_or_pull() !bool {
|
||||
last_local_commit := repo.get_last_local_commit() or {
|
||||
return error('Failed to get last local commit: ${err}')
|
||||
}
|
||||
// println('commit status: ${repo.name} ${last_local_commit} ${last_remote_commit}')
|
||||
// If remote commit is empty, it means the branch doesn't exist remotely yet
|
||||
if last_remote_commit.len == 0 {
|
||||
return true
|
||||
}
|
||||
// If local commit is different from remote and exists, we need to push
|
||||
return last_local_commit != last_remote_commit
|
||||
}
|
||||
|
||||
// Check if the repository needs to pull changes from remote
|
||||
pub fn (mut repo GitRepo) need_pull() !bool {
|
||||
repo.status_update()!
|
||||
last_remote_commit := repo.get_last_remote_commit() or {
|
||||
return error('Failed to get last remote commit: ${err}')
|
||||
}
|
||||
// If remote doesn't exist, no need to pull
|
||||
if last_remote_commit.len == 0 {
|
||||
return false
|
||||
}
|
||||
// Check if the remote commit exists in our local history
|
||||
// If it doesn't exist, we need to pull
|
||||
result := repo.exec('git merge-base --is-ancestor ${last_remote_commit} HEAD') or {
|
||||
if err.msg().contains('exit code: 1') {
|
||||
// Exit code 1 means the remote commit is not in our history
|
||||
// Therefore we need to pull
|
||||
return true
|
||||
}
|
||||
return error('Failed to check merge-base: ${err}')
|
||||
}
|
||||
// If we get here, the remote commit is in our history
|
||||
// Therefore we don't need to pull
|
||||
return false
|
||||
}
|
||||
|
||||
// Legacy function for backward compatibility
|
||||
pub fn (mut repo GitRepo) need_push_or_pull() !bool {
|
||||
return repo.need_push()! || repo.need_pull()!
|
||||
}
|
||||
|
||||
// Determine if the repository needs to checkout to a different branch or tag
|
||||
fn (mut repo GitRepo) need_checkout() bool {
|
||||
if repo.status_wanted.branch.len > 0 {
|
||||
|
||||
@@ -46,7 +46,6 @@ fn (mut repo GitRepo) load() ! {
|
||||
repo.has_changes = repo.detect_changes() or {
|
||||
return error('Failed to detect changes in repository ${repo.name}: ${err}')
|
||||
}
|
||||
|
||||
repo.cache_set() or {
|
||||
return error('Failed to update cache for repository ${repo.name}: ${err}')
|
||||
}
|
||||
|
||||
@@ -71,6 +71,12 @@ pub fn (repo GitRepo) get_relative_path() !string {
|
||||
return mypath.path_relative(repo_.gs.coderoot.path) or { panic("couldn't get relative path") }
|
||||
}
|
||||
|
||||
// path where we use ~ and its the full path
|
||||
pub fn (repo GitRepo) get_human_path() !string {
|
||||
mut mypath := repo.patho()!.path.replace(os.home_dir(), '~')
|
||||
return mypath
|
||||
}
|
||||
|
||||
pub fn (mut repo GitRepo) get_parent_dir(args GetParentDir) !string {
|
||||
repo_path := repo.path()
|
||||
parent_dir := os.dir(repo_path)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
!!hero_code.generate_installer
|
||||
name:'meilisearchinstaller'
|
||||
classname:'MeilisearchServer'
|
||||
name:'meilisearch_installer'
|
||||
classname:'MeilisearchInstaller'
|
||||
singleton:0
|
||||
templates:0
|
||||
default:1
|
||||
@@ -0,0 +1,178 @@
|
||||
module meilisearch_installer
|
||||
|
||||
import freeflowuniverse.herolib.osal
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
import freeflowuniverse.herolib.installers.ulist
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import os
|
||||
import rand
|
||||
import json
|
||||
|
||||
fn generate_master_key(length int) !string {
|
||||
mut key := []rune{}
|
||||
valid_chars := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
|
||||
for _ in 0 .. length {
|
||||
random_index := rand.int_in_range(0, valid_chars.len)!
|
||||
key << valid_chars[random_index]
|
||||
}
|
||||
|
||||
return key.string()
|
||||
}
|
||||
|
||||
fn startupcmd() ![]zinit.ZProcessNewArgs {
|
||||
mut res := []zinit.ZProcessNewArgs{}
|
||||
mut installer := get()!
|
||||
mut env := 'development'
|
||||
if installer.production {
|
||||
env = 'production'
|
||||
}
|
||||
res << zinit.ZProcessNewArgs{
|
||||
name: 'meilisearch'
|
||||
cmd: 'meilisearch --no-analytics --http-addr ${installer.host}:${installer.port} --env ${env} --db-path ${installer.path} --master-key ${installer.masterkey}'
|
||||
startuptype: .zinit
|
||||
start: true
|
||||
restart: true
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
struct MeilisearchVersionResponse {
|
||||
version string @[json: 'pkgVersion']
|
||||
commit_date string @[json: 'commitDate']
|
||||
commit_sha string @[json: 'commitSha']
|
||||
}
|
||||
|
||||
fn running() !bool {
|
||||
mut cfg := get()!
|
||||
url := 'http://${cfg.host}:${cfg.port}'
|
||||
mut conn := httpconnection.new(name: 'meilisearchinstaller', url: url)!
|
||||
conn.default_header.add(.authorization, 'Bearer ${cfg.masterkey}')
|
||||
response := conn.get(prefix: 'version', debug: true) or {
|
||||
return error('Failed to get meilisearch version: ${err}')
|
||||
}
|
||||
decoded_response := json.decode(MeilisearchVersionResponse, response) or {
|
||||
return error('Failed to decode meilisearch version: ${err}')
|
||||
}
|
||||
|
||||
if decoded_response.version == '' {
|
||||
console.print_stderr('Meilisearch is not running')
|
||||
return false
|
||||
}
|
||||
|
||||
console.print_header('Meilisearch is running')
|
||||
return true
|
||||
}
|
||||
|
||||
fn start_pre() ! {
|
||||
}
|
||||
|
||||
fn start_post() ! {
|
||||
}
|
||||
|
||||
fn stop_pre() ! {
|
||||
}
|
||||
|
||||
fn stop_post() ! {
|
||||
}
|
||||
|
||||
//////////////////// 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('${osal.profile_path_source_and()!} meilisearch -V')
|
||||
if res.exit_code != 0 {
|
||||
return false
|
||||
}
|
||||
r := res.output.split_into_lines().filter(it.trim_space().len > 0)
|
||||
if r.len != 1 {
|
||||
return error("couldn't parse meilisearch version.\n${res.output}")
|
||||
}
|
||||
r2 := r[0].all_after('meilisearch').trim(' ')
|
||||
if texttools.version(version) != texttools.version(r2) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// get the Upload List of the files
|
||||
fn ulist_get() !ulist.UList {
|
||||
// optionally build a UList which is all paths which are result of building, is then used e.g. in upload
|
||||
return ulist.UList{}
|
||||
}
|
||||
|
||||
// uploads to S3 server if configured
|
||||
fn upload() ! {}
|
||||
|
||||
fn install() ! {
|
||||
cfg := get()!
|
||||
console.print_header('install meilisearch')
|
||||
// Check if meilisearch is installed
|
||||
mut res := os.execute('meilisearch --version')
|
||||
if res.exit_code == 0 {
|
||||
console.print_header('meilisearch is already installed')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if curl is installed
|
||||
res = os.execute('curl --version')
|
||||
if res.exit_code == 0 {
|
||||
console.print_header('curl is already installed')
|
||||
} else {
|
||||
osal.package_install('curl') or {
|
||||
return error('Could not install curl, its required to install meilisearch.\nerror:\n${err}')
|
||||
}
|
||||
}
|
||||
|
||||
if os.exists('${cfg.path}') {
|
||||
os.rmdir_all('${cfg.path}') or {
|
||||
return error('Could not remove directory ${cfg.path}.\nerror:\n${err}')
|
||||
}
|
||||
}
|
||||
|
||||
os.mkdir('${cfg.path}') or {
|
||||
return error('Could not create directory ${cfg.path}.\nerror:\n${err}')
|
||||
}
|
||||
|
||||
mut cmd := 'cd ${cfg.path} && curl -L https://install.meilisearch.com | sh'
|
||||
osal.execute_stdout(cmd)!
|
||||
|
||||
cmd = 'mv /tmp/meilisearch/meilisearch /usr/local/bin/meilisearch'
|
||||
osal.execute_stdout(cmd)!
|
||||
|
||||
console.print_header('meilisearch is installed')
|
||||
}
|
||||
|
||||
fn build() ! {}
|
||||
|
||||
fn destroy() ! {
|
||||
console.print_header('destroy meilisearch')
|
||||
mut cfg := get()!
|
||||
if os.exists('${cfg.path}') {
|
||||
console.print_header('removing directory ${cfg.path}')
|
||||
os.rmdir_all('${cfg.path}') or {
|
||||
return error('Could not remove directory ${cfg.path}.\nerror:\n${err}')
|
||||
}
|
||||
}
|
||||
|
||||
res := os.execute('meilisearch --version')
|
||||
if res.exit_code == 0 {
|
||||
console.print_header('removing meilisearch binary')
|
||||
osal.execute_silent('sudo rm -rf /usr/local/bin/meilisearch')!
|
||||
}
|
||||
|
||||
mut zinit_factory := zinit.new()!
|
||||
if zinit_factory.exists('meilisearch') {
|
||||
zinit_factory.stop('meilisearch') or {
|
||||
return error('Could not stop meilisearch service due to: ${err}')
|
||||
}
|
||||
zinit_factory.delete('meilisearch') or {
|
||||
return error('Could not delete meilisearch service due to: ${err}')
|
||||
}
|
||||
}
|
||||
|
||||
console.print_header('meilisearch is destroyed')
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
module meilisearch_installer
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.sysadmin.startupmanager
|
||||
import freeflowuniverse.herolib.osal.zinit
|
||||
import time
|
||||
|
||||
__global (
|
||||
meilisearch_installer_global map[string]&MeilisearchInstaller
|
||||
meilisearch_installer_default string
|
||||
)
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn args_get(args_ ArgsGet) ArgsGet {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
args.name = 'default'
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&MeilisearchInstaller {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
mut obj := MeilisearchInstaller{}
|
||||
if args.name !in meilisearch_installer_global {
|
||||
if !exists(args)! {
|
||||
set(obj)!
|
||||
} else {
|
||||
heroscript := context.hero_config_get('meilisearch_installer', args.name)!
|
||||
mut obj_ := heroscript_loads(heroscript)!
|
||||
set_in_mem(obj_)!
|
||||
}
|
||||
}
|
||||
return meilisearch_installer_global[args.name] or {
|
||||
println(meilisearch_installer_global)
|
||||
// bug if we get here because should be in globals
|
||||
panic('could not get config for meilisearch_installer with name, is bug:${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// register the config for the future
|
||||
pub fn set(o MeilisearchInstaller) ! {
|
||||
set_in_mem(o)!
|
||||
mut context := base.context()!
|
||||
heroscript := heroscript_dumps(o)!
|
||||
context.hero_config_set('meilisearch_installer', o.name, heroscript)!
|
||||
}
|
||||
|
||||
// does the config exists?
|
||||
pub fn exists(args_ ArgsGet) !bool {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
return context.hero_config_exists('meilisearch_installer', args.name)
|
||||
}
|
||||
|
||||
pub fn delete(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
context.hero_config_delete('meilisearch_installer', args.name)!
|
||||
if args.name in meilisearch_installer_global {
|
||||
// del meilisearch_installer_global[args.name]
|
||||
}
|
||||
}
|
||||
|
||||
// only sets in mem, does not set as config
|
||||
fn set_in_mem(o MeilisearchInstaller) ! {
|
||||
mut o2 := obj_init(o)!
|
||||
meilisearch_installer_global[o.name] = &o2
|
||||
meilisearch_installer_default = o.name
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
heroscript string // if filled in then plbook will be made out of it
|
||||
plbook ?playbook.PlayBook
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut args := args_
|
||||
|
||||
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
|
||||
|
||||
mut install_actions := plbook.find(filter: 'meilisearch_installer.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
}
|
||||
}
|
||||
|
||||
mut other_actions := plbook.find(filter: 'meilisearch_installer.')!
|
||||
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 meilisearch_installer.destroy')
|
||||
destroy()!
|
||||
}
|
||||
if other_action.name == 'install' {
|
||||
console.print_debug('install action meilisearch_installer.install')
|
||||
install()!
|
||||
}
|
||||
}
|
||||
if other_action.name in ['start', 'stop', 'restart'] {
|
||||
mut p := other_action.params
|
||||
name := p.get('name')!
|
||||
mut meilisearch_installer_obj := get(name: name)!
|
||||
console.print_debug('action object:\n${meilisearch_installer_obj}')
|
||||
if other_action.name == 'start' {
|
||||
console.print_debug('install action meilisearch_installer.${other_action.name}')
|
||||
meilisearch_installer_obj.start()!
|
||||
}
|
||||
|
||||
if other_action.name == 'stop' {
|
||||
console.print_debug('install action meilisearch_installer.${other_action.name}')
|
||||
meilisearch_installer_obj.stop()!
|
||||
}
|
||||
if other_action.name == 'restart' {
|
||||
console.print_debug('install action meilisearch_installer.${other_action.name}')
|
||||
meilisearch_installer_obj.restart()!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////////////# 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()!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load from disk and make sure is properly intialized
|
||||
pub fn (mut self MeilisearchInstaller) reload() ! {
|
||||
switch(self.name)
|
||||
self = obj_init(self)!
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) start() ! {
|
||||
switch(self.name)
|
||||
if self.running()! {
|
||||
return
|
||||
}
|
||||
|
||||
console.print_header('meilisearch_installer start')
|
||||
|
||||
if !installed()! {
|
||||
install()!
|
||||
}
|
||||
|
||||
configure()!
|
||||
|
||||
start_pre()!
|
||||
|
||||
for zprocess in startupcmd()! {
|
||||
mut sm := startupmanager_get(zprocess.startuptype)!
|
||||
|
||||
console.print_debug('starting meilisearch_installer with ${zprocess.startuptype}...')
|
||||
|
||||
sm.new(zprocess)!
|
||||
|
||||
sm.start(zprocess.name)!
|
||||
}
|
||||
|
||||
start_post()!
|
||||
|
||||
for _ in 0 .. 50 {
|
||||
if self.running()! {
|
||||
return
|
||||
}
|
||||
time.sleep(100 * time.millisecond)
|
||||
}
|
||||
return error('meilisearch_installer did not install properly.')
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) install_start(args InstallArgs) ! {
|
||||
switch(self.name)
|
||||
self.install(args)!
|
||||
self.start()!
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) stop() ! {
|
||||
switch(self.name)
|
||||
stop_pre()!
|
||||
for zprocess in startupcmd()! {
|
||||
mut sm := startupmanager_get(zprocess.startuptype)!
|
||||
sm.stop(zprocess.name)!
|
||||
}
|
||||
stop_post()!
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) restart() ! {
|
||||
switch(self.name)
|
||||
self.stop()!
|
||||
self.start()!
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) running() !bool {
|
||||
switch(self.name)
|
||||
|
||||
// walk over the generic processes, if not running return
|
||||
for zprocess in startupcmd()! {
|
||||
mut sm := startupmanager_get(zprocess.startuptype)!
|
||||
r := sm.running(zprocess.name)!
|
||||
if r == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return running()!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct InstallArgs {
|
||||
pub mut:
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) install(args InstallArgs) ! {
|
||||
switch(self.name)
|
||||
if args.reset || (!installed()!) {
|
||||
install()!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) build() ! {
|
||||
switch(self.name)
|
||||
build()!
|
||||
}
|
||||
|
||||
pub fn (mut self MeilisearchInstaller) destroy() ! {
|
||||
switch(self.name)
|
||||
self.stop() or {}
|
||||
destroy()!
|
||||
}
|
||||
|
||||
// switch instance to be used for meilisearch_installer
|
||||
pub fn switch(name string) {
|
||||
meilisearch_installer_default = name
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
@[params]
|
||||
pub struct DefaultConfigArgs {
|
||||
instance string = 'default'
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
module meilisearch_installer
|
||||
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
|
||||
pub const version = '1.11.3'
|
||||
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 MeilisearchInstaller {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
path string = '/tmp/meilisearch'
|
||||
masterkey string @[secret]
|
||||
host string = 'localhost'
|
||||
port int = 7700
|
||||
production bool
|
||||
}
|
||||
|
||||
// your checking & initialization code if needed
|
||||
fn obj_init(mycfg_ MeilisearchInstaller) !MeilisearchInstaller {
|
||||
mut mycfg := mycfg_
|
||||
if mycfg.masterkey == '' {
|
||||
mycfg.masterkey = generate_master_key(16)!
|
||||
}
|
||||
|
||||
if mycfg.path == '' {
|
||||
mycfg.path = '/tmp/meilisearch'
|
||||
}
|
||||
|
||||
if mycfg.host == '' {
|
||||
mycfg.host = 'localhost'
|
||||
}
|
||||
|
||||
if mycfg.port == 0 {
|
||||
mycfg.port = 7700
|
||||
}
|
||||
|
||||
if mycfg.name == '' {
|
||||
mycfg.name = 'default'
|
||||
}
|
||||
return mycfg
|
||||
}
|
||||
|
||||
// called before start if done
|
||||
fn configure() ! {
|
||||
// mut installer := get()!
|
||||
}
|
||||
|
||||
/////////////NORMALLY NO NEED TO TOUCH
|
||||
|
||||
pub fn heroscript_dumps(obj MeilisearchInstaller) !string {
|
||||
return encoderhero.encode[MeilisearchInstaller](obj)!
|
||||
}
|
||||
|
||||
pub fn heroscript_loads(heroscript string) !MeilisearchInstaller {
|
||||
mut obj := encoderhero.decode[MeilisearchInstaller](heroscript)!
|
||||
return obj
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user