diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 03e3b194..cdaf8f1f 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -40,6 +40,19 @@ jobs:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: goveralls -coverprofile=/var/tmp/capillaries.p -service=github
+ - name: pkg/capigraph test coverage threshold check
+ env:
+ TESTCOVERAGE_THRESHOLD: 93.9
+ run: |
+ go test -v ./pkg/capigraph/... -coverprofile coverage.out -covermode count
+ totalCoverage=`go tool cover -func=coverage.out | grep total | grep -Eo '[0-9]+\.[0-9]+'`
+ if (( $(echo "$totalCoverage $TESTCOVERAGE_THRESHOLD" | awk '{print ($1 >= $2)}') )); then
+ echo -e "\033[32mOK: $totalCoverage >= $TESTCOVERAGE_THRESHOLD\033[0m"
+ else
+ echo -e "\033[31mFAILED: $totalCoverage < $TESTCOVERAGE_THRESHOLD. Cover more with unit tests or adjust threshold to a lower value.\033[0m"
+ exit 1
+ fi
+
- name: pkg/cql test coverage threshold check
env:
TESTCOVERAGE_THRESHOLD: 87.6
diff --git a/.golangci.yml b/.golangci.yml
index 977c7ada..f9f56a4e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -126,3 +126,30 @@ issues:
text: use of `fmt.Print(|f|ln)` forbidden by pattern
linters:
- forbidigo
+ - path: pkg\/capigraph\/svg_test\.go
+ text: use of `fmt.Print(|f|ln)` forbidden by pattern
+ linters:
+ - forbidigo
+ - path: pkg\/capigraph\/layering\.go
+ text: function buildLayerMap has cognitive complexity
+ linters:
+ - revive
+ - path: pkg\/capigraph\/layer_permutator\.go
+ text: function \(\*LayerPermutator\)\.swapPermutationByIdx has cognitive complexity
+ linters:
+ - revive
+ - text: struct literal uses unkeyed fields
+ linters:
+ - govet
+ - path: pkg\/api\/capigraph\.go
+ text: function GetCapigraphDiagram has (cognitive|cyclomatic) complexity
+ linters:
+ - revive
+ - path: pkg\/capigraph\/svg\.go
+ text: maximum number of return results per function exceeded
+ linters:
+ - revive
+ - path: pkg\/capigraph\/viz_node_hierarchy\.go
+ text: maximum number of return results per function exceeded
+ linters:
+ - revive
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 7fddf6fe..fca69429 100755
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -80,13 +80,22 @@
"args": []
},
{
- "name": "Toolbelt",
+ "name": "Toolbelt validate_script",
"type": "go",
"request": "launch",
"mode": "debug",
"cwd":"${workspaceFolder}/pkg/exe/toolbelt",
"program": "${workspaceFolder}/pkg/exe/toolbelt/capitoolbelt.go",
- "args": ["start_run", "-script_file=/tmp/capi_cfg/portfolio_quicktest/script.json", "-params_file=/tmp/capi_cfg/portfolio_quicktest/script_params.json", "-keyspace=portfolio_quicktest", "-start_nodes=1_read_accounts,1_read_txns,1_read_period_holdings"]
+ "args": ["validate_script", "-script_file=/tmp/capi_cfg/fannie_mae_quicktest/script.json", "-params_file=/tmp/capi_cfg/fannie_mae_quicktest/script_params.json", "-format=capigraph", "-detail="]
+ },
+ {
+ "name": "Toolbelt get_run_status_diagram",
+ "type": "go",
+ "request": "launch",
+ "mode": "debug",
+ "cwd":"${workspaceFolder}/pkg/exe/toolbelt",
+ "program": "${workspaceFolder}/pkg/exe/toolbelt/capitoolbelt.go",
+ "args": ["get_run_status_diagram", "-script_file=/tmp/capi_cfg/fannie_mae_quicktest/script.json", "-params_file=/tmp/capi_cfg/fannie_mae_quicktest/script_params.json", "-keyspace=fannie_mae_quicktest", "-run_id=1", "-format=capigraph"]
},
{
"name": "Webapi",
diff --git a/doc/binconfig.md b/doc/binconfig.md
index bc5fdb10..4558f9c1 100644
--- a/doc/binconfig.md
+++ b/doc/binconfig.md
@@ -73,7 +73,7 @@ Default: 5 threads
Path to the directory containing PEM certificates for all supported CAs. Required only if any of the following is referenced by HTTPS:
- script file
- script parameter file
-- [tag_criteria_uri](glossary.md#tag_criteria_uri)
+- [tag_criteria_url](glossary.md#tag_criteria_url)
- input data files
To obtain the PEM cert, navigate to the file URI with a browser, open certificate information, navigate to the root certificate, save it as DER/CER (say, digicert.cer), and convert it to pem using this command:
diff --git a/doc/dot-fanniemae.svg b/doc/dot-fanniemae.svg
deleted file mode 100755
index 9cafd625..00000000
--- a/doc/dot-fanniemae.svg
+++ /dev/null
@@ -1,331 +0,0 @@
-
-
-indexes
-
-
-
-deal_seller_summaries
-
-
-05_deal_seller_summaries
-
-creates table:
-deal_seller_summaries
-
-group:true, join:left
-
-
-
-
-
-05_write_file_deal_seller_summaries
-
-
-05_write_file_deal_seller_summaries
-
-creates file:
-/tmp/capi_out/fannie_mae_quicktest/deal_seller_summaries.parquet
-
-
-
-
-
-deal_seller_summaries->05_write_file_deal_seller_summaries
-
-
-deal_seller_summaries (no parallelism)
-
-
-
-/tmp/capi_out/fannie_mae_quicktest/deal_seller_summaries.parquet
-
-
-
-
-/tmp/capi_out/fannie_mae_quicktest/deal_seller_summaries.parquet
-
-
-
-
-
-05_write_file_deal_seller_summaries->/tmp/capi_out/fannie_mae_quicktest/deal_seller_summaries.parquet
-
-
-
-
-
-deal_total_upbs
-
-
-03_deal_total_upbs
-
-creates table:
-deal_total_upbs
-
-group:true, join:left
-
-
-
-
-
-deal_summaries
-
-
-05_deal_summaries
-
-creates table:
-deal_summaries
-
-group:true, join:left
-
-
-
-
-
-deal_total_upbs->deal_summaries
-
-
-deal_total_upbs (10 batches)
-
-
-
-05_write_file_deal_summaries
-
-
-05_write_file_deal_summaries
-
-creates file:
-/tmp/capi_out/fannie_mae_quicktest/deal_summaries.parquet
-
-
-
-
-
-deal_summaries->05_write_file_deal_summaries
-
-
-deal_summaries (no parallelism)
-
-
-
-loan_summaries_calculated
-
-
-04_loan_summaries_calculated
-
-creates table:
-loan_summaries_calculated
-
-
-
-
-
-loan_summaries_calculated->deal_seller_summaries
-
-
-idx_loan_summaries_calculated_deal_name_seller_name (lookup)
-
-
-
-loan_summaries_calculated->deal_summaries
-
-
-idx_loan_summaries_calculated_deal_name (lookup)
-
-
-
-04_write_file_loan_summaries_calculated
-
-
-04_write_file_loan_summaries_calculated
-
-creates file:
-/tmp/capi_out/fannie_mae_quicktest/loan_summaries_calculated.parquet
-
-
-
-
-
-loan_summaries_calculated->04_write_file_loan_summaries_calculated
-
-
-loan_summaries_calculated (no parallelism)
-
-
-
-loan_ids
-
-
-02_loan_ids
-
-creates table:
-loan_ids
-
-
-
-
-
-loan_ids->deal_total_upbs
-
-
-idx_loan_ids_deal_name (lookup)
-
-
-
-deal_names
-
-
-02_deal_names
-
-creates table:
-deal_names
-
-
-
-
-
-loan_ids->deal_names
-
-
-loan_ids (10 batches)
-
-
-
-deal_sellers
-
-
-02_deal_sellers
-
-creates table:
-deal_sellers
-
-
-
-
-
-loan_ids->deal_sellers
-
-
-loan_ids (10 batches)
-
-
-
-loan_payment_summaries
-
-
-04_loan_payment_summaries
-
-creates table:
-loan_payment_summaries
-
-group:true, join:left
-
-
-
-
-
-loan_ids->loan_payment_summaries
-
-
-loan_ids (10 batches)
-
-
-
-deal_names->deal_total_upbs
-
-
-deal_names (10 batches)
-
-
-
-deal_sellers->deal_seller_summaries
-
-
-deal_sellers (10 batches)
-
-
-
-loan_payment_summaries->loan_summaries_calculated
-
-
-loan_payment_summaries (10 batches)
-
-
-
-/tmp/capi_out/fannie_mae_quicktest/loan_summaries_calculated.parquet
-
-
-
-
-/tmp/capi_out/fannie_mae_quicktest/loan_summaries_calculated.parquet
-
-
-
-
-
-04_write_file_loan_summaries_calculated->/tmp/capi_out/fannie_mae_quicktest/loan_summaries_calculated.parquet
-
-
-
-
-
-/tmp/capi_in/fannie_mae_quicktest/CAS_2023_R08_G1_20231020_000.parquet
-
-
-/tmp/capi_in/fannie_mae_quicktest/CAS_2023_R08_G1_20231020_000.parquet
-/tmp/capi_in/fannie_mae_quicktest/CAS_2023_R08_G1_20231020_001.parquet
-
-
-
-
-
-payments
-
-
-01_read_payments
-
-creates table:
-payments
-
-
-
-
-
-/tmp/capi_in/fannie_mae_quicktest/CAS_2023_R08_G1_20231020_000.parquet->payments
-
-
-
-
-
-payments->loan_ids
-
-
-payments (10 batches)
-
-
-
-payments->loan_payment_summaries
-
-
-idx_payments_by_loan_id (lookup)
-
-
-
-/tmp/capi_out/fannie_mae_quicktest/deal_summaries.parquet
-
-
-
-
-/tmp/capi_out/fannie_mae_quicktest/deal_summaries.parquet
-
-
-
-
-
-05_write_file_deal_summaries->/tmp/capi_out/fannie_mae_quicktest/deal_summaries.parquet
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/dot-lookup.svg b/doc/dot-lookup.svg
deleted file mode 100644
index 5cfb081f..00000000
--- a/doc/dot-lookup.svg
+++ /dev/null
@@ -1,317 +0,0 @@
-
-
-indexes
-
-
-
-orders
-
-
-read_orders
-
-creates table:
-orders
-
-
-
-
-
-order_date_value_grouped_inner
-
-
-order_date_value_grouped_inner
-
-creates table:
-order_date_value_grouped_inner
-
-group:true, join:inner
-
-
-
-
-
-orders->order_date_value_grouped_inner
-
-
-orders (10 batches)
-
-
-
-order_date_value_grouped_left_outer
-
-
-order_date_value_grouped_left_outer
-
-creates table:
-order_date_value_grouped_left_outer
-
-group:true, join:left
-
-
-
-
-
-orders->order_date_value_grouped_left_outer
-
-
-orders (10 batches)
-
-
-
-order_item_date_inner
-
-
-order_item_date_inner
-
-creates table:
-order_item_date_inner
-
-group:false, join:inner
-
-
-
-
-
-orders->order_item_date_inner
-
-
-orders (10 batches)
-
-
-
-order_item_date_left_outer
-
-
-order_item_date_left_outer
-
-creates table:
-order_item_date_left_outer
-
-group:false, join:left
-
-
-
-
-
-orders->order_item_date_left_outer
-
-
-orders (10 batches)
-
-
-
-file_order_date_value_grouped_inner
-
-
-file_order_date_value_grouped_inner
-
-creates file:
-/tmp/capi_out/lookup/order_date_value_grouped_inner.csv
-
-
-
-
-
-order_date_value_grouped_inner->file_order_date_value_grouped_inner
-
-
-order_date_value_grouped_inner (no parallelism)
-
-
-
-order_items
-
-
-read_order_items
-
-creates table:
-order_items
-
-
-
-
-
-order_items->order_date_value_grouped_inner
-
-
-idx_order_items_order_id (lookup)
-
-
-
-order_items->order_date_value_grouped_left_outer
-
-
-idx_order_items_order_id (lookup)
-
-
-
-order_items->order_item_date_inner
-
-
-idx_order_items_order_id (lookup)
-
-
-
-order_items->order_item_date_left_outer
-
-
-idx_order_items_order_id (lookup)
-
-
-
-/tmp/capi_out/lookup/order_date_value_grouped_inner.csv
-
-
-
-
-/tmp/capi_out/lookup/order_date_value_grouped_inner.csv
-
-
-
-
-
-file_order_date_value_grouped_inner->/tmp/capi_out/lookup/order_date_value_grouped_inner.csv
-
-
-
-
-
-file_order_date_value_grouped_left_outer
-
-
-file_order_date_value_grouped_left_outer
-
-creates file:
-/tmp/capi_out/lookup/order_date_value_grouped_left_outer.csv
-
-
-
-
-
-order_date_value_grouped_left_outer->file_order_date_value_grouped_left_outer
-
-
-order_date_value_grouped_left_outer (no parallelism)
-
-
-
-/tmp/capi_out/lookup/order_date_value_grouped_left_outer.csv
-
-
-
-
-/tmp/capi_out/lookup/order_date_value_grouped_left_outer.csv
-
-
-
-
-
-file_order_date_value_grouped_left_outer->/tmp/capi_out/lookup/order_date_value_grouped_left_outer.csv
-
-
-
-
-
-/tmp/capi_in/lookup/olist_orders_dataset.csv
-
-
-/tmp/capi_in/lookup/olist_orders_dataset.csv
-
-
-
-
-
-/tmp/capi_in/lookup/olist_orders_dataset.csv->orders
-
-
-
-
-
-/tmp/capi_in/lookup/olist_order_items_dataset.csv
-
-
-/tmp/capi_in/lookup/olist_order_items_dataset.csv
-
-
-
-
-
-/tmp/capi_in/lookup/olist_order_items_dataset.csv->order_items
-
-
-
-
-
-file_order_item_date_inner
-
-
-file_order_item_date_inner
-
-creates file:
-/tmp/capi_out/lookup/order_item_date_inner.csv
-
-
-
-
-
-order_item_date_inner->file_order_item_date_inner
-
-
-order_item_date_inner (no parallelism)
-
-
-
-file_order_item_date_left_outer
-
-
-file_order_item_date_left_outer
-
-creates file:
-/tmp/capi_out/lookup/order_item_date_left_outer.csv
-
-
-
-
-
-order_item_date_left_outer->file_order_item_date_left_outer
-
-
-order_item_date_left_outer (no parallelism)
-
-
-
-/tmp/capi_out/lookup/order_item_date_left_outer.csv
-
-
-
-
-/tmp/capi_out/lookup/order_item_date_left_outer.csv
-
-
-
-
-
-file_order_item_date_left_outer->/tmp/capi_out/lookup/order_item_date_left_outer.csv
-
-
-
-
-
-/tmp/capi_out/lookup/order_item_date_inner.csv
-
-
-
-
-/tmp/capi_out/lookup/order_item_date_inner.csv
-
-
-
-
-
-file_order_item_date_inner->/tmp/capi_out/lookup/order_item_date_inner.csv
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/dot-proto-file-reader-creator.svg b/doc/dot-proto-file-reader-creator.svg
deleted file mode 100755
index ae162580..00000000
--- a/doc/dot-proto-file-reader-creator.svg
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-indexes
-
-
-
-/tmp/capi_in/proto_file_reader_creator_quicktest/products.csv
-
-
-/tmp/capi_in/proto_file_reader_creator_quicktest/products.csv
-
-
-
-
-
-products_csv
-
-
-read_file
-
-creates table:
-products_csv
-
-
-
-
-
-/tmp/capi_in/proto_file_reader_creator_quicktest/products.csv->products_csv
-
-
-
-
-
-write_file
-
-
-write_file
-
-creates file:
-/tmp/capi_out/proto_file_reader_creator_quicktest/products.csv
-
-
-
-
-
-products_csv->write_file
-
-
-products_csv (no parallelism)
-
-
-
-/tmp/capi_out/proto_file_reader_creator_quicktest/products.csv
-
-
-
-
-/tmp/capi_out/proto_file_reader_creator_quicktest/products.csv
-
-
-
-
-
-write_file->/tmp/capi_out/proto_file_reader_creator_quicktest/products.csv
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/dot-pycalc.svg b/doc/dot-pycalc.svg
deleted file mode 100644
index 591b88b3..00000000
--- a/doc/dot-pycalc.svg
+++ /dev/null
@@ -1,148 +0,0 @@
-
-
-indexes
-
-
-
-taxed_order_items_go
-
-
-taxed_order_items_go
-
-creates table:
-taxed_order_items_go
-
-
-
-
-
-file_taxed_order_items_go
-
-
-file_taxed_order_items_go
-
-creates file:
-/tmp/capi_out/py_calc/taxed_order_items_go_{batch_idx|string}.csv
-
-
-
-
-
-taxed_order_items_go->file_taxed_order_items_go
-
-
-taxed_order_items_go (2 batches)
-
-
-
-/tmp/capi_out/py_calc/taxed_order_items_go_{batch_idx|string}.csv
-
-
-
-
-/tmp/capi_out/py_calc/taxed_order_items_go_{batch_idx|string}.csv
-
-
-
-
-
-file_taxed_order_items_go->/tmp/capi_out/py_calc/taxed_order_items_go_{batch_idx|string}.csv
-
-
-
-
-
-order_items
-
-
-read_order_items
-
-creates table:
-order_items
-
-
-
-
-
-order_items->taxed_order_items_go
-
-
-order_items (10 batches)
-
-
-
-taxed_order_items_py
-
-
-taxed_order_items_py
-
-creates table:
-taxed_order_items_py
-
-
-
-
-
-order_items->taxed_order_items_py
-
-
-order_items (10 batches)
-
-
-
-file_taxed_order_items_py
-
-
-file_taxed_order_items_py
-
-creates file:
-/tmp/capi_out/py_calc/taxed_order_items_py_{batch_idx|string}.csv
-
-
-
-
-
-taxed_order_items_py->file_taxed_order_items_py
-
-
-taxed_order_items_py (2 batches)
-
-
-
-/tmp/capi_out/py_calc/taxed_order_items_py_{batch_idx|string}.csv
-
-
-
-
-/tmp/capi_out/py_calc/taxed_order_items_py_{batch_idx|string}.csv
-
-
-
-
-
-file_taxed_order_items_py->/tmp/capi_out/py_calc/taxed_order_items_py_{batch_idx|string}.csv
-
-
-
-
-
-/tmp/capi_in/py_calc/olist_order_items_dataset00.csv
-
-
-/tmp/capi_in/py_calc/olist_order_items_dataset00.csv
-/tmp/capi_in/py_calc/olist_order_items_dataset01.csv
-/tmp/capi_in/py_calc/olist_order_items_dataset02.csv
-/tmp/capi_in/py_calc/olist_order_items_dataset03.csv
-/tmp/capi_in/py_calc/olist_order_items_dataset04.csv
-
-
-
-
-
-/tmp/capi_in/py_calc/olist_order_items_dataset00.csv->order_items
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/dot-tag-and-denormalize.svg b/doc/dot-tag-and-denormalize.svg
deleted file mode 100644
index ad1b899b..00000000
--- a/doc/dot-tag-and-denormalize.svg
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-indexes
-
-
-
-/tmp/capi_in/tag_and_denormalize/tags.csv
-
-
-/tmp/capi_in/tag_and_denormalize/tags.csv
-
-
-
-
-
-tags
-
-
-read_tags
-
-creates table:
-tags
-
-
-
-
-
-/tmp/capi_in/tag_and_denormalize/tags.csv->tags
-
-
-
-
-
-tag_totals
-
-
-tag_totals
-
-creates table:
-tag_totals
-
-group:true, join:left
-
-
-
-
-
-tags->tag_totals
-
-
-tags (10 batches)
-
-
-
-/tmp/capi_in/tag_and_denormalize/flipcart_products.tsv
-
-
-/tmp/capi_in/tag_and_denormalize/flipcart_products.tsv
-
-
-
-
-
-products
-
-
-read_products
-
-creates table:
-products
-
-
-
-
-
-/tmp/capi_in/tag_and_denormalize/flipcart_products.tsv->products
-
-
-
-
-
-tagged_products
-
-
-tag_products
-
-creates table:
-tagged_products
-
-
-
-
-
-products->tagged_products
-
-
-products (10 batches)
-
-
-
-file_tagged_products
-
-
-file_tagged_products
-
-creates file:
-/tmp/capi_out/tag_and_denormalize/tagged_products_for_operator_review.csv
-
-
-
-
-
-tagged_products->file_tagged_products
-
-
-tagged_products (no parallelism)
-
-
-
-tagged_products->tag_totals
-
-
-idx_tagged_products_tag (lookup)
-
-
-
-/tmp/capi_out/tag_and_denormalize/tagged_products_for_operator_review.csv
-
-
-
-
-/tmp/capi_out/tag_and_denormalize/tagged_products_for_operator_review.csv
-
-
-
-
-
-file_tagged_products->/tmp/capi_out/tag_and_denormalize/tagged_products_for_operator_review.csv
-
-
-
-
-
-file_tag_totals
-
-
-file_tag_totals
-
-creates file:
-/tmp/capi_out/tag_and_denormalize/tag_totals.tsv
-
-
-
-
-
-tag_totals->file_tag_totals
-
-
-tag_totals (no parallelism)
-
-
-
-/tmp/capi_out/tag_and_denormalize/tag_totals.tsv
-
-
-
-
-/tmp/capi_out/tag_and_denormalize/tag_totals.tsv
-
-
-
-
-
-file_tag_totals->/tmp/capi_out/tag_and_denormalize/tag_totals.tsv
-
-
-
-
-
\ No newline at end of file
diff --git a/doc/glossary.md b/doc/glossary.md
index 32de72c9..08da7d83 100644
--- a/doc/glossary.md
+++ b/doc/glossary.md
@@ -118,7 +118,7 @@ The field in the target table where the tag value will be written to
tag->criteria_expression map. [Expressions](#go-expressions) are allowed to use reader fields only (`r.*`).
-### tag_criteria_uri
+### tag_criteria_url
Same as [tag_criteria](#tag_criteria), but in a separate JSON file. This is the preferred method to specify tag criteria because the list of tags:
- may contain thousands of entries, it's not a good idea to pollute the script file with those
@@ -145,10 +145,6 @@ At the moment, Capillaries supports only a limited subset of the standard Go lib
RabbitMQ queue containing messages for a [processor](#processor). The name of the queue is given by the [handler_executable_type](binconfig.md#handler_executable_type) setting.
-## DOT diagrams
-
-[Graphviz DOT language](https://graphviz.org/) - markdown-style diagram-drawing language. There is a number of free online tools that can visualize DOT language documents.
-
## Toolbelt
A command-line executable that performs common Capillaries operations by:
- reading Capillaries [script files](#script)
@@ -157,18 +153,18 @@ A command-line executable that performs common Capillaries operations by:
The Toolbelt:
- can [start/stop](api.md) [runs](#run), so solution developers can use it in their scripts
- gives very basic access to the [workflow tables](#workflow-table), see `get_*_history` commands
-- can produce rudimentary visuals using [DOT diagram language](#dot-diagrams) - see `validate_script`, `get_run_status_diagram` commands
+- can produce visual diagrams - see `validate_script`, `get_run_status_diagram` commands
See [Toolbelt and Daemon configuration](binconfig.md) for configuration settings.
-One of the main purposes of the toolbelt is to give system integrators easy access to [Capillaries API](api.md). Also, the toolbelt can be useful for visualizing [scripts](#script) and the status of their execution with [DOT diagrams](#dot-diagrams), for example:
+One of the main purposes of the toolbelt is to give system integrators easy access to [Capillaries API](api.md). Also, the toolbelt can be useful for visualizing [scripts](#script) and the status of their execution with diagrams, for example:
```
# Can be executed anytime
-go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/lookup_quicktest/script.json -params_file=../../../test/data/cfg/lookup_quicktest/script_params_two_runs.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/lookup_quicktest/script.json -params_file=../../../test/data/cfg/lookup_quicktest/script_params_two_runs.json -detail=idx
# Can be executed when the lookup script is running using two runs
-go run capitoolbelt.go get_run_status_diagram -script_file=../../../test/data/cfg/lookup_quicktest/script.json -params_file=../../../test/data/cfg/lookup_quicktest/script_params_two_runs.json -keyspace=lookup_quicktest -run_id=1
+go run capitoolbelt.go get_run_status_diagram -script_file=../../../test/data/cfg/lookup_quicktest/script.json -params_file=../../../test/data/cfg/lookup_quicktest/script_params_two_runs.json -keyspace=lookup_quicktest_one_run_json -run_id=1
```
## Deploy tool
diff --git a/doc/qna.md b/doc/qna.md
index b3fc0961..647d0f58 100644
--- a/doc/qna.md
+++ b/doc/qna.md
@@ -75,7 +75,7 @@ Q. Is there a UI for Capillaries?
A. Yes. See [Capillaries UI](../ui/README.md) project, which is a simple web single-page application that shows the status of every [run](glossary.md#run) in every [keyspace](glossary.md#keyspace). UI requirements tend to be very business-specific, it's not an easy task to come up with a cookie-cutter UI framework that would be flexible enough. Dedicated solution developers are encouraged to develop their own UI for Capillaries workflows, using [Capillaries Webapi](glossary.md#webapi) as a back-end and [Capillaries UI](../ui/README.md) as an example.
-Also please note that [Toolbelt](glossary.md#toolbelt) can produce rudimentary visuals using [DOT diagram language](glossary.md#dot-diagrams) - see [Toolbelt](glossary.md#toolbelt) `validate_script`, `get_run_status_diagram` commands.
+Also please note that [Toolbelt](glossary.md#toolbelt) can produce visual diagrams - see [Toolbelt](glossary.md#toolbelt) `validate_script`, `get_run_status_diagram` commands.
## Can Capillaries run in a Docker container?
diff --git a/doc/scriptconfig.md b/doc/scriptconfig.md
index bb8db878..5462f9a3 100644
--- a/doc/scriptconfig.md
+++ b/doc/scriptconfig.md
@@ -40,7 +40,7 @@ Node [processor type](glossary.md#processor-types)
### start_policy
- `auto`: Capillaries automatically start this node processing when all dependency nodes are successfully completed
-- `manual`: Capillaries will start this node processing only if this node is explicitly specified on the [run](glossary.md#run) start; manual nodes are marked on the [dot diagram](dot-tag-and-denormalize.svg) with a thicker border
+- `manual`: Capillaries will start this node processing only if this node is explicitly specified on the [run](glossary.md#run) start; manual nodes are marked on the [diagram](viz-tag-and-denormalize.svg) with a thicker border
Mark nodes as `manual` when you want the operator to review the results of the previous [runs](glossary.md#run) before moving ahead with the rest of the script.
diff --git a/doc/viz-fanniemae.svg b/doc/viz-fanniemae.svg
new file mode 100755
index 00000000..bcb19adc
--- /dev/null
+++ b/doc/viz-fanniemae.svg
@@ -0,0 +1,464 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 01_read_payments
+ Read source files to a table
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../CAS_2023_R08_G1_20231020_000.parquet
+ Table created: payments
+
+
+
+
+
+
+
+ 02_loan_ids
+ Select distinct loan ids
+ Processor: select distinct rows
+ Unique index: idx_loan_ids_loan_id
+ Table created: loan_ids
+
+
+
+
+ payments
+ (10 batches)
+
+
+
+
+
+
+
+ 02_deal_names
+ Select distinct deal names
+ Processor: select distinct rows
+ Unique index: idx_deal_names_deal_name
+ Table created: deal_names
+
+
+
+
+ loan_ids
+ (10 batches)
+
+
+
+
+
+
+
+ 03_deal_total_upbs
+ For each deal, calculate total UPBs
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_loan_ids_deal_name
+ Table created: deal_total_upbs
+
+
+
+
+ deal_names
+ (10 batches)
+
+
+
+
+ idx_loan_ids_deal_name
+ (lookup)
+
+
+
+
+
+
+
+ 05_deal_summaries
+ For each deal, calculate aggregates from calculated loan summaries
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_loan_summaries_calculated_deal_name
+ Table created: deal_summaries
+
+
+
+
+ deal_total_upbs
+ (10 batches)
+
+
+
+
+ idx_loan_summaries_calculated_deal_name
+ (lookup)
+
+
+
+
+
+
+
+ 05_write_file_deal_summaries
+ Write from table to file deal_summaries.parquet
+ Processor: read from table into files
+ Source table: deal_summaries
+ File(s): /tmp/capi_out/.../deal_summaries.parquet
+
+
+
+
+ deal_summaries
+ (no parallelism)
+
+
+
+
+
+
+
+ 04_loan_payment_summaries
+ For each loan, merge all payments into single json string
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_payments_by_loan_id
+ Table created: loan_payment_summaries
+
+
+
+
+ loan_ids
+ (10 batches)
+
+
+
+
+ idx_payments_by_loan_id
+ (lookup)
+
+
+
+
+
+
+
+ 04_loan_summaries_calculated
+ Apply Python calculations to loan summaries
+ Processor: apply Python calculations
+ Python file(s): /tmp/capi_cfg/.../payment_calc.py
+ Table created: loan_summaries_calculated
+
+
+
+
+ loan_payment_summaries
+ (10 batches)
+
+
+
+
+
+
+
+ 04_write_file_loan_summaries_calculated
+ Write from table to file loan_summaries.parquet
+ Processor: read from table into files
+ Source table: loan_summaries_calculated
+ File(s): /tmp/capi_out/.../loan_summaries_calculated.parquet
+
+
+
+
+ loan_summaries_calculated
+ (no parallelism)
+
+
+
+
+
+
+
+ 02_deal_sellers
+ Select distinct sellers
+ Processor: select distinct rows
+ Unique index: idx_sellers_deal_name_seller_name
+ Table created: deal_sellers
+
+
+
+
+ loan_ids
+ (10 batches)
+
+
+
+
+
+
+
+ 05_deal_seller_summaries
+ For each deal/seller, calculate aggregates from calculated loan summaries
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_loan_summaries_calculated_deal_name_seller_name
+ Table created: deal_seller_summaries
+
+
+
+
+ deal_sellers
+ (10 batches)
+
+
+
+
+ idx_loan_summaries_calculated_deal_name_seller_name
+ (lookup)
+
+
+
+
+
+
+
+ 05_write_file_deal_seller_summaries
+ Write from table to file deal_seller_summaries.parquet
+ Processor: read from table into files
+ Source table: deal_seller_summaries
+ File(s): /tmp/capi_out/.../deal_seller_summaries.parquet
+
+
+
+
+ deal_seller_summaries
+ (no parallelism)
+
+Perms 6, elapsed 0.000s, dist 2707.6
+
diff --git a/doc/viz-lookup.svg b/doc/viz-lookup.svg
new file mode 100644
index 00000000..dd458fa0
--- /dev/null
+++ b/doc/viz-lookup.svg
@@ -0,0 +1,409 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ read_order_items
+ Load order item data from CSV file
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../olist_order_items_dataset.csv
+ Table created: order_items
+
+
+
+
+
+
+
+ read_orders
+ Load order data from CSV file
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../olist_orders_dataset.csv
+ Table created: orders
+
+
+
+
+
+
+
+ order_date_value_grouped_inner
+ For each order, look up order items and perform inner join with grouping and aggregation
+ Processor: inner join with lookup table, group: true
+ Lookup index: idx_order_items_order_id
+ Table created: order_date_value_grouped_inner
+
+
+
+
+ orders
+ (10 batches)
+
+
+
+
+ idx_order_items_order_id
+ (lookup)
+
+
+
+
+
+
+
+ file_order_date_value_grouped_inner
+ Write the results of inner join with grouping to CSV file
+ Processor: read from table into files
+ Source table: order_date_value_grouped_inner
+ File(s): /tmp/capi_out/.../order_date_value_grouped_inner.csv
+
+
+
+
+ order_date_value_grouped_inner
+ (no parallelism)
+
+
+
+
+
+
+
+ order_item_date_inner
+ For each order, look up order items and perform inner join without grouping or aggregation
+ Processor: inner join with lookup table, group: false
+ Lookup index: idx_order_items_order_id
+ Table created: order_item_date_inner
+
+
+
+
+ orders
+ (10 batches)
+
+
+
+
+ idx_order_items_order_id
+ (lookup)
+
+
+
+
+
+
+
+ file_order_item_date_inner
+ Write the results of inner join without grouping to CSV file
+ Processor: read from table into files
+ Source table: order_item_date_inner
+ File(s): /tmp/capi_out/.../order_item_date_inner.csv
+
+
+
+
+ order_item_date_inner
+ (no parallelism)
+
+
+
+
+
+
+
+ order_date_value_grouped_left_outer
+ For each order, look up order items and perform left outer join with grouping and aggregation
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_order_items_order_id
+ Table created: order_date_value_grouped_left_outer
+
+
+
+
+ orders
+ (10 batches)
+
+
+
+
+ idx_order_items_order_id
+ (lookup)
+
+
+
+
+
+
+
+ file_order_date_value_grouped_left_outer
+ Write the results of left outer join with grouping to CSV file
+ Processor: read from table into files
+ Source table: order_date_value_grouped_left_outer
+ File(s): /tmp/capi_out/.../order_date_value_grouped_left_outer.csv
+
+
+
+
+ order_date_value_grouped_left_outer
+ (no parallelism)
+
+
+
+
+
+
+
+ order_item_date_left_outer
+ For each order, look up order items and perform left outer join without grouping or aggregation
+ Processor: left join with lookup table, group: false
+ Lookup index: idx_order_items_order_id
+ Table created: order_item_date_left_outer
+
+
+
+
+ orders
+ (10 batches)
+
+
+
+
+ idx_order_items_order_id
+ (lookup)
+
+
+
+
+
+
+
+ file_order_item_date_left_outer
+ Write the results of left oter join without grouping to CSV file
+ Processor: read from table into files
+ Source table: order_item_date_left_outer
+ File(s): /tmp/capi_out/.../order_item_date_left_outer.csv
+
+
+
+
+ order_item_date_left_outer
+ (no parallelism)
+
+Perms 48, elapsed 0.000s, dist 9751.2
+
diff --git a/doc/viz-proto-file-reader-creator.svg b/doc/viz-proto-file-reader-creator.svg
new file mode 100755
index 00000000..9d64101e
--- /dev/null
+++ b/doc/viz-proto-file-reader-creator.svg
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ read_file
+ Read file /tmp/capi_in/proto_file_reader_creator_quicktest/products.csv to table
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../products.csv
+ Table created: products_csv
+
+
+
+
+
+
+
+ write_file
+ Write from table to file /tmp/capi_out/proto_file_reader_creator_quicktest/products.csv
+ Processor: read from table into files
+ Source table: products_csv
+ File(s): /tmp/capi_out/.../products.csv
+
+
+
+
+ products_csv
+ (no parallelism)
+
+Perms 1, elapsed 0.000s, dist 0.0
+
diff --git a/doc/viz-pycalc.svg b/doc/viz-pycalc.svg
new file mode 100644
index 00000000..df608531
--- /dev/null
+++ b/doc/viz-pycalc.svg
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ read_order_items
+ Load order item data from CSV files to a table, one input file - one batch
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../olist_order_items_dataset00.csv
+ Table created: order_items
+
+
+
+
+
+
+
+ taxed_order_items_go
+ Apply Go-based calculations to order items
+ Processor: write from table to table
+ Source table: order_items
+ Table created: taxed_order_items_go
+
+
+
+
+ order_items
+ (10 batches)
+
+
+
+
+
+
+
+ file_taxed_order_items_go
+ Write Go-based calculations to CSV file, batch by batch
+ Processor: read from table into files
+ Source table: taxed_order_items_go
+ File(s): /tmp/capi_out/.../taxed_order_items_go_{batch_idx|string}.csv
+
+
+
+
+ taxed_order_items_go
+ (2 batches)
+
+
+
+
+
+
+
+ taxed_order_items_py
+ Apply Python-based calculations to order items
+ Processor: apply Python calculations
+ Python file(s): /tmp/capi_cfg/.../calc_order_items_code.py
+ Table created: taxed_order_items_py
+
+
+
+
+ order_items
+ (10 batches)
+
+
+
+
+
+
+
+ file_taxed_order_items_py
+ Write Python-based calculations to CSV file, batch by batch
+ Processor: read from table into files
+ Source table: taxed_order_items_py
+ File(s): /tmp/capi_out/.../taxed_order_items_py_{batch_idx|string}.csv
+
+
+
+
+ taxed_order_items_py
+ (2 batches)
+
+Perms 2, elapsed 0.000s, dist 0.0
+
diff --git a/doc/viz-tag-and-denormalize.svg b/doc/viz-tag-and-denormalize.svg
new file mode 100644
index 00000000..1831344c
--- /dev/null
+++ b/doc/viz-tag-and-denormalize.svg
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ read_products
+ Load product data from CSV files to a table, one input file - one batch
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../flipcart_products.tsv
+ Table created: products
+
+
+
+
+
+
+
+ tag_products
+ Tag products according to criteria and write product tag, id, price to a new table
+ Processor: tag and denormalize
+ Tag criteria URL: /tmp/capi_cfg/.../tag_criteria.json
+ Table created: tagged_products
+
+
+
+
+ products
+ (10 batches)
+
+
+
+
+
+
+
+ file_tagged_products_for_operator_review
+ All tagged products to file that can be inspected by the operator
+ Processor: read from table into files
+ Source table: tagged_products
+ File(s): /tmp/capi_out/.../tagged_products_for_operator_review.csv
+
+
+
+
+ tagged_products
+ (no parallelism)
+
+
+
+
+
+
+
+ read_tags
+ Load tags from file
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../tags.csv
+ Table created: tags
+
+
+
+
+
+
+
+ tag_totals
+ For each tag, look up products and perform left outer join with grouping and aggregation
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_tagged_products_tag
+ Table created: tag_totals
+
+
+
+
+ tags
+ (10 batches)
+
+
+
+
+ idx_tagged_products_tag
+ (lookup)
+
+
+
+
+
+
+
+ file_tag_totals
+ Statistics for each tag
+ Processor: read from table into files
+ Source table: tag_totals
+ File(s): /tmp/capi_out/.../tag_totals.tsv
+
+
+
+
+ tag_totals
+ (no parallelism)
+
+Perms 2, elapsed 0.000s, dist 1011.6
+
diff --git a/doc/what.md b/doc/what.md
index 6e171281..5816e846 100644
--- a/doc/what.md
+++ b/doc/what.md
@@ -15,7 +15,7 @@ Capillaries is a distributed data processing framework that:
For example, this Capillaries [script](glossary.md#script) - [test/data/cfg/tag_and_denormalize/script.json](../test/data/cfg/tag_and_denormalize_quicktest/script.json) - for the [tag_and_denormalize integration test](../test/code/tag_and_denormalize/README.md) can be described with this [DAG](glossary.md#DAG) diagram (open it the SVG in a separate browser window to see node tooltips, they may be helpful):
-
+
Workflow steps are discussed in detail below.
diff --git a/docker-compose.yml b/docker-compose.yml
index 87cda296..8deabaa3 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -51,29 +51,30 @@ services:
fluentd-async: "true"
tag: cassandra
- cassandra2:
- container_name: capillaries_cassandra2
- build:
- context: .
- dockerfile: ./test/docker/cassandra/Dockerfile
- depends_on:
- - fluentd
- networks:
- capinet:
- ipv4_address: 10.5.0.12
- ports: # Map to different host ports to avoid collision
- - 17000:7000
- - 17199:7199
- - 19042:9042
- - 17070:7070
- environment:
- CASSANDRA_SEEDS: 10.5.0.11
- logging:
- driver: fluentd
- options:
- fluentd-address: 10.5.0.7:24224
- fluentd-async: "true"
- tag: cassandra
+ # Two cassandras bring my laptop to its knees, make it one
+ # cassandra2:
+ # container_name: capillaries_cassandra2
+ # build:
+ # context: .
+ # dockerfile: ./test/docker/cassandra/Dockerfile
+ # depends_on:
+ # - fluentd
+ # networks:
+ # capinet:
+ # ipv4_address: 10.5.0.12
+ # ports: # Map to different host ports to avoid collision
+ # - 17000:7000
+ # - 17199:7199
+ # - 19042:9042
+ # - 17070:7070
+ # environment:
+ # CASSANDRA_SEEDS: 10.5.0.11
+ # logging:
+ # driver: fluentd
+ # options:
+ # fluentd-address: 10.5.0.7:24224
+ # fluentd-async: "true"
+ # tag: cassandra
prometheus:
container_name: capillaries_prometheus
diff --git a/go.mod b/go.mod
index 93547e9f..98dfd1bf 100644
--- a/go.mod
+++ b/go.mod
@@ -18,9 +18,10 @@ require (
golang.org/x/crypto v0.27.0
golang.org/x/text v0.18.0
gopkg.in/inf.v0 v0.9.1
+ gopkg.in/yaml.v3 v3.0.1
)
-require gopkg.in/yaml.v3 v3.0.1 // indirect
+require github.com/kr/pretty v0.3.1 // indirect
require (
github.com/apache/thrift v0.19.0 // indirect
diff --git a/go.sum b/go.sum
index bd3287bc..b39e05b5 100644
--- a/go.sum
+++ b/go.sum
@@ -50,6 +50,7 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -72,8 +73,9 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -83,6 +85,7 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -90,6 +93,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog=
diff --git a/pkg/api/capigraph.go b/pkg/api/capigraph.go
new file mode 100644
index 00000000..f17986d7
--- /dev/null
+++ b/pkg/api/capigraph.go
@@ -0,0 +1,390 @@
+package api
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/capillariesio/capillaries/pkg/capigraph"
+ "github.com/capillariesio/capillaries/pkg/custom/py_calc"
+ "github.com/capillariesio/capillaries/pkg/custom/tag_and_denormalize"
+ "github.com/capillariesio/capillaries/pkg/sc"
+ "github.com/capillariesio/capillaries/pkg/wfmodel"
+)
+
+const CapillariesIcons100x100 = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+
+func NodeBatchStatusToCapigraphColor(status wfmodel.NodeBatchStatusType) int32 {
+ switch status {
+ case wfmodel.NodeBatchNone:
+ return 0 // not colored, white
+ case wfmodel.NodeBatchStart:
+ return 0x0000FF // blue
+ case wfmodel.NodeBatchSuccess:
+ return 0x008000 // darkgreen
+ case wfmodel.NodeBatchFail:
+ return 0xFF0000 // red
+ case wfmodel.NodeBatchRunStopReceived:
+ return 0xFF8C00 // darkorange
+ default:
+ return 0x2F4F4F // darkslategray
+ }
+}
+
+func shortenFileName(s string) string {
+ parts := strings.Split(s, "/")
+ sb := strings.Builder{}
+ for i, s := range parts {
+ if i < 3 || i == len(parts)-1 {
+ if i > 0 {
+ sb.WriteString("/")
+ }
+ sb.WriteString(s)
+ } else if i == 3 {
+ sb.WriteString("/...")
+ }
+ }
+ return sb.String()
+}
+
+func nodeTypeDescription(node *sc.ScriptNodeDef) string {
+ switch node.Type {
+ case sc.NodeTypeFileTable:
+ return fmt.Sprintf(
+ "Processor: read from files into a table\n"+
+ "File(s): %s\n"+
+ "Table created: %s",
+ shortenFileName(node.FileReader.SrcFileUrls[0]),
+ node.TableCreator.Name)
+ case sc.NodeTypeTableTable:
+ return fmt.Sprintf(
+ "Processor: write from table to table\n"+
+ "Source table: %s\n"+
+ "Table created: %s",
+ node.TableReader.TableName,
+ node.TableCreator.Name)
+ case sc.NodeTypeTableLookupTable:
+ return fmt.Sprintf(
+ "Processor: %s join with lookup table, group: %t\n"+
+ "Lookup index: %s\n"+
+ "Table created: %s",
+ node.Lookup.LookupJoin,
+ node.Lookup.IsGroup,
+ node.Lookup.IndexName,
+ node.TableCreator.Name)
+ case sc.NodeTypeTableFile:
+ return fmt.Sprintf(
+ "Processor: read from table into files\n"+
+ "Source table: %s\n"+
+ "File(s): %s",
+ node.TableReader.TableName,
+ shortenFileName(node.FileCreator.UrlTemplate))
+ case sc.NodeTypeTableCustomTfmTable:
+ switch node.CustomProcessorType {
+ case py_calc.ProcessorPyCalcName:
+ return fmt.Sprintf(
+ "Processor: apply Python calculations\n"+
+ "Python file(s): %s\n"+
+ "Table created: %s",
+ shortenFileName(node.CustomProcessor.(*py_calc.PyCalcProcessorDef).PythonUrls[0]),
+ node.TableCreator.Name)
+ case tag_and_denormalize.ProcessorTagAndDenormalizeName:
+ tagCriteriaUrl := shortenFileName(node.CustomProcessor.(*tag_and_denormalize.TagAndDenormalizeProcessorDef).RawTagCriteriaUrl)
+ if len(tagCriteriaUrl) == 0 {
+ tagCriteriaUrl = "(inline)"
+ }
+ return fmt.Sprintf(
+ "Processor: tag and denormalize\n"+
+ "Tag criteria URL: %s\n"+
+ "Table created: %s",
+ tagCriteriaUrl,
+ node.TableCreator.Name)
+ default:
+ return "Custom processor: unknown"
+ }
+ case sc.NodeTypeDistinctTable:
+ distinctIdxName, _, err := node.TableCreator.GetSingleUniqueIndexDef()
+ if err != nil {
+ distinctIdxName = "unknown"
+ }
+
+ return fmt.Sprintf(
+ "Processor: select distinct rows\n"+
+ "Unique index: %s\n"+
+ "Table created: %s",
+ distinctIdxName,
+ node.TableCreator.Name)
+ default:
+ return "Unknown processor type: " + string(node.Type)
+ }
+}
+
+func nodeTypeIcon(node *sc.ScriptNodeDef) string {
+ switch node.Type {
+ case sc.NodeTypeFileTable:
+ return "icon-database-table-read"
+ case sc.NodeTypeTableTable:
+ return "icon-database-table-copy"
+ case sc.NodeTypeTableLookupTable:
+ return "icon-database-table-join"
+ case sc.NodeTypeTableFile:
+ if node.FileCreator.CreatorFileType == sc.CreatorFileTypeCsv {
+ return "icon-csv"
+ } else if node.FileCreator.CreatorFileType == sc.CreatorFileTypeParquet {
+ return "icon-parquet"
+ } else {
+ return ""
+ }
+ case sc.NodeTypeTableCustomTfmTable:
+ switch node.CustomProcessorType {
+ case py_calc.ProcessorPyCalcName:
+ return "icon-database-table-py"
+ case tag_and_denormalize.ProcessorTagAndDenormalizeName:
+ return "icon-database-table-tag"
+ default:
+ return ""
+ }
+ case sc.NodeTypeDistinctTable:
+ return "icon-database-table-distinct"
+ default:
+
+ return ""
+ }
+}
+
+func GetCapigraphDiagram(scriptDef *sc.ScriptDef, showIdx bool, showFields bool, useRootPalette bool, nodeStringColorMap map[string]int32) string {
+ nodeDefs := make([]capigraph.NodeDef, len(scriptDef.ScriptNodes)+1)
+ nodeDefs[0] = capigraph.NodeDef{0, "", capigraph.EdgeDef{}, []capigraph.EdgeDef{}, "", 0, false}
+ nodeNameMap := map[string]int16{}
+
+ // Populate nodes. Before that, sort node names, otherwise they may appear on the diagram in random order (when best distances are close)
+ nodeNames := make([]string, len(scriptDef.ScriptNodes))
+ nodeIdx := int16(0)
+ for nodeName := range scriptDef.ScriptNodes {
+ nodeNames[nodeIdx] = nodeName
+ nodeIdx++
+ }
+ sort.Slice(nodeNames, func(i, j int) bool { return nodeNames[i] < nodeNames[j] })
+
+ nodeIdx = int16(1)
+ for _, nodeName := range nodeNames {
+ node := scriptDef.ScriptNodes[nodeName]
+ nodeNameMap[nodeName] = nodeIdx
+ color := int32(0)
+ if nodeStringColorMap != nil {
+ color = nodeStringColorMap[node.Name]
+ }
+
+ // This is a root node marked as "manual". Do not show "manual" marker,
+ // it is redundant in this case and may be confusing
+ isReallyStartedManually := !node.HasFileReader() && node.StartPolicy == sc.NodeStartManual
+ if nodeStringColorMap != nil {
+ // Short desc
+ nodeDefs[nodeIdx] = capigraph.NodeDef{nodeIdx, fmt.Sprintf("\n%s\n", nodeName), capigraph.EdgeDef{}, []capigraph.EdgeDef{}, nodeTypeIcon(node), color, isReallyStartedManually}
+ } else {
+ // Full desc
+ nodeDefs[nodeIdx] = capigraph.NodeDef{nodeIdx, fmt.Sprintf("%s\n%s\n%s", nodeName, node.Desc, nodeTypeDescription(node)), capigraph.EdgeDef{}, []capigraph.EdgeDef{}, nodeTypeIcon(node), color, isReallyStartedManually}
+ }
+ nodeIdx++
+ }
+
+ // Populate direct parents and lookups
+ for _, node := range scriptDef.ScriptNodes {
+ nodeIdx := nodeNameMap[node.Name]
+ allUsedFields := sc.FieldRefs{}
+ if node.Type == sc.NodeTypeTableCustomTfmTable && node.CustomProcessorType == py_calc.ProcessorPyCalcName {
+ usedInPyExpressions := node.CustomProcessor.(*py_calc.PyCalcProcessorDef).GetUsedInTargetExpressionsFields()
+ allUsedFields.Append(*usedInPyExpressions)
+ } else if node.Type == sc.NodeTypeTableCustomTfmTable && node.CustomProcessorType == tag_and_denormalize.ProcessorTagAndDenormalizeName {
+ usedInTagExpressions := node.CustomProcessor.(*tag_and_denormalize.TagAndDenormalizeProcessorDef).GetUsedInTargetExpressionsFields()
+ allUsedFields.Append(*usedInTagExpressions)
+ }
+ if node.HasFileCreator() {
+ usedInAllTargetFileExpressions := node.FileCreator.GetFieldRefsUsedInAllTargetFileExpressions()
+ allUsedFields.Append(usedInAllTargetFileExpressions)
+ } else if node.HasTableCreator() {
+ usedInAllTargetTableExpressions := sc.GetFieldRefsUsedInAllTargetExpressions(node.TableCreator.Fields)
+ allUsedFields.Append(usedInAllTargetTableExpressions)
+ }
+
+ if node.HasTableReader() {
+ parentNode := scriptDef.TableCreatorNodeMap[node.TableReader.TableName]
+ parentNodeIdx := nodeNameMap[parentNode.Name]
+ nodeDefs[nodeIdx].PriIn.SrcId = parentNodeIdx
+ sb := strings.Builder{}
+ if showIdx {
+ if node.TableReader.ExpectedBatchesTotal > 1 {
+ sb.WriteString(fmt.Sprintf("%s\n(%d batches)", node.TableReader.TableName, node.TableReader.ExpectedBatchesTotal))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s\n(no parallelism)", node.TableReader.TableName))
+ }
+ }
+ if showFields {
+ if showIdx {
+ sb.WriteString("\n")
+ }
+ sort.Slice(allUsedFields, func(i, j int) bool { return allUsedFields[i].FieldName < allUsedFields[j].FieldName })
+ for i := 0; i < len(allUsedFields); i++ {
+ if allUsedFields[i].TableName == sc.ReaderAlias {
+ if sb.Len() > 0 {
+ sb.WriteString("\n")
+ }
+ sb.WriteString(allUsedFields[i].FieldName)
+ }
+ }
+ }
+ nodeDefs[nodeIdx].PriIn.Text = sb.String()
+ }
+ if node.HasLookup() {
+ lkpParentNode := scriptDef.IndexNodeMap[node.Lookup.IndexName]
+ lkpParentNodeIdx := nodeNameMap[lkpParentNode.Name]
+
+ sb := strings.Builder{}
+ if showIdx {
+ sb.WriteString(fmt.Sprintf("%s\n(lookup)", node.Lookup.IndexName))
+ }
+ if showFields {
+ if showIdx {
+ sb.WriteString("\n")
+ }
+ for i := 0; i < len(allUsedFields); i++ {
+ if allUsedFields[i].TableName == sc.LookupAlias {
+ if sb.Len() > 0 {
+ sb.WriteString("\n")
+ }
+ sb.WriteString(allUsedFields[i].FieldName)
+
+ }
+ }
+ }
+ nodeDefs[nodeIdx].SecIn = append(nodeDefs[nodeIdx].SecIn, capigraph.EdgeDef{lkpParentNodeIdx, sb.String()})
+ }
+ }
+
+ nodeFo := capigraph.FontOptions{capigraph.FontTypefaceVerdana, capigraph.FontWeightNormal, 20, 0.3}
+ edgeFo := capigraph.FontOptions{capigraph.FontTypefaceArial, capigraph.FontWeightNormal, 18, 0.3}
+ edgeOptions := capigraph.EdgeOptions{2.0}
+ palette := capigraph.DefaultPalette()
+ if nodeStringColorMap != nil || !useRootPalette {
+ palette = nil
+ }
+ svg, _, _, _, _, errOpt := capigraph.DrawOptimized(nodeDefs, nodeFo, edgeFo, edgeOptions, CapillariesIcons100x100, "", palette)
+ if errOpt != nil {
+ var errUnopt error
+ svg, _, errUnopt = capigraph.DrawUnoptimized(nodeDefs, nodeFo, edgeFo, edgeOptions, CapillariesIcons100x100, "", palette)
+ if errUnopt != nil {
+ svg = fmt.Sprintf(`
+
+%s
+%s
+ `, errOpt.Error(), errUnopt.Error())
+ }
+ }
+
+ return svg
+
+}
diff --git a/pkg/api/dot.go b/pkg/api/dot.go
new file mode 100644
index 00000000..ca5bf91a
--- /dev/null
+++ b/pkg/api/dot.go
@@ -0,0 +1,176 @@
+package api
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/capillariesio/capillaries/pkg/sc"
+ "github.com/capillariesio/capillaries/pkg/wfmodel"
+)
+
+type DiagramType string
+
+const (
+ DiagramIndexes DiagramType = "indexes"
+ DiagramFields DiagramType = "fields"
+ DiagramRunStatus DiagramType = "run_status"
+)
+
+func NodeBatchStatusToDotColor(status wfmodel.NodeBatchStatusType) string {
+ switch status {
+ case wfmodel.NodeBatchNone:
+ return "white"
+ case wfmodel.NodeBatchStart:
+ return "lightblue"
+ case wfmodel.NodeBatchSuccess:
+ return "green"
+ case wfmodel.NodeBatchFail:
+ return "red"
+ case wfmodel.NodeBatchRunStopReceived:
+ return "orangered"
+ default:
+ return "cyan"
+ }
+}
+
+func drawFileReader(node *sc.ScriptNodeDef, showFields bool, arrowFontSize int, recordFontSize int) string {
+ var b strings.Builder
+ arrowLabelBuilder := strings.Builder{}
+ if showFields {
+ for colName := range node.FileReader.Columns {
+ arrowLabelBuilder.WriteString(colName)
+ arrowLabelBuilder.WriteString("\\l")
+ }
+ }
+ fileNames := make([]string, len(node.FileReader.SrcFileUrls))
+ copy(fileNames, node.FileReader.SrcFileUrls)
+
+ b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=dotted, fontsize=\"%d\", label=\"%s\"];\n", node.FileReader.SrcFileUrls[0], node.GetTargetName(), arrowFontSize, arrowLabelBuilder.String()))
+ b.WriteString(fmt.Sprintf("\"%s\" [shape=folder, fontsize=\"%d\", label=\"%s\", tooltip=\"Source data file(s)\"];\n", node.FileReader.SrcFileUrls[0], recordFontSize, strings.Join(fileNames, "\\n")))
+ return b.String()
+}
+
+func drawFileCreator(node *sc.ScriptNodeDef, showFields bool, arrowFontSize int, recordFontSize int, allUsedFields sc.FieldRefs, penWidth string, fillColor string, urlEscaper *strings.Replacer, inSrcArrowLabel string) string {
+ var b strings.Builder
+ b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=solid, fontsize=\"%d\", label=\"%s\"];\n", node.TableReader.TableName, node.Name, arrowFontSize, inSrcArrowLabel))
+
+ // Node (file)
+ b.WriteString(fmt.Sprintf("\"%s\" [shape=record, penwidth=\"%s\", fontsize=\"%d\", fillcolor=\"%s\", style=\"filled\", label=\"{%s|creates file:\\n%s}\", tooltip=\"%s\"];\n", node.Name, penWidth, recordFontSize, fillColor, node.Name, urlEscaper.Replace(node.FileCreator.UrlTemplate), node.Desc))
+
+ // Out (file)
+ arrowLabelBuilder := strings.Builder{}
+ if showFields {
+ for i := 0; i < len(allUsedFields); i++ {
+ arrowLabelBuilder.WriteString(allUsedFields[i].FieldName)
+ arrowLabelBuilder.WriteString("\\l")
+ }
+ }
+
+ b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=dotted, fontsize=\"%d\", label=\"%s\"];\n", node.Name, node.FileCreator.UrlTemplate, arrowFontSize, arrowLabelBuilder.String()))
+ b.WriteString(fmt.Sprintf("\"%s\" [shape=note, fontsize=\"%d\", label=\"%s\", tooltip=\"Target data file(s)\"];\n", node.FileCreator.UrlTemplate, recordFontSize, node.FileCreator.UrlTemplate))
+ return b.String()
+}
+
+func drawTableReader(node *sc.ScriptNodeDef, showIdx bool, showFields bool, arrowFontSize int, recordFontSize int, allUsedFields sc.FieldRefs, penWidth string, fillColor string, urlEscaper *strings.Replacer) string {
+ var b strings.Builder
+ sb := strings.Builder{}
+ if showIdx {
+ if node.TableReader.ExpectedBatchesTotal > 1 {
+ sb.WriteString(fmt.Sprintf("%s (%d batches)", node.TableReader.TableName, node.TableReader.ExpectedBatchesTotal))
+ } else {
+ sb.WriteString(fmt.Sprintf("%s (no parallelism)", node.TableReader.TableName))
+ }
+ }
+ if showFields {
+ if showIdx {
+ sb.WriteString("\\l")
+ }
+ for i := 0; i < len(allUsedFields); i++ {
+ if allUsedFields[i].TableName == sc.ReaderAlias {
+ sb.WriteString(allUsedFields[i].FieldName)
+ sb.WriteString("\\l")
+ }
+ }
+ }
+
+ if node.HasFileCreator() {
+ b.WriteString(drawFileCreator(node, showFields, arrowFontSize, recordFontSize, allUsedFields, penWidth, fillColor, urlEscaper, sb.String()))
+ } else {
+ b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=solid, fontsize=\"%d\", label=\"%s\"];\n", node.TableReader.TableName, node.GetTargetName(), arrowFontSize, sb.String()))
+ }
+
+ if node.HasLookup() {
+ inLkpArrowLabel := fmt.Sprintf("%s (lookup)", node.Lookup.IndexName)
+ if showFields {
+ inLkpArrowLabelBuilder := strings.Builder{}
+ for i := 0; i < len(allUsedFields); i++ {
+ if allUsedFields[i].TableName == sc.LookupAlias {
+ inLkpArrowLabelBuilder.WriteString(allUsedFields[i].FieldName)
+ inLkpArrowLabelBuilder.WriteString("\\l")
+ }
+ }
+ inLkpArrowLabel = inLkpArrowLabelBuilder.String()
+ }
+ // In (lookup)
+ b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=dashed, fontsize=\"%d\", label=\"%s\"];\n", node.Lookup.TableCreator.Name, node.GetTargetName(), arrowFontSize, inLkpArrowLabel))
+ }
+ return b.String()
+}
+
+func drawTableCreator(node *sc.ScriptNodeDef, recordFontSize int, penWidth string, fillColor string) string {
+ if node.HasLookup() {
+ return fmt.Sprintf("\"%s\" [shape=record, penwidth=\"%s\", fontsize=\"%d\", fillcolor=\"%s\", style=\"filled\", label=\"{%s|creates table:\\n%s|group:%t, join:%s}\", tooltip=\"%s\"];\n",
+ node.TableCreator.Name, penWidth, recordFontSize, fillColor, node.Name, node.TableCreator.Name, node.Lookup.IsGroup, node.Lookup.LookupJoin, node.Desc)
+ }
+ return fmt.Sprintf("\"%s\" [shape=record, penwidth=\"%s\", fontsize=\"%d\", fillcolor=\"%s\", style=\"filled\", label=\"{%s|creates table:\\n%s}\", tooltip=\"%s\"];\n",
+ node.TableCreator.Name, penWidth, recordFontSize, fillColor, node.Name, node.TableCreator.Name, node.Desc)
+}
+
+// Used by Toolbelt and Webapi
+func GetDotDiagram(scriptDef *sc.ScriptDef, showIdx bool, showFields bool, nodeColorMap map[string]string) string {
+ var b strings.Builder
+
+ const recordFontSize int = 20
+ const arrowFontSize int = 18
+
+ urlEscaper := strings.NewReplacer(`{`, `\{`, `}`, `\}`, `|`, `\|`)
+ b.WriteString("\ndigraph idx_fields {\nrankdir=\"TD\";\n node [fontname=\"Helvetica\"];\nedge [fontname=\"Helvetica\"];\ngraph [splines=true, pad=\"0.5\", ranksep=\"0.5\", nodesep=\"0.5\"];\n")
+ for _, node := range scriptDef.ScriptNodes {
+ penWidth := "1"
+ if node.StartPolicy == sc.NodeStartManual {
+ penWidth = "6"
+ }
+ fillColor := "white"
+ var ok bool
+ if nodeColorMap != nil {
+ if fillColor, ok = nodeColorMap[node.Name]; !ok {
+ fillColor = "white" // This run does not affect this node, or the node was not started
+ }
+ }
+
+ if node.HasFileReader() {
+ b.WriteString(drawFileReader(node, showFields, arrowFontSize, recordFontSize))
+ }
+
+ allUsedFields := sc.FieldRefs{}
+
+ if node.HasFileCreator() {
+ usedInAllTargetFileExpressions := node.FileCreator.GetFieldRefsUsedInAllTargetFileExpressions()
+ allUsedFields.Append(usedInAllTargetFileExpressions)
+ } else if node.HasTableCreator() {
+ usedInAllTargetTableExpressions := sc.GetFieldRefsUsedInAllTargetExpressions(node.TableCreator.Fields)
+ allUsedFields.Append(usedInAllTargetTableExpressions)
+ }
+
+ if node.HasTableReader() {
+ b.WriteString(drawTableReader(node, showIdx, showFields, arrowFontSize, recordFontSize, allUsedFields, penWidth, fillColor, urlEscaper))
+ }
+
+ if node.HasTableCreator() {
+ b.WriteString(drawTableCreator(node, recordFontSize, penWidth, fillColor))
+ }
+ }
+ b.WriteString("}\n")
+
+ return b.String()
+}
diff --git a/pkg/api/run.go b/pkg/api/run.go
index 1c8bd8c5..9ecc7d42 100644
--- a/pkg/api/run.go
+++ b/pkg/api/run.go
@@ -117,8 +117,8 @@ func StartRun(envConfig *env.EnvConfig, logger *l.CapiLogger, amqpChannel *amqp.
Ts: time.Now().UnixMilli(),
MessageType: wfmodel.MessageTypeDataBatch,
Payload: wfmodel.MessagePayloadDataBatch{
- ScriptURI: scriptFilePath,
- ScriptParamsURI: paramsFilePath,
+ ScriptURL: scriptFilePath,
+ ScriptParamsURL: paramsFilePath,
DataKeyspace: keyspace,
RunId: runId,
TargetNodeName: affectedNodeName,
@@ -235,8 +235,8 @@ func RunNode(envConfig *env.EnvConfig, logger *l.CapiLogger, nodeName string, ru
batchStartTs := time.Now()
logger.Info("BatchStarted: [%d,%d]...", intervals[i][0], intervals[i][1])
dataBatchInfo := wfmodel.MessagePayloadDataBatch{
- ScriptURI: scriptFilePath,
- ScriptParamsURI: paramsFilePath,
+ ScriptURL: scriptFilePath,
+ ScriptParamsURL: paramsFilePath,
DataKeyspace: keyspace,
RunId: runId,
TargetNodeName: nodeName,
diff --git a/pkg/capigraph/fonts.go b/pkg/capigraph/fonts.go
new file mode 100644
index 00000000..47004355
--- /dev/null
+++ b/pkg/capigraph/fonts.go
@@ -0,0 +1,202 @@
+package capigraph
+
+type FontWeight int
+
+const (
+ FontWeightBold FontWeight = iota
+ FontWeightNormal
+)
+
+func FontWeightToString(w FontWeight) string {
+ switch w {
+ case FontWeightBold:
+ return "bold"
+ case FontWeightNormal:
+ return "normal"
+ default:
+ return "normal"
+ }
+}
+
+type FontTypeface int
+
+const (
+ FontTypefaceArial FontTypeface = iota
+ FontTypefaceCourier
+ FontTypefaceVerdana
+)
+
+func FontTypefaceToString(tf FontTypeface) string {
+ switch tf {
+ case FontTypefaceArial:
+ return "arial"
+ case FontTypefaceCourier:
+ return "courier"
+ case FontTypefaceVerdana:
+ return "verdana"
+ default:
+ return "courier"
+ }
+}
+
+type FontOptions struct {
+ Typeface FontTypeface
+ Weight FontWeight
+ SizeInPixels float64
+ Interval float64
+}
+
+// Generated by https://github.com/Evgenus/js-server-text-width
+// 0x0: Basic Latin, Latin-1 Supplement, Latin Extended-A, Latin Extended-B
+// 0x370: Greek and Coptic, Cyrillic
+// Normal (400) and bold (700), Arial, Courier, Verdana font size 100px
+var sizeMap = map[FontTypeface]map[FontWeight]map[int][]int{
+ FontTypefaceArial: {
+ FontWeightNormal: {
+ // "Arial|100px|400|0"
+ 0: []int{75, 75, 75, 75, 75, 75, 75, 75, 75, 28, 28, 28, 28, 28, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 28, 28, 35, 56, 56, 89, 67, 19, 33, 33, 39, 58, 28, 33, 28, 28, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 28, 28, 58, 58, 58, 56, 102, 67, 67, 72, 72, 67, 61, 78, 72, 28, 50, 67, 56, 83, 72, 78, 67, 78, 72, 67, 61, 72, 67, 94, 67, 67, 61, 28, 28, 28, 47, 56, 33, 56, 56, 50, 56, 56, 28, 56, 56, 22, 22, 50, 22, 83, 56, 56, 56, 56, 33, 50, 28, 56, 50, 72, 50, 50, 50, 33, 26, 33, 58, 50, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 28, 33, 56, 56, 56, 56, 26, 56, 33, 74, 37, 56, 58, 0, 74, 55, 40, 55, 33, 33, 33, 58, 54, 33, 33, 33, 37, 56, 83, 83, 83, 61, 67, 67, 67, 67, 67, 67, 100, 72, 67, 67, 67, 67, 28, 28, 28, 28, 72, 72, 78, 78, 78, 78, 78, 58, 78, 72, 72, 72, 72, 67, 67, 61, 56, 56, 56, 56, 56, 56, 89, 50, 56, 56, 56, 56, 28, 28, 28, 28, 56, 56, 56, 56, 56, 56, 56, 55, 61, 56, 56, 56, 56, 50, 56, 50, 67, 56, 67, 56, 67, 56, 72, 50, 72, 50, 72, 50, 72, 50, 72, 61, 72, 56, 67, 56, 67, 56, 67, 56, 67, 56, 67, 56, 78, 56, 78, 56, 78, 56, 78, 56, 72, 56, 72, 56, 28, 28, 28, 28, 28, 28, 28, 22, 28, 28, 73, 44, 50, 22, 67, 50, 50, 56, 22, 56, 22, 56, 29, 56, 33, 56, 22, 72, 56, 72, 56, 72, 56, 60, 72, 56, 78, 56, 78, 56, 78, 56, 100, 94, 72, 33, 72, 33, 72, 33, 67, 50, 67, 50, 67, 50, 67, 50, 61, 28, 61, 38, 61, 28, 72, 56, 72, 56, 72, 56, 72, 56, 72, 56, 72, 56, 94, 72, 67, 50, 67, 61, 50, 61, 50, 61, 50, 22, 56, 76, 66, 56, 66, 56, 72, 72, 50, 72, 81, 66, 56, 56, 67, 75, 60, 61, 56, 78, 62, 88, 22, 28, 67, 50, 22, 50, 89, 72, 56, 78, 86, 66, 87, 67, 75, 56, 67, 67, 50, 62, 38, 28, 61, 28, 61, 85, 67, 75, 72, 77, 50, 61, 50, 61, 61, 54, 54, 56, 56, 46, 49, 56, 26, 41, 58, 28, 133, 122, 105, 106, 83, 45, 122, 94, 77, 67, 56, 28, 22, 78, 56, 72, 56, 72, 56, 72, 56, 72, 56, 72, 56, 56, 67, 56, 67, 56, 100, 89, 78, 56, 78, 56, 67, 50, 78, 56, 78, 56, 61, 54, 22, 133, 122, 105, 78, 56, 103, 62, 72, 56, 67, 56, 100, 89, 78, 61, 67, 56, 67, 56, 67, 56, 67, 56, 28, 28, 28, 28, 78, 56, 78, 56, 72, 33, 72, 33, 72, 56, 72, 56, 67, 50, 61, 28, 54, 44, 72, 56, 71, 68, 60, 57, 61, 50, 67, 56, 67, 56, 78, 56, 78, 56, 78, 56, 78, 56, 67, 50, 35, 68, 37, 22, 89, 89, 67, 72, 50, 56, 61, 50, 50, 58, 45, 67, 72, 67, 67, 56, 50, 22, 74, 56, 72, 33, 67, 50}, // "Arial|100px|400|0"
+ // "Arial|100px|400|370"
+ 0x370: []int{58, 48, 61, 46, 33, 33, 72, 58, 75, 75, 33, 50, 50, 50, 28, 50, 75, 75, 75, 75, 33, 33, 67, 28, 78, 84, 38, 75, 77, 75, 86, 75, 22, 67, 67, 55, 67, 67, 61, 72, 78, 28, 67, 67, 83, 72, 65, 78, 72, 67, 75, 62, 61, 67, 80, 67, 84, 75, 28, 67, 58, 45, 56, 22, 55, 58, 58, 50, 56, 45, 44, 56, 56, 22, 50, 50, 58, 50, 45, 56, 69, 57, 48, 62, 40, 55, 65, 52, 71, 78, 22, 55, 56, 55, 78, 67, 58, 55, 77, 96, 77, 56, 78, 60, 78, 56, 72, 50, 61, 40, 62, 53, 76, 58, 89, 83, 67, 56, 67, 50, 67, 67, 61, 60, 74, 55, 46, 41, 60, 57, 50, 22, 78, 44, 44, 67, 56, 72, 83, 69, 57, 72, 72, 72, 67, 67, 86, 54, 72, 67, 28, 28, 50, 106, 101, 85, 58, 72, 64, 72, 67, 66, 67, 54, 68, 67, 92, 60, 72, 72, 58, 66, 83, 72, 78, 72, 67, 72, 61, 64, 76, 67, 74, 67, 92, 94, 79, 89, 66, 72, 101, 72, 56, 57, 53, 36, 58, 56, 67, 46, 56, 56, 44, 58, 69, 55, 56, 54, 56, 50, 46, 50, 82, 50, 57, 52, 80, 82, 63, 72, 52, 51, 75, 54, 56, 56, 56, 36, 51, 50, 22, 28, 22, 91, 81, 56, 44, 56, 50, 55, 134, 62, 78, 61, 95, 71, 67, 50, 90, 70, 83, 69, 105, 87, 60, 46, 80, 69, 78, 56, 80, 63, 80, 63, 107, 90, 83, 61, 119, 85, 134, 62, 72, 50, 50, 0, 0, 0, 0, 0, 0, 0, 72, 56, 66, 52, 67, 56, 49, 41, 54, 36, 67, 55, 92, 67, 60, 46, 58, 44, 58, 44, 58, 44, 74, 54, 72, 55, 88, 65, 114, 87, 75, 52, 72, 50, 61, 46, 56, 50, 56, 50, 67, 50, 93, 69, 67, 52, 67, 52, 67, 56, 86, 67, 86, 67, 28, 92, 67, 67, 55, 66, 58, 72, 55, 72, 55, 67, 52, 83, 69, 22, 67, 56, 67, 56, 100, 89, 67, 56, 75, 56, 75, 56, 92, 67, 60, 46, 60, 54, 72, 56, 72, 56, 78, 56, 78, 56, 78, 56, 72, 51, 64, 50, 64, 50, 64, 50, 67, 52, 54, 36, 89, 72, 54, 36, 67, 50, 67, 50}, // "Arial|100px|400|370"
+ },
+ FontWeightBold: {
+ // "Arial|100px|700|0"
+ 0: []int{75, 75, 75, 75, 75, 75, 75, 75, 75, 28, 28, 28, 28, 28, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 28, 33, 47, 56, 56, 89, 72, 24, 33, 33, 39, 58, 28, 33, 28, 28, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 33, 33, 58, 58, 58, 61, 98, 72, 72, 72, 72, 67, 61, 78, 72, 28, 56, 72, 61, 83, 72, 78, 67, 78, 72, 67, 61, 72, 67, 94, 67, 67, 61, 33, 28, 33, 58, 56, 33, 56, 61, 56, 61, 56, 33, 61, 61, 28, 28, 56, 28, 89, 61, 61, 61, 61, 39, 56, 33, 61, 56, 78, 56, 56, 50, 39, 28, 39, 58, 50, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 28, 33, 56, 56, 56, 56, 28, 56, 33, 74, 37, 56, 58, 0, 74, 55, 40, 55, 33, 33, 33, 58, 56, 33, 33, 33, 37, 56, 83, 83, 83, 61, 72, 72, 72, 72, 72, 72, 100, 72, 67, 67, 67, 67, 28, 28, 28, 28, 72, 72, 78, 78, 78, 78, 78, 58, 78, 72, 72, 72, 72, 67, 67, 61, 56, 56, 56, 56, 56, 56, 89, 56, 56, 56, 56, 56, 28, 28, 28, 28, 61, 61, 61, 61, 61, 61, 61, 55, 61, 61, 61, 61, 61, 56, 61, 56, 72, 56, 72, 56, 72, 56, 72, 56, 72, 56, 72, 56, 72, 56, 72, 72, 72, 61, 67, 56, 67, 56, 67, 56, 67, 56, 67, 56, 78, 61, 78, 61, 78, 61, 78, 61, 72, 61, 72, 61, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 78, 56, 56, 28, 72, 56, 56, 61, 28, 61, 28, 61, 39, 61, 48, 61, 28, 72, 61, 72, 61, 72, 61, 71, 72, 61, 78, 61, 78, 61, 78, 61, 100, 94, 72, 39, 72, 39, 72, 39, 67, 56, 67, 56, 67, 56, 67, 56, 61, 33, 61, 48, 61, 33, 72, 61, 72, 61, 72, 61, 72, 61, 72, 61, 72, 61, 94, 78, 67, 56, 67, 61, 50, 61, 50, 61, 50, 28, 61, 84, 72, 61, 72, 61, 72, 72, 56, 72, 84, 72, 61, 61, 67, 73, 63, 61, 56, 78, 64, 93, 28, 28, 72, 56, 28, 56, 99, 72, 61, 78, 85, 71, 89, 74, 78, 61, 67, 67, 56, 60, 36, 33, 61, 33, 61, 83, 72, 80, 72, 77, 56, 61, 50, 61, 61, 53, 53, 56, 56, 50, 58, 61, 28, 50, 58, 33, 133, 122, 111, 117, 89, 56, 128, 100, 89, 72, 56, 28, 28, 78, 61, 72, 61, 72, 61, 72, 61, 72, 61, 72, 61, 56, 72, 56, 72, 56, 100, 89, 78, 61, 78, 61, 72, 56, 78, 61, 78, 61, 61, 53, 28, 133, 122, 111, 78, 61, 103, 67, 72, 61, 72, 56, 100, 89, 78, 61, 72, 56, 72, 56, 67, 56, 67, 56, 28, 28, 28, 28, 78, 61, 78, 61, 72, 39, 72, 39, 72, 61, 72, 61, 67, 56, 61, 33, 58, 52, 72, 61, 70, 84, 63, 63, 61, 50, 72, 56, 67, 56, 78, 61, 78, 61, 78, 61, 78, 61, 67, 56, 50, 83, 51, 28, 95, 95, 72, 72, 56, 61, 61, 56, 50, 65, 48, 72, 72, 67, 67, 56, 56, 28, 77, 61, 72, 39, 67, 56},
+ // "Arial|100px|700|370"
+ 0x370: []int{54, 46, 61, 58, 33, 33, 72, 63, 75, 75, 33, 56, 56, 56, 33, 56, 75, 75, 75, 75, 33, 46, 72, 33, 85, 91, 47, 75, 82, 75, 93, 84, 28, 72, 72, 60, 72, 67, 61, 72, 78, 28, 72, 67, 83, 72, 64, 78, 72, 67, 75, 60, 61, 67, 82, 67, 81, 80, 28, 67, 61, 45, 61, 28, 58, 61, 61, 56, 61, 47, 46, 61, 54, 28, 56, 56, 61, 56, 45, 61, 77, 62, 52, 68, 45, 58, 72, 58, 75, 84, 28, 58, 61, 58, 84, 72, 61, 58, 77, 100, 77, 75, 84, 68, 78, 61, 72, 56, 61, 45, 75, 53, 80, 61, 99, 89, 70, 61, 70, 60, 67, 67, 64, 60, 73, 58, 51, 44, 68, 62, 56, 28, 78, 48, 48, 67, 61, 72, 83, 74, 62, 72, 72, 72, 67, 67, 89, 57, 71, 67, 28, 28, 56, 109, 106, 88, 61, 72, 62, 72, 72, 72, 72, 57, 71, 67, 90, 63, 72, 72, 61, 70, 83, 72, 78, 72, 67, 72, 61, 62, 85, 67, 73, 70, 100, 102, 87, 98, 72, 71, 103, 72, 56, 62, 61, 42, 63, 56, 71, 50, 61, 61, 50, 64, 74, 60, 61, 60, 61, 56, 49, 56, 88, 56, 61, 58, 83, 84, 73, 85, 61, 55, 85, 58, 56, 56, 61, 42, 55, 56, 28, 28, 28, 97, 91, 61, 50, 61, 56, 60, 128, 78, 87, 70, 98, 80, 67, 56, 93, 80, 81, 75, 108, 98, 63, 50, 81, 75, 78, 61, 81, 65, 81, 65, 112, 97, 81, 64, 128, 90, 128, 78, 72, 56, 58, 0, 0, 0, 0, 0, 0, 0, 72, 61, 72, 61, 67, 61, 49, 45, 57, 42, 70, 60, 90, 71, 63, 50, 61, 50, 61, 50, 61, 50, 76, 62, 72, 60, 88, 71, 113, 92, 72, 58, 72, 56, 61, 49, 56, 56, 56, 56, 67, 56, 85, 69, 70, 58, 70, 58, 70, 61, 86, 68, 86, 68, 28, 90, 71, 70, 60, 70, 64, 72, 60, 72, 60, 70, 58, 83, 74, 28, 72, 56, 72, 56, 100, 89, 67, 56, 73, 56, 73, 56, 90, 71, 63, 50, 63, 53, 72, 61, 72, 61, 78, 61, 78, 61, 78, 61, 71, 55, 62, 56, 62, 56, 62, 56, 70, 58, 57, 42, 98, 85, 57, 42, 67, 56, 67, 56},
+ },
+ },
+ FontTypefaceCourier: {
+ FontWeightNormal: {
+ // "Courier|100px|400|0"
+ 0: []int{60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 50, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 0, 0, 0, 0, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 28, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60},
+ // "Courier|100px|400|370"
+ 0x370: []int{60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60}, // "Courier|100px|400|370"
+ },
+ FontWeightBold: {
+ // "Courier|100px|700|0"
+ 0: []int{60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 50, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 0, 0, 0, 0, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 33, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60},
+ // "Courier|100px|700|370"
+ 0x370: []int{60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 0, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60},
+ },
+ },
+ FontTypefaceVerdana: {
+ FontWeightNormal: {
+ // "Verdana|100px|400|0"
+ 0: []int{100, 100, 100, 100, 100, 100, 100, 100, 100, 35, 35, 35, 35, 35, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 35, 39, 46, 82, 64, 108, 73, 27, 45, 45, 64, 82, 36, 45, 36, 45, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 45, 45, 82, 82, 82, 55, 100, 68, 69, 70, 77, 63, 57, 78, 75, 42, 45, 69, 56, 84, 75, 79, 60, 79, 70, 68, 62, 73, 68, 99, 69, 62, 69, 45, 45, 45, 82, 64, 64, 60, 62, 52, 62, 60, 35, 62, 63, 27, 34, 59, 27, 97, 63, 61, 62, 62, 43, 52, 39, 63, 59, 82, 59, 59, 53, 63, 45, 63, 82, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 35, 39, 64, 64, 64, 64, 45, 64, 64, 100, 55, 64, 82, 0, 100, 64, 54, 82, 54, 54, 64, 64, 64, 36, 64, 54, 55, 64, 100, 100, 100, 55, 68, 68, 68, 68, 68, 68, 98, 70, 63, 63, 63, 63, 42, 42, 42, 42, 78, 75, 79, 79, 79, 79, 79, 82, 79, 73, 73, 73, 73, 62, 61, 62, 60, 60, 60, 60, 60, 60, 96, 52, 60, 60, 60, 60, 27, 27, 27, 27, 61, 63, 61, 61, 61, 61, 61, 82, 61, 63, 63, 63, 63, 59, 62, 59, 68, 60, 68, 60, 68, 60, 70, 52, 70, 52, 70, 52, 70, 52, 77, 65, 78, 62, 63, 60, 63, 60, 63, 60, 63, 60, 63, 60, 78, 62, 78, 62, 78, 62, 78, 62, 75, 63, 75, 63, 42, 27, 42, 27, 42, 27, 42, 27, 42, 27, 87, 61, 45, 34, 69, 59, 59, 56, 27, 56, 27, 56, 30, 56, 46, 56, 28, 75, 63, 75, 63, 75, 63, 73, 75, 63, 79, 61, 79, 61, 79, 61, 107, 98, 70, 43, 70, 43, 70, 43, 68, 52, 68, 52, 68, 52, 68, 52, 62, 39, 62, 39, 62, 39, 73, 63, 73, 63, 73, 63, 73, 63, 73, 63, 73, 63, 99, 82, 62, 59, 62, 69, 53, 69, 53, 69, 53, 30, 50, 76, 57, 50, 57, 50, 67, 67, 44, 72, 81, 57, 50, 47, 61, 75, 50, 56, 64, 72, 72, 77, 25, 33, 72, 50, 28, 48, 82, 72, 50, 72, 81, 61, 92, 69, 65, 50, 56, 56, 39, 58, 34, 28, 61, 28, 61, 76, 66, 74, 72, 78, 50, 61, 44, 54, 54, 44, 44, 50, 50, 44, 42, 50, 20, 28, 25, 33, 133, 117, 94, 100, 89, 56, 111, 100, 78, 132, 124, 106, 91, 142, 124, 137, 127, 72, 50, 73, 63, 137, 127, 73, 63, 44, 72, 44, 72, 44, 89, 67, 72, 50, 141, 126, 133, 123, 72, 50, 72, 50, 54, 44, 98, 133, 117, 94, 78, 62, 95, 56, 75, 63, 68, 60, 98, 96, 79, 61, 72, 44, 72, 44, 61, 44, 61, 44, 33, 28, 33, 28, 72, 50, 72, 50, 67, 33, 67, 33, 72, 50, 72, 50, 68, 52, 62, 39, 56, 40, 139, 127, 65, 50, 60, 50, 61, 44, 72, 44, 61, 44, 72, 50, 72, 50, 72, 50, 72, 50, 72, 50, 28, 50, 32, 34, 77, 77, 72, 67, 50, 61, 61, 39, 44, 53, 40, 67, 72, 73, 61, 44, 39, 28, 70, 50, 67, 33, 72, 50},
+ // "Verdana|100px|400|370"
+ 0x370: []int{55, 41, 61, 44, 33, 33, 72, 54, 100, 100, 33, 44, 44, 44, 45, 39, 100, 100, 100, 100, 64, 64, 68, 45, 75, 87, 54, 100, 88, 100, 75, 91, 27, 68, 69, 57, 70, 63, 69, 75, 79, 42, 69, 69, 84, 75, 65, 79, 75, 60, 100, 67, 62, 62, 82, 69, 87, 82, 42, 62, 62, 51, 63, 27, 63, 62, 62, 59, 61, 51, 46, 63, 62, 27, 59, 59, 64, 59, 50, 61, 64, 63, 51, 63, 50, 63, 79, 59, 82, 81, 27, 63, 61, 63, 81, 66, 51, 50, 72, 89, 72, 53, 71, 56, 72, 50, 67, 42, 56, 45, 58, 45, 73, 55, 83, 78, 62, 52, 66, 44, 54, 54, 66, 58, 70, 51, 48, 39, 56, 51, 44, 28, 72, 40, 40, 56, 50, 67, 89, 63, 50, 67, 67, 67, 63, 63, 79, 57, 70, 68, 42, 42, 45, 112, 110, 82, 69, 75, 62, 75, 68, 69, 69, 57, 75, 63, 97, 62, 75, 75, 69, 73, 84, 75, 79, 75, 60, 70, 62, 62, 82, 69, 76, 71, 103, 104, 78, 92, 68, 70, 103, 71, 60, 61, 59, 47, 62, 60, 80, 52, 64, 64, 59, 62, 70, 64, 61, 64, 62, 53, 50, 59, 84, 59, 64, 61, 88, 89, 64, 79, 57, 55, 84, 60, 60, 60, 63, 47, 55, 52, 27, 27, 34, 91, 91, 63, 59, 64, 59, 64, 117, 63, 67, 54, 97, 68, 72, 59, 103, 83, 90, 69, 121, 94, 50, 40, 74, 63, 72, 50, 81, 59, 81, 59, 119, 104, 76, 57, 98, 81, 117, 63, 67, 44, 33, 0, 0, 0, 0, 64, 0, 0, 72, 54, 57, 47, 56, 50, 57, 47, 57, 47, 63, 52, 97, 80, 50, 40, 69, 59, 69, 59, 67, 49, 79, 57, 75, 64, 85, 62, 103, 79, 79, 62, 67, 44, 62, 50, 62, 59, 62, 59, 69, 59, 91, 75, 65, 50, 71, 61, 71, 63, 88, 69, 88, 69, 33, 161, 143, 67, 52, 68, 50, 72, 54, 72, 54, 65, 50, 89, 63, 28, 132, 124, 132, 124, 89, 67, 127, 123, 75, 60, 139, 123, 161, 143, 125, 116, 50, 44, 72, 54, 139, 128, 142, 124, 79, 61, 142, 124, 134, 118, 71, 50, 125, 123, 125, 123, 135, 124, 57, 47, 156, 143, 58, 41, 72, 50, 72, 50},
+ },
+ FontWeightBold: {
+ // "Verdana|100px|700|0"
+ 0: []int{100, 100, 100, 100, 100, 100, 100, 100, 100, 34, 34, 34, 34, 34, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 34, 40, 59, 87, 71, 127, 86, 33, 54, 54, 71, 87, 36, 48, 36, 69, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 40, 40, 87, 87, 87, 62, 96, 78, 76, 72, 83, 68, 65, 81, 84, 55, 56, 77, 64, 95, 85, 85, 73, 85, 78, 71, 68, 81, 76, 113, 76, 74, 69, 54, 69, 54, 87, 71, 71, 67, 70, 59, 70, 66, 42, 70, 71, 34, 40, 67, 34, 106, 71, 69, 70, 70, 50, 59, 46, 71, 65, 98, 67, 65, 60, 71, 54, 71, 87, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 34, 40, 71, 71, 71, 71, 54, 71, 71, 96, 60, 85, 87, 0, 96, 71, 59, 87, 60, 60, 71, 72, 71, 36, 71, 60, 60, 85, 118, 118, 118, 62, 78, 78, 78, 78, 78, 78, 109, 72, 68, 68, 68, 68, 55, 55, 55, 55, 83, 85, 85, 85, 85, 85, 85, 87, 85, 81, 81, 81, 81, 74, 73, 71, 67, 67, 67, 67, 67, 67, 102, 59, 66, 66, 66, 66, 34, 34, 34, 34, 68, 71, 69, 69, 69, 69, 69, 87, 69, 71, 71, 71, 71, 65, 70, 65, 78, 67, 78, 67, 78, 67, 72, 59, 72, 59, 72, 59, 72, 59, 83, 88, 83, 70, 68, 66, 68, 66, 68, 66, 68, 66, 68, 66, 81, 70, 81, 70, 81, 70, 81, 70, 84, 71, 84, 71, 55, 34, 55, 34, 55, 34, 55, 34, 55, 34, 101, 73, 56, 40, 77, 67, 67, 64, 34, 64, 34, 64, 52, 64, 56, 64, 35, 85, 71, 85, 71, 85, 71, 83, 85, 71, 85, 69, 85, 69, 85, 69, 114, 107, 78, 50, 78, 50, 78, 50, 71, 59, 71, 59, 71, 59, 71, 59, 68, 46, 68, 47, 68, 46, 81, 71, 81, 71, 81, 71, 81, 71, 81, 71, 81, 71, 113, 98, 74, 65, 74, 69, 60, 69, 60, 69, 60, 34, 56, 75, 66, 56, 66, 56, 72, 72, 44, 72, 82, 66, 56, 52, 67, 81, 53, 61, 71, 78, 72, 78, 31, 39, 78, 56, 28, 49, 94, 72, 56, 78, 91, 69, 106, 77, 69, 56, 61, 56, 39, 65, 51, 33, 67, 33, 67, 85, 74, 80, 65, 79, 50, 67, 44, 58, 58, 46, 45, 50, 50, 38, 33, 60, 22, 32, 28, 33, 139, 117, 100, 117, 100, 61, 122, 106, 89, 149, 138, 126, 105, 156, 140, 152, 142, 72, 56, 81, 71, 152, 142, 81, 71, 44, 72, 50, 72, 50, 100, 72, 78, 50, 152, 141, 148, 138, 78, 50, 78, 50, 58, 46, 111, 139, 117, 100, 81, 70, 97, 65, 85, 71, 78, 67, 109, 102, 85, 69, 72, 50, 72, 50, 67, 44, 67, 44, 39, 28, 39, 28, 78, 50, 78, 50, 72, 44, 72, 44, 72, 56, 72, 56, 71, 59, 68, 46, 55, 37, 155, 142, 73, 56, 68, 50, 67, 44, 72, 50, 67, 44, 78, 50, 78, 50, 78, 50, 78, 50, 72, 50, 28, 56, 36, 40, 82, 82, 72, 72, 50, 67, 67, 39, 44, 53, 47, 67, 72, 72, 67, 44, 50, 33, 82, 56, 72, 44, 72, 50},
+ // "Verdana|100px|700|370"
+ 0x370: []int{61, 48, 70, 52, 33, 33, 78, 58, 100, 100, 33, 44, 44, 44, 40, 50, 100, 100, 100, 100, 71, 71, 80, 40, 85, 100, 71, 100, 97, 100, 94, 97, 34, 78, 76, 64, 81, 68, 69, 84, 85, 55, 77, 78, 95, 85, 71, 85, 84, 73, 100, 68, 68, 74, 95, 76, 98, 84, 55, 74, 70, 58, 71, 34, 71, 70, 72, 65, 69, 58, 55, 71, 70, 34, 67, 65, 72, 65, 58, 69, 72, 70, 56, 73, 54, 71, 91, 64, 94, 89, 34, 71, 69, 71, 89, 78, 53, 60, 72, 92, 72, 62, 71, 58, 78, 50, 72, 42, 61, 55, 60, 46, 75, 55, 87, 83, 73, 59, 74, 44, 63, 59, 68, 61, 72, 52, 54, 45, 58, 50, 44, 33, 78, 42, 42, 61, 56, 72, 94, 68, 54, 72, 72, 72, 68, 68, 91, 64, 74, 71, 55, 55, 56, 122, 121, 94, 77, 85, 74, 84, 78, 76, 76, 64, 84, 68, 112, 71, 85, 85, 77, 85, 95, 84, 85, 84, 73, 72, 68, 74, 95, 76, 85, 79, 116, 118, 91, 106, 76, 74, 120, 79, 67, 70, 68, 53, 69, 66, 100, 59, 72, 72, 67, 71, 83, 72, 69, 72, 70, 60, 54, 65, 97, 67, 73, 68, 100, 101, 74, 94, 65, 61, 99, 68, 66, 66, 71, 53, 61, 59, 34, 34, 40, 101, 102, 71, 67, 72, 65, 72, 125, 62, 76, 60, 101, 66, 72, 50, 110, 77, 99, 73, 136, 100, 53, 40, 78, 69, 78, 50, 82, 60, 82, 60, 135, 123, 82, 59, 105, 90, 125, 62, 72, 44, 34, 0, 0, 0, 0, 64, 0, 0, 78, 58, 66, 53, 61, 56, 64, 53, 64, 53, 72, 58, 112, 100, 53, 40, 77, 67, 77, 67, 73, 58, 83, 63, 84, 72, 92, 67, 111, 84, 87, 69, 72, 44, 68, 54, 74, 65, 74, 65, 76, 67, 99, 82, 73, 56, 79, 68, 79, 71, 101, 79, 101, 79, 39, 183, 171, 74, 59, 75, 56, 78, 58, 78, 58, 73, 56, 94, 68, 28, 149, 138, 149, 138, 100, 72, 139, 138, 81, 66, 152, 138, 183, 171, 142, 130, 53, 46, 78, 58, 156, 143, 156, 140, 85, 69, 156, 140, 145, 132, 73, 50, 145, 136, 145, 136, 150, 140, 64, 53, 177, 165, 64, 45, 72, 50, 72, 50},
+ },
+ },
+}
+
+const FallbackAsciiCode int = 88 // 'X'
+
+func getCharacterWidth100(cCode int, typeface FontTypeface, weight FontWeight) float64 {
+ if weightMap, ok := sizeMap[typeface]; ok {
+ if rangeMap, ok := weightMap[weight]; ok {
+ if cCode < len(rangeMap[0]) {
+ return float64(rangeMap[0][cCode])
+ } else if cCode >= 0x370 && cCode < 0x370+len(rangeMap[0x370]) {
+ return float64(rangeMap[0x370][cCode])
+ }
+ return float64(rangeMap[0][FallbackAsciiCode])
+ }
+ }
+
+ return float64(sizeMap[FontTypefaceCourier][FontWeightNormal][0][FallbackAsciiCode])
+}
+
+/*
+func getTextRunWidth(s string, typeface FontTypeface, weight FontWeight, fontSizeInPixels float64) float64 {
+ w := 0.0
+ for _, c := range s {
+ w += float64(getCharacterWidth100(int(c), typeface, weight)*fontSizeInPixels) / 100.0
+ }
+ return w
+}
+
+func getTextDimensions(s string, typeface FontTypeface, weight FontWeight, fontSizeInPixels float64) (float64, float64) {
+ runs := strings.Split(s, "\n")
+ w := 0.0
+ h := 0.0
+ for _, r := range runs {
+ h += float64(fontSizeInPixels)
+ runWidth := getTextRunWidth(r, typeface, weight, fontSizeInPixels)
+ if w < runWidth {
+ w = runWidth
+ }
+ }
+ return w, h
+}
+*/
+
+func getTextRunWidth(s string, startIdx int, endIdx int, typeface FontTypeface, weight FontWeight, fontSizeInPixels float64) float64 {
+ w := 0.0
+ idx := startIdx
+ for idx < endIdx {
+ w += float64(getCharacterWidth100(int(s[idx]), typeface, weight)*fontSizeInPixels) / 100.0
+ idx++
+ }
+ return w
+}
+
+func getTextDimensions(s string, typeface FontTypeface, weight FontWeight, fontSizeInPixels float64, fontInterval float64) (float64, float64) {
+ w := 0.0
+ h := 0.0
+ runStartIdx := -1
+ lineCount := 0
+ for curIdx, c := range s {
+ if c == '\n' {
+ if runStartIdx > -1 && curIdx-runStartIdx > 1 {
+ // Calculate run width for: from runStartIdx to curIdx-1 including
+ runWidth := getTextRunWidth(s, runStartIdx, curIdx, typeface, weight, fontSizeInPixels)
+ if runWidth > w {
+ w = runWidth
+ }
+ }
+ lineCount++
+ h += float64(fontSizeInPixels)
+ runStartIdx = -1
+ } else {
+ if runStartIdx == -1 {
+ runStartIdx = curIdx
+ }
+ }
+ }
+
+ if runStartIdx > -1 && len(s)-runStartIdx >= 1 {
+ runWidth := getTextRunWidth(s, runStartIdx, len(s), typeface, weight, fontSizeInPixels)
+ if runWidth > w {
+ w = runWidth
+ }
+ }
+ if w > 0.0 {
+ lineCount++
+ h += float64(fontSizeInPixels)
+ } else {
+ h = 0.0
+ }
+
+ if h > 0 {
+ h += float64(lineCount-1) * fontSizeInPixels * fontInterval
+ }
+ return w, h
+}
+
+func getLabelDimensionsFromTextDimensions(w float64, h float64, textMargin float64) (float64, float64) {
+ labelWidth := 0.0
+ if w > 0.0 {
+ labelWidth = w + textMargin*2 // left + right
+ }
+ labelHeight := h
+ if labelHeight > 0.0 {
+ labelHeight = h + textMargin*2 // top + bottom
+ }
+ return labelWidth, labelHeight
+}
diff --git a/pkg/capigraph/fonts_test.go b/pkg/capigraph/fonts_test.go
new file mode 100644
index 00000000..9b6b46d7
--- /dev/null
+++ b/pkg/capigraph/fonts_test.go
@@ -0,0 +1,33 @@
+package capigraph
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestTextDimension(t *testing.T) {
+ var w, h float64
+ w, h = getTextDimensions("", FontTypefaceArial, FontWeightNormal, 20, 0.0)
+ assert.Equal(t, 0.0, w)
+ assert.Equal(t, 0.0, h)
+ w, h = getTextDimensions("A", FontTypefaceArial, FontWeightNormal, 20, 0.0)
+ assert.Equal(t, 13.4, w)
+ assert.Equal(t, 20.0, h)
+ w, h = getTextDimensions("\nA", FontTypefaceArial, FontWeightNormal, 20, 0.0)
+ assert.Equal(t, 13.4, w)
+ assert.Equal(t, 40.0, h)
+ w, h = getTextDimensions("\n", FontTypefaceArial, FontWeightNormal, 20, 0.0)
+ assert.Equal(t, 0.0, w)
+ assert.Equal(t, 0.0, h)
+}
+
+func TestRunWidth(t *testing.T) {
+ var w float64
+ w = getTextRunWidth("-A-------", 1, 2, FontTypefaceArial, FontWeightNormal, 20)
+ assert.Equal(t, 13.4, w)
+ w = getTextRunWidth("-AB------", 1, 3, FontTypefaceArial, FontWeightNormal, 20)
+ assert.Equal(t, 26.8, w)
+ w = getTextRunWidth("-\r-------", 1, 2, FontTypefaceArial, FontWeightNormal, 20)
+ assert.Equal(t, 5.6, w)
+}
diff --git a/pkg/capigraph/layer_mx.go b/pkg/capigraph/layer_mx.go
new file mode 100644
index 00000000..9695e7fe
--- /dev/null
+++ b/pkg/capigraph/layer_mx.go
@@ -0,0 +1,151 @@
+package capigraph
+
+import (
+ "fmt"
+ "strings"
+)
+
+const FakeNodeBase int16 = 10000
+
+func sanitizeFakeNodeId(nodeId int16) int16 {
+ sanitizedNodeId := nodeId
+ if nodeId > FakeNodeBase {
+ sanitizedNodeId -= FakeNodeBase
+ }
+ return sanitizedNodeId
+}
+
+type LayerMx [][]int16
+
+func NewLayerMx(nodeDefs []NodeDef, nodeLayerMap []int, rootNodes []int16) (LayerMx, error) {
+ maxLayer := 0
+ for i := range nodeDefs[1:] {
+ nodeId := int16(i + 1)
+ nodeLayer := nodeLayerMap[nodeId]
+ if nodeLayer > maxLayer {
+ maxLayer = nodeLayer
+ }
+ }
+
+ layerMx := make(LayerMx, maxLayer+1)
+
+ for layerIdx := range maxLayer + 1 {
+ layerMx[layerIdx] = make([]int16, 0, MaxLayerLen)
+ }
+
+ // This will populate mx with a sample valid ids. As we overwrite top layers, bottom layers will become invalid.
+ // This is why we have a similar piece of code in mxIterRecursive. Let's keep this code though:
+ // it inserts fake nodes and calculates each layer size nicely
+ priChildrenMap := buildPriChildrenMap(nodeDefs)
+ for _, rootNodeId := range rootNodes {
+ layerMx.addNodeRecursively(rootNodeId, nodeLayerMap, priChildrenMap)
+ }
+
+ for layerIdx := range len(layerMx) {
+ if len(layerMx[layerIdx]) > MaxLayerLen {
+ return nil, fmt.Errorf("cannot create NewLayerMx, too many nodes in row %d: %d; max allowed is %d", layerIdx, len(layerMx[layerIdx]), MaxLayerLen)
+ }
+ }
+
+ return layerMx, nil
+}
+
+func (mx LayerMx) String() string {
+ sb := strings.Builder{}
+ for i, row := range mx {
+ if i != 0 {
+ sb.WriteString(", ")
+ }
+ sb.WriteString(fmt.Sprintf("%d:%v", i, row))
+ }
+ return sb.String()
+}
+
+func (mx LayerMx) clone() LayerMx {
+ newMx := make(LayerMx, len(mx))
+ for i, row := range mx {
+ newMx[i] = make([]int16, len(row))
+ copy(newMx[i], row)
+ }
+ return newMx
+}
+
+func halfByteToChar(b int8) rune {
+ if b < 10 {
+ return rune(int('0') + int(b))
+ }
+ return rune(int('A') + int(b) - 10)
+}
+
+func (mx LayerMx) signature() string {
+ sb := strings.Builder{}
+ for _, row := range mx {
+ rowRunes := make([]rune, len(row)*4)
+ for i, id := range row {
+ rowRunes[i*4] = halfByteToChar(int8(id >> 12))
+ rowRunes[i*4+1] = halfByteToChar(int8((id >> 8) & 0x0F))
+ rowRunes[i*4+2] = halfByteToChar(int8((id >> 4) & 0x0F))
+ rowRunes[i*4+3] = halfByteToChar(int8(id & 0x0F))
+ }
+ sb.WriteString(string(rowRunes))
+ }
+ return sb.String()
+}
+func (mx LayerMx) addNodeRecursively(curNodeId int16, nodeLayerMap []int, priChildrenMap [][]int16) {
+ curLayerIdx := nodeLayerMap[curNodeId]
+ mx[curLayerIdx] = append(mx[curLayerIdx], curNodeId)
+
+ for _, childId := range priChildrenMap[curNodeId] {
+ childLayerIdx := nodeLayerMap[childId]
+ if childLayerIdx > curLayerIdx+1 {
+ // Insert fake nodes when pri edge len > 1
+ interLayerIdx := curLayerIdx + 1
+ for interLayerIdx < childLayerIdx {
+ mx[interLayerIdx] = append(mx[interLayerIdx], childId+FakeNodeBase)
+ interLayerIdx++
+ }
+ }
+ mx.addNodeRecursively(childId, nodeLayerMap, priChildrenMap)
+ }
+}
+
+// TODO: this function is unused, although works nicely. Remove later.
+func (mx LayerMx) isMonotonous(priParentMap []int16, totalNodes int) bool {
+ edgeStarts := make([]int16, MaxLayerLen)
+ edgeStartIndexes := make([]int, MaxLayerLen)
+ prevLayerNodeIdxMap := make([]int, totalNodes)
+
+ for layerIdx := range len(mx) {
+ if layerIdx > 0 {
+ edgeStarts := edgeStarts[:len(mx[layerIdx])]
+ for j, edgeEndNodeId := range mx[layerIdx] {
+ edgeStarts[j] = priParentMap[sanitizeFakeNodeId(edgeEndNodeId)]
+ }
+
+ edgeStartIndexes := edgeStartIndexes[:len(mx[layerIdx])]
+ for j, edgeStartIdx := range edgeStarts {
+ if edgeStartIdx == -1 {
+ // Simulate monotonous
+ if j == 0 {
+ edgeStartIndexes[j] = 0
+ } else {
+ edgeStartIndexes[j] = edgeStartIndexes[j-1]
+ }
+ } else {
+ edgeStartIndexes[j] = prevLayerNodeIdxMap[sanitizeFakeNodeId(edgeStartIdx)]
+ }
+ }
+
+ // Verify it's growing monotonously
+ for j := range len(edgeStartIndexes) - 1 {
+ if edgeStartIndexes[j] > edgeStartIndexes[j+1] {
+ return false
+ }
+ }
+ }
+ for idx, nodeId := range mx[layerIdx] {
+ prevLayerNodeIdxMap[sanitizeFakeNodeId(nodeId)] = idx
+ }
+ }
+ return true
+}
diff --git a/pkg/capigraph/layer_mx_permutator.go b/pkg/capigraph/layer_mx_permutator.go
new file mode 100644
index 00000000..c4aae896
--- /dev/null
+++ b/pkg/capigraph/layer_mx_permutator.go
@@ -0,0 +1,195 @@
+package capigraph
+
+import "fmt"
+
+const MaxAllowedLayerPermutations int64 = 100000000
+
+type LayerMxPermIterator struct {
+ Lps []*LayerPermutator // We need separate permutator for each layer to preserve state when we do recursion between layers
+ IntervalStarts [][]int
+ IntervalLengths [][]int
+ SrcMx LayerMx
+ WorkMx LayerMx
+ PriParentMap []int16
+ PriChildrenMap [][]int16
+ NodeLayerMap []int
+ RootNodes []int16
+}
+
+func NewLayerMxPermIterator(nodeDefs []NodeDef, srcMx LayerMx) (*LayerMxPermIterator, error) {
+ mxi := LayerMxPermIterator{}
+ mxi.Lps = make([]*LayerPermutator, len(srcMx))
+ mxi.IntervalStarts = make([][]int, len(srcMx))
+ mxi.IntervalLengths = make([][]int, len(srcMx))
+ for i := range len(mxi.Lps) {
+ mxi.Lps[i] = NewLayerPermutator()
+ mxi.IntervalStarts[i] = make([]int, MaxIntervalsInLayer)
+ mxi.IntervalLengths[i] = make([]int, MaxIntervalsInLayer)
+ }
+
+ mxi.SrcMx = srcMx
+ mxi.WorkMx = srcMx.clone()
+ mxi.PriParentMap = buildPriParentMap(nodeDefs)
+ mxi.PriChildrenMap = buildPriChildrenMap(nodeDefs)
+ mxi.NodeLayerMap = buildLayerMap(nodeDefs)
+ mxi.RootNodes = buildRootNodeList(mxi.PriParentMap)
+
+ permutations := mxi.MxIteratorCount()
+ if permutations > MaxAllowedLayerPermutations {
+ return nil, fmt.Errorf("cannot create LayerMxPermIterator, too many permutations, only %d : %d", MaxAllowedLayerPermutations, permutations)
+ }
+ return &mxi, nil
+}
+
+func harvestRowPermutationSettings(mxi *LayerMxPermIterator, layerIdx int) (int, int, int) {
+ var lastParent int16
+ var lastParentFirstIdx int
+ totalIntervals := 0
+ var insertStart, insertLen int
+ for i, curNodeId := range mxi.WorkMx[layerIdx] {
+ var curParent int16
+ if curNodeId > FakeNodeBase {
+ curParent = mxi.PriParentMap[curNodeId-FakeNodeBase]
+ } else {
+ curParent = mxi.PriParentMap[curNodeId]
+ }
+ if curParent == MissingNodeId {
+ // This is a root
+ // Wrap up an interval for lastParent
+ if i-lastParentFirstIdx > 1 {
+ // Safeguard. Unlikely, but possible
+ if totalIntervals < len(mxi.IntervalStarts[layerIdx]) {
+ mxi.IntervalStarts[layerIdx][totalIntervals] = lastParentFirstIdx
+ mxi.IntervalLengths[layerIdx][totalIntervals] = i - lastParentFirstIdx
+ totalIntervals++
+ }
+ }
+ lastParent = curParent
+ lastParentFirstIdx = i
+
+ if insertLen == 0 {
+ insertStart = i
+ insertLen = 1
+ } else {
+ insertLen++
+ }
+ } else {
+ // This is not a root
+
+ // Wrap up the interval, else - continuing current interval
+ if curParent != lastParent {
+ // Wrap up an interval for lastParent
+ if i-lastParentFirstIdx > 2 {
+ // Safeguard. Unlikely, but possible
+ if totalIntervals < len(mxi.IntervalStarts[layerIdx]) {
+ mxi.IntervalStarts[layerIdx][totalIntervals] = lastParentFirstIdx
+ mxi.IntervalLengths[layerIdx][totalIntervals] = i - lastParentFirstIdx
+ totalIntervals++
+ }
+ }
+ lastParent = curParent
+ lastParentFirstIdx = i
+ }
+ }
+
+ if i == len(mxi.WorkMx[layerIdx])-1 {
+ // If the last id is not root and it's the ending of an interval - wrap it up here
+ if curParent != MissingNodeId && i > lastParentFirstIdx {
+ // Safeguard. Unlikely, but possible
+ if totalIntervals < len(mxi.IntervalStarts[layerIdx]) {
+ mxi.IntervalStarts[layerIdx][totalIntervals] = lastParentFirstIdx
+ mxi.IntervalLengths[layerIdx][totalIntervals] = i - lastParentFirstIdx + 1
+ totalIntervals++
+ }
+ }
+ }
+ }
+ return totalIntervals, insertStart, insertLen
+}
+
+func (mxi *LayerMxPermIterator) MxIterator(f func(int, LayerMx)) {
+ mxIterRecursive(mxi, 0, 0, f)
+}
+
+func mxIterRecursive(mxi *LayerMxPermIterator, layerIdx int, totalCnt int, f func(totalCnt int, perm LayerMx)) {
+ cbInner := func(int, LayerMx) {
+ f(totalCnt, mxi.WorkMx)
+ totalCnt++
+ }
+ cb := func(int, []int16) {
+ mxi.WorkMx[layerIdx] = mxi.Lps[layerIdx].WorkPerm
+ mxIterRecursive(mxi, layerIdx+1, totalCnt, cbInner)
+ }
+ if layerIdx == len(mxi.SrcMx) {
+ f(totalCnt, mxi.WorkMx)
+ totalCnt++
+ return
+ }
+
+ newNodeIdx := 0
+ if layerIdx > 0 {
+ for _, nodeId := range mxi.WorkMx[layerIdx-1] {
+ if nodeId > FakeNodeBase {
+ // Fake parent node, has only one child. Decide either this (layerIdx) child is fake or not.
+ childLayer := mxi.NodeLayerMap[nodeId-FakeNodeBase]
+ if childLayer == layerIdx {
+ mxi.WorkMx[layerIdx][newNodeIdx] = nodeId - FakeNodeBase // This is the true node
+ } else {
+ mxi.WorkMx[layerIdx][newNodeIdx] = nodeId // Keep faking, keep in mind that nodeId will have children that reference same Def.NodeId
+ }
+ newNodeIdx++
+ } else {
+ // Normal (non-fake) parent
+ children := mxi.PriChildrenMap[nodeId]
+ for _, childId := range children {
+ childLayer := mxi.NodeLayerMap[childId]
+ if childLayer > layerIdx {
+ // Add another fake node until childLayer == layerIdx
+ mxi.WorkMx[layerIdx][newNodeIdx] = childId + FakeNodeBase
+ } else {
+ // Add the real node, childLayer == layerIdx
+ mxi.WorkMx[layerIdx][newNodeIdx] = childId
+ }
+ newNodeIdx++
+ }
+ }
+ }
+ }
+
+ // Add new roots
+ for _, rootId := range mxi.RootNodes {
+ if mxi.NodeLayerMap[rootId] == layerIdx {
+ mxi.WorkMx[layerIdx][newNodeIdx] = rootId
+ newNodeIdx++
+ }
+ }
+
+ totalIntervals, insertStart, insertLen := harvestRowPermutationSettings(mxi, layerIdx)
+ if totalIntervals > 0 && insertLen > 0 && insertStart >= 0 {
+ mxi.Lps[layerIdx].SwapAndInsertIterator(mxi.IntervalStarts[layerIdx], mxi.IntervalLengths[layerIdx], totalIntervals, insertStart, insertLen, mxi.WorkMx[layerIdx], cb)
+ } else if totalIntervals > 0 {
+ mxi.Lps[layerIdx].SwapIterator(mxi.IntervalStarts[layerIdx], mxi.IntervalLengths[layerIdx], totalIntervals, mxi.WorkMx[layerIdx], cb)
+ } else if insertLen > 0 {
+ mxi.Lps[layerIdx].InsertIterator(insertStart, insertLen, mxi.WorkMx[layerIdx], cb)
+ } else {
+ // No permutations available, just re-use mxi.WorkMx[layerIdx] without modifications
+ // Here, we want to call cb, but without mxi.WorkMx[layerIdx] = mxi.Lps[layerIdx].WorkPerm
+ mxIterRecursive(mxi, layerIdx+1, totalCnt, cbInner)
+ }
+}
+
+func (mxi *LayerMxPermIterator) MxIteratorCount() int64 {
+ acc := int64(1)
+ for layerIdx := range len(mxi.SrcMx) {
+ totalIntervals, insertStart, insertLen := harvestRowPermutationSettings(mxi, layerIdx)
+ for i := range totalIntervals {
+ intervalLen := mxi.IntervalLengths[layerIdx][i]
+ acc *= int64(mxi.Lps[layerIdx].Fact[intervalLen])
+ }
+ for range insertLen {
+ acc *= int64(insertStart + 1)
+ insertStart++
+ }
+ }
+ return acc
+}
diff --git a/pkg/capigraph/layer_mx_permutator_test.go b/pkg/capigraph/layer_mx_permutator_test.go
new file mode 100644
index 00000000..20ec4935
--- /dev/null
+++ b/pkg/capigraph/layer_mx_permutator_test.go
@@ -0,0 +1,313 @@
+package capigraph
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Common MxPermutator and SVG tests
+
+func helperAll(t *testing.T,
+ nodeDefs []NodeDef,
+ expectedLayerMap string,
+ expectedStartMx string,
+ expectedIterCount int64,
+ expectedPermMxs string,
+ expectedHierarchyFirst string,
+ expectedHierarchyLast string) {
+ priParentMap := buildPriParentMap(nodeDefs)
+ layerMap := buildLayerMap(nodeDefs)
+ assert.Equal(t, expectedLayerMap, fmt.Sprintf("%v", layerMap))
+
+ rootNodes := buildRootNodeList(priParentMap)
+ mx, _ := NewLayerMx(nodeDefs, layerMap, rootNodes)
+ assert.Equal(t, expectedStartMx, mx.String())
+
+ mxi, _ := NewLayerMxPermIterator(nodeDefs, mx)
+ vnh := NewVizNodeHierarchy(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions())
+
+ vnh.buildNewRootSubtreeHierarchy(mx)
+
+ sbPerms := strings.Builder{}
+ var hierarchyFirst, hierarchyLast string
+ mxi.MxIterator(func(i int, mxPerm LayerMx) {
+ if sbPerms.Len() != 0 {
+ sbPerms.WriteString(", ")
+ }
+ sbPerms.WriteString(fmt.Sprintf("{p%d: {%s}}", i, mxPerm.String()))
+ vnh.reuseRootSubtreeHierarchy(mxPerm)
+ vnh.PopulateNodeTotalWidth()
+ vnh.PopulateNodesXCoords()
+ vnh.PopulateEdgeLabelDimensions()
+ vnh.PopulateUpperLayerGapMap(DefaultEdgeLabelFontOptions().SizeInPixels)
+ vnh.PopulateNodesYCoords()
+ hierarchyString := vnh.String()
+ if hierarchyFirst == "" {
+ hierarchyFirst = hierarchyString
+ }
+ hierarchyLast = hierarchyString
+ })
+
+ assert.Equal(t, expectedIterCount, mxi.MxIteratorCount())
+ assert.Equal(t, expectedPermMxs, sbPerms.String())
+ assert.Equal(t, expectedHierarchyFirst, hierarchyFirst)
+ assert.Equal(t, expectedHierarchyLast, hierarchyLast)
+}
+
+func helperIteratorAndIncrementalCount(t *testing.T,
+ nodeDefs []NodeDef,
+ expectedLayerMap string,
+ expectedStartMx string,
+ expectedIterCount int64,
+ expectedIncrementalCount int64) {
+ priParentMap := buildPriParentMap(nodeDefs)
+ layerMap := buildLayerMap(nodeDefs)
+ assert.Equal(t, expectedLayerMap, fmt.Sprintf("%v", layerMap))
+
+ rootNodes := buildRootNodeList(priParentMap)
+ mx, _ := NewLayerMx(nodeDefs, layerMap, rootNodes)
+ assert.Equal(t, expectedStartMx, mx.String())
+
+ mxi, _ := NewLayerMxPermIterator(nodeDefs, mx)
+
+ cnt := int64(0)
+ mxi.MxIterator(func(_ int, _ LayerMx) {
+ cnt++
+ })
+
+ assert.Equal(t, expectedIterCount, mxi.MxIteratorCount())
+ assert.Equal(t, expectedIncrementalCount, cnt)
+}
+
+func TestBasicMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsBasic,
+ "[-4 0 1 0]",
+ "0:[1 3], 1:[2]",
+ int64(2),
+ "{p0: {0:[1 3], 1:[2]}}, {p1: {0:[3 1], 1:[2]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:3 X:0.00 Y:0.00 W:69.12 H:36.00}] PriRootOut:[]}, {Id:3 RootId:3 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:3 X:0.00 Y:0.00 W:69.12 H:36.00}] PriRootOut:[]}, {Id:3 RootId:3 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ )
+}
+
+func TestTrivialParallelMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsTrivialParallel,
+ "[-4 0 1 0 1]",
+ "0:[1 3], 1:[2 4]",
+ int64(2),
+ "{p0: {0:[1 3], 1:[2 4]}}, {p1: {0:[3 1], 1:[4 2]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:3 RootId:3 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[4]}, {Id:4 RootId:3 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:3 RootId:3 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[4]}, {Id:4 RootId:3 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestOneEnclosingOneLevelMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsOneEnclosedOneLevel,
+ "[-4 0 1 1 2 2 1]",
+ "0:[1], 1:[2 3 6], 2:[4 5]",
+ int64(6),
+ "{p0: {0:[1], 1:[2 3 6], 2:[4 5]}}, {p1: {0:[1], 1:[2 6 3], 2:[4 5]}}, {p2: {0:[1], 1:[6 2 3], 2:[4 5]}}, {p3: {0:[1], 1:[3 2 6], 2:[5 4]}}, {p4: {0:[1], 1:[3 6 2], 2:[5 4]}}, {p5: {0:[1], 1:[6 3 2], 2:[5 4]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 3]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:3 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:4 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:144.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[3 2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:3 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:4 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:144.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}")
+}
+
+func TestOneEnclosedTwoLevelsMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsOneEnclosedTwoLevels,
+ "[-4 0 1 2 3 1 2 3 2]",
+ "0:[1], 1:[2 5], 2:[3 6 8], 3:[4 7]",
+ int64(6),
+ "{p0: {0:[1], 1:[2 5], 2:[3 6 8], 3:[4 7]}}, {p1: {0:[1], 1:[2 5], 2:[3 8 6], 3:[4 7]}}, {p2: {0:[1], 1:[2 5], 2:[8 3 6], 3:[4 7]}}, {p3: {0:[1], 1:[5 2], 2:[6 3 8], 3:[7 4]}}, {p4: {0:[1], 1:[5 2], 2:[6 8 3], 3:[7 4]}}, {p5: {0:[1], 1:[5 2], 2:[8 6 3], 3:[7 4]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 5]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6]}, {Id:6 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:8 RootId:8 Layer:2 X:144.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[5 2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6]}, {Id:6 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:8 RootId:8 Layer:2 X:144.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}")
+}
+
+func TestNoIntervalsMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsNoIntervals,
+ "[-4 0 1 2 1 2]",
+ "0:[1], 1:[2 4], 2:[3 5]",
+ int64(2),
+ "{p0: {0:[1], 1:[2 4], 2:[3 5]}}, {p1: {0:[1], 1:[4 2], 2:[5 3]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 4]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:4 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[4 2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:4 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestFlat10MxPermutator(t *testing.T) {
+ helperIteratorAndIncrementalCount(t,
+ testNodeDefsFlat10,
+ "[-4 0 0 0 0 0 0 0 0 0 0]",
+ "0:[1 2 3 4 5 6 7 8 9 10]",
+ int64(3628800),
+ int64(3628800))
+}
+
+func TestTwoEnclosingTwoLevelsNodeSizeMattersMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsTwoEnclosedNodeSizeMatters,
+ "[-4 0 1 1 2 2 3 3 2 2]",
+ "0:[1], 1:[2 3], 2:[4 5 8 9], 3:[6 7]",
+ int64(24),
+ "{p0: {0:[1], 1:[2 3], 2:[4 5 8 9], 3:[6 7]}}, {p1: {0:[1], 1:[2 3], 2:[4 5 9 8], 3:[6 7]}}, {p2: {0:[1], 1:[2 3], 2:[4 9 5 8], 3:[6 7]}}, {p3: {0:[1], 1:[2 3], 2:[9 4 5 8], 3:[6 7]}}, {p4: {0:[1], 1:[2 3], 2:[4 8 5 9], 3:[6 7]}}, {p5: {0:[1], 1:[2 3], 2:[4 8 9 5], 3:[6 7]}}, {p6: {0:[1], 1:[2 3], 2:[4 9 8 5], 3:[6 7]}}, {p7: {0:[1], 1:[2 3], 2:[9 4 8 5], 3:[6 7]}}, {p8: {0:[1], 1:[2 3], 2:[8 4 5 9], 3:[6 7]}}, {p9: {0:[1], 1:[2 3], 2:[8 4 9 5], 3:[6 7]}}, {p10: {0:[1], 1:[2 3], 2:[8 9 4 5], 3:[6 7]}}, {p11: {0:[1], 1:[2 3], 2:[9 8 4 5], 3:[6 7]}}, {p12: {0:[1], 1:[3 2], 2:[5 4 8 9], 3:[7 6]}}, {p13: {0:[1], 1:[3 2], 2:[5 4 9 8], 3:[7 6]}}, {p14: {0:[1], 1:[3 2], 2:[5 9 4 8], 3:[7 6]}}, {p15: {0:[1], 1:[3 2], 2:[9 5 4 8], 3:[7 6]}}, {p16: {0:[1], 1:[3 2], 2:[5 8 4 9], 3:[7 6]}}, {p17: {0:[1], 1:[3 2], 2:[5 8 9 4], 3:[7 6]}}, {p18: {0:[1], 1:[3 2], 2:[5 9 8 4], 3:[7 6]}}, {p19: {0:[1], 1:[3 2], 2:[9 5 8 4], 3:[7 6]}}, {p20: {0:[1], 1:[3 2], 2:[8 5 4 9], 3:[7 6]}}, {p21: {0:[1], 1:[3 2], 2:[8 5 9 4], 3:[7 6]}}, {p22: {0:[1], 1:[3 2], 2:[8 9 5 4], 3:[7 6]}}, {p23: {0:[1], 1:[3 2], 2:[9 8 5 4], 3:[7 6]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 3]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:3 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:4 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6]}, {Id:5 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:6 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:9 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:7 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:9 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:8 RootId:8 Layer:2 X:144.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}, {Id:9 RootId:9 Layer:2 X:216.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[3 2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:3 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:4 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6]}, {Id:5 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:6 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:9 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:7 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:9 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:8 RootId:8 Layer:2 X:216.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}, {Id:9 RootId:9 Layer:2 X:144.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}")
+}
+
+func TestOneSecondaryMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsOneSecondary,
+ "[-4 0 1 0 1 2 1]",
+ "0:[1 3], 1:[2 4 6], 2:[5]",
+ int64(6),
+ "{p0: {0:[1 3], 1:[2 4 6], 2:[5]}}, {p1: {0:[1 3], 1:[2 6 4], 2:[5]}}, {p2: {0:[1 3], 1:[6 2 4], 2:[5]}}, {p3: {0:[3 1], 1:[4 2 6], 2:[5]}}, {p4: {0:[3 1], 1:[4 6 2], 2:[5]}}, {p5: {0:[3 1], 1:[6 4 2], 2:[5]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:3 RootId:3 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[4]}, {Id:4 RootId:3 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:3 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:144.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:3 RootId:3 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[4]}, {Id:4 RootId:3 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:3 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:144.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}")
+}
+
+func TestDiamonMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsDiamond,
+ "[-4 0 1 1 1 2 1]",
+ "0:[1], 1:[2 3 4 6], 2:[5]",
+ int64(24),
+ "{p0: {0:[1], 1:[2 3 4 6], 2:[5]}}, {p1: {0:[1], 1:[2 3 6 4], 2:[5]}}, {p2: {0:[1], 1:[2 6 3 4], 2:[5]}}, {p3: {0:[1], 1:[6 2 3 4], 2:[5]}}, {p4: {0:[1], 1:[2 4 3 6], 2:[5]}}, {p5: {0:[1], 1:[2 4 6 3], 2:[5]}}, {p6: {0:[1], 1:[2 6 4 3], 2:[5]}}, {p7: {0:[1], 1:[6 2 4 3], 2:[5]}}, {p8: {0:[1], 1:[4 3 2 6], 2:[5]}}, {p9: {0:[1], 1:[4 3 6 2], 2:[5]}}, {p10: {0:[1], 1:[4 6 3 2], 2:[5]}}, {p11: {0:[1], 1:[6 4 3 2], 2:[5]}}, {p12: {0:[1], 1:[3 2 4 6], 2:[5]}}, {p13: {0:[1], 1:[3 2 6 4], 2:[5]}}, {p14: {0:[1], 1:[3 6 2 4], 2:[5]}}, {p15: {0:[1], 1:[6 3 2 4], 2:[5]}}, {p16: {0:[1], 1:[3 4 2 6], 2:[5]}}, {p17: {0:[1], 1:[3 4 6 2], 2:[5]}}, {p18: {0:[1], 1:[3 6 4 2], 2:[5]}}, {p19: {0:[1], 1:[6 3 4 2], 2:[5]}}, {p20: {0:[1], 1:[4 2 3 6], 2:[5]}}, {p21: {0:[1], 1:[4 2 6 3], 2:[5]}}, {p22: {0:[1], 1:[4 6 2 3], 2:[5]}}, {p23: {0:[1], 1:[6 4 2 3], 2:[5]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:196.00 W:52.00 H:60.00 In:[] PriRootOut:[2 3 4]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:3 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:4 RootId:1 Layer:1 X:144.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:216.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:196.00 W:52.00 H:60.00 In:[] PriRootOut:[4 2 3]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:3 RootId:1 Layer:1 X:144.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:4 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:2 X:144.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:216.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}")
+}
+
+func TestSubtreeBelowLongMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsSubtreeBelowLong,
+ "[-4 0 1 2 3 2 3 4]",
+ "0:[1], 1:[2], 2:[3 5], 3:[4 6], 4:[7]",
+ int64(2),
+ "{p0: {0:[1], 1:[2], 2:[3 5], 3:[4 6], 4:[7]}}, {p1: {0:[1], 1:[2], 2:[5 3], 3:[6 4], 4:[7]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:5 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[6]}, {Id:6 RootId:5 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:5 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:5 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[6]}, {Id:6 RootId:5 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:5 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestOneNotTwoLevelsDownMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsOneNotTwoLevelsDown,
+ "[-4 0 1 2 3 4 1 2 3]",
+ "0:[1], 1:[2 6], 2:[3 7], 3:[4 8], 4:[5]",
+ int64(2),
+ "{p0: {0:[1], 1:[2 6], 2:[3 7], 3:[4 8], 4:[5]}}, {p1: {0:[1], 1:[6 2], 2:[7 3], 3:[8 4], 4:[5]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:1 Layer:4 X:0.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[7]}, {Id:7 RootId:6 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[8]}, {Id:8 RootId:6 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:7 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:1 Layer:4 X:0.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:6 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[7]}, {Id:7 RootId:6 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[8]}, {Id:8 RootId:6 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:7 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestMultiSecParentPullDownMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsMultiSecParentPullDown,
+ "[-4 0 1 2 3 1 2]",
+ "0:[1], 1:[2 5], 2:[3 6], 3:[4]",
+ int64(2),
+ "{p0: {0:[1], 1:[2 5], 2:[3 6], 3:[4]}}, {p1: {0:[1], 1:[5 2], 2:[6 3], 3:[4]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:5 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[6]}, {Id:6 RootId:5 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:5 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[6]}, {Id:6 RootId:5 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestMultiSecParentNoPullDownMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsMultiSecParentNoPullDown,
+ "[-4 0 1 2 0 1]",
+ "0:[1 4], 1:[2 5], 2:[3]",
+ int64(2),
+ "{p0: {0:[1 4], 1:[2 5], 2:[3]}}, {p1: {0:[4 1], 1:[5 2], 2:[3]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:4 RootId:4 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[5]}, {Id:5 RootId:4 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:4 RootId:4 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[5]}, {Id:5 RootId:4 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestTwoLevelsFromOneParentMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsTwoLevelsFromOneParent,
+ "[-4 0 0 1 2]",
+ "0:[1 2], 1:[3], 2:[4]",
+ int64(2),
+ "{p0: {0:[1 2], 1:[3], 2:[4]}}, {p1: {0:[2 1], 1:[3], 2:[4]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[3]}, {Id:2 RootId:2 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}, {Id:3 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[3]}, {Id:2 RootId:2 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}, {Id:3 RootId:1 Layer:1 X:72.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestTwoLevelsFromOneParentSameRootMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsTwoLevelsFromOneParentSameRoot,
+ "[-4 0 1 2 3 2 3 4]",
+ "0:[1], 1:[2 10005], 2:[3 5], 3:[4 6], 4:[7]",
+ int64(2),
+ "{p0: {0:[1], 1:[2 10005], 2:[3 5], 3:[4 6], 4:[7]}}, {p1: {0:[1], 1:[10005 2], 2:[5 3], 3:[6 4], 4:[7]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 5]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6]}, {Id:6 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:1 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 5]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6]}, {Id:6 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:1 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestTwoLevelsFromOneParentSameRootTwoFakesMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsTwoLevelsFromOneParentSameRootTwoFakes,
+ "[-4 0 1 2 3 4 3 4 5]",
+ "0:[1], 1:[2 10006], 2:[3 10006], 3:[4 6], 4:[5 7], 5:[8]",
+ int64(2),
+ "{p0: {0:[1], 1:[2 10006], 2:[3 10006], 3:[4 6], 4:[5 7], 5:[8]}}, {p1: {0:[1], 1:[10006 2], 2:[10006 3], 3:[6 4], 4:[7 5], 5:[8]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 6]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:1 Layer:4 X:0.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:1 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[8]}, {Id:8 RootId:1 Layer:5 X:72.00 Y:600.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:7 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:124.00 W:52.00 H:60.00 In:[] PriRootOut:[2 6]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5]}, {Id:5 RootId:1 Layer:4 X:0.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:1 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[8]}, {Id:8 RootId:1 Layer:5 X:72.00 Y:600.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:7 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestDuplicateSecLabelsMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsDuplicateSecLabels,
+ "[-4 0 0 0 0 0 0 1 1 1 1 1]",
+ "0:[1 2 3 4 5 6], 1:[7 8 9 10 11]",
+ int64(720),
+ "{p0: {0:[1 2 3 4 5 6], 1:[7 8 9 10 11]}}, {p1: {0:[1 2 3 4 6 5], 1:[7 8 9 11 10]}}, {p2: {0:[1 2 3 6 4 5], 1:[7 8 11 9 10]}}, {p3: {0:[1 2 6 3 4 5], 1:[7 11 8 9 10]}}, {p4: {0:[1 6 2 3 4 5], 1:[11 7 8 9 10]}}, {p5: {0:[6 1 2 3 4 5], 1:[11 7 8 9 10]}}, {p6: {0:[1 2 3 5 4 6], 1:[7 8 10 9 11]}}, {p7: {0:[1 2 3 5 6 4], 1:[7 8 10 11 9]}}, {p8: {0:[1 2 3 6 5 4], 1:[7 8 11 10 9]}}, {p9: {0:[1 2 6 3 5 4], 1:[7 11 8 10 9]}}, {p10: {0:[1 6 2 3 5 4], 1:[11 7 8 10 9]}}, {p11: {0:[6 1 2 3 5 4], 1:[11 7 8 10 9]}}, {p12: {0:[1 2 5 3 4 6], 1:[7 10 8 9 11]}}, {p13: {0:[1 2 5 3 6 4], 1:[7 10 8 11 9]}}, {p14: {0:[1 2 5 6 3 4], 1:[7 10 11 8 9]}}, {p15: {0:[1 2 6 5 3 4], 1:[7 11 10 8 9]}}, {p16: {0:[1 6 2 5 3 4], 1:[11 7 10 8 9]}}, {p17: {0:[6 1 2 5 3 4], 1:[11 7 10 8 9]}}, {p18: {0:[1 5 2 3 4 6], 1:[10 7 8 9 11]}}, {p19: {0:[1 5 2 3 6 4], 1:[10 7 8 11 9]}}, {p20: {0:[1 5 2 6 3 4], 1:[10 7 11 8 9]}}, {p21: {0:[1 5 6 2 3 4], 1:[10 11 7 8 9]}}, {p22: {0:[1 6 5 2 3 4], 1:[11 10 7 8 9]}}, {p23: {0:[6 1 5 2 3 4], 1:[11 10 7 8 9]}}, {p24: {0:[5 1 2 3 4 6], 1:[10 7 8 9 11]}}, {p25: {0:[5 1 2 3 6 4], 1:[10 7 8 11 9]}}, {p26: {0:[5 1 2 6 3 4], 1:[10 7 11 8 9]}}, {p27: {0:[5 1 6 2 3 4], 1:[10 11 7 8 9]}}, {p28: {0:[5 6 1 2 3 4], 1:[10 11 7 8 9]}}, {p29: {0:[6 5 1 2 3 4], 1:[11 10 7 8 9]}}, {p30: {0:[1 2 4 3 5 6], 1:[7 9 8 10 11]}}, {p31: {0:[1 2 4 3 6 5], 1:[7 9 8 11 10]}}, {p32: {0:[1 2 4 6 3 5], 1:[7 9 11 8 10]}}, {p33: {0:[1 2 6 4 3 5], 1:[7 11 9 8 10]}}, {p34: {0:[1 6 2 4 3 5], 1:[11 7 9 8 10]}}, {p35: {0:[6 1 2 4 3 5], 1:[11 7 9 8 10]}}, {p36: {0:[1 2 4 5 3 6], 1:[7 9 10 8 11]}}, {p37: {0:[1 2 4 5 6 3], 1:[7 9 10 11 8]}}, {p38: {0:[1 2 4 6 5 3], 1:[7 9 11 10 8]}}, {p39: {0:[1 2 6 4 5 3], 1:[7 11 9 10 8]}}, {p40: {0:[1 6 2 4 5 3], 1:[11 7 9 10 8]}}, {p41: {0:[6 1 2 4 5 3], 1:[11 7 9 10 8]}}, {p42: {0:[1 2 5 4 3 6], 1:[7 10 9 8 11]}}, {p43: {0:[1 2 5 4 6 3], 1:[7 10 9 11 8]}}, {p44: {0:[1 2 5 6 4 3], 1:[7 10 11 9 8]}}, {p45: {0:[1 2 6 5 4 3], 1:[7 11 10 9 8]}}, {p46: {0:[1 6 2 5 4 3], 1:[11 7 10 9 8]}}, {p47: {0:[6 1 2 5 4 3], 1:[11 7 10 9 8]}}, {p48: {0:[1 5 2 4 3 6], 1:[10 7 9 8 11]}}, {p49: {0:[1 5 2 4 6 3], 1:[10 7 9 11 8]}}, {p50: {0:[1 5 2 6 4 3], 1:[10 7 11 9 8]}}, {p51: {0:[1 5 6 2 4 3], 1:[10 11 7 9 8]}}, {p52: {0:[1 6 5 2 4 3], 1:[11 10 7 9 8]}}, {p53: {0:[6 1 5 2 4 3], 1:[11 10 7 9 8]}}, {p54: {0:[5 1 2 4 3 6], 1:[10 7 9 8 11]}}, {p55: {0:[5 1 2 4 6 3], 1:[10 7 9 11 8]}}, {p56: {0:[5 1 2 6 4 3], 1:[10 7 11 9 8]}}, {p57: {0:[5 1 6 2 4 3], 1:[10 11 7 9 8]}}, {p58: {0:[5 6 1 2 4 3], 1:[10 11 7 9 8]}}, {p59: {0:[6 5 1 2 4 3], 1:[11 10 7 9 8]}}, {p60: {0:[1 4 2 3 5 6], 1:[9 7 8 10 11]}}, {p61: {0:[1 4 2 3 6 5], 1:[9 7 8 11 10]}}, {p62: {0:[1 4 2 6 3 5], 1:[9 7 11 8 10]}}, {p63: {0:[1 4 6 2 3 5], 1:[9 11 7 8 10]}}, {p64: {0:[1 6 4 2 3 5], 1:[11 9 7 8 10]}}, {p65: {0:[6 1 4 2 3 5], 1:[11 9 7 8 10]}}, {p66: {0:[1 4 2 5 3 6], 1:[9 7 10 8 11]}}, {p67: {0:[1 4 2 5 6 3], 1:[9 7 10 11 8]}}, {p68: {0:[1 4 2 6 5 3], 1:[9 7 11 10 8]}}, {p69: {0:[1 4 6 2 5 3], 1:[9 11 7 10 8]}}, {p70: {0:[1 6 4 2 5 3], 1:[11 9 7 10 8]}}, {p71: {0:[6 1 4 2 5 3], 1:[11 9 7 10 8]}}, {p72: {0:[1 4 5 2 3 6], 1:[9 10 7 8 11]}}, {p73: {0:[1 4 5 2 6 3], 1:[9 10 7 11 8]}}, {p74: {0:[1 4 5 6 2 3], 1:[9 10 11 7 8]}}, {p75: {0:[1 4 6 5 2 3], 1:[9 11 10 7 8]}}, {p76: {0:[1 6 4 5 2 3], 1:[11 9 10 7 8]}}, {p77: {0:[6 1 4 5 2 3], 1:[11 9 10 7 8]}}, {p78: {0:[1 5 4 2 3 6], 1:[10 9 7 8 11]}}, {p79: {0:[1 5 4 2 6 3], 1:[10 9 7 11 8]}}, {p80: {0:[1 5 4 6 2 3], 1:[10 9 11 7 8]}}, {p81: {0:[1 5 6 4 2 3], 1:[10 11 9 7 8]}}, {p82: {0:[1 6 5 4 2 3], 1:[11 10 9 7 8]}}, {p83: {0:[6 1 5 4 2 3], 1:[11 10 9 7 8]}}, {p84: {0:[5 1 4 2 3 6], 1:[10 9 7 8 11]}}, {p85: {0:[5 1 4 2 6 3], 1:[10 9 7 11 8]}}, {p86: {0:[5 1 4 6 2 3], 1:[10 9 11 7 8]}}, {p87: {0:[5 1 6 4 2 3], 1:[10 11 9 7 8]}}, {p88: {0:[5 6 1 4 2 3], 1:[10 11 9 7 8]}}, {p89: {0:[6 5 1 4 2 3], 1:[11 10 9 7 8]}}, {p90: {0:[4 1 2 3 5 6], 1:[9 7 8 10 11]}}, {p91: {0:[4 1 2 3 6 5], 1:[9 7 8 11 10]}}, {p92: {0:[4 1 2 6 3 5], 1:[9 7 11 8 10]}}, {p93: {0:[4 1 6 2 3 5], 1:[9 11 7 8 10]}}, {p94: {0:[4 6 1 2 3 5], 1:[9 11 7 8 10]}}, {p95: {0:[6 4 1 2 3 5], 1:[11 9 7 8 10]}}, {p96: {0:[4 1 2 5 3 6], 1:[9 7 10 8 11]}}, {p97: {0:[4 1 2 5 6 3], 1:[9 7 10 11 8]}}, {p98: {0:[4 1 2 6 5 3], 1:[9 7 11 10 8]}}, {p99: {0:[4 1 6 2 5 3], 1:[9 11 7 10 8]}}, {p100: {0:[4 6 1 2 5 3], 1:[9 11 7 10 8]}}, {p101: {0:[6 4 1 2 5 3], 1:[11 9 7 10 8]}}, {p102: {0:[4 1 5 2 3 6], 1:[9 10 7 8 11]}}, {p103: {0:[4 1 5 2 6 3], 1:[9 10 7 11 8]}}, {p104: {0:[4 1 5 6 2 3], 1:[9 10 11 7 8]}}, {p105: {0:[4 1 6 5 2 3], 1:[9 11 10 7 8]}}, {p106: {0:[4 6 1 5 2 3], 1:[9 11 10 7 8]}}, {p107: {0:[6 4 1 5 2 3], 1:[11 9 10 7 8]}}, {p108: {0:[4 5 1 2 3 6], 1:[9 10 7 8 11]}}, {p109: {0:[4 5 1 2 6 3], 1:[9 10 7 11 8]}}, {p110: {0:[4 5 1 6 2 3], 1:[9 10 11 7 8]}}, {p111: {0:[4 5 6 1 2 3], 1:[9 10 11 7 8]}}, {p112: {0:[4 6 5 1 2 3], 1:[9 11 10 7 8]}}, {p113: {0:[6 4 5 1 2 3], 1:[11 9 10 7 8]}}, {p114: {0:[5 4 1 2 3 6], 1:[10 9 7 8 11]}}, {p115: {0:[5 4 1 2 6 3], 1:[10 9 7 11 8]}}, {p116: {0:[5 4 1 6 2 3], 1:[10 9 11 7 8]}}, {p117: {0:[5 4 6 1 2 3], 1:[10 9 11 7 8]}}, {p118: {0:[5 6 4 1 2 3], 1:[10 11 9 7 8]}}, {p119: {0:[6 5 4 1 2 3], 1:[11 10 9 7 8]}}, {p120: {0:[1 3 2 4 5 6], 1:[8 7 9 10 11]}}, {p121: {0:[1 3 2 4 6 5], 1:[8 7 9 11 10]}}, {p122: {0:[1 3 2 6 4 5], 1:[8 7 11 9 10]}}, {p123: {0:[1 3 6 2 4 5], 1:[8 11 7 9 10]}}, {p124: {0:[1 6 3 2 4 5], 1:[11 8 7 9 10]}}, {p125: {0:[6 1 3 2 4 5], 1:[11 8 7 9 10]}}, {p126: {0:[1 3 2 5 4 6], 1:[8 7 10 9 11]}}, {p127: {0:[1 3 2 5 6 4], 1:[8 7 10 11 9]}}, {p128: {0:[1 3 2 6 5 4], 1:[8 7 11 10 9]}}, {p129: {0:[1 3 6 2 5 4], 1:[8 11 7 10 9]}}, {p130: {0:[1 6 3 2 5 4], 1:[11 8 7 10 9]}}, {p131: {0:[6 1 3 2 5 4], 1:[11 8 7 10 9]}}, {p132: {0:[1 3 5 2 4 6], 1:[8 10 7 9 11]}}, {p133: {0:[1 3 5 2 6 4], 1:[8 10 7 11 9]}}, {p134: {0:[1 3 5 6 2 4], 1:[8 10 11 7 9]}}, {p135: {0:[1 3 6 5 2 4], 1:[8 11 10 7 9]}}, {p136: {0:[1 6 3 5 2 4], 1:[11 8 10 7 9]}}, {p137: {0:[6 1 3 5 2 4], 1:[11 8 10 7 9]}}, {p138: {0:[1 5 3 2 4 6], 1:[10 8 7 9 11]}}, {p139: {0:[1 5 3 2 6 4], 1:[10 8 7 11 9]}}, {p140: {0:[1 5 3 6 2 4], 1:[10 8 11 7 9]}}, {p141: {0:[1 5 6 3 2 4], 1:[10 11 8 7 9]}}, {p142: {0:[1 6 5 3 2 4], 1:[11 10 8 7 9]}}, {p143: {0:[6 1 5 3 2 4], 1:[11 10 8 7 9]}}, {p144: {0:[5 1 3 2 4 6], 1:[10 8 7 9 11]}}, {p145: {0:[5 1 3 2 6 4], 1:[10 8 7 11 9]}}, {p146: {0:[5 1 3 6 2 4], 1:[10 8 11 7 9]}}, {p147: {0:[5 1 6 3 2 4], 1:[10 11 8 7 9]}}, {p148: {0:[5 6 1 3 2 4], 1:[10 11 8 7 9]}}, {p149: {0:[6 5 1 3 2 4], 1:[11 10 8 7 9]}}, {p150: {0:[1 3 4 2 5 6], 1:[8 9 7 10 11]}}, {p151: {0:[1 3 4 2 6 5], 1:[8 9 7 11 10]}}, {p152: {0:[1 3 4 6 2 5], 1:[8 9 11 7 10]}}, {p153: {0:[1 3 6 4 2 5], 1:[8 11 9 7 10]}}, {p154: {0:[1 6 3 4 2 5], 1:[11 8 9 7 10]}}, {p155: {0:[6 1 3 4 2 5], 1:[11 8 9 7 10]}}, {p156: {0:[1 3 4 5 2 6], 1:[8 9 10 7 11]}}, {p157: {0:[1 3 4 5 6 2], 1:[8 9 10 11 7]}}, {p158: {0:[1 3 4 6 5 2], 1:[8 9 11 10 7]}}, {p159: {0:[1 3 6 4 5 2], 1:[8 11 9 10 7]}}, {p160: {0:[1 6 3 4 5 2], 1:[11 8 9 10 7]}}, {p161: {0:[6 1 3 4 5 2], 1:[11 8 9 10 7]}}, {p162: {0:[1 3 5 4 2 6], 1:[8 10 9 7 11]}}, {p163: {0:[1 3 5 4 6 2], 1:[8 10 9 11 7]}}, {p164: {0:[1 3 5 6 4 2], 1:[8 10 11 9 7]}}, {p165: {0:[1 3 6 5 4 2], 1:[8 11 10 9 7]}}, {p166: {0:[1 6 3 5 4 2], 1:[11 8 10 9 7]}}, {p167: {0:[6 1 3 5 4 2], 1:[11 8 10 9 7]}}, {p168: {0:[1 5 3 4 2 6], 1:[10 8 9 7 11]}}, {p169: {0:[1 5 3 4 6 2], 1:[10 8 9 11 7]}}, {p170: {0:[1 5 3 6 4 2], 1:[10 8 11 9 7]}}, {p171: {0:[1 5 6 3 4 2], 1:[10 11 8 9 7]}}, {p172: {0:[1 6 5 3 4 2], 1:[11 10 8 9 7]}}, {p173: {0:[6 1 5 3 4 2], 1:[11 10 8 9 7]}}, {p174: {0:[5 1 3 4 2 6], 1:[10 8 9 7 11]}}, {p175: {0:[5 1 3 4 6 2], 1:[10 8 9 11 7]}}, {p176: {0:[5 1 3 6 4 2], 1:[10 8 11 9 7]}}, {p177: {0:[5 1 6 3 4 2], 1:[10 11 8 9 7]}}, {p178: {0:[5 6 1 3 4 2], 1:[10 11 8 9 7]}}, {p179: {0:[6 5 1 3 4 2], 1:[11 10 8 9 7]}}, {p180: {0:[1 4 3 2 5 6], 1:[9 8 7 10 11]}}, {p181: {0:[1 4 3 2 6 5], 1:[9 8 7 11 10]}}, {p182: {0:[1 4 3 6 2 5], 1:[9 8 11 7 10]}}, {p183: {0:[1 4 6 3 2 5], 1:[9 11 8 7 10]}}, {p184: {0:[1 6 4 3 2 5], 1:[11 9 8 7 10]}}, {p185: {0:[6 1 4 3 2 5], 1:[11 9 8 7 10]}}, {p186: {0:[1 4 3 5 2 6], 1:[9 8 10 7 11]}}, {p187: {0:[1 4 3 5 6 2], 1:[9 8 10 11 7]}}, {p188: {0:[1 4 3 6 5 2], 1:[9 8 11 10 7]}}, {p189: {0:[1 4 6 3 5 2], 1:[9 11 8 10 7]}}, {p190: {0:[1 6 4 3 5 2], 1:[11 9 8 10 7]}}, {p191: {0:[6 1 4 3 5 2], 1:[11 9 8 10 7]}}, {p192: {0:[1 4 5 3 2 6], 1:[9 10 8 7 11]}}, {p193: {0:[1 4 5 3 6 2], 1:[9 10 8 11 7]}}, {p194: {0:[1 4 5 6 3 2], 1:[9 10 11 8 7]}}, {p195: {0:[1 4 6 5 3 2], 1:[9 11 10 8 7]}}, {p196: {0:[1 6 4 5 3 2], 1:[11 9 10 8 7]}}, {p197: {0:[6 1 4 5 3 2], 1:[11 9 10 8 7]}}, {p198: {0:[1 5 4 3 2 6], 1:[10 9 8 7 11]}}, {p199: {0:[1 5 4 3 6 2], 1:[10 9 8 11 7]}}, {p200: {0:[1 5 4 6 3 2], 1:[10 9 11 8 7]}}, {p201: {0:[1 5 6 4 3 2], 1:[10 11 9 8 7]}}, {p202: {0:[1 6 5 4 3 2], 1:[11 10 9 8 7]}}, {p203: {0:[6 1 5 4 3 2], 1:[11 10 9 8 7]}}, {p204: {0:[5 1 4 3 2 6], 1:[10 9 8 7 11]}}, {p205: {0:[5 1 4 3 6 2], 1:[10 9 8 11 7]}}, {p206: {0:[5 1 4 6 3 2], 1:[10 9 11 8 7]}}, {p207: {0:[5 1 6 4 3 2], 1:[10 11 9 8 7]}}, {p208: {0:[5 6 1 4 3 2], 1:[10 11 9 8 7]}}, {p209: {0:[6 5 1 4 3 2], 1:[11 10 9 8 7]}}, {p210: {0:[4 1 3 2 5 6], 1:[9 8 7 10 11]}}, {p211: {0:[4 1 3 2 6 5], 1:[9 8 7 11 10]}}, {p212: {0:[4 1 3 6 2 5], 1:[9 8 11 7 10]}}, {p213: {0:[4 1 6 3 2 5], 1:[9 11 8 7 10]}}, {p214: {0:[4 6 1 3 2 5], 1:[9 11 8 7 10]}}, {p215: {0:[6 4 1 3 2 5], 1:[11 9 8 7 10]}}, {p216: {0:[4 1 3 5 2 6], 1:[9 8 10 7 11]}}, {p217: {0:[4 1 3 5 6 2], 1:[9 8 10 11 7]}}, {p218: {0:[4 1 3 6 5 2], 1:[9 8 11 10 7]}}, {p219: {0:[4 1 6 3 5 2], 1:[9 11 8 10 7]}}, {p220: {0:[4 6 1 3 5 2], 1:[9 11 8 10 7]}}, {p221: {0:[6 4 1 3 5 2], 1:[11 9 8 10 7]}}, {p222: {0:[4 1 5 3 2 6], 1:[9 10 8 7 11]}}, {p223: {0:[4 1 5 3 6 2], 1:[9 10 8 11 7]}}, {p224: {0:[4 1 5 6 3 2], 1:[9 10 11 8 7]}}, {p225: {0:[4 1 6 5 3 2], 1:[9 11 10 8 7]}}, {p226: {0:[4 6 1 5 3 2], 1:[9 11 10 8 7]}}, {p227: {0:[6 4 1 5 3 2], 1:[11 9 10 8 7]}}, {p228: {0:[4 5 1 3 2 6], 1:[9 10 8 7 11]}}, {p229: {0:[4 5 1 3 6 2], 1:[9 10 8 11 7]}}, {p230: {0:[4 5 1 6 3 2], 1:[9 10 11 8 7]}}, {p231: {0:[4 5 6 1 3 2], 1:[9 10 11 8 7]}}, {p232: {0:[4 6 5 1 3 2], 1:[9 11 10 8 7]}}, {p233: {0:[6 4 5 1 3 2], 1:[11 9 10 8 7]}}, {p234: {0:[5 4 1 3 2 6], 1:[10 9 8 7 11]}}, {p235: {0:[5 4 1 3 6 2], 1:[10 9 8 11 7]}}, {p236: {0:[5 4 1 6 3 2], 1:[10 9 11 8 7]}}, {p237: {0:[5 4 6 1 3 2], 1:[10 9 11 8 7]}}, {p238: {0:[5 6 4 1 3 2], 1:[10 11 9 8 7]}}, {p239: {0:[6 5 4 1 3 2], 1:[11 10 9 8 7]}}, {p240: {0:[3 1 2 4 5 6], 1:[8 7 9 10 11]}}, {p241: {0:[3 1 2 4 6 5], 1:[8 7 9 11 10]}}, {p242: {0:[3 1 2 6 4 5], 1:[8 7 11 9 10]}}, {p243: {0:[3 1 6 2 4 5], 1:[8 11 7 9 10]}}, {p244: {0:[3 6 1 2 4 5], 1:[8 11 7 9 10]}}, {p245: {0:[6 3 1 2 4 5], 1:[11 8 7 9 10]}}, {p246: {0:[3 1 2 5 4 6], 1:[8 7 10 9 11]}}, {p247: {0:[3 1 2 5 6 4], 1:[8 7 10 11 9]}}, {p248: {0:[3 1 2 6 5 4], 1:[8 7 11 10 9]}}, {p249: {0:[3 1 6 2 5 4], 1:[8 11 7 10 9]}}, {p250: {0:[3 6 1 2 5 4], 1:[8 11 7 10 9]}}, {p251: {0:[6 3 1 2 5 4], 1:[11 8 7 10 9]}}, {p252: {0:[3 1 5 2 4 6], 1:[8 10 7 9 11]}}, {p253: {0:[3 1 5 2 6 4], 1:[8 10 7 11 9]}}, {p254: {0:[3 1 5 6 2 4], 1:[8 10 11 7 9]}}, {p255: {0:[3 1 6 5 2 4], 1:[8 11 10 7 9]}}, {p256: {0:[3 6 1 5 2 4], 1:[8 11 10 7 9]}}, {p257: {0:[6 3 1 5 2 4], 1:[11 8 10 7 9]}}, {p258: {0:[3 5 1 2 4 6], 1:[8 10 7 9 11]}}, {p259: {0:[3 5 1 2 6 4], 1:[8 10 7 11 9]}}, {p260: {0:[3 5 1 6 2 4], 1:[8 10 11 7 9]}}, {p261: {0:[3 5 6 1 2 4], 1:[8 10 11 7 9]}}, {p262: {0:[3 6 5 1 2 4], 1:[8 11 10 7 9]}}, {p263: {0:[6 3 5 1 2 4], 1:[11 8 10 7 9]}}, {p264: {0:[5 3 1 2 4 6], 1:[10 8 7 9 11]}}, {p265: {0:[5 3 1 2 6 4], 1:[10 8 7 11 9]}}, {p266: {0:[5 3 1 6 2 4], 1:[10 8 11 7 9]}}, {p267: {0:[5 3 6 1 2 4], 1:[10 8 11 7 9]}}, {p268: {0:[5 6 3 1 2 4], 1:[10 11 8 7 9]}}, {p269: {0:[6 5 3 1 2 4], 1:[11 10 8 7 9]}}, {p270: {0:[3 1 4 2 5 6], 1:[8 9 7 10 11]}}, {p271: {0:[3 1 4 2 6 5], 1:[8 9 7 11 10]}}, {p272: {0:[3 1 4 6 2 5], 1:[8 9 11 7 10]}}, {p273: {0:[3 1 6 4 2 5], 1:[8 11 9 7 10]}}, {p274: {0:[3 6 1 4 2 5], 1:[8 11 9 7 10]}}, {p275: {0:[6 3 1 4 2 5], 1:[11 8 9 7 10]}}, {p276: {0:[3 1 4 5 2 6], 1:[8 9 10 7 11]}}, {p277: {0:[3 1 4 5 6 2], 1:[8 9 10 11 7]}}, {p278: {0:[3 1 4 6 5 2], 1:[8 9 11 10 7]}}, {p279: {0:[3 1 6 4 5 2], 1:[8 11 9 10 7]}}, {p280: {0:[3 6 1 4 5 2], 1:[8 11 9 10 7]}}, {p281: {0:[6 3 1 4 5 2], 1:[11 8 9 10 7]}}, {p282: {0:[3 1 5 4 2 6], 1:[8 10 9 7 11]}}, {p283: {0:[3 1 5 4 6 2], 1:[8 10 9 11 7]}}, {p284: {0:[3 1 5 6 4 2], 1:[8 10 11 9 7]}}, {p285: {0:[3 1 6 5 4 2], 1:[8 11 10 9 7]}}, {p286: {0:[3 6 1 5 4 2], 1:[8 11 10 9 7]}}, {p287: {0:[6 3 1 5 4 2], 1:[11 8 10 9 7]}}, {p288: {0:[3 5 1 4 2 6], 1:[8 10 9 7 11]}}, {p289: {0:[3 5 1 4 6 2], 1:[8 10 9 11 7]}}, {p290: {0:[3 5 1 6 4 2], 1:[8 10 11 9 7]}}, {p291: {0:[3 5 6 1 4 2], 1:[8 10 11 9 7]}}, {p292: {0:[3 6 5 1 4 2], 1:[8 11 10 9 7]}}, {p293: {0:[6 3 5 1 4 2], 1:[11 8 10 9 7]}}, {p294: {0:[5 3 1 4 2 6], 1:[10 8 9 7 11]}}, {p295: {0:[5 3 1 4 6 2], 1:[10 8 9 11 7]}}, {p296: {0:[5 3 1 6 4 2], 1:[10 8 11 9 7]}}, {p297: {0:[5 3 6 1 4 2], 1:[10 8 11 9 7]}}, {p298: {0:[5 6 3 1 4 2], 1:[10 11 8 9 7]}}, {p299: {0:[6 5 3 1 4 2], 1:[11 10 8 9 7]}}, {p300: {0:[3 4 1 2 5 6], 1:[8 9 7 10 11]}}, {p301: {0:[3 4 1 2 6 5], 1:[8 9 7 11 10]}}, {p302: {0:[3 4 1 6 2 5], 1:[8 9 11 7 10]}}, {p303: {0:[3 4 6 1 2 5], 1:[8 9 11 7 10]}}, {p304: {0:[3 6 4 1 2 5], 1:[8 11 9 7 10]}}, {p305: {0:[6 3 4 1 2 5], 1:[11 8 9 7 10]}}, {p306: {0:[3 4 1 5 2 6], 1:[8 9 10 7 11]}}, {p307: {0:[3 4 1 5 6 2], 1:[8 9 10 11 7]}}, {p308: {0:[3 4 1 6 5 2], 1:[8 9 11 10 7]}}, {p309: {0:[3 4 6 1 5 2], 1:[8 9 11 10 7]}}, {p310: {0:[3 6 4 1 5 2], 1:[8 11 9 10 7]}}, {p311: {0:[6 3 4 1 5 2], 1:[11 8 9 10 7]}}, {p312: {0:[3 4 5 1 2 6], 1:[8 9 10 7 11]}}, {p313: {0:[3 4 5 1 6 2], 1:[8 9 10 11 7]}}, {p314: {0:[3 4 5 6 1 2], 1:[8 9 10 11 7]}}, {p315: {0:[3 4 6 5 1 2], 1:[8 9 11 10 7]}}, {p316: {0:[3 6 4 5 1 2], 1:[8 11 9 10 7]}}, {p317: {0:[6 3 4 5 1 2], 1:[11 8 9 10 7]}}, {p318: {0:[3 5 4 1 2 6], 1:[8 10 9 7 11]}}, {p319: {0:[3 5 4 1 6 2], 1:[8 10 9 11 7]}}, {p320: {0:[3 5 4 6 1 2], 1:[8 10 9 11 7]}}, {p321: {0:[3 5 6 4 1 2], 1:[8 10 11 9 7]}}, {p322: {0:[3 6 5 4 1 2], 1:[8 11 10 9 7]}}, {p323: {0:[6 3 5 4 1 2], 1:[11 8 10 9 7]}}, {p324: {0:[5 3 4 1 2 6], 1:[10 8 9 7 11]}}, {p325: {0:[5 3 4 1 6 2], 1:[10 8 9 11 7]}}, {p326: {0:[5 3 4 6 1 2], 1:[10 8 9 11 7]}}, {p327: {0:[5 3 6 4 1 2], 1:[10 8 11 9 7]}}, {p328: {0:[5 6 3 4 1 2], 1:[10 11 8 9 7]}}, {p329: {0:[6 5 3 4 1 2], 1:[11 10 8 9 7]}}, {p330: {0:[4 3 1 2 5 6], 1:[9 8 7 10 11]}}, {p331: {0:[4 3 1 2 6 5], 1:[9 8 7 11 10]}}, {p332: {0:[4 3 1 6 2 5], 1:[9 8 11 7 10]}}, {p333: {0:[4 3 6 1 2 5], 1:[9 8 11 7 10]}}, {p334: {0:[4 6 3 1 2 5], 1:[9 11 8 7 10]}}, {p335: {0:[6 4 3 1 2 5], 1:[11 9 8 7 10]}}, {p336: {0:[4 3 1 5 2 6], 1:[9 8 10 7 11]}}, {p337: {0:[4 3 1 5 6 2], 1:[9 8 10 11 7]}}, {p338: {0:[4 3 1 6 5 2], 1:[9 8 11 10 7]}}, {p339: {0:[4 3 6 1 5 2], 1:[9 8 11 10 7]}}, {p340: {0:[4 6 3 1 5 2], 1:[9 11 8 10 7]}}, {p341: {0:[6 4 3 1 5 2], 1:[11 9 8 10 7]}}, {p342: {0:[4 3 5 1 2 6], 1:[9 8 10 7 11]}}, {p343: {0:[4 3 5 1 6 2], 1:[9 8 10 11 7]}}, {p344: {0:[4 3 5 6 1 2], 1:[9 8 10 11 7]}}, {p345: {0:[4 3 6 5 1 2], 1:[9 8 11 10 7]}}, {p346: {0:[4 6 3 5 1 2], 1:[9 11 8 10 7]}}, {p347: {0:[6 4 3 5 1 2], 1:[11 9 8 10 7]}}, {p348: {0:[4 5 3 1 2 6], 1:[9 10 8 7 11]}}, {p349: {0:[4 5 3 1 6 2], 1:[9 10 8 11 7]}}, {p350: {0:[4 5 3 6 1 2], 1:[9 10 8 11 7]}}, {p351: {0:[4 5 6 3 1 2], 1:[9 10 11 8 7]}}, {p352: {0:[4 6 5 3 1 2], 1:[9 11 10 8 7]}}, {p353: {0:[6 4 5 3 1 2], 1:[11 9 10 8 7]}}, {p354: {0:[5 4 3 1 2 6], 1:[10 9 8 7 11]}}, {p355: {0:[5 4 3 1 6 2], 1:[10 9 8 11 7]}}, {p356: {0:[5 4 3 6 1 2], 1:[10 9 8 11 7]}}, {p357: {0:[5 4 6 3 1 2], 1:[10 9 11 8 7]}}, {p358: {0:[5 6 4 3 1 2], 1:[10 11 9 8 7]}}, {p359: {0:[6 5 4 3 1 2], 1:[11 10 9 8 7]}}, {p360: {0:[2 1 3 4 5 6], 1:[7 8 9 10 11]}}, {p361: {0:[2 1 3 4 6 5], 1:[7 8 9 11 10]}}, {p362: {0:[2 1 3 6 4 5], 1:[7 8 11 9 10]}}, {p363: {0:[2 1 6 3 4 5], 1:[7 11 8 9 10]}}, {p364: {0:[2 6 1 3 4 5], 1:[7 11 8 9 10]}}, {p365: {0:[6 2 1 3 4 5], 1:[11 7 8 9 10]}}, {p366: {0:[2 1 3 5 4 6], 1:[7 8 10 9 11]}}, {p367: {0:[2 1 3 5 6 4], 1:[7 8 10 11 9]}}, {p368: {0:[2 1 3 6 5 4], 1:[7 8 11 10 9]}}, {p369: {0:[2 1 6 3 5 4], 1:[7 11 8 10 9]}}, {p370: {0:[2 6 1 3 5 4], 1:[7 11 8 10 9]}}, {p371: {0:[6 2 1 3 5 4], 1:[11 7 8 10 9]}}, {p372: {0:[2 1 5 3 4 6], 1:[7 10 8 9 11]}}, {p373: {0:[2 1 5 3 6 4], 1:[7 10 8 11 9]}}, {p374: {0:[2 1 5 6 3 4], 1:[7 10 11 8 9]}}, {p375: {0:[2 1 6 5 3 4], 1:[7 11 10 8 9]}}, {p376: {0:[2 6 1 5 3 4], 1:[7 11 10 8 9]}}, {p377: {0:[6 2 1 5 3 4], 1:[11 7 10 8 9]}}, {p378: {0:[2 5 1 3 4 6], 1:[7 10 8 9 11]}}, {p379: {0:[2 5 1 3 6 4], 1:[7 10 8 11 9]}}, {p380: {0:[2 5 1 6 3 4], 1:[7 10 11 8 9]}}, {p381: {0:[2 5 6 1 3 4], 1:[7 10 11 8 9]}}, {p382: {0:[2 6 5 1 3 4], 1:[7 11 10 8 9]}}, {p383: {0:[6 2 5 1 3 4], 1:[11 7 10 8 9]}}, {p384: {0:[5 2 1 3 4 6], 1:[10 7 8 9 11]}}, {p385: {0:[5 2 1 3 6 4], 1:[10 7 8 11 9]}}, {p386: {0:[5 2 1 6 3 4], 1:[10 7 11 8 9]}}, {p387: {0:[5 2 6 1 3 4], 1:[10 7 11 8 9]}}, {p388: {0:[5 6 2 1 3 4], 1:[10 11 7 8 9]}}, {p389: {0:[6 5 2 1 3 4], 1:[11 10 7 8 9]}}, {p390: {0:[2 1 4 3 5 6], 1:[7 9 8 10 11]}}, {p391: {0:[2 1 4 3 6 5], 1:[7 9 8 11 10]}}, {p392: {0:[2 1 4 6 3 5], 1:[7 9 11 8 10]}}, {p393: {0:[2 1 6 4 3 5], 1:[7 11 9 8 10]}}, {p394: {0:[2 6 1 4 3 5], 1:[7 11 9 8 10]}}, {p395: {0:[6 2 1 4 3 5], 1:[11 7 9 8 10]}}, {p396: {0:[2 1 4 5 3 6], 1:[7 9 10 8 11]}}, {p397: {0:[2 1 4 5 6 3], 1:[7 9 10 11 8]}}, {p398: {0:[2 1 4 6 5 3], 1:[7 9 11 10 8]}}, {p399: {0:[2 1 6 4 5 3], 1:[7 11 9 10 8]}}, {p400: {0:[2 6 1 4 5 3], 1:[7 11 9 10 8]}}, {p401: {0:[6 2 1 4 5 3], 1:[11 7 9 10 8]}}, {p402: {0:[2 1 5 4 3 6], 1:[7 10 9 8 11]}}, {p403: {0:[2 1 5 4 6 3], 1:[7 10 9 11 8]}}, {p404: {0:[2 1 5 6 4 3], 1:[7 10 11 9 8]}}, {p405: {0:[2 1 6 5 4 3], 1:[7 11 10 9 8]}}, {p406: {0:[2 6 1 5 4 3], 1:[7 11 10 9 8]}}, {p407: {0:[6 2 1 5 4 3], 1:[11 7 10 9 8]}}, {p408: {0:[2 5 1 4 3 6], 1:[7 10 9 8 11]}}, {p409: {0:[2 5 1 4 6 3], 1:[7 10 9 11 8]}}, {p410: {0:[2 5 1 6 4 3], 1:[7 10 11 9 8]}}, {p411: {0:[2 5 6 1 4 3], 1:[7 10 11 9 8]}}, {p412: {0:[2 6 5 1 4 3], 1:[7 11 10 9 8]}}, {p413: {0:[6 2 5 1 4 3], 1:[11 7 10 9 8]}}, {p414: {0:[5 2 1 4 3 6], 1:[10 7 9 8 11]}}, {p415: {0:[5 2 1 4 6 3], 1:[10 7 9 11 8]}}, {p416: {0:[5 2 1 6 4 3], 1:[10 7 11 9 8]}}, {p417: {0:[5 2 6 1 4 3], 1:[10 7 11 9 8]}}, {p418: {0:[5 6 2 1 4 3], 1:[10 11 7 9 8]}}, {p419: {0:[6 5 2 1 4 3], 1:[11 10 7 9 8]}}, {p420: {0:[2 4 1 3 5 6], 1:[7 9 8 10 11]}}, {p421: {0:[2 4 1 3 6 5], 1:[7 9 8 11 10]}}, {p422: {0:[2 4 1 6 3 5], 1:[7 9 11 8 10]}}, {p423: {0:[2 4 6 1 3 5], 1:[7 9 11 8 10]}}, {p424: {0:[2 6 4 1 3 5], 1:[7 11 9 8 10]}}, {p425: {0:[6 2 4 1 3 5], 1:[11 7 9 8 10]}}, {p426: {0:[2 4 1 5 3 6], 1:[7 9 10 8 11]}}, {p427: {0:[2 4 1 5 6 3], 1:[7 9 10 11 8]}}, {p428: {0:[2 4 1 6 5 3], 1:[7 9 11 10 8]}}, {p429: {0:[2 4 6 1 5 3], 1:[7 9 11 10 8]}}, {p430: {0:[2 6 4 1 5 3], 1:[7 11 9 10 8]}}, {p431: {0:[6 2 4 1 5 3], 1:[11 7 9 10 8]}}, {p432: {0:[2 4 5 1 3 6], 1:[7 9 10 8 11]}}, {p433: {0:[2 4 5 1 6 3], 1:[7 9 10 11 8]}}, {p434: {0:[2 4 5 6 1 3], 1:[7 9 10 11 8]}}, {p435: {0:[2 4 6 5 1 3], 1:[7 9 11 10 8]}}, {p436: {0:[2 6 4 5 1 3], 1:[7 11 9 10 8]}}, {p437: {0:[6 2 4 5 1 3], 1:[11 7 9 10 8]}}, {p438: {0:[2 5 4 1 3 6], 1:[7 10 9 8 11]}}, {p439: {0:[2 5 4 1 6 3], 1:[7 10 9 11 8]}}, {p440: {0:[2 5 4 6 1 3], 1:[7 10 9 11 8]}}, {p441: {0:[2 5 6 4 1 3], 1:[7 10 11 9 8]}}, {p442: {0:[2 6 5 4 1 3], 1:[7 11 10 9 8]}}, {p443: {0:[6 2 5 4 1 3], 1:[11 7 10 9 8]}}, {p444: {0:[5 2 4 1 3 6], 1:[10 7 9 8 11]}}, {p445: {0:[5 2 4 1 6 3], 1:[10 7 9 11 8]}}, {p446: {0:[5 2 4 6 1 3], 1:[10 7 9 11 8]}}, {p447: {0:[5 2 6 4 1 3], 1:[10 7 11 9 8]}}, {p448: {0:[5 6 2 4 1 3], 1:[10 11 7 9 8]}}, {p449: {0:[6 5 2 4 1 3], 1:[11 10 7 9 8]}}, {p450: {0:[4 2 1 3 5 6], 1:[9 7 8 10 11]}}, {p451: {0:[4 2 1 3 6 5], 1:[9 7 8 11 10]}}, {p452: {0:[4 2 1 6 3 5], 1:[9 7 11 8 10]}}, {p453: {0:[4 2 6 1 3 5], 1:[9 7 11 8 10]}}, {p454: {0:[4 6 2 1 3 5], 1:[9 11 7 8 10]}}, {p455: {0:[6 4 2 1 3 5], 1:[11 9 7 8 10]}}, {p456: {0:[4 2 1 5 3 6], 1:[9 7 10 8 11]}}, {p457: {0:[4 2 1 5 6 3], 1:[9 7 10 11 8]}}, {p458: {0:[4 2 1 6 5 3], 1:[9 7 11 10 8]}}, {p459: {0:[4 2 6 1 5 3], 1:[9 7 11 10 8]}}, {p460: {0:[4 6 2 1 5 3], 1:[9 11 7 10 8]}}, {p461: {0:[6 4 2 1 5 3], 1:[11 9 7 10 8]}}, {p462: {0:[4 2 5 1 3 6], 1:[9 7 10 8 11]}}, {p463: {0:[4 2 5 1 6 3], 1:[9 7 10 11 8]}}, {p464: {0:[4 2 5 6 1 3], 1:[9 7 10 11 8]}}, {p465: {0:[4 2 6 5 1 3], 1:[9 7 11 10 8]}}, {p466: {0:[4 6 2 5 1 3], 1:[9 11 7 10 8]}}, {p467: {0:[6 4 2 5 1 3], 1:[11 9 7 10 8]}}, {p468: {0:[4 5 2 1 3 6], 1:[9 10 7 8 11]}}, {p469: {0:[4 5 2 1 6 3], 1:[9 10 7 11 8]}}, {p470: {0:[4 5 2 6 1 3], 1:[9 10 7 11 8]}}, {p471: {0:[4 5 6 2 1 3], 1:[9 10 11 7 8]}}, {p472: {0:[4 6 5 2 1 3], 1:[9 11 10 7 8]}}, {p473: {0:[6 4 5 2 1 3], 1:[11 9 10 7 8]}}, {p474: {0:[5 4 2 1 3 6], 1:[10 9 7 8 11]}}, {p475: {0:[5 4 2 1 6 3], 1:[10 9 7 11 8]}}, {p476: {0:[5 4 2 6 1 3], 1:[10 9 7 11 8]}}, {p477: {0:[5 4 6 2 1 3], 1:[10 9 11 7 8]}}, {p478: {0:[5 6 4 2 1 3], 1:[10 11 9 7 8]}}, {p479: {0:[6 5 4 2 1 3], 1:[11 10 9 7 8]}}, {p480: {0:[2 3 1 4 5 6], 1:[7 8 9 10 11]}}, {p481: {0:[2 3 1 4 6 5], 1:[7 8 9 11 10]}}, {p482: {0:[2 3 1 6 4 5], 1:[7 8 11 9 10]}}, {p483: {0:[2 3 6 1 4 5], 1:[7 8 11 9 10]}}, {p484: {0:[2 6 3 1 4 5], 1:[7 11 8 9 10]}}, {p485: {0:[6 2 3 1 4 5], 1:[11 7 8 9 10]}}, {p486: {0:[2 3 1 5 4 6], 1:[7 8 10 9 11]}}, {p487: {0:[2 3 1 5 6 4], 1:[7 8 10 11 9]}}, {p488: {0:[2 3 1 6 5 4], 1:[7 8 11 10 9]}}, {p489: {0:[2 3 6 1 5 4], 1:[7 8 11 10 9]}}, {p490: {0:[2 6 3 1 5 4], 1:[7 11 8 10 9]}}, {p491: {0:[6 2 3 1 5 4], 1:[11 7 8 10 9]}}, {p492: {0:[2 3 5 1 4 6], 1:[7 8 10 9 11]}}, {p493: {0:[2 3 5 1 6 4], 1:[7 8 10 11 9]}}, {p494: {0:[2 3 5 6 1 4], 1:[7 8 10 11 9]}}, {p495: {0:[2 3 6 5 1 4], 1:[7 8 11 10 9]}}, {p496: {0:[2 6 3 5 1 4], 1:[7 11 8 10 9]}}, {p497: {0:[6 2 3 5 1 4], 1:[11 7 8 10 9]}}, {p498: {0:[2 5 3 1 4 6], 1:[7 10 8 9 11]}}, {p499: {0:[2 5 3 1 6 4], 1:[7 10 8 11 9]}}, {p500: {0:[2 5 3 6 1 4], 1:[7 10 8 11 9]}}, {p501: {0:[2 5 6 3 1 4], 1:[7 10 11 8 9]}}, {p502: {0:[2 6 5 3 1 4], 1:[7 11 10 8 9]}}, {p503: {0:[6 2 5 3 1 4], 1:[11 7 10 8 9]}}, {p504: {0:[5 2 3 1 4 6], 1:[10 7 8 9 11]}}, {p505: {0:[5 2 3 1 6 4], 1:[10 7 8 11 9]}}, {p506: {0:[5 2 3 6 1 4], 1:[10 7 8 11 9]}}, {p507: {0:[5 2 6 3 1 4], 1:[10 7 11 8 9]}}, {p508: {0:[5 6 2 3 1 4], 1:[10 11 7 8 9]}}, {p509: {0:[6 5 2 3 1 4], 1:[11 10 7 8 9]}}, {p510: {0:[2 3 4 1 5 6], 1:[7 8 9 10 11]}}, {p511: {0:[2 3 4 1 6 5], 1:[7 8 9 11 10]}}, {p512: {0:[2 3 4 6 1 5], 1:[7 8 9 11 10]}}, {p513: {0:[2 3 6 4 1 5], 1:[7 8 11 9 10]}}, {p514: {0:[2 6 3 4 1 5], 1:[7 11 8 9 10]}}, {p515: {0:[6 2 3 4 1 5], 1:[11 7 8 9 10]}}, {p516: {0:[2 3 4 5 1 6], 1:[7 8 9 10 11]}}, {p517: {0:[2 3 4 5 6 1], 1:[7 8 9 10 11]}}, {p518: {0:[2 3 4 6 5 1], 1:[7 8 9 11 10]}}, {p519: {0:[2 3 6 4 5 1], 1:[7 8 11 9 10]}}, {p520: {0:[2 6 3 4 5 1], 1:[7 11 8 9 10]}}, {p521: {0:[6 2 3 4 5 1], 1:[11 7 8 9 10]}}, {p522: {0:[2 3 5 4 1 6], 1:[7 8 10 9 11]}}, {p523: {0:[2 3 5 4 6 1], 1:[7 8 10 9 11]}}, {p524: {0:[2 3 5 6 4 1], 1:[7 8 10 11 9]}}, {p525: {0:[2 3 6 5 4 1], 1:[7 8 11 10 9]}}, {p526: {0:[2 6 3 5 4 1], 1:[7 11 8 10 9]}}, {p527: {0:[6 2 3 5 4 1], 1:[11 7 8 10 9]}}, {p528: {0:[2 5 3 4 1 6], 1:[7 10 8 9 11]}}, {p529: {0:[2 5 3 4 6 1], 1:[7 10 8 9 11]}}, {p530: {0:[2 5 3 6 4 1], 1:[7 10 8 11 9]}}, {p531: {0:[2 5 6 3 4 1], 1:[7 10 11 8 9]}}, {p532: {0:[2 6 5 3 4 1], 1:[7 11 10 8 9]}}, {p533: {0:[6 2 5 3 4 1], 1:[11 7 10 8 9]}}, {p534: {0:[5 2 3 4 1 6], 1:[10 7 8 9 11]}}, {p535: {0:[5 2 3 4 6 1], 1:[10 7 8 9 11]}}, {p536: {0:[5 2 3 6 4 1], 1:[10 7 8 11 9]}}, {p537: {0:[5 2 6 3 4 1], 1:[10 7 11 8 9]}}, {p538: {0:[5 6 2 3 4 1], 1:[10 11 7 8 9]}}, {p539: {0:[6 5 2 3 4 1], 1:[11 10 7 8 9]}}, {p540: {0:[2 4 3 1 5 6], 1:[7 9 8 10 11]}}, {p541: {0:[2 4 3 1 6 5], 1:[7 9 8 11 10]}}, {p542: {0:[2 4 3 6 1 5], 1:[7 9 8 11 10]}}, {p543: {0:[2 4 6 3 1 5], 1:[7 9 11 8 10]}}, {p544: {0:[2 6 4 3 1 5], 1:[7 11 9 8 10]}}, {p545: {0:[6 2 4 3 1 5], 1:[11 7 9 8 10]}}, {p546: {0:[2 4 3 5 1 6], 1:[7 9 8 10 11]}}, {p547: {0:[2 4 3 5 6 1], 1:[7 9 8 10 11]}}, {p548: {0:[2 4 3 6 5 1], 1:[7 9 8 11 10]}}, {p549: {0:[2 4 6 3 5 1], 1:[7 9 11 8 10]}}, {p550: {0:[2 6 4 3 5 1], 1:[7 11 9 8 10]}}, {p551: {0:[6 2 4 3 5 1], 1:[11 7 9 8 10]}}, {p552: {0:[2 4 5 3 1 6], 1:[7 9 10 8 11]}}, {p553: {0:[2 4 5 3 6 1], 1:[7 9 10 8 11]}}, {p554: {0:[2 4 5 6 3 1], 1:[7 9 10 11 8]}}, {p555: {0:[2 4 6 5 3 1], 1:[7 9 11 10 8]}}, {p556: {0:[2 6 4 5 3 1], 1:[7 11 9 10 8]}}, {p557: {0:[6 2 4 5 3 1], 1:[11 7 9 10 8]}}, {p558: {0:[2 5 4 3 1 6], 1:[7 10 9 8 11]}}, {p559: {0:[2 5 4 3 6 1], 1:[7 10 9 8 11]}}, {p560: {0:[2 5 4 6 3 1], 1:[7 10 9 11 8]}}, {p561: {0:[2 5 6 4 3 1], 1:[7 10 11 9 8]}}, {p562: {0:[2 6 5 4 3 1], 1:[7 11 10 9 8]}}, {p563: {0:[6 2 5 4 3 1], 1:[11 7 10 9 8]}}, {p564: {0:[5 2 4 3 1 6], 1:[10 7 9 8 11]}}, {p565: {0:[5 2 4 3 6 1], 1:[10 7 9 8 11]}}, {p566: {0:[5 2 4 6 3 1], 1:[10 7 9 11 8]}}, {p567: {0:[5 2 6 4 3 1], 1:[10 7 11 9 8]}}, {p568: {0:[5 6 2 4 3 1], 1:[10 11 7 9 8]}}, {p569: {0:[6 5 2 4 3 1], 1:[11 10 7 9 8]}}, {p570: {0:[4 2 3 1 5 6], 1:[9 7 8 10 11]}}, {p571: {0:[4 2 3 1 6 5], 1:[9 7 8 11 10]}}, {p572: {0:[4 2 3 6 1 5], 1:[9 7 8 11 10]}}, {p573: {0:[4 2 6 3 1 5], 1:[9 7 11 8 10]}}, {p574: {0:[4 6 2 3 1 5], 1:[9 11 7 8 10]}}, {p575: {0:[6 4 2 3 1 5], 1:[11 9 7 8 10]}}, {p576: {0:[4 2 3 5 1 6], 1:[9 7 8 10 11]}}, {p577: {0:[4 2 3 5 6 1], 1:[9 7 8 10 11]}}, {p578: {0:[4 2 3 6 5 1], 1:[9 7 8 11 10]}}, {p579: {0:[4 2 6 3 5 1], 1:[9 7 11 8 10]}}, {p580: {0:[4 6 2 3 5 1], 1:[9 11 7 8 10]}}, {p581: {0:[6 4 2 3 5 1], 1:[11 9 7 8 10]}}, {p582: {0:[4 2 5 3 1 6], 1:[9 7 10 8 11]}}, {p583: {0:[4 2 5 3 6 1], 1:[9 7 10 8 11]}}, {p584: {0:[4 2 5 6 3 1], 1:[9 7 10 11 8]}}, {p585: {0:[4 2 6 5 3 1], 1:[9 7 11 10 8]}}, {p586: {0:[4 6 2 5 3 1], 1:[9 11 7 10 8]}}, {p587: {0:[6 4 2 5 3 1], 1:[11 9 7 10 8]}}, {p588: {0:[4 5 2 3 1 6], 1:[9 10 7 8 11]}}, {p589: {0:[4 5 2 3 6 1], 1:[9 10 7 8 11]}}, {p590: {0:[4 5 2 6 3 1], 1:[9 10 7 11 8]}}, {p591: {0:[4 5 6 2 3 1], 1:[9 10 11 7 8]}}, {p592: {0:[4 6 5 2 3 1], 1:[9 11 10 7 8]}}, {p593: {0:[6 4 5 2 3 1], 1:[11 9 10 7 8]}}, {p594: {0:[5 4 2 3 1 6], 1:[10 9 7 8 11]}}, {p595: {0:[5 4 2 3 6 1], 1:[10 9 7 8 11]}}, {p596: {0:[5 4 2 6 3 1], 1:[10 9 7 11 8]}}, {p597: {0:[5 4 6 2 3 1], 1:[10 9 11 7 8]}}, {p598: {0:[5 6 4 2 3 1], 1:[10 11 9 7 8]}}, {p599: {0:[6 5 4 2 3 1], 1:[11 10 9 7 8]}}, {p600: {0:[3 2 1 4 5 6], 1:[8 7 9 10 11]}}, {p601: {0:[3 2 1 4 6 5], 1:[8 7 9 11 10]}}, {p602: {0:[3 2 1 6 4 5], 1:[8 7 11 9 10]}}, {p603: {0:[3 2 6 1 4 5], 1:[8 7 11 9 10]}}, {p604: {0:[3 6 2 1 4 5], 1:[8 11 7 9 10]}}, {p605: {0:[6 3 2 1 4 5], 1:[11 8 7 9 10]}}, {p606: {0:[3 2 1 5 4 6], 1:[8 7 10 9 11]}}, {p607: {0:[3 2 1 5 6 4], 1:[8 7 10 11 9]}}, {p608: {0:[3 2 1 6 5 4], 1:[8 7 11 10 9]}}, {p609: {0:[3 2 6 1 5 4], 1:[8 7 11 10 9]}}, {p610: {0:[3 6 2 1 5 4], 1:[8 11 7 10 9]}}, {p611: {0:[6 3 2 1 5 4], 1:[11 8 7 10 9]}}, {p612: {0:[3 2 5 1 4 6], 1:[8 7 10 9 11]}}, {p613: {0:[3 2 5 1 6 4], 1:[8 7 10 11 9]}}, {p614: {0:[3 2 5 6 1 4], 1:[8 7 10 11 9]}}, {p615: {0:[3 2 6 5 1 4], 1:[8 7 11 10 9]}}, {p616: {0:[3 6 2 5 1 4], 1:[8 11 7 10 9]}}, {p617: {0:[6 3 2 5 1 4], 1:[11 8 7 10 9]}}, {p618: {0:[3 5 2 1 4 6], 1:[8 10 7 9 11]}}, {p619: {0:[3 5 2 1 6 4], 1:[8 10 7 11 9]}}, {p620: {0:[3 5 2 6 1 4], 1:[8 10 7 11 9]}}, {p621: {0:[3 5 6 2 1 4], 1:[8 10 11 7 9]}}, {p622: {0:[3 6 5 2 1 4], 1:[8 11 10 7 9]}}, {p623: {0:[6 3 5 2 1 4], 1:[11 8 10 7 9]}}, {p624: {0:[5 3 2 1 4 6], 1:[10 8 7 9 11]}}, {p625: {0:[5 3 2 1 6 4], 1:[10 8 7 11 9]}}, {p626: {0:[5 3 2 6 1 4], 1:[10 8 7 11 9]}}, {p627: {0:[5 3 6 2 1 4], 1:[10 8 11 7 9]}}, {p628: {0:[5 6 3 2 1 4], 1:[10 11 8 7 9]}}, {p629: {0:[6 5 3 2 1 4], 1:[11 10 8 7 9]}}, {p630: {0:[3 2 4 1 5 6], 1:[8 7 9 10 11]}}, {p631: {0:[3 2 4 1 6 5], 1:[8 7 9 11 10]}}, {p632: {0:[3 2 4 6 1 5], 1:[8 7 9 11 10]}}, {p633: {0:[3 2 6 4 1 5], 1:[8 7 11 9 10]}}, {p634: {0:[3 6 2 4 1 5], 1:[8 11 7 9 10]}}, {p635: {0:[6 3 2 4 1 5], 1:[11 8 7 9 10]}}, {p636: {0:[3 2 4 5 1 6], 1:[8 7 9 10 11]}}, {p637: {0:[3 2 4 5 6 1], 1:[8 7 9 10 11]}}, {p638: {0:[3 2 4 6 5 1], 1:[8 7 9 11 10]}}, {p639: {0:[3 2 6 4 5 1], 1:[8 7 11 9 10]}}, {p640: {0:[3 6 2 4 5 1], 1:[8 11 7 9 10]}}, {p641: {0:[6 3 2 4 5 1], 1:[11 8 7 9 10]}}, {p642: {0:[3 2 5 4 1 6], 1:[8 7 10 9 11]}}, {p643: {0:[3 2 5 4 6 1], 1:[8 7 10 9 11]}}, {p644: {0:[3 2 5 6 4 1], 1:[8 7 10 11 9]}}, {p645: {0:[3 2 6 5 4 1], 1:[8 7 11 10 9]}}, {p646: {0:[3 6 2 5 4 1], 1:[8 11 7 10 9]}}, {p647: {0:[6 3 2 5 4 1], 1:[11 8 7 10 9]}}, {p648: {0:[3 5 2 4 1 6], 1:[8 10 7 9 11]}}, {p649: {0:[3 5 2 4 6 1], 1:[8 10 7 9 11]}}, {p650: {0:[3 5 2 6 4 1], 1:[8 10 7 11 9]}}, {p651: {0:[3 5 6 2 4 1], 1:[8 10 11 7 9]}}, {p652: {0:[3 6 5 2 4 1], 1:[8 11 10 7 9]}}, {p653: {0:[6 3 5 2 4 1], 1:[11 8 10 7 9]}}, {p654: {0:[5 3 2 4 1 6], 1:[10 8 7 9 11]}}, {p655: {0:[5 3 2 4 6 1], 1:[10 8 7 9 11]}}, {p656: {0:[5 3 2 6 4 1], 1:[10 8 7 11 9]}}, {p657: {0:[5 3 6 2 4 1], 1:[10 8 11 7 9]}}, {p658: {0:[5 6 3 2 4 1], 1:[10 11 8 7 9]}}, {p659: {0:[6 5 3 2 4 1], 1:[11 10 8 7 9]}}, {p660: {0:[3 4 2 1 5 6], 1:[8 9 7 10 11]}}, {p661: {0:[3 4 2 1 6 5], 1:[8 9 7 11 10]}}, {p662: {0:[3 4 2 6 1 5], 1:[8 9 7 11 10]}}, {p663: {0:[3 4 6 2 1 5], 1:[8 9 11 7 10]}}, {p664: {0:[3 6 4 2 1 5], 1:[8 11 9 7 10]}}, {p665: {0:[6 3 4 2 1 5], 1:[11 8 9 7 10]}}, {p666: {0:[3 4 2 5 1 6], 1:[8 9 7 10 11]}}, {p667: {0:[3 4 2 5 6 1], 1:[8 9 7 10 11]}}, {p668: {0:[3 4 2 6 5 1], 1:[8 9 7 11 10]}}, {p669: {0:[3 4 6 2 5 1], 1:[8 9 11 7 10]}}, {p670: {0:[3 6 4 2 5 1], 1:[8 11 9 7 10]}}, {p671: {0:[6 3 4 2 5 1], 1:[11 8 9 7 10]}}, {p672: {0:[3 4 5 2 1 6], 1:[8 9 10 7 11]}}, {p673: {0:[3 4 5 2 6 1], 1:[8 9 10 7 11]}}, {p674: {0:[3 4 5 6 2 1], 1:[8 9 10 11 7]}}, {p675: {0:[3 4 6 5 2 1], 1:[8 9 11 10 7]}}, {p676: {0:[3 6 4 5 2 1], 1:[8 11 9 10 7]}}, {p677: {0:[6 3 4 5 2 1], 1:[11 8 9 10 7]}}, {p678: {0:[3 5 4 2 1 6], 1:[8 10 9 7 11]}}, {p679: {0:[3 5 4 2 6 1], 1:[8 10 9 7 11]}}, {p680: {0:[3 5 4 6 2 1], 1:[8 10 9 11 7]}}, {p681: {0:[3 5 6 4 2 1], 1:[8 10 11 9 7]}}, {p682: {0:[3 6 5 4 2 1], 1:[8 11 10 9 7]}}, {p683: {0:[6 3 5 4 2 1], 1:[11 8 10 9 7]}}, {p684: {0:[5 3 4 2 1 6], 1:[10 8 9 7 11]}}, {p685: {0:[5 3 4 2 6 1], 1:[10 8 9 7 11]}}, {p686: {0:[5 3 4 6 2 1], 1:[10 8 9 11 7]}}, {p687: {0:[5 3 6 4 2 1], 1:[10 8 11 9 7]}}, {p688: {0:[5 6 3 4 2 1], 1:[10 11 8 9 7]}}, {p689: {0:[6 5 3 4 2 1], 1:[11 10 8 9 7]}}, {p690: {0:[4 3 2 1 5 6], 1:[9 8 7 10 11]}}, {p691: {0:[4 3 2 1 6 5], 1:[9 8 7 11 10]}}, {p692: {0:[4 3 2 6 1 5], 1:[9 8 7 11 10]}}, {p693: {0:[4 3 6 2 1 5], 1:[9 8 11 7 10]}}, {p694: {0:[4 6 3 2 1 5], 1:[9 11 8 7 10]}}, {p695: {0:[6 4 3 2 1 5], 1:[11 9 8 7 10]}}, {p696: {0:[4 3 2 5 1 6], 1:[9 8 7 10 11]}}, {p697: {0:[4 3 2 5 6 1], 1:[9 8 7 10 11]}}, {p698: {0:[4 3 2 6 5 1], 1:[9 8 7 11 10]}}, {p699: {0:[4 3 6 2 5 1], 1:[9 8 11 7 10]}}, {p700: {0:[4 6 3 2 5 1], 1:[9 11 8 7 10]}}, {p701: {0:[6 4 3 2 5 1], 1:[11 9 8 7 10]}}, {p702: {0:[4 3 5 2 1 6], 1:[9 8 10 7 11]}}, {p703: {0:[4 3 5 2 6 1], 1:[9 8 10 7 11]}}, {p704: {0:[4 3 5 6 2 1], 1:[9 8 10 11 7]}}, {p705: {0:[4 3 6 5 2 1], 1:[9 8 11 10 7]}}, {p706: {0:[4 6 3 5 2 1], 1:[9 11 8 10 7]}}, {p707: {0:[6 4 3 5 2 1], 1:[11 9 8 10 7]}}, {p708: {0:[4 5 3 2 1 6], 1:[9 10 8 7 11]}}, {p709: {0:[4 5 3 2 6 1], 1:[9 10 8 7 11]}}, {p710: {0:[4 5 3 6 2 1], 1:[9 10 8 11 7]}}, {p711: {0:[4 5 6 3 2 1], 1:[9 10 11 8 7]}}, {p712: {0:[4 6 5 3 2 1], 1:[9 11 10 8 7]}}, {p713: {0:[6 4 5 3 2 1], 1:[11 9 10 8 7]}}, {p714: {0:[5 4 3 2 1 6], 1:[10 9 8 7 11]}}, {p715: {0:[5 4 3 2 6 1], 1:[10 9 8 7 11]}}, {p716: {0:[5 4 3 6 2 1], 1:[10 9 8 11 7]}}, {p717: {0:[5 4 6 3 2 1], 1:[10 9 11 8 7]}}, {p718: {0:[5 6 4 3 2 1], 1:[10 11 9 8 7]}}, {p719: {0:[6 5 4 3 2 1], 1:[11 10 9 8 7]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}, {Id:2 RootId:2 Layer:0 X:72.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[7]}, {Id:3 RootId:3 Layer:0 X:144.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[8]}, {Id:4 RootId:4 Layer:0 X:216.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[9]}, {Id:5 RootId:5 Layer:0 X:288.00 Y:0.00 TotalW:64.00 W:52.00 H:60.00 In:[] PriRootOut:[10]}, {Id:6 RootId:6 Layer:0 X:372.00 Y:0.00 TotalW:64.00 W:52.00 H:60.00 In:[] PriRootOut:[11]}, {Id:7 RootId:2 Layer:1 X:72.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:8 RootId:3 Layer:1 X:144.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:9 RootId:4 Layer:1 X:216.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:10 RootId:5 Layer:1 X:288.00 Y:276.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:11 RootId:6 Layer:1 X:372.00 Y:276.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:384.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[]}, {Id:2 RootId:2 Layer:0 X:312.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[7]}, {Id:3 RootId:3 Layer:0 X:240.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[8]}, {Id:4 RootId:4 Layer:0 X:168.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[9]}, {Id:5 RootId:5 Layer:0 X:84.00 Y:0.00 TotalW:64.00 W:52.00 H:60.00 In:[] PriRootOut:[10]}, {Id:6 RootId:6 Layer:0 X:0.00 Y:0.00 TotalW:64.00 W:52.00 H:60.00 In:[] PriRootOut:[11]}, {Id:7 RootId:2 Layer:1 X:312.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:8 RootId:3 Layer:1 X:240.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:9 RootId:4 Layer:1 X:168.00 Y:276.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:10 RootId:5 Layer:1 X:84.00 Y:276.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}, {Id:11 RootId:6 Layer:1 X:0.00 Y:276.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:1 X:0.00 Y:0.00 W:37.08 H:36.00}] PriRootOut:[]}")
+}
+
+func TestLayerLongRootsMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsLayerLongRoots,
+ "[-4 2 3 4 5 0 1 2 3 3 4 5 6]",
+ "0:[5], 1:[6], 2:[1 7], 3:[2 8 9], 4:[3 10], 5:[4 11], 6:[12]",
+ int64(6),
+ "{p0: {0:[5], 1:[6], 2:[7 1], 3:[8 2 9], 4:[3 10], 5:[4 11], 6:[12]}}, {p1: {0:[5], 1:[6], 2:[7 1], 3:[8 9 2], 4:[10 3], 5:[11 4], 6:[12]}}, {p2: {0:[5], 1:[6], 2:[7 1], 3:[9 8 2], 4:[10 3], 5:[11 4], 6:[12]}}, {p3: {0:[5], 1:[6], 2:[1 7], 3:[2 8 9], 4:[3 10], 5:[4 11], 6:[12]}}, {p4: {0:[5], 1:[6], 2:[1 7], 3:[2 9 8], 4:[3 10], 5:[4 11], 6:[12]}}, {p5: {0:[5], 1:[6], 2:[1 7], 3:[9 2 8], 4:[10 3], 5:[11 4], 6:[12]}}",
+ "{Id:1 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:5 X:72.00 Y:600.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:5 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[6]}, {Id:6 RootId:5 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:5 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[8]}, {Id:8 RootId:5 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:7 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:9 RootId:9 Layer:3 X:144.00 Y:360.00 TotalW:64.00 W:52.00 H:60.00 In:[] PriRootOut:[10]}, {Id:10 RootId:9 Layer:4 X:144.00 Y:480.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:9 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[11]}, {Id:11 RootId:9 Layer:5 X:144.00 Y:600.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:10 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[12]}, {Id:12 RootId:9 Layer:6 X:144.00 Y:720.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:11 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:2 X:72.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3]}, {Id:3 RootId:1 Layer:4 X:72.00 Y:480.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:8 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4]}, {Id:4 RootId:1 Layer:5 X:72.00 Y:600.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:5 Layer:0 X:0.00 Y:0.00 TotalW:52.00 W:52.00 H:60.00 In:[] PriRootOut:[6]}, {Id:6 RootId:5 Layer:1 X:0.00 Y:120.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:5 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7]}, {Id:7 RootId:5 Layer:2 X:0.00 Y:240.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[8]}, {Id:8 RootId:5 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:7 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:9 RootId:9 Layer:3 X:144.00 Y:360.00 TotalW:64.00 W:52.00 H:60.00 In:[] PriRootOut:[10]}, {Id:10 RootId:9 Layer:4 X:144.00 Y:480.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:9 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[11]}, {Id:11 RootId:9 Layer:5 X:144.00 Y:600.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:10 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[12]}, {Id:12 RootId:9 Layer:6 X:144.00 Y:720.00 TotalW:64.00 W:64.00 H:60.00 In:[{HT:pri SrcId:11 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:4 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func TestPriAndSecInfinitePulldownMxPermutator(t *testing.T) {
+ helperAll(t,
+ testNodeDefsPriAndSecInfinitePulldown,
+ "[-4 0 1 2 3 3 2 3 3]",
+ "0:[1], 1:[2], 2:[3 6], 3:[4 5 7 8]",
+ int64(4),
+ "{p0: {0:[1], 1:[2], 2:[3 6], 3:[4 5 7 8]}}, {p1: {0:[1], 1:[2], 2:[3 6], 3:[4 5 8 7]}}, {p2: {0:[1], 1:[2], 2:[6 3], 3:[7 8 4 5]}}, {p3: {0:[1], 1:[2], 2:[6 3], 3:[7 8 5 4]}}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:268.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:268.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[3 6]}, {Id:3 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:124.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[4 5]}, {Id:4 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:1 Layer:2 X:144.00 Y:240.00 TotalW:124.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7 8]}, {Id:7 RootId:1 Layer:3 X:144.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:8 RootId:1 Layer:3 X:216.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}",
+ "{Id:1 RootId:1 Layer:0 X:0.00 Y:0.00 TotalW:268.00 W:52.00 H:60.00 In:[] PriRootOut:[2]}, {Id:2 RootId:1 Layer:1 X:0.00 Y:120.00 TotalW:268.00 W:52.00 H:60.00 In:[{HT:pri SrcId:1 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[6 3]}, {Id:3 RootId:1 Layer:2 X:144.00 Y:240.00 TotalW:124.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[5 4]}, {Id:4 RootId:1 Layer:3 X:216.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:5 RootId:1 Layer:3 X:144.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:3 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:6 RootId:1 Layer:2 X:0.00 Y:240.00 TotalW:124.00 W:52.00 H:60.00 In:[{HT:pri SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[7 8]}, {Id:7 RootId:1 Layer:3 X:0.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}, {Id:8 RootId:1 Layer:3 X:72.00 Y:360.00 TotalW:52.00 W:52.00 H:60.00 In:[{HT:pri SrcId:6 X:0.00 Y:0.00 W:0.00 H:0.00} {HT:sec SrcId:2 X:0.00 Y:0.00 W:0.00 H:0.00}] PriRootOut:[]}")
+}
+
+func Test40milPermsMxPermutator(t *testing.T) {
+ // defer profile.Start(profile.CPUProfile).Stop()
+ helperIteratorAndIncrementalCount(t,
+ testNodeDefs40milPerms,
+ "[-4 0 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1]",
+ "0:[1], 1:[2 3 4 20], 2:[5 6 7 8 9 10 11 12 13 14 15 16 17 18 19]",
+ int64(41472000),
+ int64(41472000))
+}
+
+func Test300bilPermsMxPermutator(t *testing.T) {
+ layerMap := buildLayerMap(testNodeDefs300bilPerms)
+ assert.Equal(t, "[-4 0 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1]", fmt.Sprintf("%v", layerMap))
+
+ rootNodes := buildRootNodeList(buildPriParentMap(testNodeDefs300bilPerms))
+ mx, _ := NewLayerMx(testNodeDefs300bilPerms, layerMap, rootNodes)
+ assert.Equal(t, "0:[1], 1:[2 3 4 22 23 24 25], 2:[5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21]", mx.String())
+
+ _, err := NewLayerMxPermIterator(testNodeDefs300bilPerms, mx)
+ assert.Contains(t, err.Error(), "313528320000")
+}
diff --git a/pkg/capigraph/layer_mx_test.go b/pkg/capigraph/layer_mx_test.go
new file mode 100644
index 00000000..75a8a7ab
--- /dev/null
+++ b/pkg/capigraph/layer_mx_test.go
@@ -0,0 +1,229 @@
+package capigraph
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// 0: 1
+// - | \
+// 1: 2 4
+// - | |
+// 2: 3 5
+func TestTrivial(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ }
+ priParentMap := buildPriParentMap(nodeDefs)
+
+ mx := LayerMx{
+ {1},
+ {2, 4},
+ {3, 5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1},
+ {2, 4},
+ {5, 3},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+}
+
+// 0: 1
+// - | \
+// 1: 2 4
+// - | |
+// 2: F3 5
+// - | /
+// 3: 3
+func TestOneFake(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{{5, ""}}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ }
+ priParentMap := buildPriParentMap(nodeDefs)
+
+ mx := LayerMx{
+ {1},
+ {2, 4},
+ {10003, 5},
+ {3},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1},
+ {4, 2},
+ {10003, 5},
+ {3},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+}
+
+// 0: 1
+// - | \
+// 1: 2 4
+// - | |
+// 2: F3 5
+// - | |
+// 3: F3 6
+// - | /
+// 4: 3
+func TestTwoFake(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{5, ""}, []EdgeDef{}, "", 0, false},
+ }
+ priParentMap := buildPriParentMap(nodeDefs)
+ mx := LayerMx{
+ {1},
+ {2, 4},
+ {10003, 5},
+ {10003, 6},
+ {3},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1},
+ {4, 2},
+ {10003, 5},
+ {6, 10003},
+ {3},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1},
+ {2, 4},
+ {5, 10003},
+ {10003, 6},
+ {3},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+}
+
+// 0: 1 3
+// - | |
+// 1: 2 4 6
+// - | /
+// 2: 5
+func TestParentless(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ }
+ priParentMap := buildPriParentMap(nodeDefs)
+ var mx LayerMx
+
+ mx = LayerMx{
+ {3, 1},
+ {6, 4, 2},
+ {5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {3, 1},
+ {4, 6, 2},
+ {5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {3, 1},
+ {4, 2, 6},
+ {5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {3, 1},
+ {6, 2, 4},
+ {5},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {3, 1},
+ {2, 6, 4},
+ {5},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {3, 1},
+ {2, 4, 6},
+ {5},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1, 3},
+ {2, 4, 6},
+ {5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1, 3},
+ {6, 2, 4},
+ {5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1, 3},
+ {2, 6, 4},
+ {5},
+ }
+ assert.True(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1, 3},
+ {4, 2, 6},
+ {5},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1, 3},
+ {4, 6, 2},
+ {5},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+
+ mx = LayerMx{
+ {1, 3},
+ {6, 4, 2},
+ {5},
+ }
+ assert.False(t, mx.isMonotonous(priParentMap, len(nodeDefs)))
+}
+
+func TestSignature(t *testing.T) {
+ mx := LayerMx{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32, 64, 128, 256, 512, 1024, 1025}}
+ assert.Equal(t, "0000000100020003000400050006000700080009000A000B000C000D000E000F00100020004000800100020004000401", mx.signature())
+}
diff --git a/pkg/capigraph/layer_permutator.go b/pkg/capigraph/layer_permutator.go
new file mode 100644
index 00000000..23a18f12
--- /dev/null
+++ b/pkg/capigraph/layer_permutator.go
@@ -0,0 +1,250 @@
+package capigraph
+
+import (
+ "fmt"
+)
+
+func swap(a []int16, i1 int, i2 int) {
+ t := a[i2]
+ a[i2] = a[i1]
+ a[i1] = t
+}
+
+const MaxSupportedFact int = 6 // Max amount of pri children originating from a node
+const MaxNodesToInsert int = 10 // Cannot insert more than this amt of new roots at a layer
+const MaxIntervalsInLayer int = 20 // Max parent nodes containing more than one child on this layer
+const MaxLayerLen int = 200
+
+type LayerPermutator struct {
+ P [][][]int // Swap permutation strategy by permIdx: [interval size 2-6][permIdx 2!-6!][positions to swap with]
+ Fact []int
+ SrcPerm []int16
+ WorkPerm []int16
+ IntervalStarts []int
+ IntervalLengths []int
+}
+
+func NewLayerPermutator() *LayerPermutator {
+ lp := LayerPermutator{}
+ lp.init()
+ return &lp
+}
+
+func (lp *LayerPermutator) insertPermutationByIdx(perm []int16, itemToInsertSrcIdx int, permIdx int) {
+ if permIdx == itemToInsertSrcIdx {
+ // Identity permutation, totally valid
+ return
+ }
+ if permIdx > itemToInsertSrcIdx {
+ panic(fmt.Sprintf("insertPermutationByIdx(): cannot move item %d from % d to %d", perm[itemToInsertSrcIdx], itemToInsertSrcIdx, permIdx))
+ }
+ idToInsert := perm[itemToInsertSrcIdx]
+ // Shift
+ copy(perm[permIdx+1:], perm[permIdx:itemToInsertSrcIdx])
+ // Set the item to be inserted
+ perm[permIdx] = idToInsert
+}
+
+// Execute swap perm strategy for a specific intervalLen and permIdx
+
+func (lp *LayerPermutator) swapPermutationByIdx(perm []int16, firstIdx int, intervalLen int, permIdx int) {
+ if intervalLen > MaxSupportedFact {
+ panic(fmt.Sprintf("permutationByIdx(): factorial value not supported %d, max supported %d", intervalLen, MaxSupportedFact))
+ }
+ if intervalLen == 2 {
+ if intervalLen-1 > lp.P[2][permIdx][0] {
+ swap(perm, firstIdx+intervalLen-1, firstIdx+lp.P[2][permIdx][0])
+ }
+ } else if intervalLen == 3 {
+ if intervalLen-2 > lp.P[3][permIdx][0] {
+ swap(perm, firstIdx+intervalLen-2, firstIdx+lp.P[3][permIdx][0])
+ }
+ if intervalLen-1 > lp.P[3][permIdx][1] {
+ swap(perm, firstIdx+intervalLen-1, firstIdx+lp.P[3][permIdx][1])
+ }
+ } else if intervalLen == 4 {
+ if intervalLen-3 > lp.P[4][permIdx][0] {
+ swap(perm, firstIdx+intervalLen-3, firstIdx+lp.P[4][permIdx][0])
+ }
+ if intervalLen-2 > lp.P[4][permIdx][1] {
+ swap(perm, firstIdx+intervalLen-2, firstIdx+lp.P[4][permIdx][1])
+ }
+ if intervalLen-1 > lp.P[4][permIdx][2] {
+ swap(perm, firstIdx+intervalLen-1, firstIdx+lp.P[4][permIdx][2])
+ }
+ } else if intervalLen == 5 {
+ if intervalLen-4 > lp.P[5][permIdx][0] {
+ swap(perm, firstIdx+intervalLen-4, firstIdx+lp.P[5][permIdx][0])
+ }
+ if intervalLen-3 > lp.P[5][permIdx][1] {
+ swap(perm, firstIdx+intervalLen-3, firstIdx+lp.P[5][permIdx][1])
+ }
+ if intervalLen-2 > lp.P[5][permIdx][2] {
+ swap(perm, firstIdx+intervalLen-2, firstIdx+lp.P[5][permIdx][2])
+ }
+ if intervalLen-1 > lp.P[5][permIdx][3] {
+ swap(perm, firstIdx+intervalLen-1, firstIdx+lp.P[5][permIdx][3])
+ }
+ } else if intervalLen == 6 {
+ if intervalLen-5 > lp.P[6][permIdx][0] {
+ swap(perm, firstIdx+intervalLen-5, firstIdx+lp.P[6][permIdx][0])
+ }
+ if intervalLen-4 > lp.P[6][permIdx][1] {
+ swap(perm, firstIdx+intervalLen-4, firstIdx+lp.P[6][permIdx][1])
+ }
+ if intervalLen-3 > lp.P[6][permIdx][2] {
+ swap(perm, firstIdx+intervalLen-3, firstIdx+lp.P[6][permIdx][2])
+ }
+ if intervalLen-2 > lp.P[6][permIdx][3] {
+ swap(perm, firstIdx+intervalLen-2, firstIdx+lp.P[6][permIdx][3])
+ }
+ if intervalLen-1 > lp.P[6][permIdx][4] {
+ swap(perm, firstIdx+intervalLen-1, firstIdx+lp.P[6][permIdx][4])
+ }
+ }
+}
+
+func (lp *LayerPermutator) init() {
+ lp.Fact = make([]int, MaxSupportedFact+1)
+ acc := 1
+ for i := range MaxSupportedFact + 1 {
+ lp.Fact[i] = acc
+ acc *= (i + 1)
+ }
+
+ lp.P = make([][][]int, MaxSupportedFact+1)
+ i := 2
+ for i <= 6 {
+ lp.P[i] = make([][]int, lp.Fact[i])
+ p := lp.P[i]
+ for j := range len(p) {
+ p[j] = make([]int, i-1)
+ }
+
+ k := 0
+ for k < i-1 {
+ streakSize := len(p) / lp.Fact[k+2]
+ maxStreakVal := k + 1
+ curVal := 0
+ curPosWithinStreak := 0
+
+ for j := range len(p) {
+ p[j][k] = curVal
+ curPosWithinStreak++
+ if curPosWithinStreak == streakSize {
+ curPosWithinStreak = 0
+ curVal++
+ }
+ if curVal == maxStreakVal+1 {
+ curVal = 0
+ }
+ }
+ k++
+ }
+ i++
+ }
+}
+
+func (lp *LayerPermutator) initSource(in []int16) {
+ lp.SrcPerm = in
+ if lp.WorkPerm == nil {
+ lp.WorkPerm = make([]int16, len(lp.SrcPerm), MaxLayerLen) // Assume this capacity is enough
+ } else if len(lp.WorkPerm) != len(lp.SrcPerm) {
+ lp.WorkPerm = lp.WorkPerm[:len(lp.SrcPerm)] // Shrink, or grow, hopefully no reallocation
+ }
+ copy(lp.WorkPerm, lp.SrcPerm)
+}
+
+func (lp *LayerPermutator) initIntervals(permIntervalStarts []int, permIntervalLengths []int, totalIntervals int) {
+ lp.IntervalStarts = make([]int, MaxIntervalsInLayer)
+ lp.IntervalLengths = make([]int, MaxIntervalsInLayer)
+ lp.IntervalStarts = lp.IntervalStarts[:totalIntervals]
+ copy(lp.IntervalStarts, permIntervalStarts[:totalIntervals])
+ lp.IntervalLengths = lp.IntervalLengths[:totalIntervals]
+ copy(lp.IntervalLengths, permIntervalLengths[:totalIntervals])
+}
+
+func (lp *LayerPermutator) SwapIterator(intervalStarts []int, intervalLengths []int, totalIntervals int, in []int16, f func(int, []int16)) {
+ lp.initSource(in)
+ lp.initIntervals(intervalStarts, intervalLengths, totalIntervals)
+ swapFuncIteratorRecursive(lp, 0, 0, f)
+}
+
+func swapFuncIteratorRecursive(lp *LayerPermutator, intervalIdx int, totalCnt int, f func(int, []int16)) {
+ cb := func(int, []int16) {
+ f(totalCnt, lp.WorkPerm)
+ totalCnt++
+ }
+ intStart := lp.IntervalStarts[intervalIdx]
+ intLen := lp.IntervalLengths[intervalIdx]
+ if intLen > MaxSupportedFact {
+ panic(fmt.Sprintf("swapFuncIteratorRecursive: swap interval too big: %d, max supported %d", intLen, MaxSupportedFact))
+ }
+ // Apply permutation backwards. The only reason for this is:
+ // the "original, untouched" permutation ("swap nothing") is the last one in the permutation table,
+ // and that is the one we want to to win all other factors equal. So it goes first.
+ i := lp.Fact[intLen] - 1
+ for i >= 0 {
+ // Re-initialize slice interval we work with
+ copy(lp.WorkPerm[intStart:], lp.SrcPerm[intStart:intStart+intLen])
+ // Swap in-place
+ lp.swapPermutationByIdx(lp.WorkPerm, intStart, intLen, i)
+ if intervalIdx == len(lp.IntervalStarts)-1 {
+ f(totalCnt, lp.WorkPerm)
+ totalCnt++
+ } else {
+ swapFuncIteratorRecursive(lp, intervalIdx+1, totalCnt, cb)
+ }
+ i--
+ }
+}
+
+func (lp *LayerPermutator) InsertIterator(insertSrcStart int, insertSrcLen int, in []int16, f func(int, []int16)) {
+ lp.initSource(in)
+ insertFuncIteratorRecursive(lp, insertSrcStart, insertSrcLen, insertSrcStart, 0, f)
+}
+
+func insertFuncIteratorRecursive(lp *LayerPermutator, insertSrcStart int, insertSrcLen int, curInsertSrc int, totalCnt int, f func(int, []int16)) {
+ if insertSrcLen > MaxNodesToInsert {
+ panic(fmt.Sprintf("insertFuncIteratorRecursive: too many ids to insert: %d, max supported %d", insertSrcLen, MaxNodesToInsert))
+ }
+ cb := func(int, []int16) {
+ f(totalCnt, lp.WorkPerm)
+ totalCnt++
+ }
+ backupBetweenInserts := []int16{MaxLayerLen: int16(0)}[:len(lp.WorkPerm)]
+ // Apply permutation backwards. The only reason for this is:
+ // the "original, untouched" permutation ("insert nothing") is the last one,
+ // and that is the one we want to to win all other factors equal. So it goes first.
+ permIdx := curInsertSrc
+ for permIdx >= 0 {
+ copy(backupBetweenInserts, lp.WorkPerm)
+ // Shift-insert in-place
+ lp.insertPermutationByIdx(lp.WorkPerm, curInsertSrc, permIdx)
+ if curInsertSrc == insertSrcStart+insertSrcLen-1 {
+ f(totalCnt, lp.WorkPerm)
+ totalCnt++
+ } else {
+ insertFuncIteratorRecursive(lp, insertSrcStart, insertSrcLen, curInsertSrc+1, totalCnt, cb)
+ }
+ copy(lp.WorkPerm, backupBetweenInserts)
+ permIdx--
+ }
+}
+
+func (lp *LayerPermutator) SwapAndInsertIterator(intervalStarts []int, intervalLengths []int, totalIntervals int, insertSrcStart int, insertSrcLen int, in []int16, f func(int, []int16)) {
+ lp.initSource(in)
+ lp.initIntervals(intervalStarts, intervalLengths, totalIntervals)
+ swapAndInsertFuncIterator(lp, 0, insertSrcStart, insertSrcLen, insertSrcStart, 0, f)
+}
+
+func swapAndInsertFuncIterator(lp *LayerPermutator, intervalIdx int, insertSrcStart int, insertSrcLen int, curInsertSrc int, totalCnt int, f func(int, []int16)) {
+ realTotalCnt := 0
+ cb := func(int, []int16) {
+ f(realTotalCnt, lp.WorkPerm)
+ realTotalCnt++
+ }
+ swapFuncIteratorRecursive(lp, intervalIdx, totalCnt, func(int, []int16) {
+ insertFuncIteratorRecursive(lp, insertSrcStart, insertSrcLen, curInsertSrc, 0, cb)
+ })
+}
diff --git a/pkg/capigraph/layer_permutator_test.go b/pkg/capigraph/layer_permutator_test.go
new file mode 100644
index 00000000..a8ff87fd
--- /dev/null
+++ b/pkg/capigraph/layer_permutator_test.go
@@ -0,0 +1,87 @@
+package capigraph
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+var testPermStrategies6 = "[null,null,[[0],[1]],[[0,0],[0,1],[0,2],[1,0],[1,1],[1,2]],[[0,0,0],[0,0,1],[0,0,2],[0,0,3],[0,1,0],[0,1,1],[0,1,2],[0,1,3],[0,2,0],[0,2,1],[0,2,2],[0,2,3],[1,0,0],[1,0,1],[1,0,2],[1,0,3],[1,1,0],[1,1,1],[1,1,2],[1,1,3],[1,2,0],[1,2,1],[1,2,2],[1,2,3]],[[0,0,0,0],[0,0,0,1],[0,0,0,2],[0,0,0,3],[0,0,0,4],[0,0,1,0],[0,0,1,1],[0,0,1,2],[0,0,1,3],[0,0,1,4],[0,0,2,0],[0,0,2,1],[0,0,2,2],[0,0,2,3],[0,0,2,4],[0,0,3,0],[0,0,3,1],[0,0,3,2],[0,0,3,3],[0,0,3,4],[0,1,0,0],[0,1,0,1],[0,1,0,2],[0,1,0,3],[0,1,0,4],[0,1,1,0],[0,1,1,1],[0,1,1,2],[0,1,1,3],[0,1,1,4],[0,1,2,0],[0,1,2,1],[0,1,2,2],[0,1,2,3],[0,1,2,4],[0,1,3,0],[0,1,3,1],[0,1,3,2],[0,1,3,3],[0,1,3,4],[0,2,0,0],[0,2,0,1],[0,2,0,2],[0,2,0,3],[0,2,0,4],[0,2,1,0],[0,2,1,1],[0,2,1,2],[0,2,1,3],[0,2,1,4],[0,2,2,0],[0,2,2,1],[0,2,2,2],[0,2,2,3],[0,2,2,4],[0,2,3,0],[0,2,3,1],[0,2,3,2],[0,2,3,3],[0,2,3,4],[1,0,0,0],[1,0,0,1],[1,0,0,2],[1,0,0,3],[1,0,0,4],[1,0,1,0],[1,0,1,1],[1,0,1,2],[1,0,1,3],[1,0,1,4],[1,0,2,0],[1,0,2,1],[1,0,2,2],[1,0,2,3],[1,0,2,4],[1,0,3,0],[1,0,3,1],[1,0,3,2],[1,0,3,3],[1,0,3,4],[1,1,0,0],[1,1,0,1],[1,1,0,2],[1,1,0,3],[1,1,0,4],[1,1,1,0],[1,1,1,1],[1,1,1,2],[1,1,1,3],[1,1,1,4],[1,1,2,0],[1,1,2,1],[1,1,2,2],[1,1,2,3],[1,1,2,4],[1,1,3,0],[1,1,3,1],[1,1,3,2],[1,1,3,3],[1,1,3,4],[1,2,0,0],[1,2,0,1],[1,2,0,2],[1,2,0,3],[1,2,0,4],[1,2,1,0],[1,2,1,1],[1,2,1,2],[1,2,1,3],[1,2,1,4],[1,2,2,0],[1,2,2,1],[1,2,2,2],[1,2,2,3],[1,2,2,4],[1,2,3,0],[1,2,3,1],[1,2,3,2],[1,2,3,3],[1,2,3,4]],[[0,0,0,0,0],[0,0,0,0,1],[0,0,0,0,2],[0,0,0,0,3],[0,0,0,0,4],[0,0,0,0,5],[0,0,0,1,0],[0,0,0,1,1],[0,0,0,1,2],[0,0,0,1,3],[0,0,0,1,4],[0,0,0,1,5],[0,0,0,2,0],[0,0,0,2,1],[0,0,0,2,2],[0,0,0,2,3],[0,0,0,2,4],[0,0,0,2,5],[0,0,0,3,0],[0,0,0,3,1],[0,0,0,3,2],[0,0,0,3,3],[0,0,0,3,4],[0,0,0,3,5],[0,0,0,4,0],[0,0,0,4,1],[0,0,0,4,2],[0,0,0,4,3],[0,0,0,4,4],[0,0,0,4,5],[0,0,1,0,0],[0,0,1,0,1],[0,0,1,0,2],[0,0,1,0,3],[0,0,1,0,4],[0,0,1,0,5],[0,0,1,1,0],[0,0,1,1,1],[0,0,1,1,2],[0,0,1,1,3],[0,0,1,1,4],[0,0,1,1,5],[0,0,1,2,0],[0,0,1,2,1],[0,0,1,2,2],[0,0,1,2,3],[0,0,1,2,4],[0,0,1,2,5],[0,0,1,3,0],[0,0,1,3,1],[0,0,1,3,2],[0,0,1,3,3],[0,0,1,3,4],[0,0,1,3,5],[0,0,1,4,0],[0,0,1,4,1],[0,0,1,4,2],[0,0,1,4,3],[0,0,1,4,4],[0,0,1,4,5],[0,0,2,0,0],[0,0,2,0,1],[0,0,2,0,2],[0,0,2,0,3],[0,0,2,0,4],[0,0,2,0,5],[0,0,2,1,0],[0,0,2,1,1],[0,0,2,1,2],[0,0,2,1,3],[0,0,2,1,4],[0,0,2,1,5],[0,0,2,2,0],[0,0,2,2,1],[0,0,2,2,2],[0,0,2,2,3],[0,0,2,2,4],[0,0,2,2,5],[0,0,2,3,0],[0,0,2,3,1],[0,0,2,3,2],[0,0,2,3,3],[0,0,2,3,4],[0,0,2,3,5],[0,0,2,4,0],[0,0,2,4,1],[0,0,2,4,2],[0,0,2,4,3],[0,0,2,4,4],[0,0,2,4,5],[0,0,3,0,0],[0,0,3,0,1],[0,0,3,0,2],[0,0,3,0,3],[0,0,3,0,4],[0,0,3,0,5],[0,0,3,1,0],[0,0,3,1,1],[0,0,3,1,2],[0,0,3,1,3],[0,0,3,1,4],[0,0,3,1,5],[0,0,3,2,0],[0,0,3,2,1],[0,0,3,2,2],[0,0,3,2,3],[0,0,3,2,4],[0,0,3,2,5],[0,0,3,3,0],[0,0,3,3,1],[0,0,3,3,2],[0,0,3,3,3],[0,0,3,3,4],[0,0,3,3,5],[0,0,3,4,0],[0,0,3,4,1],[0,0,3,4,2],[0,0,3,4,3],[0,0,3,4,4],[0,0,3,4,5],[0,1,0,0,0],[0,1,0,0,1],[0,1,0,0,2],[0,1,0,0,3],[0,1,0,0,4],[0,1,0,0,5],[0,1,0,1,0],[0,1,0,1,1],[0,1,0,1,2],[0,1,0,1,3],[0,1,0,1,4],[0,1,0,1,5],[0,1,0,2,0],[0,1,0,2,1],[0,1,0,2,2],[0,1,0,2,3],[0,1,0,2,4],[0,1,0,2,5],[0,1,0,3,0],[0,1,0,3,1],[0,1,0,3,2],[0,1,0,3,3],[0,1,0,3,4],[0,1,0,3,5],[0,1,0,4,0],[0,1,0,4,1],[0,1,0,4,2],[0,1,0,4,3],[0,1,0,4,4],[0,1,0,4,5],[0,1,1,0,0],[0,1,1,0,1],[0,1,1,0,2],[0,1,1,0,3],[0,1,1,0,4],[0,1,1,0,5],[0,1,1,1,0],[0,1,1,1,1],[0,1,1,1,2],[0,1,1,1,3],[0,1,1,1,4],[0,1,1,1,5],[0,1,1,2,0],[0,1,1,2,1],[0,1,1,2,2],[0,1,1,2,3],[0,1,1,2,4],[0,1,1,2,5],[0,1,1,3,0],[0,1,1,3,1],[0,1,1,3,2],[0,1,1,3,3],[0,1,1,3,4],[0,1,1,3,5],[0,1,1,4,0],[0,1,1,4,1],[0,1,1,4,2],[0,1,1,4,3],[0,1,1,4,4],[0,1,1,4,5],[0,1,2,0,0],[0,1,2,0,1],[0,1,2,0,2],[0,1,2,0,3],[0,1,2,0,4],[0,1,2,0,5],[0,1,2,1,0],[0,1,2,1,1],[0,1,2,1,2],[0,1,2,1,3],[0,1,2,1,4],[0,1,2,1,5],[0,1,2,2,0],[0,1,2,2,1],[0,1,2,2,2],[0,1,2,2,3],[0,1,2,2,4],[0,1,2,2,5],[0,1,2,3,0],[0,1,2,3,1],[0,1,2,3,2],[0,1,2,3,3],[0,1,2,3,4],[0,1,2,3,5],[0,1,2,4,0],[0,1,2,4,1],[0,1,2,4,2],[0,1,2,4,3],[0,1,2,4,4],[0,1,2,4,5],[0,1,3,0,0],[0,1,3,0,1],[0,1,3,0,2],[0,1,3,0,3],[0,1,3,0,4],[0,1,3,0,5],[0,1,3,1,0],[0,1,3,1,1],[0,1,3,1,2],[0,1,3,1,3],[0,1,3,1,4],[0,1,3,1,5],[0,1,3,2,0],[0,1,3,2,1],[0,1,3,2,2],[0,1,3,2,3],[0,1,3,2,4],[0,1,3,2,5],[0,1,3,3,0],[0,1,3,3,1],[0,1,3,3,2],[0,1,3,3,3],[0,1,3,3,4],[0,1,3,3,5],[0,1,3,4,0],[0,1,3,4,1],[0,1,3,4,2],[0,1,3,4,3],[0,1,3,4,4],[0,1,3,4,5],[0,2,0,0,0],[0,2,0,0,1],[0,2,0,0,2],[0,2,0,0,3],[0,2,0,0,4],[0,2,0,0,5],[0,2,0,1,0],[0,2,0,1,1],[0,2,0,1,2],[0,2,0,1,3],[0,2,0,1,4],[0,2,0,1,5],[0,2,0,2,0],[0,2,0,2,1],[0,2,0,2,2],[0,2,0,2,3],[0,2,0,2,4],[0,2,0,2,5],[0,2,0,3,0],[0,2,0,3,1],[0,2,0,3,2],[0,2,0,3,3],[0,2,0,3,4],[0,2,0,3,5],[0,2,0,4,0],[0,2,0,4,1],[0,2,0,4,2],[0,2,0,4,3],[0,2,0,4,4],[0,2,0,4,5],[0,2,1,0,0],[0,2,1,0,1],[0,2,1,0,2],[0,2,1,0,3],[0,2,1,0,4],[0,2,1,0,5],[0,2,1,1,0],[0,2,1,1,1],[0,2,1,1,2],[0,2,1,1,3],[0,2,1,1,4],[0,2,1,1,5],[0,2,1,2,0],[0,2,1,2,1],[0,2,1,2,2],[0,2,1,2,3],[0,2,1,2,4],[0,2,1,2,5],[0,2,1,3,0],[0,2,1,3,1],[0,2,1,3,2],[0,2,1,3,3],[0,2,1,3,4],[0,2,1,3,5],[0,2,1,4,0],[0,2,1,4,1],[0,2,1,4,2],[0,2,1,4,3],[0,2,1,4,4],[0,2,1,4,5],[0,2,2,0,0],[0,2,2,0,1],[0,2,2,0,2],[0,2,2,0,3],[0,2,2,0,4],[0,2,2,0,5],[0,2,2,1,0],[0,2,2,1,1],[0,2,2,1,2],[0,2,2,1,3],[0,2,2,1,4],[0,2,2,1,5],[0,2,2,2,0],[0,2,2,2,1],[0,2,2,2,2],[0,2,2,2,3],[0,2,2,2,4],[0,2,2,2,5],[0,2,2,3,0],[0,2,2,3,1],[0,2,2,3,2],[0,2,2,3,3],[0,2,2,3,4],[0,2,2,3,5],[0,2,2,4,0],[0,2,2,4,1],[0,2,2,4,2],[0,2,2,4,3],[0,2,2,4,4],[0,2,2,4,5],[0,2,3,0,0],[0,2,3,0,1],[0,2,3,0,2],[0,2,3,0,3],[0,2,3,0,4],[0,2,3,0,5],[0,2,3,1,0],[0,2,3,1,1],[0,2,3,1,2],[0,2,3,1,3],[0,2,3,1,4],[0,2,3,1,5],[0,2,3,2,0],[0,2,3,2,1],[0,2,3,2,2],[0,2,3,2,3],[0,2,3,2,4],[0,2,3,2,5],[0,2,3,3,0],[0,2,3,3,1],[0,2,3,3,2],[0,2,3,3,3],[0,2,3,3,4],[0,2,3,3,5],[0,2,3,4,0],[0,2,3,4,1],[0,2,3,4,2],[0,2,3,4,3],[0,2,3,4,4],[0,2,3,4,5],[1,0,0,0,0],[1,0,0,0,1],[1,0,0,0,2],[1,0,0,0,3],[1,0,0,0,4],[1,0,0,0,5],[1,0,0,1,0],[1,0,0,1,1],[1,0,0,1,2],[1,0,0,1,3],[1,0,0,1,4],[1,0,0,1,5],[1,0,0,2,0],[1,0,0,2,1],[1,0,0,2,2],[1,0,0,2,3],[1,0,0,2,4],[1,0,0,2,5],[1,0,0,3,0],[1,0,0,3,1],[1,0,0,3,2],[1,0,0,3,3],[1,0,0,3,4],[1,0,0,3,5],[1,0,0,4,0],[1,0,0,4,1],[1,0,0,4,2],[1,0,0,4,3],[1,0,0,4,4],[1,0,0,4,5],[1,0,1,0,0],[1,0,1,0,1],[1,0,1,0,2],[1,0,1,0,3],[1,0,1,0,4],[1,0,1,0,5],[1,0,1,1,0],[1,0,1,1,1],[1,0,1,1,2],[1,0,1,1,3],[1,0,1,1,4],[1,0,1,1,5],[1,0,1,2,0],[1,0,1,2,1],[1,0,1,2,2],[1,0,1,2,3],[1,0,1,2,4],[1,0,1,2,5],[1,0,1,3,0],[1,0,1,3,1],[1,0,1,3,2],[1,0,1,3,3],[1,0,1,3,4],[1,0,1,3,5],[1,0,1,4,0],[1,0,1,4,1],[1,0,1,4,2],[1,0,1,4,3],[1,0,1,4,4],[1,0,1,4,5],[1,0,2,0,0],[1,0,2,0,1],[1,0,2,0,2],[1,0,2,0,3],[1,0,2,0,4],[1,0,2,0,5],[1,0,2,1,0],[1,0,2,1,1],[1,0,2,1,2],[1,0,2,1,3],[1,0,2,1,4],[1,0,2,1,5],[1,0,2,2,0],[1,0,2,2,1],[1,0,2,2,2],[1,0,2,2,3],[1,0,2,2,4],[1,0,2,2,5],[1,0,2,3,0],[1,0,2,3,1],[1,0,2,3,2],[1,0,2,3,3],[1,0,2,3,4],[1,0,2,3,5],[1,0,2,4,0],[1,0,2,4,1],[1,0,2,4,2],[1,0,2,4,3],[1,0,2,4,4],[1,0,2,4,5],[1,0,3,0,0],[1,0,3,0,1],[1,0,3,0,2],[1,0,3,0,3],[1,0,3,0,4],[1,0,3,0,5],[1,0,3,1,0],[1,0,3,1,1],[1,0,3,1,2],[1,0,3,1,3],[1,0,3,1,4],[1,0,3,1,5],[1,0,3,2,0],[1,0,3,2,1],[1,0,3,2,2],[1,0,3,2,3],[1,0,3,2,4],[1,0,3,2,5],[1,0,3,3,0],[1,0,3,3,1],[1,0,3,3,2],[1,0,3,3,3],[1,0,3,3,4],[1,0,3,3,5],[1,0,3,4,0],[1,0,3,4,1],[1,0,3,4,2],[1,0,3,4,3],[1,0,3,4,4],[1,0,3,4,5],[1,1,0,0,0],[1,1,0,0,1],[1,1,0,0,2],[1,1,0,0,3],[1,1,0,0,4],[1,1,0,0,5],[1,1,0,1,0],[1,1,0,1,1],[1,1,0,1,2],[1,1,0,1,3],[1,1,0,1,4],[1,1,0,1,5],[1,1,0,2,0],[1,1,0,2,1],[1,1,0,2,2],[1,1,0,2,3],[1,1,0,2,4],[1,1,0,2,5],[1,1,0,3,0],[1,1,0,3,1],[1,1,0,3,2],[1,1,0,3,3],[1,1,0,3,4],[1,1,0,3,5],[1,1,0,4,0],[1,1,0,4,1],[1,1,0,4,2],[1,1,0,4,3],[1,1,0,4,4],[1,1,0,4,5],[1,1,1,0,0],[1,1,1,0,1],[1,1,1,0,2],[1,1,1,0,3],[1,1,1,0,4],[1,1,1,0,5],[1,1,1,1,0],[1,1,1,1,1],[1,1,1,1,2],[1,1,1,1,3],[1,1,1,1,4],[1,1,1,1,5],[1,1,1,2,0],[1,1,1,2,1],[1,1,1,2,2],[1,1,1,2,3],[1,1,1,2,4],[1,1,1,2,5],[1,1,1,3,0],[1,1,1,3,1],[1,1,1,3,2],[1,1,1,3,3],[1,1,1,3,4],[1,1,1,3,5],[1,1,1,4,0],[1,1,1,4,1],[1,1,1,4,2],[1,1,1,4,3],[1,1,1,4,4],[1,1,1,4,5],[1,1,2,0,0],[1,1,2,0,1],[1,1,2,0,2],[1,1,2,0,3],[1,1,2,0,4],[1,1,2,0,5],[1,1,2,1,0],[1,1,2,1,1],[1,1,2,1,2],[1,1,2,1,3],[1,1,2,1,4],[1,1,2,1,5],[1,1,2,2,0],[1,1,2,2,1],[1,1,2,2,2],[1,1,2,2,3],[1,1,2,2,4],[1,1,2,2,5],[1,1,2,3,0],[1,1,2,3,1],[1,1,2,3,2],[1,1,2,3,3],[1,1,2,3,4],[1,1,2,3,5],[1,1,2,4,0],[1,1,2,4,1],[1,1,2,4,2],[1,1,2,4,3],[1,1,2,4,4],[1,1,2,4,5],[1,1,3,0,0],[1,1,3,0,1],[1,1,3,0,2],[1,1,3,0,3],[1,1,3,0,4],[1,1,3,0,5],[1,1,3,1,0],[1,1,3,1,1],[1,1,3,1,2],[1,1,3,1,3],[1,1,3,1,4],[1,1,3,1,5],[1,1,3,2,0],[1,1,3,2,1],[1,1,3,2,2],[1,1,3,2,3],[1,1,3,2,4],[1,1,3,2,5],[1,1,3,3,0],[1,1,3,3,1],[1,1,3,3,2],[1,1,3,3,3],[1,1,3,3,4],[1,1,3,3,5],[1,1,3,4,0],[1,1,3,4,1],[1,1,3,4,2],[1,1,3,4,3],[1,1,3,4,4],[1,1,3,4,5],[1,2,0,0,0],[1,2,0,0,1],[1,2,0,0,2],[1,2,0,0,3],[1,2,0,0,4],[1,2,0,0,5],[1,2,0,1,0],[1,2,0,1,1],[1,2,0,1,2],[1,2,0,1,3],[1,2,0,1,4],[1,2,0,1,5],[1,2,0,2,0],[1,2,0,2,1],[1,2,0,2,2],[1,2,0,2,3],[1,2,0,2,4],[1,2,0,2,5],[1,2,0,3,0],[1,2,0,3,1],[1,2,0,3,2],[1,2,0,3,3],[1,2,0,3,4],[1,2,0,3,5],[1,2,0,4,0],[1,2,0,4,1],[1,2,0,4,2],[1,2,0,4,3],[1,2,0,4,4],[1,2,0,4,5],[1,2,1,0,0],[1,2,1,0,1],[1,2,1,0,2],[1,2,1,0,3],[1,2,1,0,4],[1,2,1,0,5],[1,2,1,1,0],[1,2,1,1,1],[1,2,1,1,2],[1,2,1,1,3],[1,2,1,1,4],[1,2,1,1,5],[1,2,1,2,0],[1,2,1,2,1],[1,2,1,2,2],[1,2,1,2,3],[1,2,1,2,4],[1,2,1,2,5],[1,2,1,3,0],[1,2,1,3,1],[1,2,1,3,2],[1,2,1,3,3],[1,2,1,3,4],[1,2,1,3,5],[1,2,1,4,0],[1,2,1,4,1],[1,2,1,4,2],[1,2,1,4,3],[1,2,1,4,4],[1,2,1,4,5],[1,2,2,0,0],[1,2,2,0,1],[1,2,2,0,2],[1,2,2,0,3],[1,2,2,0,4],[1,2,2,0,5],[1,2,2,1,0],[1,2,2,1,1],[1,2,2,1,2],[1,2,2,1,3],[1,2,2,1,4],[1,2,2,1,5],[1,2,2,2,0],[1,2,2,2,1],[1,2,2,2,2],[1,2,2,2,3],[1,2,2,2,4],[1,2,2,2,5],[1,2,2,3,0],[1,2,2,3,1],[1,2,2,3,2],[1,2,2,3,3],[1,2,2,3,4],[1,2,2,3,5],[1,2,2,4,0],[1,2,2,4,1],[1,2,2,4,2],[1,2,2,4,3],[1,2,2,4,4],[1,2,2,4,5],[1,2,3,0,0],[1,2,3,0,1],[1,2,3,0,2],[1,2,3,0,3],[1,2,3,0,4],[1,2,3,0,5],[1,2,3,1,0],[1,2,3,1,1],[1,2,3,1,2],[1,2,3,1,3],[1,2,3,1,4],[1,2,3,1,5],[1,2,3,2,0],[1,2,3,2,1],[1,2,3,2,2],[1,2,3,2,3],[1,2,3,2,4],[1,2,3,2,5],[1,2,3,3,0],[1,2,3,3,1],[1,2,3,3,2],[1,2,3,3,3],[1,2,3,3,4],[1,2,3,3,5],[1,2,3,4,0],[1,2,3,4,1],[1,2,3,4,2],[1,2,3,4,3],[1,2,3,4,4],[1,2,3,4,5]]]"
+var testFact6 = "[1,1,2,6,24,120,720]"
+
+func TestInit(t *testing.T) {
+ lp := NewLayerPermutator()
+ b, _ := json.Marshal(lp.P)
+ assert.Equal(t, testPermStrategies6, string(b))
+ b, _ = json.Marshal(lp.Fact)
+ assert.Equal(t, testFact6, string(b))
+}
+
+func TestSwapFunc(t *testing.T) {
+ lp := NewLayerPermutator()
+ intervalStarts := []int{1, 6}
+ intervalLengths := []int{4, 2}
+ in := []int16{-1, 10, 20, 30, 40, -2, 100, 200, -3}
+ sb := strings.Builder{}
+ lp.SwapIterator(intervalStarts, intervalLengths, len(intervalLengths), in, func(totalCnt int, perm []int16) {
+ if sb.Len() > 0 {
+ sb.WriteString(", ")
+ }
+ sb.WriteString(fmt.Sprintf("%d:%v", totalCnt, perm))
+ })
+ assert.Equal(t, "0:[-1 10 20 30 40 -2 100 200 -3], 1:[-1 10 20 30 40 -2 200 100 -3], 2:[-1 10 20 40 30 -2 100 200 -3], 3:[-1 10 20 40 30 -2 200 100 -3], 4:[-1 10 40 30 20 -2 100 200 -3], 5:[-1 10 40 30 20 -2 200 100 -3], 6:[-1 40 20 30 10 -2 100 200 -3], 7:[-1 40 20 30 10 -2 200 100 -3], 8:[-1 10 30 20 40 -2 100 200 -3], 9:[-1 10 30 20 40 -2 200 100 -3], 10:[-1 10 30 40 20 -2 100 200 -3], 11:[-1 10 30 40 20 -2 200 100 -3], 12:[-1 10 40 20 30 -2 100 200 -3], 13:[-1 10 40 20 30 -2 200 100 -3], 14:[-1 40 30 20 10 -2 100 200 -3], 15:[-1 40 30 20 10 -2 200 100 -3], 16:[-1 30 20 10 40 -2 100 200 -3], 17:[-1 30 20 10 40 -2 200 100 -3], 18:[-1 30 20 40 10 -2 100 200 -3], 19:[-1 30 20 40 10 -2 200 100 -3], 20:[-1 30 40 10 20 -2 100 200 -3], 21:[-1 30 40 10 20 -2 200 100 -3], 22:[-1 40 20 10 30 -2 100 200 -3], 23:[-1 40 20 10 30 -2 200 100 -3], 24:[-1 20 10 30 40 -2 100 200 -3], 25:[-1 20 10 30 40 -2 200 100 -3], 26:[-1 20 10 40 30 -2 100 200 -3], 27:[-1 20 10 40 30 -2 200 100 -3], 28:[-1 20 40 30 10 -2 100 200 -3], 29:[-1 20 40 30 10 -2 200 100 -3], 30:[-1 40 10 30 20 -2 100 200 -3], 31:[-1 40 10 30 20 -2 200 100 -3], 32:[-1 20 30 10 40 -2 100 200 -3], 33:[-1 20 30 10 40 -2 200 100 -3], 34:[-1 20 30 40 10 -2 100 200 -3], 35:[-1 20 30 40 10 -2 200 100 -3], 36:[-1 20 40 10 30 -2 100 200 -3], 37:[-1 20 40 10 30 -2 200 100 -3], 38:[-1 40 30 10 20 -2 100 200 -3], 39:[-1 40 30 10 20 -2 200 100 -3], 40:[-1 30 10 20 40 -2 100 200 -3], 41:[-1 30 10 20 40 -2 200 100 -3], 42:[-1 30 10 40 20 -2 100 200 -3], 43:[-1 30 10 40 20 -2 200 100 -3], 44:[-1 30 40 20 10 -2 100 200 -3], 45:[-1 30 40 20 10 -2 200 100 -3], 46:[-1 40 10 20 30 -2 100 200 -3], 47:[-1 40 10 20 30 -2 200 100 -3]", sb.String())
+}
+
+func TestInsert(t *testing.T) {
+ lp := NewLayerPermutator()
+
+ in := []int16{-1, -2, -3, -4, 100}
+ lp.insertPermutationByIdx(in, 4, 1)
+ assert.Equal(t, "[-1 100 -2 -3 -4]", fmt.Sprintf("%v", in))
+
+ // Insert 100 and 200 starting at 0, 6x7=42 variants
+ in = []int16{1, 2, 3, 4, 5, 100, 200}
+ sb := strings.Builder{}
+ lp.InsertIterator(5, 2, in, func(totalCnt int, perm []int16) {
+ if sb.Len() > 0 {
+ sb.WriteString(", ")
+ }
+ sb.WriteString(fmt.Sprintf("%d:%v", totalCnt, perm))
+ })
+ assert.Equal(t, "0:[1 2 3 4 5 100 200], 1:[1 2 3 4 5 200 100], 2:[1 2 3 4 200 5 100], 3:[1 2 3 200 4 5 100], 4:[1 2 200 3 4 5 100], 5:[1 200 2 3 4 5 100], 6:[200 1 2 3 4 5 100], 7:[1 2 3 4 100 5 200], 8:[1 2 3 4 100 200 5], 9:[1 2 3 4 200 100 5], 10:[1 2 3 200 4 100 5], 11:[1 2 200 3 4 100 5], 12:[1 200 2 3 4 100 5], 13:[200 1 2 3 4 100 5], 14:[1 2 3 100 4 5 200], 15:[1 2 3 100 4 200 5], 16:[1 2 3 100 200 4 5], 17:[1 2 3 200 100 4 5], 18:[1 2 200 3 100 4 5], 19:[1 200 2 3 100 4 5], 20:[200 1 2 3 100 4 5], 21:[1 2 100 3 4 5 200], 22:[1 2 100 3 4 200 5], 23:[1 2 100 3 200 4 5], 24:[1 2 100 200 3 4 5], 25:[1 2 200 100 3 4 5], 26:[1 200 2 100 3 4 5], 27:[200 1 2 100 3 4 5], 28:[1 100 2 3 4 5 200], 29:[1 100 2 3 4 200 5], 30:[1 100 2 3 200 4 5], 31:[1 100 2 200 3 4 5], 32:[1 100 200 2 3 4 5], 33:[1 200 100 2 3 4 5], 34:[200 1 100 2 3 4 5], 35:[100 1 2 3 4 5 200], 36:[100 1 2 3 4 200 5], 37:[100 1 2 3 200 4 5], 38:[100 1 2 200 3 4 5], 39:[100 1 200 2 3 4 5], 40:[100 200 1 2 3 4 5], 41:[200 100 1 2 3 4 5]", sb.String())
+}
+
+/*
+ func TestSwap(t *testing.T) {
+ lp := NewLayerPermutator()
+
+ // All permutations of 10/20/30/40 and 100/200 intervals
+ permIntervalStarts := [20]int{1, 6}
+ permIntervalLengths := [20]int{4, 2}
+ a := []int16{-1, 10, 20, 30, 40, -2, 100, 200, -3}
+ sb := strings.Builder{}
+ for i, perm := range lp.SwapPermIterator(&permIntervalStarts, &permIntervalLengths, 2, a) {
+ sb.WriteString(fmt.Sprintf("%d %v, ", i, perm))
+ }
+ assert.Equal(t, "0 [-1 40 10 20 30 -2 200 100 -3], 1 [-1 40 10 20 30 -2 100 200 -3], 2 [-1 30 40 20 10 -2 200 100 -3], 3 [-1 30 40 20 10 -2 100 200 -3], 4 [-1 30 10 40 20 -2 200 100 -3], 5 [-1 30 10 40 20 -2 100 200 -3], 6 [-1 30 10 20 40 -2 200 100 -3], 7 [-1 30 10 20 40 -2 100 200 -3], 8 [-1 40 30 10 20 -2 200 100 -3], 9 [-1 40 30 10 20 -2 100 200 -3], 10 [-1 20 40 10 30 -2 200 100 -3], 11 [-1 20 40 10 30 -2 100 200 -3], 12 [-1 20 30 40 10 -2 200 100 -3], 13 [-1 20 30 40 10 -2 100 200 -3], 14 [-1 20 30 10 40 -2 200 100 -3], 15 [-1 20 30 10 40 -2 100 200 -3], 16 [-1 40 10 30 20 -2 200 100 -3], 17 [-1 40 10 30 20 -2 100 200 -3], 18 [-1 20 40 30 10 -2 200 100 -3], 19 [-1 20 40 30 10 -2 100 200 -3], 20 [-1 20 10 40 30 -2 200 100 -3], 21 [-1 20 10 40 30 -2 100 200 -3], 22 [-1 20 10 30 40 -2 200 100 -3], 23 [-1 20 10 30 40 -2 100 200 -3], 24 [-1 40 20 10 30 -2 200 100 -3], 25 [-1 40 20 10 30 -2 100 200 -3], 26 [-1 30 40 10 20 -2 200 100 -3], 27 [-1 30 40 10 20 -2 100 200 -3], 28 [-1 30 20 40 10 -2 200 100 -3], 29 [-1 30 20 40 10 -2 100 200 -3], 30 [-1 30 20 10 40 -2 200 100 -3], 31 [-1 30 20 10 40 -2 100 200 -3], 32 [-1 40 30 20 10 -2 200 100 -3], 33 [-1 40 30 20 10 -2 100 200 -3], 34 [-1 10 40 20 30 -2 200 100 -3], 35 [-1 10 40 20 30 -2 100 200 -3], 36 [-1 10 30 40 20 -2 200 100 -3], 37 [-1 10 30 40 20 -2 100 200 -3], 38 [-1 10 30 20 40 -2 200 100 -3], 39 [-1 10 30 20 40 -2 100 200 -3], 40 [-1 40 20 30 10 -2 200 100 -3], 41 [-1 40 20 30 10 -2 100 200 -3], 42 [-1 10 40 30 20 -2 200 100 -3], 43 [-1 10 40 30 20 -2 100 200 -3], 44 [-1 10 20 40 30 -2 200 100 -3], 45 [-1 10 20 40 30 -2 100 200 -3], 46 [-1 10 20 30 40 -2 200 100 -3], 47 [-1 10 20 30 40 -2 100 200 -3], ", sb.String())
+ }
+*/
+func TestSwapAndInsert(t *testing.T) {
+ lp := NewLayerPermutator()
+
+ // Swap permutations of 10/20 and 100/200, and insert 1000 and 2000 before 1000, total 2x2x8*9=288 final permutations
+ in := []int16{-1, 10, 20, -2, 100, 200, -3, 1000, 2000}
+ intervalStarts := []int{1, 4}
+ intervalLengths := []int{2, 2}
+ sb := strings.Builder{}
+ lp.SwapAndInsertIterator(intervalStarts, intervalLengths, len(intervalStarts), 7, 2, in, func(totalCnt int, perm []int16) {
+ if sb.Len() > 0 {
+ sb.WriteString(", ")
+ }
+ sb.WriteString(fmt.Sprintf("%d:%v", totalCnt, perm))
+ })
+ assert.Equal(t, "0:[-1 10 20 -2 100 200 -3 1000 2000], 1:[-1 10 20 -2 100 200 -3 2000 1000], 2:[-1 10 20 -2 100 200 2000 -3 1000], 3:[-1 10 20 -2 100 2000 200 -3 1000], 4:[-1 10 20 -2 2000 100 200 -3 1000], 5:[-1 10 20 2000 -2 100 200 -3 1000], 6:[-1 10 2000 20 -2 100 200 -3 1000], 7:[-1 2000 10 20 -2 100 200 -3 1000], 8:[2000 -1 10 20 -2 100 200 -3 1000], 9:[-1 10 20 -2 100 200 1000 -3 2000], 10:[-1 10 20 -2 100 200 1000 2000 -3], 11:[-1 10 20 -2 100 200 2000 1000 -3], 12:[-1 10 20 -2 100 2000 200 1000 -3], 13:[-1 10 20 -2 2000 100 200 1000 -3], 14:[-1 10 20 2000 -2 100 200 1000 -3], 15:[-1 10 2000 20 -2 100 200 1000 -3], 16:[-1 2000 10 20 -2 100 200 1000 -3], 17:[2000 -1 10 20 -2 100 200 1000 -3], 18:[-1 10 20 -2 100 1000 200 -3 2000], 19:[-1 10 20 -2 100 1000 200 2000 -3], 20:[-1 10 20 -2 100 1000 2000 200 -3], 21:[-1 10 20 -2 100 2000 1000 200 -3], 22:[-1 10 20 -2 2000 100 1000 200 -3], 23:[-1 10 20 2000 -2 100 1000 200 -3], 24:[-1 10 2000 20 -2 100 1000 200 -3], 25:[-1 2000 10 20 -2 100 1000 200 -3], 26:[2000 -1 10 20 -2 100 1000 200 -3], 27:[-1 10 20 -2 1000 100 200 -3 2000], 28:[-1 10 20 -2 1000 100 200 2000 -3], 29:[-1 10 20 -2 1000 100 2000 200 -3], 30:[-1 10 20 -2 1000 2000 100 200 -3], 31:[-1 10 20 -2 2000 1000 100 200 -3], 32:[-1 10 20 2000 -2 1000 100 200 -3], 33:[-1 10 2000 20 -2 1000 100 200 -3], 34:[-1 2000 10 20 -2 1000 100 200 -3], 35:[2000 -1 10 20 -2 1000 100 200 -3], 36:[-1 10 20 1000 -2 100 200 -3 2000], 37:[-1 10 20 1000 -2 100 200 2000 -3], 38:[-1 10 20 1000 -2 100 2000 200 -3], 39:[-1 10 20 1000 -2 2000 100 200 -3], 40:[-1 10 20 1000 2000 -2 100 200 -3], 41:[-1 10 20 2000 1000 -2 100 200 -3], 42:[-1 10 2000 20 1000 -2 100 200 -3], 43:[-1 2000 10 20 1000 -2 100 200 -3], 44:[2000 -1 10 20 1000 -2 100 200 -3], 45:[-1 10 1000 20 -2 100 200 -3 2000], 46:[-1 10 1000 20 -2 100 200 2000 -3], 47:[-1 10 1000 20 -2 100 2000 200 -3], 48:[-1 10 1000 20 -2 2000 100 200 -3], 49:[-1 10 1000 20 2000 -2 100 200 -3], 50:[-1 10 1000 2000 20 -2 100 200 -3], 51:[-1 10 2000 1000 20 -2 100 200 -3], 52:[-1 2000 10 1000 20 -2 100 200 -3], 53:[2000 -1 10 1000 20 -2 100 200 -3], 54:[-1 1000 10 20 -2 100 200 -3 2000], 55:[-1 1000 10 20 -2 100 200 2000 -3], 56:[-1 1000 10 20 -2 100 2000 200 -3], 57:[-1 1000 10 20 -2 2000 100 200 -3], 58:[-1 1000 10 20 2000 -2 100 200 -3], 59:[-1 1000 10 2000 20 -2 100 200 -3], 60:[-1 1000 2000 10 20 -2 100 200 -3], 61:[-1 2000 1000 10 20 -2 100 200 -3], 62:[2000 -1 1000 10 20 -2 100 200 -3], 63:[1000 -1 10 20 -2 100 200 -3 2000], 64:[1000 -1 10 20 -2 100 200 2000 -3], 65:[1000 -1 10 20 -2 100 2000 200 -3], 66:[1000 -1 10 20 -2 2000 100 200 -3], 67:[1000 -1 10 20 2000 -2 100 200 -3], 68:[1000 -1 10 2000 20 -2 100 200 -3], 69:[1000 -1 2000 10 20 -2 100 200 -3], 70:[1000 2000 -1 10 20 -2 100 200 -3], 71:[2000 1000 -1 10 20 -2 100 200 -3], 72:[-1 10 20 -2 200 100 -3 1000 2000], 73:[-1 10 20 -2 200 100 -3 2000 1000], 74:[-1 10 20 -2 200 100 2000 -3 1000], 75:[-1 10 20 -2 200 2000 100 -3 1000], 76:[-1 10 20 -2 2000 200 100 -3 1000], 77:[-1 10 20 2000 -2 200 100 -3 1000], 78:[-1 10 2000 20 -2 200 100 -3 1000], 79:[-1 2000 10 20 -2 200 100 -3 1000], 80:[2000 -1 10 20 -2 200 100 -3 1000], 81:[-1 10 20 -2 200 100 1000 -3 2000], 82:[-1 10 20 -2 200 100 1000 2000 -3], 83:[-1 10 20 -2 200 100 2000 1000 -3], 84:[-1 10 20 -2 200 2000 100 1000 -3], 85:[-1 10 20 -2 2000 200 100 1000 -3], 86:[-1 10 20 2000 -2 200 100 1000 -3], 87:[-1 10 2000 20 -2 200 100 1000 -3], 88:[-1 2000 10 20 -2 200 100 1000 -3], 89:[2000 -1 10 20 -2 200 100 1000 -3], 90:[-1 10 20 -2 200 1000 100 -3 2000], 91:[-1 10 20 -2 200 1000 100 2000 -3], 92:[-1 10 20 -2 200 1000 2000 100 -3], 93:[-1 10 20 -2 200 2000 1000 100 -3], 94:[-1 10 20 -2 2000 200 1000 100 -3], 95:[-1 10 20 2000 -2 200 1000 100 -3], 96:[-1 10 2000 20 -2 200 1000 100 -3], 97:[-1 2000 10 20 -2 200 1000 100 -3], 98:[2000 -1 10 20 -2 200 1000 100 -3], 99:[-1 10 20 -2 1000 200 100 -3 2000], 100:[-1 10 20 -2 1000 200 100 2000 -3], 101:[-1 10 20 -2 1000 200 2000 100 -3], 102:[-1 10 20 -2 1000 2000 200 100 -3], 103:[-1 10 20 -2 2000 1000 200 100 -3], 104:[-1 10 20 2000 -2 1000 200 100 -3], 105:[-1 10 2000 20 -2 1000 200 100 -3], 106:[-1 2000 10 20 -2 1000 200 100 -3], 107:[2000 -1 10 20 -2 1000 200 100 -3], 108:[-1 10 20 1000 -2 200 100 -3 2000], 109:[-1 10 20 1000 -2 200 100 2000 -3], 110:[-1 10 20 1000 -2 200 2000 100 -3], 111:[-1 10 20 1000 -2 2000 200 100 -3], 112:[-1 10 20 1000 2000 -2 200 100 -3], 113:[-1 10 20 2000 1000 -2 200 100 -3], 114:[-1 10 2000 20 1000 -2 200 100 -3], 115:[-1 2000 10 20 1000 -2 200 100 -3], 116:[2000 -1 10 20 1000 -2 200 100 -3], 117:[-1 10 1000 20 -2 200 100 -3 2000], 118:[-1 10 1000 20 -2 200 100 2000 -3], 119:[-1 10 1000 20 -2 200 2000 100 -3], 120:[-1 10 1000 20 -2 2000 200 100 -3], 121:[-1 10 1000 20 2000 -2 200 100 -3], 122:[-1 10 1000 2000 20 -2 200 100 -3], 123:[-1 10 2000 1000 20 -2 200 100 -3], 124:[-1 2000 10 1000 20 -2 200 100 -3], 125:[2000 -1 10 1000 20 -2 200 100 -3], 126:[-1 1000 10 20 -2 200 100 -3 2000], 127:[-1 1000 10 20 -2 200 100 2000 -3], 128:[-1 1000 10 20 -2 200 2000 100 -3], 129:[-1 1000 10 20 -2 2000 200 100 -3], 130:[-1 1000 10 20 2000 -2 200 100 -3], 131:[-1 1000 10 2000 20 -2 200 100 -3], 132:[-1 1000 2000 10 20 -2 200 100 -3], 133:[-1 2000 1000 10 20 -2 200 100 -3], 134:[2000 -1 1000 10 20 -2 200 100 -3], 135:[1000 -1 10 20 -2 200 100 -3 2000], 136:[1000 -1 10 20 -2 200 100 2000 -3], 137:[1000 -1 10 20 -2 200 2000 100 -3], 138:[1000 -1 10 20 -2 2000 200 100 -3], 139:[1000 -1 10 20 2000 -2 200 100 -3], 140:[1000 -1 10 2000 20 -2 200 100 -3], 141:[1000 -1 2000 10 20 -2 200 100 -3], 142:[1000 2000 -1 10 20 -2 200 100 -3], 143:[2000 1000 -1 10 20 -2 200 100 -3], 144:[-1 20 10 -2 100 200 -3 1000 2000], 145:[-1 20 10 -2 100 200 -3 2000 1000], 146:[-1 20 10 -2 100 200 2000 -3 1000], 147:[-1 20 10 -2 100 2000 200 -3 1000], 148:[-1 20 10 -2 2000 100 200 -3 1000], 149:[-1 20 10 2000 -2 100 200 -3 1000], 150:[-1 20 2000 10 -2 100 200 -3 1000], 151:[-1 2000 20 10 -2 100 200 -3 1000], 152:[2000 -1 20 10 -2 100 200 -3 1000], 153:[-1 20 10 -2 100 200 1000 -3 2000], 154:[-1 20 10 -2 100 200 1000 2000 -3], 155:[-1 20 10 -2 100 200 2000 1000 -3], 156:[-1 20 10 -2 100 2000 200 1000 -3], 157:[-1 20 10 -2 2000 100 200 1000 -3], 158:[-1 20 10 2000 -2 100 200 1000 -3], 159:[-1 20 2000 10 -2 100 200 1000 -3], 160:[-1 2000 20 10 -2 100 200 1000 -3], 161:[2000 -1 20 10 -2 100 200 1000 -3], 162:[-1 20 10 -2 100 1000 200 -3 2000], 163:[-1 20 10 -2 100 1000 200 2000 -3], 164:[-1 20 10 -2 100 1000 2000 200 -3], 165:[-1 20 10 -2 100 2000 1000 200 -3], 166:[-1 20 10 -2 2000 100 1000 200 -3], 167:[-1 20 10 2000 -2 100 1000 200 -3], 168:[-1 20 2000 10 -2 100 1000 200 -3], 169:[-1 2000 20 10 -2 100 1000 200 -3], 170:[2000 -1 20 10 -2 100 1000 200 -3], 171:[-1 20 10 -2 1000 100 200 -3 2000], 172:[-1 20 10 -2 1000 100 200 2000 -3], 173:[-1 20 10 -2 1000 100 2000 200 -3], 174:[-1 20 10 -2 1000 2000 100 200 -3], 175:[-1 20 10 -2 2000 1000 100 200 -3], 176:[-1 20 10 2000 -2 1000 100 200 -3], 177:[-1 20 2000 10 -2 1000 100 200 -3], 178:[-1 2000 20 10 -2 1000 100 200 -3], 179:[2000 -1 20 10 -2 1000 100 200 -3], 180:[-1 20 10 1000 -2 100 200 -3 2000], 181:[-1 20 10 1000 -2 100 200 2000 -3], 182:[-1 20 10 1000 -2 100 2000 200 -3], 183:[-1 20 10 1000 -2 2000 100 200 -3], 184:[-1 20 10 1000 2000 -2 100 200 -3], 185:[-1 20 10 2000 1000 -2 100 200 -3], 186:[-1 20 2000 10 1000 -2 100 200 -3], 187:[-1 2000 20 10 1000 -2 100 200 -3], 188:[2000 -1 20 10 1000 -2 100 200 -3], 189:[-1 20 1000 10 -2 100 200 -3 2000], 190:[-1 20 1000 10 -2 100 200 2000 -3], 191:[-1 20 1000 10 -2 100 2000 200 -3], 192:[-1 20 1000 10 -2 2000 100 200 -3], 193:[-1 20 1000 10 2000 -2 100 200 -3], 194:[-1 20 1000 2000 10 -2 100 200 -3], 195:[-1 20 2000 1000 10 -2 100 200 -3], 196:[-1 2000 20 1000 10 -2 100 200 -3], 197:[2000 -1 20 1000 10 -2 100 200 -3], 198:[-1 1000 20 10 -2 100 200 -3 2000], 199:[-1 1000 20 10 -2 100 200 2000 -3], 200:[-1 1000 20 10 -2 100 2000 200 -3], 201:[-1 1000 20 10 -2 2000 100 200 -3], 202:[-1 1000 20 10 2000 -2 100 200 -3], 203:[-1 1000 20 2000 10 -2 100 200 -3], 204:[-1 1000 2000 20 10 -2 100 200 -3], 205:[-1 2000 1000 20 10 -2 100 200 -3], 206:[2000 -1 1000 20 10 -2 100 200 -3], 207:[1000 -1 20 10 -2 100 200 -3 2000], 208:[1000 -1 20 10 -2 100 200 2000 -3], 209:[1000 -1 20 10 -2 100 2000 200 -3], 210:[1000 -1 20 10 -2 2000 100 200 -3], 211:[1000 -1 20 10 2000 -2 100 200 -3], 212:[1000 -1 20 2000 10 -2 100 200 -3], 213:[1000 -1 2000 20 10 -2 100 200 -3], 214:[1000 2000 -1 20 10 -2 100 200 -3], 215:[2000 1000 -1 20 10 -2 100 200 -3], 216:[-1 20 10 -2 200 100 -3 1000 2000], 217:[-1 20 10 -2 200 100 -3 2000 1000], 218:[-1 20 10 -2 200 100 2000 -3 1000], 219:[-1 20 10 -2 200 2000 100 -3 1000], 220:[-1 20 10 -2 2000 200 100 -3 1000], 221:[-1 20 10 2000 -2 200 100 -3 1000], 222:[-1 20 2000 10 -2 200 100 -3 1000], 223:[-1 2000 20 10 -2 200 100 -3 1000], 224:[2000 -1 20 10 -2 200 100 -3 1000], 225:[-1 20 10 -2 200 100 1000 -3 2000], 226:[-1 20 10 -2 200 100 1000 2000 -3], 227:[-1 20 10 -2 200 100 2000 1000 -3], 228:[-1 20 10 -2 200 2000 100 1000 -3], 229:[-1 20 10 -2 2000 200 100 1000 -3], 230:[-1 20 10 2000 -2 200 100 1000 -3], 231:[-1 20 2000 10 -2 200 100 1000 -3], 232:[-1 2000 20 10 -2 200 100 1000 -3], 233:[2000 -1 20 10 -2 200 100 1000 -3], 234:[-1 20 10 -2 200 1000 100 -3 2000], 235:[-1 20 10 -2 200 1000 100 2000 -3], 236:[-1 20 10 -2 200 1000 2000 100 -3], 237:[-1 20 10 -2 200 2000 1000 100 -3], 238:[-1 20 10 -2 2000 200 1000 100 -3], 239:[-1 20 10 2000 -2 200 1000 100 -3], 240:[-1 20 2000 10 -2 200 1000 100 -3], 241:[-1 2000 20 10 -2 200 1000 100 -3], 242:[2000 -1 20 10 -2 200 1000 100 -3], 243:[-1 20 10 -2 1000 200 100 -3 2000], 244:[-1 20 10 -2 1000 200 100 2000 -3], 245:[-1 20 10 -2 1000 200 2000 100 -3], 246:[-1 20 10 -2 1000 2000 200 100 -3], 247:[-1 20 10 -2 2000 1000 200 100 -3], 248:[-1 20 10 2000 -2 1000 200 100 -3], 249:[-1 20 2000 10 -2 1000 200 100 -3], 250:[-1 2000 20 10 -2 1000 200 100 -3], 251:[2000 -1 20 10 -2 1000 200 100 -3], 252:[-1 20 10 1000 -2 200 100 -3 2000], 253:[-1 20 10 1000 -2 200 100 2000 -3], 254:[-1 20 10 1000 -2 200 2000 100 -3], 255:[-1 20 10 1000 -2 2000 200 100 -3], 256:[-1 20 10 1000 2000 -2 200 100 -3], 257:[-1 20 10 2000 1000 -2 200 100 -3], 258:[-1 20 2000 10 1000 -2 200 100 -3], 259:[-1 2000 20 10 1000 -2 200 100 -3], 260:[2000 -1 20 10 1000 -2 200 100 -3], 261:[-1 20 1000 10 -2 200 100 -3 2000], 262:[-1 20 1000 10 -2 200 100 2000 -3], 263:[-1 20 1000 10 -2 200 2000 100 -3], 264:[-1 20 1000 10 -2 2000 200 100 -3], 265:[-1 20 1000 10 2000 -2 200 100 -3], 266:[-1 20 1000 2000 10 -2 200 100 -3], 267:[-1 20 2000 1000 10 -2 200 100 -3], 268:[-1 2000 20 1000 10 -2 200 100 -3], 269:[2000 -1 20 1000 10 -2 200 100 -3], 270:[-1 1000 20 10 -2 200 100 -3 2000], 271:[-1 1000 20 10 -2 200 100 2000 -3], 272:[-1 1000 20 10 -2 200 2000 100 -3], 273:[-1 1000 20 10 -2 2000 200 100 -3], 274:[-1 1000 20 10 2000 -2 200 100 -3], 275:[-1 1000 20 2000 10 -2 200 100 -3], 276:[-1 1000 2000 20 10 -2 200 100 -3], 277:[-1 2000 1000 20 10 -2 200 100 -3], 278:[2000 -1 1000 20 10 -2 200 100 -3], 279:[1000 -1 20 10 -2 200 100 -3 2000], 280:[1000 -1 20 10 -2 200 100 2000 -3], 281:[1000 -1 20 10 -2 200 2000 100 -3], 282:[1000 -1 20 10 -2 2000 200 100 -3], 283:[1000 -1 20 10 2000 -2 200 100 -3], 284:[1000 -1 20 2000 10 -2 200 100 -3], 285:[1000 -1 2000 20 10 -2 200 100 -3], 286:[1000 2000 -1 20 10 -2 200 100 -3], 287:[2000 1000 -1 20 10 -2 200 100 -3]", sb.String())
+}
diff --git a/pkg/capigraph/layering.go b/pkg/capigraph/layering.go
new file mode 100644
index 00000000..5ef3720d
--- /dev/null
+++ b/pkg/capigraph/layering.go
@@ -0,0 +1,112 @@
+package capigraph
+
+import (
+ "math"
+ "slices"
+)
+
+func maxDistToPullSubtreeDownRecursive(subtreeRoot int16, nodeLayerMap []int, nodeToRootMap []int16, priChildrenMap [][]int16, secChildrenMap [][]int16) int {
+ allowedDist := math.MaxInt
+ for _, secChild := range secChildrenMap[subtreeRoot] {
+ // If this sec child has the same root as subtreeRoot - ignore it,
+ // we can safely push it down later, so no distance restrictions added.
+ // If we don't do this, we may end up in an infinite pull/push loop, see testNodeDefsPriAndSecInfinitePulldown
+ if nodeToRootMap[subtreeRoot] == nodeToRootMap[secChild] {
+ continue
+ }
+ thisLayer := nodeLayerMap[subtreeRoot]
+ secDependantLayer := nodeLayerMap[secChild]
+ maxAllowedDistCandidate := secDependantLayer - thisLayer - 1
+ if maxAllowedDistCandidate < 0 {
+ // secChild is already at the level of subtreeRoot or even higher.
+ // It will be probably pushed down on the next iteration.
+ // Anyways, no go.
+ return 0
+ }
+ if maxAllowedDistCandidate < allowedDist {
+ allowedDist = maxAllowedDistCandidate
+ }
+ }
+
+ // Use primary children for recursion - we are checking a subtree of this root.
+ for _, priChild := range priChildrenMap[subtreeRoot] {
+ priChildAllowedDist := maxDistToPullSubtreeDownRecursive(priChild, nodeLayerMap, nodeToRootMap, priChildrenMap, secChildrenMap)
+ // Do not allow pulling down more than this child allows
+ if priChildAllowedDist < allowedDist {
+ allowedDist = priChildAllowedDist
+ }
+ }
+ return allowedDist
+}
+
+func buildLayerMap(nodeDefs []NodeDef) []int {
+ allChildrenMap := buildAllChildrenMap(nodeDefs)
+ secChildrenMap := buildSecChildrenMap(nodeDefs)
+ priChildrenMap := buildPriChildrenMap(nodeDefs)
+ nodeToRootMap := buildNodeToRootMap(buildPriParentMap(nodeDefs))
+ nodeLayerMap := slices.Repeat([]int{MissingLayer}, len(nodeDefs))
+ // Initialize layers for root nodes
+ for i := range len(nodeDefs) - 1 {
+ nodeIdx := i + 1
+ if nodeDefs[nodeIdx].PriIn.SrcId == 0 {
+ nodeLayerMap[nodeIdx] = 0
+ }
+ }
+
+ for {
+ reScan := false
+ // Push down stressed by pri or sec parent
+ for i := range len(nodeDefs) - 1 {
+ nodeIdx := int16(i + 1)
+ parentNodeIdx := nodeDefs[nodeIdx].PriIn.SrcId
+ if parentNodeIdx != 0 && nodeLayerMap[parentNodeIdx] != MissingLayer {
+ if nodeLayerMap[nodeIdx] == MissingLayer || nodeLayerMap[nodeIdx] <= nodeLayerMap[parentNodeIdx] {
+ nodeLayerMap[nodeIdx] = nodeLayerMap[parentNodeIdx] + 1
+ reScan = true
+ }
+ }
+ for _, secParent := range nodeDefs[nodeIdx].SecIn {
+ parentNodeIdx := secParent.SrcId
+ if parentNodeIdx != 0 && nodeLayerMap[parentNodeIdx] != MissingLayer {
+ if nodeLayerMap[nodeIdx] == MissingLayer || nodeLayerMap[nodeIdx] <= nodeLayerMap[parentNodeIdx] {
+ nodeLayerMap[nodeIdx] = nodeLayerMap[parentNodeIdx] + 1
+ reScan = true
+ }
+ }
+ }
+ }
+
+ // Pull down if there is room
+ for i := range len(nodeDefs) - 1 {
+ nodeIdx := int16(i + 1)
+ minChildLayer := math.MaxInt
+ for _, childIdx := range allChildrenMap[nodeIdx] {
+ if nodeLayerMap[childIdx] < minChildLayer {
+ minChildLayer = nodeLayerMap[childIdx]
+ }
+ }
+ if minChildLayer < math.MaxInt {
+ allowedDist := minChildLayer - 1 - nodeLayerMap[nodeIdx]
+ if allowedDist > 0 {
+ nodeLayerMap[nodeIdx] += allowedDist
+ reScan = true
+ } else {
+ // Ok, no room between this nodeIdx and its immediate children (pri or sec).
+ // Can we pull the whole nodeIdx subtree down?
+ subtreeAllowedDist := maxDistToPullSubtreeDownRecursive(nodeIdx, nodeLayerMap, nodeToRootMap, priChildrenMap, secChildrenMap)
+ // It can return math.MaxInt which means we can pull down this subtree to infinity. But it does not make sense, so leave it where it is.
+ if subtreeAllowedDist < math.MaxInt && subtreeAllowedDist > 0 {
+ nodeLayerMap[nodeIdx] += subtreeAllowedDist
+ reScan = true
+ }
+ }
+ }
+ }
+
+ if !reScan {
+ break
+ }
+ }
+
+ return nodeLayerMap
+}
diff --git a/pkg/capigraph/node_def.go b/pkg/capigraph/node_def.go
new file mode 100644
index 00000000..df4ce2b5
--- /dev/null
+++ b/pkg/capigraph/node_def.go
@@ -0,0 +1,196 @@
+package capigraph
+
+import (
+ "fmt"
+ "slices"
+)
+
+const MissingNodeId int16 = -1
+const MissingDistanceFromRootToNode int = -2
+const MissingRootSubtreeHeight int = -3
+const MissingLayer int = -4
+
+type EdgeDef struct {
+ SrcId int16
+ Text string
+}
+
+type NodeDef struct {
+ Id int16
+ Text string
+ PriIn EdgeDef
+ SecIn []EdgeDef
+ IconId string
+ Color int32
+ Selected bool
+}
+
+func buildPriParentMap(nodeDefs []NodeDef) []int16 {
+ pMap := slices.Repeat([]int16{MissingNodeId}, len(nodeDefs))
+ for i, nodeDef := range nodeDefs[1:] {
+ nodeId := int16(i + 1)
+ if nodeDef.PriIn.SrcId == 0 {
+ pMap[nodeId] = MissingNodeId
+ } else {
+ pMap[nodeId] = nodeDefs[nodeDef.PriIn.SrcId].Id
+ }
+ }
+ return pMap
+}
+
+func buildNodeToRootMap(priParentMap []int16) []int16 {
+ rMap := slices.Repeat([]int16{MissingNodeId}, len(priParentMap))
+ for i, parentId := range priParentMap[1:] {
+ srcNodeId := int16(i + 1)
+ candidateNodeId := int16(srcNodeId)
+ for parentId != MissingNodeId {
+ candidateNodeId = parentId
+ parentId = priParentMap[parentId]
+ }
+
+ rMap[srcNodeId] = candidateNodeId
+ }
+ return rMap
+}
+
+func buildAllChildrenMap(nodeDefs []NodeDef) [][]int16 {
+ acMap := make([][]int16, len(nodeDefs))
+ for i, nodeDef := range nodeDefs[1:] {
+ nodeId := int16(i + 1)
+ parentId := nodeDef.PriIn.SrcId
+ if parentId != 0 {
+ if acMap[parentId] == nil {
+ acMap[parentId] = make([]int16, 0, 32)
+ }
+ acMap[parentId] = append(acMap[parentId], int16(nodeId))
+ }
+
+ for _, edge := range nodeDef.SecIn {
+ if acMap[edge.SrcId] == nil {
+ acMap[edge.SrcId] = make([]int16, 0, 32)
+ }
+ acMap[edge.SrcId] = append(acMap[edge.SrcId], int16(nodeId))
+ }
+ }
+ return acMap
+}
+
+func buildSecChildrenMap(nodeDefs []NodeDef) [][]int16 {
+ scMap := make([][]int16, len(nodeDefs))
+ for i, nodeDef := range nodeDefs[1:] {
+ nodeId := int16(i + 1)
+ for _, edge := range nodeDef.SecIn {
+ if scMap[edge.SrcId] == nil {
+ scMap[edge.SrcId] = make([]int16, 0, 32)
+ }
+ scMap[edge.SrcId] = append(scMap[edge.SrcId], int16(nodeId))
+ }
+ }
+ return scMap
+}
+
+func buildPriChildrenMap(nodeDefs []NodeDef) [][]int16 {
+ pcMap := make([][]int16, len(nodeDefs))
+ for i, nodeDef := range nodeDefs[1:] {
+ nodeId := int16(i + 1)
+ parentId := nodeDef.PriIn.SrcId
+ if parentId != 0 {
+ if pcMap[parentId] == nil {
+ pcMap[parentId] = make([]int16, 0, 32)
+ }
+ pcMap[parentId] = append(pcMap[parentId], int16(nodeId))
+ }
+ }
+ return pcMap
+}
+
+func buildRootNodeList(priParentMap []int16) []int16 {
+ rootNodes := make([]int16, 0, 100)
+ for i, parentId := range priParentMap[1:] {
+ nodeId := int16(i + 1)
+ if parentId == MissingNodeId {
+ rootNodes = append(rootNodes, int16(nodeId))
+ }
+ }
+ return rootNodes
+}
+
+func assignDistanceFromRoot(acMap [][]int16, nodeId int16, dist int, fromOneRootToNodeDistanceMap []int) {
+ existingDist := fromOneRootToNodeDistanceMap[nodeId]
+ if existingDist < dist {
+ fromOneRootToNodeDistanceMap[nodeId] = dist
+ }
+ for _, childId := range acMap[nodeId] {
+ assignDistanceFromRoot(acMap, int16(childId), dist+1, fromOneRootToNodeDistanceMap)
+ }
+}
+
+func buildRootToNodeDistanceMap(totalNodes int, rootNodes []int16, acMap [][]int16) [][]int {
+ fromRootToNodeDistanceMap := make([][]int, totalNodes)
+ for _, rootNodeId := range rootNodes {
+ fromRootToNodeDistanceMap[rootNodeId] = slices.Repeat([]int{MissingDistanceFromRootToNode}, len(acMap))
+ assignDistanceFromRoot(acMap, rootNodeId, 0, fromRootToNodeDistanceMap[rootNodeId])
+ }
+ return fromRootToNodeDistanceMap
+}
+
+func getMaxSubtreeHeightByRoot(rootId int16, fromRootToNodeDistanceMap [][]int) int {
+ maxHeight := 0
+ for _, distanceFromRoot := range fromRootToNodeDistanceMap[rootId] {
+ if distanceFromRoot > maxHeight {
+ maxHeight = distanceFromRoot
+ }
+ }
+ return maxHeight
+}
+
+func buildRootSubtreeHeightsMap(totalNodes int, rootNodes []int16, rootToNodeDistanceMap [][]int) ([]int, int) {
+ rootSubtreeHeights := slices.Repeat([]int{MissingRootSubtreeHeight}, totalNodes)
+ maxSubtreeHeight := 0
+ for _, rootNodeId := range rootNodes {
+ subtreeHeight := getMaxSubtreeHeightByRoot(rootNodeId, rootToNodeDistanceMap)
+ rootSubtreeHeights[rootNodeId] = subtreeHeight
+ if subtreeHeight > maxSubtreeHeight {
+ maxSubtreeHeight = subtreeHeight
+ }
+ }
+ return rootSubtreeHeights, maxSubtreeHeight
+}
+
+func checkForLoopsRecursive(nodeId int16, nodeDefs []NodeDef, startNode int16) error {
+ if nodeDefs[nodeId].PriIn.SrcId != 0 {
+ if nodeDefs[nodeId].PriIn.SrcId == startNode {
+ return fmt.Errorf("%d<=%d", nodeId, nodeDefs[nodeId].PriIn.SrcId)
+ }
+ if err := checkForLoopsRecursive(nodeDefs[nodeId].PriIn.SrcId, nodeDefs, startNode); err != nil {
+ return fmt.Errorf("%d<=%s", nodeId, err.Error())
+ }
+ }
+ for i := range nodeDefs[nodeId].SecIn {
+ edge := &(nodeDefs[nodeId].SecIn[i])
+ if edge.SrcId == startNode {
+ return fmt.Errorf("%d<-%d", nodeId, edge.SrcId)
+ }
+ if err := checkForLoopsRecursive(edge.SrcId, nodeDefs, startNode); err != nil {
+ return fmt.Errorf("%d<-%s", nodeId, err.Error())
+ }
+ }
+ return nil
+}
+
+func checkNodeDef(nodeId int16, nodeDefs []NodeDef) error {
+ if nodeDefs[nodeId].PriIn.SrcId == 0 && len(nodeDefs[nodeId].SecIn) > 0 {
+ return fmt.Errorf("cannot process node def %d: it has no primary parent, but has secondary parents like %d", nodeId, nodeDefs[nodeId].SecIn[0].SrcId)
+ }
+ return checkForLoopsRecursive(nodeId, nodeDefs, nodeId)
+
+}
+
+func checkNodeIds(nodeDefs []NodeDef) error {
+ for i := range nodeDefs {
+ if nodeDefs[i].Id != int16(i) {
+ return fmt.Errorf("cannot process node at index %d, it has id %d", i, nodeDefs[i].Id)
+ }
+ }
+ return nil
+}
diff --git a/pkg/capigraph/node_def_test.go b/pkg/capigraph/node_def_test.go
new file mode 100644
index 00000000..b9a72620
--- /dev/null
+++ b/pkg/capigraph/node_def_test.go
@@ -0,0 +1,34 @@
+package capigraph
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestLoop(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{}, []EdgeDef{{1, ""}}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{{2, ""}}, "", 0, false},
+ }
+ assert.Equal(t, "3<=2<-1<=3", checkNodeDef(3, nodeDefs).Error())
+}
+
+func TestCheckBadParents(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{{2, ""}}, "", 0, false},
+ {2, "2", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ }
+ assert.Equal(t, "cannot process node def 1: it has no primary parent, but has secondary parents like 2", checkNodeDef(1, nodeDefs).Error())
+}
+
+func TestCheckNodeIds(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ }
+ assert.Equal(t, "cannot process node at index 0, it has id 1", checkNodeIds(nodeDefs).Error())
+}
diff --git a/pkg/capigraph/svg.go b/pkg/capigraph/svg.go
new file mode 100644
index 00000000..8e2914fd
--- /dev/null
+++ b/pkg/capigraph/svg.go
@@ -0,0 +1,413 @@
+package capigraph
+
+import (
+ "fmt"
+ "math"
+ "slices"
+ "sort"
+ "strings"
+)
+
+func DefaultNodeFontOptions() FontOptions {
+ return FontOptions{FontTypefaceCourier, FontWeightNormal, 20, 0.3}
+}
+
+func DefaultEdgeLabelFontOptions() FontOptions {
+ return FontOptions{FontTypefaceArial, FontWeightNormal, 18, 0.3}
+}
+
+func DefaultEdgeOptions() EdgeOptions {
+ return EdgeOptions{2.0}
+}
+
+func intToCssColor(c int32) string {
+ colorRunes := make([]rune, 6)
+ colorRunes[0] = halfByteToChar(int8((c >> 20) & 0x0F))
+ colorRunes[1] = halfByteToChar(int8((c >> 16) & 0x0F))
+ colorRunes[2] = halfByteToChar(int8((c >> 12) & 0x0F))
+ colorRunes[3] = halfByteToChar(int8((c >> 8) & 0x0F))
+ colorRunes[4] = halfByteToChar(int8((c >> 4) & 0x0F))
+ colorRunes[5] = halfByteToChar(int8(c & 0x0F))
+ return string(colorRunes)
+}
+
+func getColorOverride(defaultColor string, rootId int16, rootColorMap []int32) string {
+ if rootColorMap[rootId] == -1 {
+ return defaultColor
+ }
+ return intToCssColor(rootColorMap[rootId])
+}
+
+func getStyleColorOverrideForRoot(attrName string, rootId int16, rootColorMap []int32) string {
+ if rootColorMap[rootId] == -1 {
+ return ""
+ }
+ return fmt.Sprintf(`style="%s:#%s;"`, attrName, intToCssColor(rootColorMap[rootId]))
+}
+func getStyleColorOverrideForNode(attrName string, color int32) string {
+ if color == int32(0) {
+ return ""
+ }
+ return fmt.Sprintf(`style="%s:#%s;"`, attrName, intToCssColor(color))
+}
+
+func getStyleColorOverrideForNodeWithOpacity2(attrName string, color int32) string {
+ if color == int32(0) {
+ return ""
+ }
+ return fmt.Sprintf(`style="%s:#%s;opacity:0.3;"`, attrName, intToCssColor(color))
+}
+
+func drawNodeSelections(vizNodeMap []VizNode, nodeFo FontOptions) string {
+ sb := strings.Builder{}
+ for i := range len(vizNodeMap) - 1 {
+ curItem := vizNodeMap[i+1]
+ nodeX := curItem.X + curItem.TotalW/2 - curItem.NodeW/2
+ if curItem.Def.Selected {
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ nodeX-SelectedNodeMargin*nodeFo.SizeInPixels, curItem.Y-SelectedNodeMargin*nodeFo.SizeInPixels, curItem.NodeW+SelectedNodeMargin*nodeFo.SizeInPixels*2, curItem.NodeH+SelectedNodeMargin*nodeFo.SizeInPixels*2))
+ }
+ }
+ return sb.String()
+
+}
+func drawEdgeLines(vizNodeMap []VizNode, curItem *VizNode, nodeFo FontOptions, edgeFo FontOptions, eo EdgeOptions, rootStrokeColorMap []int32) string {
+ sb := strings.Builder{}
+ if curItem.Def != nil {
+ for _, edge := range curItem.Def.SecIn {
+ parentItem := &(vizNodeMap[edge.SrcId])
+
+ startX := parentItem.X + parentItem.TotalW/2.0
+ endX := curItem.X + curItem.TotalW/2.0
+ startOffset, endOffset := getSecOffsetX(startX, endX, nodeFo.SizeInPixels/2.0)
+ startX += startOffset
+ endX += endOffset
+
+ startY := parentItem.Y + parentItem.NodeH + eo.StrokeWidth*2.0
+ endY := curItem.Y - eo.StrokeWidth*3
+
+ deltaX := endX - startX
+ deltaY := endY - startY
+
+ // For nearly-vertical sec connector, add a twist - so it has a better chance not to interfere with some pri connector
+ curveDeltaForSimilarX := 0.0
+ if math.Abs(startY-endY)/math.Abs(startX-endX) > 8 {
+ curveDeltaForSimilarX = deltaX * math.Abs(startY-endY) / math.Abs(startX-endX) / 4
+ }
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ parentItem.RootId,
+ startX, startY, startX+deltaX*0.2, startY+deltaY*0.5, startX+deltaX*0.8+curveDeltaForSimilarX, startY+deltaY*0.5, endX, endY))
+ }
+ }
+
+ for _, childItem := range curItem.PriChildrenAndEnclosedRoots {
+ if curItem.Def != nil {
+ if childItem.Def.PriIn.SrcId == curItem.Def.Id {
+ startX := curItem.X + curItem.TotalW/2
+ endX := childItem.X + childItem.TotalW/2
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ curItem.RootId,
+ startX,
+ curItem.Y+curItem.NodeH+eo.StrokeWidth*2.0,
+ endX,
+ childItem.Y-eo.StrokeWidth*3))
+ }
+ }
+ sb.WriteString(drawEdgeLines(vizNodeMap, childItem, nodeFo, edgeFo, eo, rootStrokeColorMap))
+ }
+ return sb.String()
+}
+
+func drawNodesAndEdgeLabels(vizNodeMap []VizNode, curItem *VizNode, nodeFo FontOptions, edgeFo FontOptions, eo EdgeOptions, rootColorMap []int32) string {
+ xmlReplacer := strings.NewReplacer("\"", """, "'", "'", "<", "<", ">", ">", "&", "&")
+ sb := strings.Builder{}
+ if curItem.Def != nil {
+ title := strings.TrimSpace(xmlReplacer.Replace(fmt.Sprintf("%d %s", curItem.Def.Id, curItem.Def.IconId)))
+ sb.WriteString(fmt.Sprintf(``+"\n", title))
+ nodeX := curItem.X + curItem.TotalW/2 - curItem.NodeW/2
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ curItem.RootId,
+ getStyleColorOverrideForNodeWithOpacity2("fill", curItem.Def.Color),
+ nodeX, curItem.Y, curItem.NodeW, curItem.NodeH))
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ curItem.RootId,
+ getStyleColorOverrideForNode("stroke", curItem.Def.Color),
+ nodeX, curItem.Y, curItem.NodeW, curItem.NodeH))
+ actualIconSize := 0.0
+ if curItem.Def.IconId != "" {
+ actualIconSize = curItem.NodeH - nodeFo.SizeInPixels*NodeTextDimensionMargin*2
+ iconColorCssOverride := getStyleColorOverrideForRoot("fill", curItem.RootId, rootColorMap)
+ if curItem.Def.Color != 0 {
+ iconColorCssOverride = getStyleColorOverrideForNode("fill", curItem.Def.Color)
+ }
+ sb.WriteString(fmt.Sprintf(` `+"\n "+` `+"\n \n",
+ nodeX+nodeFo.SizeInPixels*NodeTextDimensionMargin,
+ curItem.Y+nodeFo.SizeInPixels*NodeTextDimensionMargin,
+ actualIconSize/100.0,
+ curItem.Def.IconId,
+ iconColorCssOverride,
+ ))
+ }
+ for i, r := range strings.Split(curItem.Def.Text, "\n") {
+ textX := curItem.X + curItem.TotalW/2 - curItem.NodeW/2 + nodeFo.SizeInPixels*NodeTextDimensionMargin
+ if actualIconSize > 0.0 {
+ textX += actualIconSize + nodeFo.SizeInPixels*NodeTextIconInterval
+ }
+ sb.WriteString(fmt.Sprintf(` %s `+"\n",
+ textX,
+ curItem.Y+nodeFo.SizeInPixels*NodeTextDimensionMargin+float64(i)*nodeFo.SizeInPixels*(1.0+nodeFo.Interval),
+ xmlReplacer.Replace(r)))
+ }
+ sb.WriteString(" \n")
+
+ eolReplacer := strings.NewReplacer("\r", "", "\n", " ")
+ // Incoming edge labels
+ for _, edgeItem := range curItem.IncomingVizEdges {
+ // Do not draw zero-dimension label, it's a sec duplicate or just empty (pri or sec)
+ if edgeItem.W == 0.0 {
+ continue
+ }
+ sb.WriteString(fmt.Sprintf(``+"\n", strings.TrimSpace(xmlReplacer.Replace(eolReplacer.Replace(edgeItem.Edge.Text)))))
+ parentItem := &(vizNodeMap[edgeItem.Edge.SrcId])
+ if edgeItem.HierarchyType == HierarchySec {
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ edgeItem.X, edgeItem.Y, edgeItem.W, edgeItem.H))
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ parentItem.RootId,
+ edgeItem.X, edgeItem.Y, edgeItem.W, edgeItem.H))
+ for i, r := range strings.Split(edgeItem.Edge.Text, "\n") {
+ sb.WriteString(fmt.Sprintf(` %s `+"\n",
+ edgeItem.X+edgeFo.SizeInPixels*LabelTextDimensionMargin,
+ edgeItem.Y+edgeFo.SizeInPixels*LabelTextDimensionMargin+float64(i)*edgeFo.SizeInPixels*(1+edgeFo.Interval),
+ xmlReplacer.Replace(r)))
+ }
+ } else if edgeItem.HierarchyType == HierarchyPri {
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ edgeItem.X, edgeItem.Y, edgeItem.W, edgeItem.H))
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ parentItem.RootId,
+ edgeItem.X, edgeItem.Y, edgeItem.W, edgeItem.H))
+ for i, r := range strings.Split(edgeItem.Edge.Text, "\n") {
+ sb.WriteString(fmt.Sprintf(` %s `+"\n",
+ edgeItem.X+edgeFo.SizeInPixels*LabelTextDimensionMargin,
+ edgeItem.Y+edgeFo.SizeInPixels*LabelTextDimensionMargin+float64(i)*edgeFo.SizeInPixels*(1+edgeFo.Interval),
+ xmlReplacer.Replace(r)))
+ }
+ }
+ sb.WriteString(" \n")
+ }
+ }
+
+ for _, childItem := range curItem.PriChildrenAndEnclosedRoots {
+ sb.WriteString(drawNodesAndEdgeLabels(vizNodeMap, childItem, nodeFo, edgeFo, eo, rootColorMap))
+ }
+ return sb.String()
+}
+
+type EdgeOptions struct {
+ StrokeWidth float64
+}
+
+func DefaultPalette() []int32 {
+ // return []int32{0x4E79A7, 0xF28E2C, 0xE15759, 0x76B7B2, 0x59A14F, 0xEDC949, 0xAF7AA1, 0xFF9DA7, 0x9C755F, 0xBAB0A} // New T10
+ // return []int32{0x4C72B0, 0xDD8452, 0x55A868, 0xC44E52, 0x8172B3, 0x937860, 0xDA8BC3, 0x8C8C8C, 0xCCB974, 0x64B5CD} // deep
+ // return []int32{0xA1C9F4, 0xFFB482, 0x8DE5A1, 0xFF9F9B, 0xD0BBFF, 0xDEBB9B, 0xFAB0E4, 0xCFCFCF, 0xFFFEA3, 0xB9F2F0} // pastel, too bleached
+ return []int32{0x023EFF, 0xFF7C00, 0x1AC938, 0xE8000B, 0x8B2BE2, 0x9F4800, 0xF14CC1, 0xA3A3A3, 0xFFC400, 0x00D7FF} // bright, very visible
+}
+
+// Currently not used
+// func getTextColorForBackground(bgColor int32) int32 {
+// r := (bgColor >> 16) & 0xFF
+// g := (bgColor >> 8) & 0xFF
+// b := bgColor & 0xFF
+// uicolors := []float64{float64(r / 255.0), float64(g / 255.0), float64(b / 255.0)}
+// c := make([]float64, len(uicolors))
+// for i, col := range uicolors {
+// if col <= 0.03928 {
+// c[i] = col / 12.92
+// } else {
+// c[i] = math.Pow((col+0.055)/1.055, 2.4)
+// }
+// }
+// l := (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2])
+// if l > 0.179 {
+// return 0
+// } else {
+// return 0x00FFFFFF
+// }
+// }
+
+func buildRootColorMap(vizNodeMap []VizNode, palette []int32) []int32 {
+ rootIds := make([]int16, 0, len(vizNodeMap))
+ rootCountMap := make([]int, len(vizNodeMap))
+ for i := range len(vizNodeMap) - 1 {
+ vizNode := &vizNodeMap[i+1]
+ if rootCountMap[vizNode.RootId] == 0 {
+ rootIds = append(rootIds, vizNode.RootId)
+ }
+ rootCountMap[vizNode.RootId]++
+ }
+ sort.Slice(rootIds, func(i, j int) bool {
+ return rootCountMap[rootIds[i]] > rootCountMap[rootIds[j]]
+ })
+
+ rootColorMap := slices.Repeat([]int32{-1}, len(vizNodeMap))
+ if len(palette) > 0 {
+ colorCounter := 0
+ for _, rootId := range rootIds {
+ rootColorMap[rootId] = palette[colorCounter%len(palette)]
+ colorCounter++
+ }
+ }
+ return rootColorMap
+}
+
+const SelectedNodeMargin float64 = 0.0
+
+func draw(vizNodeMap []VizNode, nodeFo FontOptions, edgeFo FontOptions, eo EdgeOptions, defsXml string, css string, palette []int32, totalPermutations int64, elapsed float64, bestDist float64) string {
+ topCoord := math.MaxFloat64
+ bottomCoord := -math.MaxFloat64
+ minLeft := math.MaxFloat64
+ maxRight := -math.MaxFloat64
+ for i := range len(vizNodeMap) - 1 {
+ hi := &vizNodeMap[i+1]
+ for _, edge := range hi.IncomingVizEdges {
+ if edge.X < minLeft {
+ minLeft = edge.X
+ }
+ if edge.X+edge.W > maxRight {
+ maxRight = edge.X + edge.W
+ }
+ }
+ nodeLeft := hi.X
+ nodeRight := hi.X + hi.NodeW
+ nodeTop := hi.Y
+ nodeBottom := hi.Y + hi.NodeH
+ if hi.Def.Selected {
+ nodeLeft -= SelectedNodeMargin * nodeFo.SizeInPixels
+ nodeRight += SelectedNodeMargin * nodeFo.SizeInPixels
+ nodeTop -= SelectedNodeMargin * nodeFo.SizeInPixels
+ nodeBottom += SelectedNodeMargin * nodeFo.SizeInPixels
+ }
+ if nodeLeft < minLeft {
+ minLeft = nodeLeft
+ }
+ if nodeRight > maxRight {
+ maxRight = nodeRight
+ }
+ if nodeBottom > bottomCoord {
+ bottomCoord = nodeBottom
+ }
+ if nodeTop < topCoord {
+ topCoord = nodeTop
+ }
+ }
+
+ vbLeft := int(minLeft - 10.0)
+ vbRight := int(maxRight + 20.0)
+ vbTop := int(topCoord - 10.0)
+ vbBottom := int(bottomCoord + 20.0)
+
+ rootColorMap := buildRootColorMap(vizNodeMap, palette)
+
+ sb := strings.Builder{}
+ sb.WriteString(fmt.Sprintf(
+ ``+"\n", vbLeft, vbTop, vbRight-vbLeft, vbBottom-vbTop))
+ sb.WriteString("\n")
+
+ for rootId := range len(rootColorMap) {
+ sb.WriteString(fmt.Sprintf(` `+"\n",
+ rootId,
+ getColorOverride("000000", int16(rootId), rootColorMap)))
+ }
+
+ // Caller-provided defs (icons etc)
+ sb.WriteString(defsXml)
+ sb.WriteString(" \n")
+ sb.WriteString("\n")
+ sb.WriteString(fmt.Sprintf(` `+"\n", vbLeft, vbTop, vbRight-vbLeft, vbBottom-vbTop))
+
+ // Node selections at the z-bottom
+ sb.WriteString(drawNodeSelections(vizNodeMap, nodeFo))
+
+ // Edge lines first, nodes and labels can overlap with them
+ topItem := &vizNodeMap[0]
+ sb.WriteString(drawEdgeLines(vizNodeMap, topItem, nodeFo, edgeFo, eo, rootColorMap))
+ // Nodes and labels
+ sb.WriteString(drawNodesAndEdgeLabels(vizNodeMap, topItem, nodeFo, edgeFo, eo, rootColorMap))
+
+ sb.WriteString(fmt.Sprintf(`Perms %d, elapsed %.3fs, dist %.1f `+"\n", totalPermutations, elapsed, bestDist))
+
+ sb.WriteString(" \n")
+ return sb.String()
+}
+
+func DrawOptimized(nodeDefs []NodeDef, nodeFo FontOptions, edgeFo FontOptions, edgeOptions EdgeOptions, defsOverride string, cssOverride string, palette []int32) (string, []VizNode, int64, float64, float64, error) {
+ if err := checkNodeIds(nodeDefs); err != nil {
+ return "", nil, int64(0), 0.0, 0.0, err
+ }
+
+ for i := range len(nodeDefs) - 1 {
+ if err := checkNodeDef(int16(i+1), nodeDefs); err != nil {
+ return "", nil, int64(0), 0.0, 0.0, err
+ }
+ }
+ vizNodeMap, totalPermutations, elapsed, bestDist, err := getBestHierarchy(nodeDefs, nodeFo, edgeFo, true)
+ if err != nil {
+ return "", nil, int64(0), 0.0, 0.0, err
+ }
+ svgString := draw(vizNodeMap, nodeFo, edgeFo, edgeOptions, defsOverride, cssOverride, palette, totalPermutations, elapsed, bestDist)
+ return svgString, vizNodeMap, totalPermutations, elapsed, bestDist, nil
+}
+
+func DrawUnoptimized(nodeDefs []NodeDef, nodeFo FontOptions, edgeFo FontOptions, edgeOptions EdgeOptions, defsOverride string, cssOverride string, palette []int32) (string, []VizNode, error) {
+ if err := checkNodeIds(nodeDefs); err != nil {
+ return "", nil, err
+ }
+
+ for i := range len(nodeDefs) - 1 {
+ if err := checkNodeDef(int16(i+1), nodeDefs); err != nil {
+ return "", nil, err
+ }
+ }
+
+ vizNodeMap, totalPermutations, elapsed, bestDist, err := getBestHierarchy(nodeDefs, nodeFo, edgeFo, false)
+ if err != nil {
+ return "", nil, err
+ }
+ svgString := draw(vizNodeMap, nodeFo, edgeFo, edgeOptions, defsOverride, cssOverride, palette, totalPermutations, elapsed, bestDist)
+ return svgString, vizNodeMap, nil
+}
diff --git a/pkg/capigraph/svg_icons.go b/pkg/capigraph/svg_icons.go
new file mode 100644
index 00000000..52728a0d
--- /dev/null
+++ b/pkg/capigraph/svg_icons.go
@@ -0,0 +1,107 @@
+package capigraph
+
+const CapillariesIcons100x100 = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
diff --git a/pkg/capigraph/svg_icons_test.svg b/pkg/capigraph/svg_icons_test.svg
new file mode 100644
index 00000000..ae89859c
--- /dev/null
+++ b/pkg/capigraph/svg_icons_test.svg
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkg/capigraph/svg_test.go b/pkg/capigraph/svg_test.go
new file mode 100644
index 00000000..a75b48de
--- /dev/null
+++ b/pkg/capigraph/svg_test.go
@@ -0,0 +1,936 @@
+package capigraph
+
+import (
+ "fmt"
+ "math"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// Common MxPermutator and SVG tests
+
+func TestBasicSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsBasic, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 72.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TrivialParallelSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsTrivialParallel, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 47.2, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestOneEnclosingOneLevelSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsOneEnclosedOneLevel, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(6), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestOneEnclosedTwoLevelsSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsOneEnclosedTwoLevels, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(6), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestNoIntervalsSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsNoIntervals, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 0.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestFlat10Svg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsFlat10, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(3628800), totalPermutations)
+ assert.Equal(t, 0.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestTwoEnclosingTwoLevelsNodeSizeMattersSvg(t *testing.T) {
+ // Only one of 8, 9 is enclosed
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsTwoEnclosedNodeSizeMatters, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(24), totalPermutations)
+ assert.Equal(t, 432.0, bestDist)
+ fmt.Printf("%s\n", svg)
+
+ // Now make nodes 4 and 5 considerably wider - it will change the best hierarchy, 8 and 9 enclosed
+ testNodeDefsTwoEnclosedNodeSizeMatters[4].Text += " wider"
+ testNodeDefsTwoEnclosedNodeSizeMatters[5].Text += " wider"
+
+ svg, _, _, _, bestDist, _ = DrawOptimized(testNodeDefsTwoEnclosedNodeSizeMatters, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, 576.0, math.Round(bestDist*100.0)/100.0)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestOneSecondarySvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsOneSecondary, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(6), totalPermutations)
+ assert.Equal(t, 72.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestDiamonSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsDiamond, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(24), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestSubtreeBelowLongSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsSubtreeBelowLong, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 72.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestOneNotTwoLevelsDownSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsOneNotTwoLevelsDown, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestMultiSecParentPullDownSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsMultiSecParentPullDown, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestMultiSecParentNoPullDownSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsMultiSecParentNoPullDown, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestTwoLevelsFromOneParentSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsTwoLevelsFromOneParent, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestTwoLevelsFromOneParentSameRootSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsTwoLevelsFromOneParentSameRoot, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 72.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestTwoLevelsFromOneParentSameRootTwoFakesSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsTwoLevelsFromOneParentSameRootTwoFakes, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 72.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestDuplicateSecLabelsSvg(t *testing.T) {
+ svg, vizNodeMap, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsDuplicateSecLabels, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(720), totalPermutations)
+ assert.Equal(t, 660.0, bestDist)
+ // Zero-width sec labels to avoid interlapped
+ assert.Equal(t, 0.0, vizNodeMap[8].IncomingVizEdges[1].W)
+ // Displayed sec labels
+ assert.Equal(t, 37.08, vizNodeMap[7].IncomingVizEdges[1].W)
+ assert.Equal(t, 37.08, vizNodeMap[9].IncomingVizEdges[1].W)
+ assert.Equal(t, 37.08, vizNodeMap[10].IncomingVizEdges[1].W)
+ assert.Equal(t, 37.08, vizNodeMap[11].IncomingVizEdges[1].W)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestLayerLongRootsSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsLayerLongRoots, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(6), totalPermutations)
+ assert.Equal(t, 216.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestPriAndSecInfinitePulldownSvg(t *testing.T) {
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefsPriAndSecInfinitePulldown, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(4), totalPermutations)
+ assert.Equal(t, 144.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+// Takes 15 seconds, disable for quick testing
+// func Test40milPermsSvg(t *testing.T) {
+// defer profile.Start(profile.CPUProfile).Stop()
+// svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(testNodeDefs40milPerms, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+// assert.Equal(t, int64(41472000), totalPermutations)
+// assert.Equal(t, 84.0, math.Round(bestDist*100.0)/100.0)
+// fmt.Printf("%s\n", svg)
+// }
+
+func Test300bilPermsSvg(t *testing.T) {
+ _, _, _, _, _, err := DrawOptimized(testNodeDefs300bilPerms, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Contains(t, err.Error(), "313528320000")
+ svg, _, err := DrawUnoptimized(testNodeDefs300bilPerms, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, nil, err)
+ fmt.Printf("%s\n", svg)
+}
+
+// SVG-specific tests
+
+// Unoptimized only, too many permutations
+func TestUnoptimizedSvg(t *testing.T) {
+ nodeDefs := make([]NodeDef, 0, 10000)
+ nodeDefs = append(nodeDefs, NodeDef{0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false})
+ var populateChildren func(parentIdx int, firstChildIdx int, layer int) int
+ populateChildren = func(parentIdx int, firstChildIdx int, layer int) int {
+ if layer == 4 {
+ return firstChildIdx
+ }
+ nextChildIdx := firstChildIdx
+ for range 5 - layer {
+ parentOverrideIdx := parentIdx
+ if firstChildIdx%7 == 0 {
+ parentOverrideIdx = 0
+ }
+ newNode := NodeDef{int16(nextChildIdx), fmt.Sprintf("%d", nextChildIdx), EdgeDef{int16(parentOverrideIdx), ""}, []EdgeDef{}, "", 0, false}
+ if parentOverrideIdx != 0 && firstChildIdx%9 == 0 {
+ newNode.SecIn = append(newNode.SecIn, EdgeDef{int16(firstChildIdx / 2), ""})
+ }
+ nodeDefs = append(nodeDefs, newNode)
+ nextChildIdx = populateChildren(nextChildIdx, nextChildIdx+1, layer+1)
+ }
+ return nextChildIdx
+ }
+ populateChildren(0, 1, 0)
+
+ svg, _, err := DrawUnoptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, nil, err)
+ fmt.Printf("%s\n", svg)
+}
+
+// Takes 160s to complete, but working. Fake nodes, enclosed subtrees, 20 levels, 484 nodes.
+// func TestInsanelyBigBinaryTreeSvg(t *testing.T) {
+// nodeDefs := make([]NodeDef, 0, 10000)
+// nodeDefs = append(nodeDefs, NodeDef{0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false})
+// var populateChildren func(parentIdx int, firstChildIdx int, layer int) int
+// populateChildren = func(parentIdx int, firstChildIdx int, layer int) int {
+// if layer == 7 {
+// return firstChildIdx
+// }
+// nextChildIdx := firstChildIdx
+// for range 2 {
+// parentOverrideIdx := parentIdx
+// if firstChildIdx%7 == 0 && firstChildIdx < 20 {
+// parentOverrideIdx = 0
+// layer = 0
+// }
+// newNode := NodeDef{int16(nextChildIdx), fmt.Sprintf("%d", nextChildIdx), EdgeDef{int16(parentOverrideIdx), ""}, []EdgeDef{}, "", 0, false}
+// if parentOverrideIdx != 0 && firstChildIdx%9 == 0 {
+// newNode.SecIn = append(newNode.SecIn, EdgeDef{int16(firstChildIdx / 2), ""})
+// }
+// nodeDefs = append(nodeDefs, newNode)
+// nextChildIdx = populateChildren(nextChildIdx, nextChildIdx+1, layer+1)
+// }
+// return nextChildIdx
+// }
+// populateChildren(0, 1, 0)
+
+// svg, _, totalPermutations, _, _, err := DrawOptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+// assert.Equal(t, int64(3819584), totalPermutations)
+// assert.Equal(t, nil, err)
+// fmt.Printf("%s\n", svg)
+// }
+
+func TestEnclosingOneLevelWideNodes(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "A1\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "A21\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{1, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A1 to A21"}, []EdgeDef{}, "", 0, false},
+ {3, "A22\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{1, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A1 to A22"}, []EdgeDef{}, "", 0, false},
+ {4, "A31\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{2, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A21 to A31"}, []EdgeDef{{6, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom B1 to A31"}}, "", 0, false},
+ {5, "A32\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{3, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A22 to A32"}, []EdgeDef{{6, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom B1 to A32"}}, "", 0, false},
+ {6, "B1\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ }
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(6), totalPermutations)
+ assert.Equal(t, 768.0, bestDist)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestHalfComplexWithEnclosed(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "A1\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "A2\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{1, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A1 to A2"}, []EdgeDef{}, "", 0, false},
+ {3, "A31\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{2, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A2 to A31"}, []EdgeDef{{10, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom B2 to A31"}}, "", 0, false},
+ {4, "A32\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{2, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A2 to A32"}, []EdgeDef{{14, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom C3 to A32"}}, "", 0, false},
+ {5, "A41\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{4, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A32 to A41"}, []EdgeDef{{11, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom B3 to A41"}}, "", 0, false},
+ {6, "A42\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{4, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A32 to A42"}, []EdgeDef{{14, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom C3 to A42"}}, "", 0, false},
+ {7, "A51\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{5, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A41 to A51"}, []EdgeDef{{15, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom D1 to A51"}}, "", 0, false},
+ {8, "A52\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{6, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom A42 to A52"}, []EdgeDef{{15, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom D1 to A52"}}, "", 0, false},
+ {9, "B1\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {10, "B2\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{9, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom B1 to B2"}, []EdgeDef{}, "", 0, false},
+ {11, "B3\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{10, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom B2 to B3"}, []EdgeDef{}, "", 0, false},
+ {12, "C1\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {13, "C2\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{12, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom C1 to C2"}, []EdgeDef{}, "", 0, false},
+ {14, "C3\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{13, "Lorem\n\n\nipsum\ndolor\nsit\namet\nfrom C2 to C3"}, []EdgeDef{}, "", 0, false},
+ {15, "D1\nlorem ipsum dolor sit amet,\nconsectetur adipisci elit,\nsed eiusmod tempor incidunt\nut labore\net dolore magna aliqua", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ }
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(48), totalPermutations)
+ assert.Equal(t, 3072.0, math.Round(bestDist))
+ fmt.Printf("%s\n", svg)
+}
+
+func TestConflictingSecAndTotalViewboxWidthAdjustedToLabel(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "A", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "B", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {3, "C", EdgeDef{1, "A to C"}, []EdgeDef{{2, "B to ? duplicate going really wide"}}, "", 0, false},
+ {4, "D", EdgeDef{3, "C to D"}, []EdgeDef{{2, "B to ? duplicate going really wide"}}, "", 0, false},
+ }
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, int64(2), totalPermutations)
+ assert.Equal(t, 144.0, math.Round(bestDist*100.0)/100.0)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestCapillariesIcons(t *testing.T) {
+ nodeDefs := []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {
+ 1,
+ "01_read_payments\n" +
+ "Read from files into a table\n" +
+ "Files: /tmp/capi_in/.../CAS_2023_R08_G1_20231020_000.parquet\n" +
+ "Table created: payments",
+ EdgeDef{},
+ []EdgeDef{},
+ "icon-database-table-read",
+ 0,
+ true,
+ },
+ {
+ 2,
+ "02_loan_ids\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(loan_id)\n" +
+ "Table created: loan_ids",
+ EdgeDef{1, "payments\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ true,
+ },
+ {
+ 3,
+ "02_deal_names\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name)\n" +
+ "Table created: deal_names",
+ EdgeDef{2, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ true,
+ },
+ {
+ 4,
+ "02_deal_sellers\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name, seller_name)\n" +
+ "Table created: deal_sellers",
+ EdgeDef{2, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ true,
+ },
+ {
+ 5,
+ "03_deal_total_upbs\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_total_upbs",
+ EdgeDef{3, "deal_names\n(10 batches)"},
+ []EdgeDef{{2, "idx_loan_ids_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ true,
+ },
+ {
+ 6,
+ "04_loan_payment_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_payment_summaries",
+ EdgeDef{2, "loan_ids\n(10 batches)"},
+ []EdgeDef{{1, "idx_payments_by_loan_id\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ true,
+ },
+ {
+ 7,
+ "04_loan_summaries_calculated\n" +
+ "Apply Python calculations\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_summaries_calculated",
+ EdgeDef{6, "loan_payment_summaries\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-py",
+ 0,
+ true,
+ },
+ {
+ 8,
+ "05_deal_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_summaries",
+ EdgeDef{5, "deal_total_upbs\n(10 batches)"},
+ []EdgeDef{{7, "idx_loan_summaries_calculated_deal_name\n(lookup)\ndeal_name"}},
+ "icon-database-table-join",
+ 0,
+ true,
+ },
+ {
+ 9,
+ "05_deal_seller_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_seller_summaries",
+ EdgeDef{4, "deal_sellers\n(10 batches)"},
+ []EdgeDef{{7, "idx_loan_summaries_calculated_deal_name\n(lookup)\ndeal_name\nseller_name"}},
+ "icon-database-table-join",
+ 0,
+ true,
+ },
+ {
+ 10,
+ "04_write_file_loan_summaries_calculated\n" +
+ "Write from table to files\n" +
+ "Table: loan_summaries_calculated\n" +
+ "Files: /tmp/.../.../loan_summaries_calculated.parquet",
+ EdgeDef{7, "loan_summaries_calculated\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ true,
+ },
+ {
+ 11,
+ "05_write_file_deal_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_summaries\n" +
+ "Files: /tmp/.../.../deal_summaries.parquet",
+ EdgeDef{8, "deal_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ true,
+ },
+ {
+ 12,
+ "05_write_file_deal_seller_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_seller_summaries\n" +
+ "Files: /tmp/.../.../deal_seller_summaries.parquet",
+ EdgeDef{9, "deal_seller_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ true,
+ },
+
+ {
+ 13,
+ "01_read_payments\n" +
+ "Read from files into a table\n" +
+ "Files: /tmp/capi_in/.../CAS_2023_R08_G1_20231020_000.parquet\n" +
+ "Table created: payments",
+ EdgeDef{},
+ []EdgeDef{},
+ "icon-database-table-read",
+ 0,
+ false,
+ },
+ {
+ 14,
+ "02_loan_ids\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(loan_id)\n" +
+ "Table created: loan_ids",
+ EdgeDef{13, "payments\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 15,
+ "02_deal_names\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name)\n" +
+ "Table created: deal_names",
+ EdgeDef{14, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 16,
+ "02_deal_sellers\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name, seller_name)\n" +
+ "Table created: deal_sellers",
+ EdgeDef{14, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 17,
+ "03_deal_total_upbs\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_total_upbs",
+ EdgeDef{15, "deal_names\n(10 batches)"},
+ []EdgeDef{{14, "idx_loan_ids_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 18,
+ "04_loan_payment_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_payment_summaries",
+ EdgeDef{14, "loan_ids\n(10 batches)"},
+ []EdgeDef{{13, "idx_payments_by_loan_id\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 19,
+ "04_loan_summaries_calculated\n" +
+ "Apply Python calculations\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_summaries_calculated",
+ EdgeDef{18, "loan_payment_summaries\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-py",
+ 0,
+ false,
+ },
+ {
+ 20,
+ "05_deal_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_summaries",
+ EdgeDef{17, "deal_total_upbs\n(10 batches)"},
+ []EdgeDef{{19, "idx_loan_summaries_calculated_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 21,
+ "05_deal_seller_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_seller_summaries",
+ EdgeDef{16, "deal_sellers\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 22,
+ "04_write_file_loan_summaries_calculated\n" +
+ "Write from table to files\n" +
+ "Table: loan_summaries_calculated\n" +
+ "Files: /tmp/.../.../loan_summaries_calculated.parquet",
+ EdgeDef{19, "loan_summaries_calculated\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ {
+ 23,
+ "05_write_file_deal_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_summaries\n" +
+ "Files: /tmp/.../.../deal_summaries.parquet",
+ EdgeDef{20, "deal_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ {
+ 24,
+ "05_write_file_deal_seller_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_seller_summaries\n" +
+ "Files: /tmp/.../.../deal_seller_summaries.parquet",
+ EdgeDef{21, "deal_seller_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+
+ {
+ 25,
+ "01_read_payments\n" +
+ "Read from files into a table\n" +
+ "Files: /tmp/capi_in/.../CAS_2023_R08_G1_20231020_000.parquet\n" +
+ "Table created: payments",
+ EdgeDef{},
+ []EdgeDef{},
+ "icon-database-table-read",
+ 0,
+ false,
+ },
+ {
+ 26,
+ "02_loan_ids\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(loan_id)\n" +
+ "Table created: loan_ids",
+ EdgeDef{25, "payments\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 27,
+ "02_deal_names\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name)\n" +
+ "Table created: deal_names",
+ EdgeDef{26, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 28,
+ "02_deal_sellers\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name, seller_name)\n" +
+ "Table created: deal_sellers",
+ EdgeDef{26, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 29,
+ "03_deal_total_upbs\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_total_upbs",
+ EdgeDef{27, "deal_names\n(10 batches)"},
+ []EdgeDef{{26, "idx_loan_ids_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 30,
+ "04_loan_payment_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_payment_summaries",
+ EdgeDef{26, "loan_ids\n(10 batches)"},
+ []EdgeDef{{25, "idx_payments_by_loan_id\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 31,
+ "04_loan_summaries_calculated\n" +
+ "Apply Python calculations\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_summaries_calculated",
+ EdgeDef{30, "loan_payment_summaries\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-py",
+ 0,
+ false,
+ },
+ {
+ 32,
+ "05_deal_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_summaries",
+ EdgeDef{29, "deal_total_upbs\n(10 batches)"},
+ []EdgeDef{{31, "idx_loan_summaries_calculated_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 33,
+ "05_deal_seller_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_seller_summaries",
+ EdgeDef{28, "deal_sellers\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 34,
+ "04_write_file_loan_summaries_calculated\n" +
+ "Write from table to files\n" +
+ "Table: loan_summaries_calculated\n" +
+ "Files: /tmp/.../.../loan_summaries_calculated.parquet",
+ EdgeDef{31, "loan_summaries_calculated\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ {
+ 35,
+ "05_write_file_deal_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_summaries\n" +
+ "Files: /tmp/.../.../deal_summaries.parquet",
+ EdgeDef{32, "deal_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ {
+ 36,
+ "05_write_file_deal_seller_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_seller_summaries\n" +
+ "Files: /tmp/.../.../deal_seller_summaries.parquet",
+ EdgeDef{33, "deal_seller_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+
+ {
+ 37,
+ "01_read_payments\n" +
+ "Read from files into a table\n" +
+ "Files: /tmp/capi_in/.../CAS_2023_R08_G1_20231020_000.parquet\n" +
+ "Table created: payments",
+ EdgeDef{},
+ []EdgeDef{},
+ "icon-database-table-read",
+ 0,
+ false,
+ },
+ {
+ 38,
+ "02_loan_ids\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(loan_id)\n" +
+ "Table created: loan_ids",
+ EdgeDef{37, "payments\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 39,
+ "02_deal_names\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name)\n" +
+ "Table created: deal_names",
+ EdgeDef{38, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 40,
+ "02_deal_sellers\n" +
+ "Select distinct rows\n" +
+ "Index used: unique(deal_name, seller_name)\n" +
+ "Table created: deal_sellers",
+ EdgeDef{38, "loan_ids\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-distinct",
+ 0,
+ false,
+ },
+ {
+ 41,
+ "03_deal_total_upbs\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_total_upbs",
+ EdgeDef{39, "deal_names\n(10 batches)"},
+ []EdgeDef{{38, "idx_loan_ids_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 42,
+ "04_loan_payment_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_payment_summaries",
+ EdgeDef{38, "loan_ids\n(10 batches)"},
+ []EdgeDef{{37, "idx_payments_by_loan_id\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 43,
+ "04_loan_summaries_calculated\n" +
+ "Apply Python calculations\n" +
+ "Group: true, join: left\n" +
+ "Table created: loan_summaries_calculated",
+ EdgeDef{42, "loan_payment_summaries\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-py",
+ 0,
+ false,
+ },
+ {
+ 44,
+ "05_deal_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_summaries",
+ EdgeDef{41, "deal_total_upbs\n(10 batches)"},
+ []EdgeDef{{43, "idx_loan_summaries_calculated_deal_name\n(lookup)"}},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 45,
+ "05_deal_seller_summaries\n" +
+ "Join with lookup table\n" +
+ "Group: true, join: left\n" +
+ "Table created: deal_seller_summaries",
+ EdgeDef{40, "deal_sellers\n(10 batches)"},
+ []EdgeDef{},
+ "icon-database-table-join",
+ 0,
+ false,
+ },
+ {
+ 46,
+ "04_write_file_loan_summaries_calculated\n" +
+ "Write from table to files\n" +
+ "Table: loan_summaries_calculated\n" +
+ "Files: /tmp/.../.../loan_summaries_calculated.parquet",
+ EdgeDef{43, "loan_summaries_calculated\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ {
+ 47,
+ "05_write_file_deal_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_summaries\n" +
+ "Files: /tmp/.../.../deal_summaries.parquet",
+ EdgeDef{44, "deal_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ {
+ 48,
+ "05_write_file_deal_seller_summaries\n" +
+ "Write from table to files\n" +
+ "Table: deal_seller_summaries\n" +
+ "Files: /tmp/.../.../deal_seller_summaries.parquet",
+ EdgeDef{45, "deal_seller_summaries\n(no parallelism)"},
+ []EdgeDef{},
+ "icon-parquet",
+ 0,
+ false,
+ },
+ }
+ // overrideCss := ".rect-node-background {rx:20; ry:20;} .rect-node {rx:20; ry:20;} .capigraph-rendering-stats {fill:black;}"
+ // nodeColorMap := []int32{0x010101, 0x0000FF, 0x008000, 0xFF0000, 0xFF8C00, 0x2F4F4F} //none, blue, darkgreen, red, darkorange, darkslategray (none, start, success, fail, stopreceived, unknown)
+ // for nodeIdx := range nodeDefs {
+ // nodeDefs[nodeIdx].Color = nodeColorMap[nodeIdx%len(nodeColorMap)]
+ // }
+ svg, _, totalPermutations, _, bestDist, _ := DrawOptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), CapillariesIcons100x100, "" /* overrideCss*/, DefaultPalette())
+ assert.Equal(t, int64(31104), totalPermutations)
+ assert.Equal(t, 6618.0, math.Round(bestDist*100.0)/100.0)
+ fmt.Printf("%s\n", svg)
+}
+
+func TestPrefixTree(t *testing.T) {
+ rootWord := "trip"
+ allWords := []string{"tri", "trip", "trim", "trio", "trib", "trig", "trix", "trin", "trid", "trit", "trial", "tried", "trips", "tries", "trick", "tribe", "trina", "triad", "trims", "trier", "trike", "trios", "trice", "trine", "trite", "tripe", "trill", "triac", "trias", "trigo", "triga", "trist", "trica", "trixy", "trior", "tripl", "triol", "tript", "tripy", "trink", "trigs", "trifa", "trials", "triple", "tricks", "tribal", "trivia", "tribes", "tripod", "tricky", "triton", "tricia", "trisha", "trixie", "triage", "trifle", "trichy", "tricot", "triads", "trivet", "triste", "triumf", "triune", "triode", "trigon", "tringa", "tripos", "trilby", "trimer", "triply", "trills", "tripel", "trillo", "triers", "tricon", "triops", "trines", "trifid", "trices", "triose", "triols", "triole", "triwet", "trivat", "tripot", "trityl", "tripes", "tritor", "tripla", "tricae", "triter", "trisul", "tripal", "trinol", "trifly", "triens", "triene", "triduo", "tridra", "tridii", "triace", "trichi", "triced", "tricar", "triazo", "triary", "trigae", "trigly", "trinil", "trigla", "trined", "trinal", "trimly", "trilli", "trilit", "trikir", "triker", "trijet", "trigyn", "trigos", "triact", "tribune", "trigger", "tribute", "trinity", "triumph", "trilogy", "trivial", "trivium", "trimmed", "tristan", "trimmer", "tripods", "trident", "triples", "trickle", "tricked", "triplet", "tritium", "tripoli", "tripled", "trinket", "tripped", "triplex", "tripple", "triceps", "trisomy", "tripper", "tribble", "trifles", "trifold", "trianon", "trivets", "trigram", "tritone", "tripler", "triadic", "tritons", "tricker", "triable", "tripack", "trimers", "trifled", "trionfo", "tricorn", "trinkle", "triller", "tribuna", "trireme", "tristam", "trilled", "triodes", "triduum", "triolet", "trionfi", "triplum", "trionyx", "tripart", "trionym", "triurid", "triones", "trioxid", "trioses", "trional", "tripara", "tripery", "tripody", "tritely", "tritest", "tritish", "tritolo", "tritoma", "triture", "triunal", "triunes", "triuris", "trivant", "trivvet", "trizoic", "tritaph", "trisulc", "trisula", "trippet", "tripsis", "triquet", "trisalt", "trisazo", "trisect", "triseme", "trishaw", "trishna", "trismic", "trismus", "trisome", "trizone", "trioecs", "triacid", "tribrac", "tribual", "trichia", "tricing", "trickie", "trickly", "tricksy", "triclad", "tricots", "triduam", "triduan", "triedly", "trienes", "trifler", "triblet", "tribase", "triaene", "triages", "triakid", "triamid", "triamin", "triarch", "triarii", "triaryl", "triatic", "triaxal", "triaxon", "triazin", "tribade", "tribady", "triflet", "trifoil", "trifoly", "trillet", "trillil", "trilobe", "trimera", "trinary", "trindle", "trinely", "tringle", "trining", "trinkum", "trinode", "trintle", "triobol", "triodia", "trilium", "trilith", "triform", "trigamy", "trigged", "triglid", "triglot", "trigona", "trigone", "trigons", "trijets", "trikaya", "triketo", "trilabe", "trilisa", "trilite", "triodon", "trinidad", "triangle", "tribunal", "triggers", "trillion", "trimming", "tripping", "tributes", "trimmers", "triumphs", "tristate", "triplets", "tricycle", "trillium", "tripwire", "tristram", "trinkets", "trickery", "tribulus", "triassic", "trifecta", "tricking", "triptych", "trifling", "tricolor", "tributed", "triticum", "trickier", "tripling", "trickled", "trioxide", "triaxial", "triazine", "trilling", "trickles", "trippers", "triskele", "trigonal", "triploid", "tristeza", "trimaran", "tribally", "tribunes", "trifocal", "triazole", "trigrams", "triology", "trimeric", "triumvir", "triplane", "trialist", "triatoma", "triturus", "tripolis", "trichome", "trivette", "trimurti", "tringoid", "trinerve", "triplite", "triplice", "trindled", "tripodal", "triplopy", "trindles", "tringine", "triphora", "triphony", "trinodal", "trinkums", "trinklet", "trioecia", "trioleic", "triolein", "triolets", "tripenny", "trioxids", "tripacks", "trinkety", "tripedal", "tripeman", "triodion", "triphane", "trinitro", "triphase", "trinomen", "trizonia", "tritomas", "tritiums", "triticin", "tritical", "tritiate", "trithing", "tristyly", "tristive", "tristich", "tristful", "tritonal", "tritones", "trizonal", "trizomal", "trivirga", "trivalve", "triunity", "triunion", "tritural", "tritoral", "tritonic", "tritonia", "triposes", "trispast", "triptote", "triptane", "tripsome", "tripsill", "trippler", "trippist", "trippets", "trippant", "tripoter", "tripolar", "triptyca", "tripudia", "trisomic", "trisomes", "trisetum", "trisemic", "trisemes", "trisects", "triscele", "triremes", "triratna", "triradii", "tripodic", "tridacna", "trickful", "trickers", "trichord", "trichoma", "trichoid", "trichode", "trichite", "trichion", "trichina", "triceria", "trickily", "trickish", "tricklet", "trictrac", "tricouni", "tricotee", "tricosyl", "tricorns", "tricorne", "triconch", "tricolon", "tricolic", "triclads", "tributer", "tribular", "triander", "triamino", "triamine", "triamide", "triality", "trialism", "trialate", "triadist", "triadism", "triadics", "triapsal", "triarchy", "triareal", "tribrach", "tribelet", "tribasic", "tribadic", "tribades", "triazoic", "triazins", "triazane", "triaster", "triarian", "triacids", "trimtram", "trilloes", "trilliin", "trilleto", "trillers", "trillado", "trilemma", "trilbies", "trikeria", "trihoral", "trihedra", "trilobal", "trilobed", "trilogic", "trimotor", "trimorph", "trimoric", "trimodal", "trimness", "trimmest", "trimeter", "trimesyl", "trimesic", "trimacer", "trihalid", "trigynia", "trifilar", "triethyl", "triequal", "trientes", "triental", "triennia", "triduums", "tridents", "tridecyl", "triddler", "triflers", "triforia", "trifuran", "trigraph", "trigonum", "trigonon", "trigonid", "trigonic", "trigonia", "trigness", "triglyph", "trigging", "triggest", "tridaily", "triggered", "triangles", "trimester", "tribunals", "tributary", "trimmings", "trigraphs", "trickster", "triennial", "trivially", "tricyclic", "trillions", "triumphed", "trickling", "tricycles", "triumphal", "tribesmen", "trifolium", "triticale", "tricuspid", "tribology", "trivalent", "trimethyl", "tristania", "tribalism", "trilobite", "tricolour", "triplexes", "trilinear", "tribesman", "trickiest", "triennium", "trilogies", "trichloro", "tritiated", "tristesse", "trisodium", "triazines", "triquetra", "trinomial", "tributing", "trichomes", "triazoles", "tripitaka", "trimarans", "tribolium", "trichuris", "triphasic", "triclinic", "tricyrtis", "tripeshop", "triparted", "trioxides", "tripelike", "triperies", "triozonid", "tripewife", "triploidy", "triploids", "triplites", "trisilane", "triorchis", "triplegia", "triplasic", "triplaris", "triplanes", "triphenyl", "triphasia", "triphaser", "triplopia", "triosteum", "trinketer", "trinketed", "trinitrin", "trimotors", "trindling", "trinitrid", "trinities", "trinidado", "trineural", "trinerved", "trinchera", "trination", "trinalize", "trinality", "trinketry", "trinodine", "trinovant", "triopidae", "trimetric", "trionymal", "triolefin", "trioleate", "triolcous", "trioicous", "trioecism", "trimorphs", "triocular", "trioctile", "triobolon", "trinunity", "trimstone", "triweekly", "triticums", "triticoid", "triticism", "triticeum", "trithings", "tritheite", "tritheist", "tritheism", "triteness", "triteleia", "tritanope", "tritactic", "trisulfid", "tristichs", "tristezas", "tritocone", "tritomite", "triverbal", "trivantly", "trivalves", "triumviry", "triumvirs", "triumviri", "triumpher", "triturium", "triturate", "tritoxide", "tritorium", "tritopine", "tritonous", "tritonoid", "tritoness", "trisquare", "trisporic", "tripudium", "tripudist", "tripudial", "triptyque", "triptychs", "triptycas", "triptanes", "tripsacum", "trippings", "tripotage", "tripolite", "tripoline", "tripodies", "tripodian", "tripodial", "tripylaea", "tripylean", "trisonant", "trisomies", "trisomics", "trismuses", "triskelia", "triskeles", "trisetose", "triserial", "trisector", "trisected", "trisceles", "trisagion", "triregnum", "triradius", "triradial", "tripmadam", "trimeters", "tricrural", "trichroic", "trichosis", "trichomic", "trichogen", "trichitis", "trichitic", "trichites", "trichions", "trichinas", "trichinal", "trichinae", "trichilia", "tricerium", "tricerion", "tricepses", "trichrome", "tricinium", "tricrotic", "tricresol", "tricotine", "tricosane", "tricornes", "tricolors", "triclinia", "triactine", "tricksome", "tricksily", "tricksier", "trickment", "tricklike", "tricklier", "trickless", "tricephal", "tricenary", "triazolic", "triatomic", "triarctic", "triannual", "triangula", "triangler", "triangled", "triandria", "triamorph", "trialogue", "triagonal", "triaenose", "triadisms", "triadical", "tribalist", "tribarred", "tricaudal", "tricarbon", "tricalcic", "tributist", "tribunate", "tribunary", "tribuloid", "tribulate", "tribually", "tribromid", "tribrachs", "tribonema", "tribeship", "tribelike", "tribeless", "triadenum", "trimerous", "trihydrol", "trihydric", "trihybrid", "trihourly", "trihedron", "trihedral", "trihalide", "trigynous", "trigynian", "trigonous", "trigonoid", "trigonite", "triglyphs", "triglidae", "trigintal", "trijugate", "trijugous", "trimerite", "trimeride", "trimellic", "trilogist", "trilobita", "trilobate", "trilliums", "trillibub", "trilletto", "trillando", "trilithon", "trilithic", "trilaurin", "triketone", "trikerion", "trigemini", "trigatron", "triennias", "trieennia", "triedness", "triecious", "tridymite", "tridrachm", "tridermic", "tridental", "tridecoic", "tridecene", "tridecane", "tridactyl", "tricycler", "tricycled", "tricuspal", "trierarch", "trierucin", "trigamous", "trigamist", "trifurcal", "trifornia", "triformin", "triformed", "triforium", "triforial", "trifocals", "trifloral", "triflings", "trifledom", "triferous", "trifacial", "trieteric", "trictracs", "triangular", "triggering", "triumphant", "tripartite", "trigeminal", "trilateral", "triplicate", "trilingual", "trimesters", "tricksters", "triviality", "trihydrate", "trivialize", "triumphing", "trinocular", "tricolored", "tridentine", "trinitrate", "triangulum", "tripeptide", "tricalcium", "trigonella", "triggerman", "triskelion", "tricholoma", "trihydroxy", "triborough", "trichechus", "trillionth", "trifoliate", "tripsomely", "triplefold", "tripterous", "trippingly", "triphysite", "tripinnate", "triplasian", "tripleback", "tripolitan", "triplicist", "tripleness", "tripliform", "triplexity", "triploidic", "triplewise", "triplumbic", "tripletree", "tripletail", "tripodical", "tripointed", "triplicity", "triphylite", "triodontes", "trinucleus", "trinacrian", "trinoctile", "trinoctial", "trinketing", "trinkermen", "trinkerman", "trinitride", "trinervate", "trimscript", "trimotored", "trimorphic", "trioecious", "triolefine", "triphyline", "triphthong", "triphibian", "triphammer", "tripewoman", "tripestone", "tripennate", "tripaschal", "triozonide", "trioxazine", "triovulate", "triorchism", "trionychid", "trimonthly", "trivirgate", "triturated", "triturable", "trittichan", "tritozooid", "tritonymph", "tritonidae", "tritoconid", "triticeous", "tritically", "trithrinax", "trithionic", "triterpene", "triternate", "tritanopic", "tritanopia", "triturates", "triturator", "trivialist", "trivialism", "trivialise", "trivetwise", "triverbial", "trivariant", "trivalerin", "trivalency", "trivalence", "triunities", "triungulin", "triumviral", "trivoltine", "triumfetta", "tritylodon", "tritangent", "trisylabic", "triseptate", "trisensory", "trisectrix", "trisection", "trisecting", "triradiate", "triquinoyl", "triquinate", "triquetrum", "triquetric", "triquetral", "tripylaean", "tripunctal", "tripudiate", "tripudiary", "triseriate", "trisilicic", "trisulphid", "trisulfone", "trisulfide", "trisulfate", "trisulcate", "tristylous", "tristichic", "tristfully", "tristeness", "tristearin", "trisporous", "trispinose", "trispaston", "trismegist", "trisinuate", "tripudiant", "triconodon", "tricipital", "trichromic", "trichromat", "trichroism", "trichotomy", "trichopter", "trichopore", "trichoplax", "trichology", "trichogyne", "trichocyst", "trichlorid", "trichiurus", "trichiurid", "trickeries", "trickiness", "tricolette", "tricoccous", "tricoccose", "triclinium", "triclinial", "triclinate", "tricladida", "tricktrack", "tricksiest", "tricksical", "trickproof", "trickliest", "trickishly", "trickingly", "trichinous", "trichinoid", "tribasilar", "triaxonian", "triarthrus", "triarchies", "triarchate", "triapsidal", "trianthous", "triangulid", "triandrous", "triandrian", "triamylose", "triactinal", "triaconter", "triacontad", "tribesfolk", "triblastic", "trichinize", "trichinise", "trichiasis", "trichevron", "trichauxis", "tricentral", "tricennial", "tricaudate", "tricarpous", "tributyrin", "tributable", "tribromide", "tribrachic", "tribometer", "triacetate", "tricornute", "trimnesses", "trilineate", "trilaminar", "trilabiate", "trihydride", "trihemimer", "trihemeral", "trihedrons", "trigraphic", "trigrammic", "trigonitis", "trigonally", "trignesses", "triglyphic", "triglyphed", "trilinguar", "triliteral", "trimmingly", "trimethoxy", "trimestral", "trimesitic", "trimesinic", "trimensual", "trimembral", "trimacular", "triluminar", "trilogical", "trilocular", "trilobitic", "trilobated", "trillachan", "triglyphal", "triglochin", "trielaidin", "tridiurnal", "tridepside", "tridentate", "tridecylic", "tricyclist", "tricycling", "tricyclene", "tricyanide", "tricussate", "tricurvate", "tricrotous", "tricrotism", "tricosylic", "trienniums", "trientalis", "triglochid", "trigesimal", "trigeneric", "trigeminus", "trifurcate", "triformous", "triformity", "triflorous", "triflorate", "triflingly", "trifarious", "trifanious", "trieterics", "trierarchy", "tricostate", "tributaries", "tribulation", "triumvirate", "trinitarian", "triceratops", "tridiagonal", "trichomonas", "trichoderma", "trichoptera", "trinidadian", "triggerfish", "trichloride", "triangulate", "trichinella", "trifluralin", "trichinosis", "trifluoride", "tribunitian", "tristimulus", "trinovantes", "triphyletic", "triphyllous", "tripinnated", "trinorantum", "trinopticon", "trinucleate", "tripemonger", "tripersonal", "tripartient", "tripartible", "tripartedly", "tripalmitin", "tripetaloid", "trionychoid", "tripetalous", "triphibious", "triodontoid", "triplicated", "trinomially", "trinobantes", "trimetallic", "trimetalism", "trimestrial", "trimercuric", "trimellitic", "trimargarin", "trimaculate", "triluminous", "trilophodon", "triloculate", "trimetrical", "trimetrogon", "trimodality", "trinklement", "trinketries", "trinitytide", "trinityhood", "trinational", "trimyristin", "trimuscular", "trimscripts", "trimorphous", "trimorphism", "trilobation", "triweeklies", "tritogeneia", "triticality", "trithionate", "tritheistic", "tritemorion", "tritanoptic", "tritanopsia", "tritagonist", "trisyllable", "trisyllabic", "tritonality", "triturating", "trituration", "trivialness", "trivialised", "trivalvular", "triuridales", "triumphwise", "triumphator", "triumphancy", "triumphance", "triturature", "triturators", "trisulphone", "trisulphide", "trisulphate", "trisections", "trisceptral", "triradiuses", "triradiated", "triradially", "triquetrous", "tripyrenous", "tripylarian", "tripunctate", "triploidite", "trisepalous", "triserially", "triseriatim", "trisulfoxid", "trisulcated", "tristiloquy", "tristichous", "tristearate", "trispermous", "trisotropis", "trisinuated", "trisilicate", "trisilicane", "triplicates", "trillionths", "trichophyte", "trichinotic", "trichinoses", "trichinosed", "trichinized", "trichinised", "trichechine", "tricephalus", "tricephalic", "tricenarium", "tricenaries", "trichiuroid", "trichlorfon", "trichoblast", "trichophore", "trichopathy", "trichonotid", "trichonosus", "trichonosis", "trichomonal", "trichomonad", "trichomanes", "tricholaena", "trichogynic", "tricellular", "tricarinate", "tribadistic", "triaxiality", "triarcuated", "triantelope", "triannulate", "trianguloid", "triammonium", "triadically", "triacontane", "triachenium", "tribasicity", "tribeswoman", "tribeswomen", "tricapsular", "tributorian", "tributarily", "tribunitive", "tribunitial", "tribunician", "tribunicial", "tribuneship", "tribrachial", "tribologist", "triableness", "trillionize", "triglyceryl", "triggerless", "trigeminous", "trifurcated", "trifoliosis", "trifoliated", "trierarchic", "trierarchal", "triennially", "trieciously", "trigonellin", "trigoneutic", "trigoniidae", "trilliaceae", "trilinolate", "trilineated", "trilaminate", "trilamellar", "trijunction", "trihydrated", "trihemiobol", "trigonotype", "trigonodont", "tridynamous", "tridominium", "tricliniary", "triclclinia", "trickstress", "tricksiness", "tricklingly", "tricircular", "trichronous", "trichromate", "trichotomic", "trichostema", "tricolumnar", "tricompound", "triconodont", "tridigitate", "tridiapason", "tridentlike", "tridentated", "tridecylene", "tridacnidae", "tricuspidal", "tricosanone", "tricorporal", "tricornered", "trichorrhea", "trigonometry", "triphosphate", "tribulations", "triglyceride", "triumphantly", "triangulated", "trivialities", "tribological", "trichophyton", "trivializing", "trichogramma", "triangularis", "trinitarians", "tribespeople", "trimolecular", "trimyristate", "trinitration", "trigrammatic", "trinomialism", "trinomialist", "trinomiality", "triodontidae", "trioeciously", "trimaculated", "tripaleolate", "tripalmitate", "tripartitely", "trigonometer", "tripartition", "trimethylene", "trimetallism", "trihemimeris", "trilarcenous", "trilaterally", "trihemimeral", "trilingually", "trilinoleate", "trilinolenin", "triliterally", "trilliaceous", "trillionaire", "trilophodont", "triguttulate", "trimargarate", "trimastigate", "trimeresurus", "trimesitinic", "trionychidae", "triphthongal", "trisulphonic", "trisulphoxid", "trisyllabism", "trisyllabity", "triternately", "triterpenoid", "tritheocracy", "trithionates", "triticalness", "tritonymphal", "tritopatores", "trituberculy", "triweekliess", "triumvirates", "triumvirship", "triunitarian", "triuridaceae", "trisulfoxide", "tristisonous", "tripinnately", "triplicately", "triplicating", "triplication", "triplicative", "triplicature", "triplicities", "triplinerved", "tripotassium", "trippingness", "tripudiation", "triradiately", "triradiation", "trismegistic", "tristachyous", "tristfulness", "tristigmatic", "trivialising", "trigoniacean", "triacetamide", "trichinizing", "trichinopoli", "trichinopoly", "trichiuridae", "trichobezoar", "trichoclasia", "trichoclasis", "trichocystic", "trichogenous", "trichogynial", "trichologist", "trichomatose", "trichomatous", "trichopathic", "trichophobia", "trichophoric", "trichophytia", "trichinising", "trichiniasis", "trichechidae", "triadelphous", "triamorphous", "triangleways", "trianglewise", "trianglework", "triangularly", "triangulates", "triangulator", "triatomicity", "tribophysics", "tribracteate", "tribunitiary", "tricarbimide", "tricarinated", "tricenarious", "tricentenary", "tricephalous", "trichophytic", "trichopteran", "tridactylous", "tridentinian", "tridiametral", "trienniality", "trierarchies", "trifasciated", "trifistulary", "triflingness", "trifluouride", "trifoliolate", "trifoveolate", "trifurcating", "trifurcation", "triglandular", "triglyphical", "trigonelline", "trigoneutism", "tricuspidate", "tricoryphean", "tricorporate", "trichopteron", "trichosporum", "trichostasis", "trichotomies", "trichotomism", "trichotomist", "trichotomize", "trichotomous", "trichromatic", "trichuriases", "trichuriasis", "trickishness", "trickstering", "tricliniarch", "triconodonta", "triconodonty", "tricophorous", "trigoniaceae", "triglycerides", "triangulation", "trigonometric", "triamcinolone", "trinucleotide", "triethylamine", "triangulating", "tricarboxylic", "trichodesmium", "tricentennial", "trilateration", "trinitroxylol", "trimethadione", "triodontoidea", "triodontoidei", "trioperculate", "triorthogonal", "trimucronatus", "trihypostatic", "trimerization", "triliterality", "trilamellated", "triliteralism", "trilaterality", "trilinolenate", "trilingualism", "tripersonally", "triphenylated", "tripinnatifid", "tristichaceae", "tristigmatose", "trisulphoxide", "trisyllabical", "tritangential", "tritheistical", "tritocerebral", "tritocerebrum", "trisplanchnic", "trisaccharose", "trisaccharide", "triplications", "triplicostate", "triploblastic", "triplocaulous", "triquadrantal", "triquetrously", "trirhomboidal", "triricinolein", "tritubercular", "trigrammatism", "trigonometria", "tricarpellate", "triceratopses", "trichatrophia", "trichechodont", "trichinoscope", "trichinoscopy", "trichocarpous", "trichoglossia", "tricarpellary", "tributariness", "triangulately", "triarticulate", "triatomically", "tribesmanship", "triboelectric", "tribonemaceae", "tribromacetic", "tribromphenol", "trichological", "trichomaphyte", "trichromatist", "triconodontid", "tricuspidated", "triflagellate", "triggerfishes", "trigintennial", "trigoniaceous", "trigonocerous", "trichromatism", "trichothallic", "trichomatosis", "trichomonadal", "trichomycosis", "trichopterous", "trichorrhexic", "trichorrhexis", "trichosanthes", "trichoschisis", "triangularity", "triangulations", "trichomoniasis", "trivialization", "trimethylamine", "tripelennamine", "trionychoidean", "trinitroxylene", "trinitrotoluol", "trinitrophenol", "trinitrocresol", "trinitarianism", "triliteralness", "trilateralness", "triiodomethane", "trihemiobolion", "trigonometries", "tripersonalism", "tripersonalist", "trivialisation", "triunsaturated", "triunification", "trituberculism", "trituberculata", "tritriacontane", "tritencephalon", "trisubstituted", "trisoctahedron", "trisoctahedral", "trirectangular", "tripinnatisect", "triphenylamine", "tripersonality", "trigonocephaly", "tridimensioned", "trichobranchia", "trichobacteria", "trichinophobia", "trichinization", "trichinisation", "trichiniferous", "tricentennials", "tricentenarian", "tricarballylic", "tribromphenate", "tribromophenol", "tribracteolate", "triantaphyllos", "trichocephalus", "trichodontidae", "trichoglossine", "tridimensional", "tridentiferous", "tridecilateral", "triconsonantal", "triconodontoid", "triclinohedric", "trichotomously", "trichosporange", "trichoschistic", "trichopterygid", "trichophytosis", "trichophyllous", "trichomonacide", "triacetonamine", "trichloroethane", "triethanolamine", "trifluoperazine", "trichloroacetic", "trinitrotoluene", "trinitromethane", "trinitrobenzene", "trinitroaniline", "trimethylacetic", "triacontaeterid", "triodontophorus", "trioxymethylene", "triphenylmethyl", "trirhombohedral", "tristetrahedron", "trisubstitution", "trisyllabically", "trithioaldehyde", "trigonometrical", "trigonocephalus", "tribromoethanol", "trichlormethane", "trichloromethyl", "trichoglossidae", "trichoglossinae", "trichomonacidal", "trichomonadidae", "trichoschistism", "trichostrongyle", "trichromatopsia", "tricotyledonous", "triethylstibine", "trigonocephalic", "trithiocarbonic", "triiodothyronine", "trichotillomania", "trimethylbenzene", "trimethylglycine", "trimethylmethane", "trimethylstibine", "trinitrocarbolic", "trinitroglycerin", "trinitroresorcin", "triphenylmethane", "triplocaulescent", "triskaidekaphobe", "tritetartemorion", "triboelectricity", "trigonometrician", "trigonocephalous", "tribofluorescent", "triboluminescent", "trichlorethylene", "trichloromethane", "trichobranchiate", "trichopterygidae", "trichosporangial", "trichosporangium", "trichostrongylid", "trichostrongylus", "tridimensionally", "trithiocarbonate", "trichloroethylene", "triakisoctahedral", "tridimensionality", "trigonometrically", "trinitrocellulose", "trionychoideachid", "triphenylcarbinol", "triplochitonaceae", "trisacramentarian", "triskaidekaphobes", "triconsonantalism", "trichopathophobia", "trichogrammatidae", "triakisoctahedrid", "triakisoctahedron", "tribofluorescence", "triboluminescence", "trichlorethylenes", "trichloromethanes", "trichocephaliasis", "trichoepithelioma", "triskaidekaphobia", "triphenylphosphine", "triakisicosahedral", "triakisicosahedron", "triakistetrahedral", "triakistetrahedron", "triangulopyramidal"}
+
+ // The whole list would produce an insanely big SVG that chromium browsers can't handle (500% zoom is not enough), so focus on words with prefix "trip"
+ words := make([]string, 0, 1000)
+ words = append(words, "")
+ for _, w := range allWords {
+ if strings.HasPrefix(w, "trip") {
+ words = append(words, w)
+ }
+ }
+
+ prefixMap := map[string]int{}
+ parentMap := make([]int, len(words))
+ for idx, w := range words {
+ prefixMap[w] = idx
+ }
+ for idx, w := range words {
+ if len(w) <= len(rootWord) {
+ continue
+ }
+ prefixLen := len(w) - 1
+ for {
+ prefixIdx, ok := prefixMap[w[:prefixLen]]
+ if ok {
+ parentMap[idx] = prefixIdx
+ break
+ }
+ prefixLen--
+ }
+ }
+ nodeDefs := make([]NodeDef, len(words))
+ for idx, w := range words {
+ prefix := words[parentMap[idx]]
+ nodeDefs[idx] = NodeDef{int16(idx), w[len(prefix):], EdgeDef{int16(parentMap[idx]), ""}, []EdgeDef{}, "", 0, false}
+ }
+ // Don't even try optimized, it will ask for fact(51)
+ svg, _, err := DrawUnoptimized(nodeDefs, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions(), DefaultEdgeOptions(), "", "", DefaultPalette())
+ assert.Equal(t, nil, err)
+ fmt.Printf("%s\n", svg)
+}
diff --git a/pkg/capigraph/test_vizzes.go b/pkg/capigraph/test_vizzes.go
new file mode 100644
index 00000000..3916cd65
--- /dev/null
+++ b/pkg/capigraph/test_vizzes.go
@@ -0,0 +1,394 @@
+package capigraph
+
+// 0: 1 3
+// - |/
+// 1: 2
+var testNodeDefsBasic = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{{3, "from 3"}}, "", 0, false},
+ {3, "3", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1 3
+// - | |
+// 1: 2 4
+var testNodeDefsTrivialParallel = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - | \
+// 1: 2 6 3
+// - | / \|
+// 2: 4 5
+var testNodeDefsOneEnclosedOneLevel = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{2, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {5, "5", EdgeDef{3, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - | \
+// 1: 2 5
+// - | |
+// 2: 3 8 6
+// - | / \ |
+// 3: 4 7
+var testNodeDefsOneEnclosedTwoLevels = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{{8, ""}}, "", 0, false},
+ {5, "5", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{5, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{{8, ""}}, "", 0, false},
+ {8, "8", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - | \
+// 1: 2 4
+// - | |
+// 2: 3 5
+var testNodeDefsNoIntervals = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1 2 3 4 5 6 7 8 9 10
+var testNodeDefsFlat10 = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {8, "8", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {9, "9", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {10, "10", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - | \
+// 1: 2 3
+// - | |
+// 2: 4 8 5 9
+// - | \| /
+// - | / / | /
+// - | // |
+// 2: 6 7
+var testNodeDefsTwoEnclosedNodeSizeMatters = []NodeDef{
+ {0, "", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{4, ""}, []EdgeDef{{8, ""}, {9, ""}}, "", 0, false},
+ {7, "7", EdgeDef{5, ""}, []EdgeDef{{8, ""}, {9, ""}}, "", 0, false},
+ {8, "8", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {9, "9", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1 3
+// - | |
+// 1: 2 4 6
+// - | /
+// 2: 5
+var testNodeDefsOneSecondary = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - / | \
+// 1: 2 3 4 6
+// - | //
+// 2: 5
+var testNodeDefsDiamond = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{3, ""}, []EdgeDef{{4, ""}, {6, ""}}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+var testNodeDefs40milPerms = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+
+ {5, "5", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {8, "8", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {9, "9", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {10, "10", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {11, "11", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {12, "12", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {13, "13", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {14, "14", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {15, "15", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {16, "16", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {17, "17", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {18, "18", EdgeDef{4, ""}, []EdgeDef{{20, ""}}, "", 0, false},
+ // {19, "19", EdgeDef{4, ""}, []EdgeDef{{21, ""}}, "", 0, false},
+ {19, "19", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+
+ {20, "20", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ // {21, "21", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+var testNodeDefs300bilPerms = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+
+ {5, "5", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {8, "8", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {9, "9", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {10, "10", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {11, "11", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {12, "12", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {13, "13", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {14, "14", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {15, "15", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {16, "16", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {17, "17", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {18, "18", EdgeDef{4, ""}, []EdgeDef{{22, ""}}, "", 0, false},
+ {19, "19", EdgeDef{4, ""}, []EdgeDef{{23, ""}}, "", 0, false},
+ {20, "20", EdgeDef{4, ""}, []EdgeDef{{24, ""}}, "", 0, false},
+ {21, "21", EdgeDef{4, ""}, []EdgeDef{{25, ""}}, "", 0, false},
+
+ {22, "22", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {23, "23", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {24, "24", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {25, "25", EdgeDef{}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1 2
+// - | /|
+// 1: 3 |
+// - | /
+// 2: 4
+var testNodeDefsTwoLevelsFromOneParent = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{1, ""}, []EdgeDef{{2, ""}}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{{2, ""}}, "", 0, false},
+}
+
+// 0: 1
+// - | \
+// 1: 2 5
+// - | |
+// 2: 3 F
+// - | \ |
+// 3: 4 6
+// - |
+// 4: 7
+var testNodeDefsTwoLevelsFromOneParentSameRoot = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{5, ""}, []EdgeDef{{3, ""}}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - | \
+// 1: 2 6
+// - | |
+// 2: 3 F
+// - | |
+// 3: 4 F
+// - | \ |
+// 4: 5 7
+// - |
+// 5: 8
+var testNodeDefsTwoLevelsFromOneParentSameRootTwoFakes = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{{4, ""}}, "", 0, false},
+ {8, "8", EdgeDef{7, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - |
+// 1: 2
+// - |
+// 2: 3 5
+// - | / |
+// 3: 4 6
+// - |
+// 4: 7
+var testNodeDefsSubtreeBelowLong = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{{5, ""}}, "", 0, false},
+ {5, "5", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{5, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - |
+// 1: 2 6
+// - | / |
+// 2: 3 / 7
+// - | / |
+// 3: 4 8
+// - | /
+// 4: 5
+var testNodeDefsOneNotTwoLevelsDown = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{{8, ""}}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{}, "", 0, false},
+ {8, "8", EdgeDef{7, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - |
+// 1: 2 5
+// - | /|
+// 2: 3 / 6
+// - | //
+// 3: 4
+var testNodeDefsMultiSecParentPullDown = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{{5, ""}, {6, ""}}, "", 0, false},
+ {5, "5", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{5, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1 4
+// - | /|
+// 1: 2 / 5
+// - |//
+// 2: 3
+var testNodeDefsMultiSecParentNoPullDown = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{{4, ""}, {5, ""}}, "", 0, false},
+ {4, "4", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{4, ""}, []EdgeDef{}, "", 0, false},
+}
+
+// 0: 1
+// - \\\\\\
+// 1: 2 3 4 5 6
+var testNodeDefsDuplicateSecLabels = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+
+ {2, "2", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{}, []EdgeDef{}, "", 0, false},
+
+ {7, "7", EdgeDef{2, ""}, []EdgeDef{{1, "txt"}}, "", 0, false},
+ {8, "8", EdgeDef{3, ""}, []EdgeDef{{1, "txt"}}, "", 0, false},
+ {9, "9", EdgeDef{4, ""}, []EdgeDef{{1, "txt"}}, "", 0, false},
+ {10, "10", EdgeDef{5, ""}, []EdgeDef{{1, "txt"}}, "", 0, false},
+ {11, "11", EdgeDef{6, ""}, []EdgeDef{{1, "txt"}}, "", 0, false},
+}
+
+// 0: 5
+// - |
+// 1: 6 - -
+// - | \
+// 2: 7 1 |
+// - | | |
+// 3: 8 2 | 9
+// - \ | | |
+// 4: 3 | 10
+// - | / |
+// 5: 4 11
+// - \ |
+// 6: 12
+var testNodeDefsLayerLongRoots = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{{8, ""}}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{{6, ""}}, "", 0, false},
+ {5, "5", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{5, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{}, "", 0, false},
+ {8, "8", EdgeDef{7, ""}, []EdgeDef{}, "", 0, false},
+ {9, "9", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {10, "10", EdgeDef{9, ""}, []EdgeDef{}, "", 0, false},
+ {11, "11", EdgeDef{10, ""}, []EdgeDef{}, "", 0, false},
+ {12, "12", EdgeDef{11, ""}, []EdgeDef{{4, ""}}, "", 0, false},
+}
+
+// 0: 1
+// - |
+// 1: 2 ------\
+// - | \ |
+// 2: 3 6 |
+// - |\ |\//
+// 3: 4 5 7 8
+var testNodeDefsPriAndSecInfinitePulldown = []NodeDef{
+ {0, "top node", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {1, "1", EdgeDef{}, []EdgeDef{}, "", 0, false},
+ {2, "2", EdgeDef{1, ""}, []EdgeDef{}, "", 0, false},
+ {3, "3", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {4, "4", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {5, "5", EdgeDef{3, ""}, []EdgeDef{}, "", 0, false},
+ {6, "6", EdgeDef{2, ""}, []EdgeDef{}, "", 0, false},
+ {7, "7", EdgeDef{6, ""}, []EdgeDef{{2, ""}}, "", 0, false},
+ {8, "8", EdgeDef{6, ""}, []EdgeDef{{2, ""}}, "", 0, false},
+}
diff --git a/pkg/capigraph/viz_node.go b/pkg/capigraph/viz_node.go
new file mode 100644
index 00000000..e6fb9162
--- /dev/null
+++ b/pkg/capigraph/viz_node.go
@@ -0,0 +1,71 @@
+package capigraph
+
+import (
+ "fmt"
+ "strings"
+)
+
+type HierarchyType int
+
+const (
+ HierarchyPri HierarchyType = iota
+ HierarchySec
+)
+
+type VizNode struct {
+ Def *NodeDef
+ IncomingVizEdges []VizEdge
+ RootId int16
+ Layer int
+ TotalW float64
+ X float64 // Total X
+ Y float64
+ NodeW float64
+ NodeH float64
+ PriChildrenAndEnclosedRoots []*VizNode
+}
+
+func (vn *VizNode) String() string {
+ sb := strings.Builder{}
+ sb.WriteString(fmt.Sprintf("{Id:%d RootId:%d Layer:%d ", vn.Def.Id, vn.RootId, vn.Layer))
+ sb.WriteString(fmt.Sprintf("X:%.2f Y:%.2f TotalW:%.2f W:%.2f H:%.2f ", vn.X, vn.Y, vn.TotalW, vn.NodeW, vn.NodeH))
+ sb.WriteString("In:[")
+ for i, e := range vn.IncomingVizEdges {
+ if i > 0 {
+ sb.WriteString(" ")
+ }
+ ht := "pri"
+ if e.HierarchyType == HierarchySec {
+ ht = "sec"
+ }
+ sb.WriteString(fmt.Sprintf("{HT:%s SrcId:%d X:%.2f Y:%.2f W:%.2f H:%.2f}", ht, e.Edge.SrcId, e.X, e.Y, e.W, e.H))
+ }
+ sb.WriteString("] ")
+ sb.WriteString("PriRootOut:[")
+ for i, c := range vn.PriChildrenAndEnclosedRoots {
+ if i > 0 {
+ sb.WriteString(" ")
+ }
+ sb.WriteString(fmt.Sprintf("%d", c.Def.Id))
+ }
+ sb.WriteString("]")
+ sb.WriteString("}")
+ return sb.String()
+}
+
+func (vn *VizNode) cleanPropertiesSubjectToPermutation() {
+ // Do not clean up static properties like Def, NodeW, NodeH etc
+ vn.PriChildrenAndEnclosedRoots = vn.PriChildrenAndEnclosedRoots[:0] // Reset size, not capacity
+ vn.TotalW = 0.0
+ vn.X = 0.0
+ vn.Y = 0.0
+}
+
+type VizEdge struct {
+ Edge EdgeDef
+ HierarchyType HierarchyType
+ X float64
+ Y float64
+ W float64
+ H float64
+}
diff --git a/pkg/capigraph/viz_node_hierarchy.go b/pkg/capigraph/viz_node_hierarchy.go
new file mode 100644
index 00000000..b8d8870d
--- /dev/null
+++ b/pkg/capigraph/viz_node_hierarchy.go
@@ -0,0 +1,607 @@
+package capigraph
+
+import (
+ "fmt"
+ "math"
+ "slices"
+ "sort"
+ "strings"
+ "time"
+)
+
+type RectDimension struct {
+ W float64
+ H float64
+}
+
+type VizNodeHierarchy struct {
+ VizNodeMap []VizNode
+ NodeDefs []NodeDef
+ PriParentMap []int16
+ RootMap []int16
+ NodeFo FontOptions
+ EdgeFo FontOptions
+ NodeDimensionMap []RectDimension
+ PriEdgeLabelDimMap []RectDimension
+ SecEdgeLabelDimMap [][]RectDimension
+ TotalLayers int
+ UpperLayerGapMap []float64
+}
+
+func (vnh *VizNodeHierarchy) String() string {
+ sb := strings.Builder{}
+ for i, vn := range vnh.VizNodeMap {
+ if i == 0 {
+ continue
+ }
+ if i > 1 {
+ sb.WriteString(", ")
+ }
+ sb.WriteString(vn.String())
+ }
+ return sb.String()
+}
+
+func NewVizNodeHierarchy(nodeDefs []NodeDef, nodeFo FontOptions, edgeFo FontOptions) *VizNodeHierarchy {
+ vnh := VizNodeHierarchy{}
+ vnh.NodeDefs = nodeDefs
+ vnh.PriParentMap = buildPriParentMap(nodeDefs)
+ vnh.RootMap = buildNodeToRootMap(vnh.PriParentMap)
+ vnh.NodeFo = nodeFo
+ vnh.EdgeFo = edgeFo
+ vnh.NodeDimensionMap = make([]RectDimension, len(nodeDefs))
+ for i := range len(nodeDefs) - 1 {
+ nodeDef := nodeDefs[i+1]
+ vnh.NodeDimensionMap[nodeDef.Id] = getNodeDimensions(&nodeDef, vnh.NodeFo)
+ }
+
+ vnh.PriEdgeLabelDimMap = make([]RectDimension, len(nodeDefs))
+ vnh.SecEdgeLabelDimMap = make([][]RectDimension, len(nodeDefs))
+ for _, nodeDef := range nodeDefs {
+ if nodeDef.PriIn.SrcId != 0 {
+ w, h := getTextDimensions(nodeDef.PriIn.Text, edgeFo.Typeface, edgeFo.Weight, edgeFo.SizeInPixels, edgeFo.Interval)
+ vnh.PriEdgeLabelDimMap[nodeDef.Id].W, vnh.PriEdgeLabelDimMap[nodeDef.Id].H = getLabelDimensionsFromTextDimensions(w, h, edgeFo.SizeInPixels*LabelTextDimensionMargin)
+ }
+ vnh.SecEdgeLabelDimMap[nodeDef.Id] = make([]RectDimension, len(nodeDef.SecIn))
+ for edgeIdx, edgeDef := range nodeDef.SecIn {
+ w, h := getTextDimensions(edgeDef.Text, edgeFo.Typeface, edgeFo.Weight, edgeFo.SizeInPixels, edgeFo.Interval)
+ vnh.SecEdgeLabelDimMap[nodeDef.Id][edgeIdx].W, vnh.SecEdgeLabelDimMap[nodeDef.Id][edgeIdx].H = getLabelDimensionsFromTextDimensions(w, h, edgeFo.SizeInPixels*LabelTextDimensionMargin)
+ }
+ }
+ return &vnh
+}
+
+func (vnh *VizNodeHierarchy) insertRootToNearestParent(rootVizNode *VizNode, leftId int16, rightId int16) {
+ leftParentVisitedMap := make([]*VizNode, len(vnh.VizNodeMap))
+
+ // Left branch: walk up and harvest ids
+ leftChildId := leftId
+ for {
+ leftParentId := vnh.PriParentMap[sanitizeFakeNodeId(leftChildId)]
+ if leftParentId == MissingNodeId {
+ break
+ }
+ leftParentVisitedMap[leftParentId] = &(vnh.VizNodeMap[leftParentId])
+ leftChildId = leftParentId
+ }
+
+ // Right branch: go up, identify first common parent, insert
+ var rightParentId int16
+ rightChildId := sanitizeFakeNodeId(rightId)
+ for {
+ rightParentId = vnh.PriParentMap[rightChildId]
+ commonParentVizNode := leftParentVisitedMap[rightParentId]
+ if commonParentVizNode != nil {
+ // Now rightChildId and commonParentVizNode contain the place to insert
+ for childIdx, childVizNode := range commonParentVizNode.PriChildrenAndEnclosedRoots {
+ if childVizNode.Def.Id == rightChildId {
+ commonParentVizNode.PriChildrenAndEnclosedRoots = slices.Insert(commonParentVizNode.PriChildrenAndEnclosedRoots, childIdx, rootVizNode)
+ return
+ }
+ }
+ panic(fmt.Sprintf("insertRootToNearestParent for root id %d cannot find parent for left %d and right %d", rootVizNode.Def.Id, leftId, rightId))
+ }
+ rightChildId = rightParentId
+ }
+}
+
+func (vnh *VizNodeHierarchy) insertRoot(rootVizNode *VizNode, perm []int16, idx int) {
+ thisRootId := perm[idx]
+ // Start at the left neighbour, move left
+ i := idx - 1
+ for i >= 0 {
+ leftRootId := vnh.RootMap[sanitizeFakeNodeId(perm[i])]
+ if leftRootId != thisRootId {
+ // Left neighbour belongs to another AND the closest root subtree, now find the nearest common parent for enclosure.
+ // Start with the nearest right neighbour and move right.
+ j := idx + 1
+ for j < len(perm) {
+ if leftRootId == vnh.RootMap[sanitizeFakeNodeId(perm[j])] {
+ // Same root as the left neighbour. Now:
+ // - go up and find the nearst common parent.
+ // - insert rootVizNode between first-generation children of the nearst common parent
+ vnh.insertRootToNearestParent(rootVizNode, perm[i], perm[j])
+ return
+ }
+ j++
+ }
+ }
+ i--
+ }
+
+ // No enclose, resort to adding it to the top ubernode
+ topItem := &(vnh.VizNodeMap[0])
+ topItem.PriChildrenAndEnclosedRoots = append(topItem.PriChildrenAndEnclosedRoots, rootVizNode)
+}
+
+func (vnh *VizNodeHierarchy) buildNewRootSubtreeHierarchy(mx LayerMx) {
+ vnh.VizNodeMap = make([]VizNode, len(vnh.NodeDefs))
+ topItem := &(vnh.VizNodeMap[0])
+ topItem.Layer = -1
+ topItem.PriChildrenAndEnclosedRoots = make([]*VizNode, 0, MaxLayerLen)
+
+ vnh.TotalLayers = 0
+ // Initialize static (non-hierarchy-related) properties
+ for layer, row := range mx {
+ if layer+1 > vnh.TotalLayers {
+ vnh.TotalLayers = layer + 1
+ }
+ for _, nodeId := range row {
+ // Static properties: remain the same regardless of the mx
+ vizNode := &(vnh.VizNodeMap[sanitizeFakeNodeId(nodeId)])
+ vizNode.Def = &(vnh.NodeDefs[sanitizeFakeNodeId(nodeId)])
+ vizNode.RootId = vnh.RootMap[sanitizeFakeNodeId(nodeId)]
+ if nodeId > FakeNodeBase {
+ // For now, set it to -1, later iteratin will eventually set it to proper layer (where the actual child node resides)
+ // This is not crucial, but may help when troubleshooting
+ vizNode.Layer = -1
+ } else {
+ // This is the real node - the end of fake seq
+ vizNode.Layer = layer
+ }
+ r := vnh.NodeDimensionMap[vizNode.Def.Id]
+ vizNode.NodeW = r.W
+ vizNode.NodeH = r.H
+ incomingEdgesLen := len(vizNode.Def.SecIn)
+ if vizNode.Def.PriIn.SrcId != 0 {
+ incomingEdgesLen++
+ }
+ vizNode.IncomingVizEdges = make([]VizEdge, incomingEdgesLen) // Just pre-allocate, it will be populated by PopulateEdgeLabelDimensions
+
+ // Properties to change from mx to mx
+ vizNode.PriChildrenAndEnclosedRoots = make([]*VizNode, 0, MaxLayerLen) // No need to fill at the moment, just pre-allocate
+ }
+ }
+}
+
+func (vnh *VizNodeHierarchy) reuseRootSubtreeHierarchy(mx LayerMx) {
+ vnh.VizNodeMap[0].cleanPropertiesSubjectToPermutation()
+ vnh.VizNodeMap[0].Layer = -1
+
+ // Re-init non-static properties and add pri children to PriChildrenAndEnclosedRoots
+ for layer, row := range mx {
+ for _, nodeId := range row {
+ vizNode := &vnh.VizNodeMap[sanitizeFakeNodeId(nodeId)]
+ vizNode.cleanPropertiesSubjectToPermutation()
+ // Do not handle root items yet - handling them requires enclosing tree in place.
+ // For non-roots - add it to some node on some previous layer in the order they appear on this layer, honoring the perm
+ rootId := vnh.RootMap[sanitizeFakeNodeId(nodeId)]
+ if rootId != nodeId {
+ // Add only the last node of the fake sequence, so there is only one copy of it among the children
+ if vizNode.Layer == layer {
+ parentVizNode := &vnh.VizNodeMap[vnh.PriParentMap[sanitizeFakeNodeId(nodeId)]]
+ parentVizNode.PriChildrenAndEnclosedRoots = append(parentVizNode.PriChildrenAndEnclosedRoots, vizNode)
+ }
+ }
+ }
+ }
+
+ // Add roots to PriChildrenAndEnclosedRoots (now i's ok to do that)
+ for _, row := range mx {
+ for j, nodeId := range row {
+ if nodeId < FakeNodeBase {
+ rootId := vnh.RootMap[nodeId]
+ if rootId == nodeId {
+ vn := &(vnh.VizNodeMap[nodeId])
+ vnh.insertRoot(vn, row, j)
+ }
+ }
+ }
+ }
+}
+
+const NodeTextDimensionMargin float64 = 1.0
+const NodeTextIconInterval float64 = 1.0
+const LabelTextDimensionMargin float64 = 0.5
+
+func getNodeDimensions(nodeDef *NodeDef, fo FontOptions) RectDimension {
+ w, h := getTextDimensions(nodeDef.Text, fo.Typeface, fo.Weight, fo.SizeInPixels, fo.Interval)
+ w += float64(fo.SizeInPixels) * NodeTextDimensionMargin * 2 // left+right
+ if nodeDef.IconId != "" {
+ // Add space for the HxH icon plus font-size*some coefficient
+ w += h + fo.SizeInPixels*NodeTextIconInterval
+ }
+ h += float64(fo.SizeInPixels) * NodeTextDimensionMargin * 2 // top+bottom
+ return RectDimension{w, h}
+}
+
+const (
+ SecEdgeLabelGapFromSourceInLines float64 = 5.0
+ PriEdgeLabelGapFromDestinatioInLines float64 = 2.0
+ gapBetweenSecAndPrimeEdgeLabelsInPixels float64 = 10.0
+ NodeHorizontalGapInPixels float64 = 20.0
+ SecEdgeOffsetX float64 = 10
+)
+
+func (vnh *VizNodeHierarchy) populateNodeTotalWidthRecursive(vizNode *VizNode) {
+ // Recursively visit children and add their TotalW to this TotalW
+ for i, childItem := range vizNode.PriChildrenAndEnclosedRoots {
+ vnh.populateNodeTotalWidthRecursive(childItem)
+ if i != 0 {
+ vizNode.TotalW += NodeHorizontalGapInPixels
+ }
+ vizNode.TotalW += childItem.TotalW
+ }
+
+ // If this node has really wide text, it may be even wider than
+ // children subtree. Pay attention to this case.
+ if vizNode.Def != nil {
+ if vizNode.TotalW < vizNode.NodeW {
+ vizNode.TotalW = vizNode.NodeW
+ }
+ }
+}
+
+func (vnh *VizNodeHierarchy) PopulateNodeTotalWidth() {
+ vnh.populateNodeTotalWidthRecursive(&vnh.VizNodeMap[0])
+}
+func populateNodeXCoordRecursive(vizNode *VizNode) {
+ // Decide where to start drawing child items: their cumulative width may be well smaller than parent's
+ cumulativeChildrenAndEnclosedRootsWidth := 0.0
+ for j, childItem := range vizNode.PriChildrenAndEnclosedRoots {
+ cumulativeChildrenAndEnclosedRootsWidth += childItem.TotalW
+ if j != len(vizNode.PriChildrenAndEnclosedRoots)-1 {
+ cumulativeChildrenAndEnclosedRootsWidth += NodeHorizontalGapInPixels
+ }
+ }
+ curX := vizNode.X + (vizNode.TotalW-cumulativeChildrenAndEnclosedRootsWidth)/2
+ for _, childItem := range vizNode.PriChildrenAndEnclosedRoots {
+ childItem.X = curX
+ populateNodeXCoordRecursive(childItem)
+ curX += childItem.TotalW + NodeHorizontalGapInPixels
+ }
+}
+
+func (vnh *VizNodeHierarchy) PopulateNodesXCoords() {
+ vnh.VizNodeMap[0].X = 0
+ populateNodeXCoordRecursive(&vnh.VizNodeMap[0])
+}
+
+func (vnh *VizNodeHierarchy) CalculateTotalHorizontalShift() float64 {
+ sum := 0.0
+ for i := range len(vnh.VizNodeMap) {
+ tgtVizNode := &vnh.VizNodeMap[i]
+ if tgtVizNode.Def != nil && len(tgtVizNode.Def.SecIn) > 0 {
+ for _, edge := range tgtVizNode.Def.SecIn {
+ srcVizNode := vnh.VizNodeMap[edge.SrcId]
+ startX := srcVizNode.X + srcVizNode.TotalW/2.0 - srcVizNode.NodeW/2.0
+ endX := tgtVizNode.X + tgtVizNode.TotalW/2.0 - tgtVizNode.NodeW/2.0
+ sum += math.Abs(endX - startX)
+ }
+ }
+ }
+ return sum
+}
+
+// Merely copies pre-calculated edge label dimensions to the hierarchy vizitems
+func (vnh *VizNodeHierarchy) PopulateEdgeLabelDimensions() {
+ for i := range len(vnh.VizNodeMap) - 1 {
+ dstVizNode := &(vnh.VizNodeMap[i+1])
+
+ // Pri edge
+ incomingEdgeIdx := 0
+ if dstVizNode.Def.PriIn.SrcId != 0 {
+ labelRectDim := vnh.PriEdgeLabelDimMap[dstVizNode.Def.Id]
+ dstVizNode.IncomingVizEdges[incomingEdgeIdx] = VizEdge{dstVizNode.Def.PriIn, HierarchyPri, 0.0, 0.0, labelRectDim.W, labelRectDim.H}
+ incomingEdgeIdx++
+ }
+
+ // Sec edges
+ secLabelRectDims := vnh.SecEdgeLabelDimMap[dstVizNode.Def.Id]
+ for edgeIdx, edge := range dstVizNode.Def.SecIn {
+ dstVizNode.IncomingVizEdges[incomingEdgeIdx] = VizEdge{edge, HierarchySec, 0.0, 0.0, 0.0, 0.0}
+ if secLabelRectDims[edgeIdx].W > 0.0 {
+ dstVizNode.IncomingVizEdges[incomingEdgeIdx].W = secLabelRectDims[edgeIdx].W
+ dstVizNode.IncomingVizEdges[incomingEdgeIdx].H = secLabelRectDims[edgeIdx].H
+ }
+ incomingEdgeIdx++
+ }
+ }
+}
+
+func (vnh *VizNodeHierarchy) PopulateUpperLayerGapMap(edgeFontSizeInPixels float64) {
+ minLayerGap := math.Max(vnh.VizNodeMap[0].TotalW/20.0, vnh.NodeFo.SizeInPixels*3.0) // Purely empiric
+ maxPriEdgeLabelHightMap := slices.Repeat([]float64{-1.0}, vnh.TotalLayers)
+ maxSecEdgeLabelHightMap := slices.Repeat([]float64{-1.0}, vnh.TotalLayers)
+ for i := range len(vnh.VizNodeMap) - 1 {
+ hi := &vnh.VizNodeMap[i+1]
+ for _, edge := range hi.IncomingVizEdges {
+ if edge.HierarchyType == HierarchyPri {
+ prevMaxEdgeLabelHeight := maxPriEdgeLabelHightMap[hi.Layer]
+ if prevMaxEdgeLabelHeight == -1 || prevMaxEdgeLabelHeight < edge.H {
+ maxPriEdgeLabelHightMap[hi.Layer] = edge.H
+ }
+ } else if edge.HierarchyType == HierarchySec {
+ // Make sure it's for the correspondent layer,
+ // otherwise it's not gonna work for cases when an edge goes up more than one level
+ layer := vnh.VizNodeMap[edge.Edge.SrcId].Layer + 1
+ prevMaxEdgeLabelHeight := maxSecEdgeLabelHightMap[layer]
+ if prevMaxEdgeLabelHeight == -1 || prevMaxEdgeLabelHeight < edge.H {
+ maxSecEdgeLabelHightMap[layer] = edge.H
+ }
+ } else {
+ panic(fmt.Sprintf("PopulateUpperLayerGapMap: unknown hierarchy type %d", edge.HierarchyType))
+ }
+ }
+
+ // Make sure there are no empty map elements for each layer
+ if maxPriEdgeLabelHightMap[hi.Layer] == -1 {
+ maxPriEdgeLabelHightMap[hi.Layer] = 0
+ }
+ if maxSecEdgeLabelHightMap[hi.Layer] == -1 {
+ maxSecEdgeLabelHightMap[hi.Layer] = 0
+ }
+ }
+
+ vnh.UpperLayerGapMap = make([]float64, vnh.TotalLayers)
+ for layer, maxPriEdgeLabelHeight := range maxPriEdgeLabelHightMap {
+ maxSecEdgeLabelHeight := maxSecEdgeLabelHightMap[layer]
+ if maxSecEdgeLabelHeight > 0 && maxPriEdgeLabelHeight > 0 {
+ vnh.UpperLayerGapMap[layer] = edgeFontSizeInPixels*SecEdgeLabelGapFromSourceInLines + maxSecEdgeLabelHeight + gapBetweenSecAndPrimeEdgeLabelsInPixels + maxPriEdgeLabelHeight + edgeFontSizeInPixels*PriEdgeLabelGapFromDestinatioInLines
+ } else if maxSecEdgeLabelHeight > 0 {
+ // Only sec labels
+ vnh.UpperLayerGapMap[layer] = edgeFontSizeInPixels*SecEdgeLabelGapFromSourceInLines + maxSecEdgeLabelHeight + edgeFontSizeInPixels*SecEdgeLabelGapFromSourceInLines
+ } else if maxPriEdgeLabelHeight > 0 {
+ // Only pri labels here
+ vnh.UpperLayerGapMap[layer] = edgeFontSizeInPixels*PriEdgeLabelGapFromDestinatioInLines + maxPriEdgeLabelHeight + edgeFontSizeInPixels*PriEdgeLabelGapFromDestinatioInLines
+ } else {
+ vnh.UpperLayerGapMap[layer] = 0
+ }
+ if vnh.UpperLayerGapMap[layer] < minLayerGap {
+ vnh.UpperLayerGapMap[layer] = minLayerGap
+ }
+ }
+}
+
+func populateLayerHeightsRecursive(vizNode *VizNode, layerHeightMap []float64) {
+ if vizNode.Def != nil {
+ prevCollectedMaxHeight := layerHeightMap[vizNode.Layer]
+ if prevCollectedMaxHeight == -1.0 || prevCollectedMaxHeight < vizNode.NodeH {
+ layerHeightMap[vizNode.Layer] = vizNode.NodeH
+ }
+ }
+
+ for _, childItem := range vizNode.PriChildrenAndEnclosedRoots {
+ populateLayerHeightsRecursive(childItem, layerHeightMap)
+ }
+}
+
+func populateNodeYCoordRecursive(vizNode *VizNode, layerYCoords []float64) {
+ if vizNode.Def != nil {
+ vizNode.Y = layerYCoords[vizNode.Layer]
+ }
+ for _, childItem := range vizNode.PriChildrenAndEnclosedRoots {
+ populateNodeYCoordRecursive(childItem, layerYCoords)
+ }
+}
+
+func (vnh *VizNodeHierarchy) PopulateNodesYCoords() {
+ // First, assign all level heights recursively
+ layerHeightMap := slices.Repeat([]float64{-1.0}, vnh.TotalLayers)
+ populateLayerHeightsRecursive(&vnh.VizNodeMap[0], layerHeightMap)
+
+ // Second, figure out the Y coord for each layer
+ layerYCoords := make([]float64, len(layerHeightMap))
+ curY := 0.0
+ for i := range len(layerHeightMap) {
+ layerYCoords[i] = curY
+ if i < len(layerHeightMap)-1 {
+ curY += layerHeightMap[i] + vnh.UpperLayerGapMap[i+1]
+ }
+ }
+
+ // Third, assign those Y coords to nodes recursively
+ populateNodeYCoordRecursive(&vnh.VizNodeMap[0], layerYCoords)
+}
+
+func getSecOffsetX(startX float64, endX float64, offset float64) (float64, float64) {
+ if startX-offset >= endX+offset {
+ // Offset regions do not intersect, upper node is way right
+ return -offset, offset
+ } else if startX+offset <= endX-offset {
+ // Offset regions do not intersect, upper node is way left
+ return offset, -offset
+ } else if startX > endX {
+ // Intersect: left to left
+ return -offset, -offset
+ }
+ // Intersect: right to right
+ return SecEdgeOffsetX, SecEdgeOffsetX
+}
+
+func (vnh *VizNodeHierarchy) PopulateEdgeLabelCoords() {
+ for i := range len(vnh.VizNodeMap) - 1 {
+ dstVizNode := &vnh.VizNodeMap[i+1]
+
+ // Pri edge
+ if dstVizNode.Def.PriIn.SrcId != 0 {
+ srcHiearchyItem := &vnh.VizNodeMap[dstVizNode.Def.PriIn.SrcId]
+ startX := srcHiearchyItem.X + srcHiearchyItem.TotalW/2
+ startY := srcHiearchyItem.Y + srcHiearchyItem.NodeH
+ endX := dstVizNode.X + dstVizNode.TotalW/2
+ endY := dstVizNode.Y
+ deltaX := endX - startX
+ deltaY := endY - startY
+ for i := range len(dstVizNode.IncomingVizEdges) {
+ if dstVizNode.IncomingVizEdges[i].Edge.SrcId == dstVizNode.Def.PriIn.SrcId {
+ labelCenterY := endY - vnh.EdgeFo.SizeInPixels*2 - dstVizNode.IncomingVizEdges[i].H/2
+ labelCenterX := endX - (endY-labelCenterY)*deltaX/deltaY
+ dstVizNode.IncomingVizEdges[i].Y = labelCenterY - dstVizNode.IncomingVizEdges[i].H/2
+ dstVizNode.IncomingVizEdges[i].X = labelCenterX - dstVizNode.IncomingVizEdges[i].W/2
+ }
+ }
+ }
+
+ // Sec edges
+ for _, edge := range dstVizNode.Def.SecIn {
+ srcHiearchyItem := &vnh.VizNodeMap[edge.SrcId]
+
+ startX := srcHiearchyItem.X + srcHiearchyItem.TotalW/2.0
+ endX := dstVizNode.X + dstVizNode.TotalW/2.0
+ startOffset, endOffset := getSecOffsetX(startX, endX, vnh.NodeFo.SizeInPixels/2.0)
+ startX += startOffset
+ endX += endOffset
+
+ startY := srcHiearchyItem.Y + srcHiearchyItem.NodeH
+ endY := dstVizNode.Y
+ if dstVizNode.Layer > srcHiearchyItem.Layer+1 {
+ // Adjust position: we want to put the label between the srcLayer and srcLayer+1
+ trueEndY := startY + vnh.UpperLayerGapMap[srcHiearchyItem.Layer+1]
+ endX = startX + (endX-startX)*(trueEndY-startY)/(endY-startY)
+ endY = trueEndY
+ }
+ deltaX := endX - startX
+ deltaY := endY - startY
+ for i := range len(dstVizNode.IncomingVizEdges) {
+ if dstVizNode.IncomingVizEdges[i].Edge.SrcId == edge.SrcId {
+ labelCenterY := startY + vnh.EdgeFo.SizeInPixels*5 + dstVizNode.IncomingVizEdges[i].H/2
+ labelCenterX := startX + (labelCenterY-startY)*deltaX/deltaY
+ dstVizNode.IncomingVizEdges[i].Y = labelCenterY - dstVizNode.IncomingVizEdges[i].H/2
+ dstVizNode.IncomingVizEdges[i].X = labelCenterX - dstVizNode.IncomingVizEdges[i].W/2
+ }
+ }
+ }
+ }
+}
+
+func (vnh *VizNodeHierarchy) RemoveDuplicateSecEdgeLabels() {
+ secLabelsFromItemMap := map[int16]map[string][]*VizEdge{} // Fight srcid->secText duplicates
+ for i := range len(vnh.VizNodeMap) - 1 {
+ vizNode := vnh.VizNodeMap[i+1]
+ for j := range vizNode.IncomingVizEdges {
+ e := &vizNode.IncomingVizEdges[j]
+ if e.HierarchyType != HierarchySec {
+ continue
+ }
+ edgeTextMap, ok := secLabelsFromItemMap[e.Edge.SrcId]
+ if !ok {
+ edgeTextMap = map[string][]*VizEdge{}
+ secLabelsFromItemMap[e.Edge.SrcId] = edgeTextMap
+ }
+ _, ok = edgeTextMap[e.Edge.Text]
+ if !ok {
+ edgeTextMap[e.Edge.Text] = make([]*VizEdge, 0, 10)
+ }
+ edgeTextMap[e.Edge.Text] = append(edgeTextMap[e.Edge.Text], e)
+ }
+ }
+
+ for _, edgeTextMap := range secLabelsFromItemMap {
+ for _, edges := range edgeTextMap {
+ sort.Slice(edges, func(i int, j int) bool {
+ return edges[i].X < edges[j].X
+ })
+ i := 0
+ j := 1
+ for i < len(edges) {
+ for i < j && j < len(edges) {
+ if edges[i].X+edges[i].W > edges[j].X {
+ // We have an intersection, remove j
+ edges[j].W = 0.0
+ edges[j].H = 0.0
+ edges = slices.Delete(edges, j, j+1)
+ } else {
+ j++
+ }
+ }
+ i++
+ j = i + 1
+ }
+ }
+ }
+
+}
+
+func getBestHierarchy(nodeDefs []NodeDef, nodeFo FontOptions, edgeFo FontOptions, optimize bool) ([]VizNode, int64, float64, float64, error) {
+ priParentMap := buildPriParentMap(nodeDefs)
+ layerMap := buildLayerMap(nodeDefs)
+ rootNodes := buildRootNodeList(priParentMap)
+ mx, err := NewLayerMx(nodeDefs, layerMap, rootNodes)
+ if err != nil {
+ return nil, int64(0), 0.0, 0.0, err
+ }
+
+ vnh := NewVizNodeHierarchy(nodeDefs, nodeFo, edgeFo)
+
+ vnh.buildNewRootSubtreeHierarchy(mx)
+
+ var bestMx LayerMx
+ mxPermCnt := 0
+ bestDistSec := math.MaxFloat64
+ var tElapsed float64
+ if optimize {
+ bestSignature := "z"
+ tStart := time.Now()
+ mxi, err := NewLayerMxPermIterator(nodeDefs, mx)
+ if err != nil {
+ return nil, int64(0), 0.0, 0.0, err
+ }
+ mxi.MxIterator(func(_ int, mxPerm LayerMx) {
+
+ // Hierarchy
+ vnh.reuseRootSubtreeHierarchy(mxPerm)
+
+ // X coord
+ vnh.PopulateNodeTotalWidth()
+ vnh.PopulateNodesXCoords()
+
+ distSec := vnh.CalculateTotalHorizontalShift()
+ if distSec < bestDistSec {
+ // This: 1. Adds determinism 2. helps user choose ids that go first (to some extent)
+ signature := mxPerm.signature()
+ if distSec < bestDistSec-0.1 || signature < bestSignature {
+ bestDistSec = distSec
+ bestMx = mxPerm.clone()
+ bestSignature = signature
+ }
+ }
+ mxPermCnt++
+ })
+ tElapsed = time.Since(tStart).Seconds()
+
+ if bestMx == nil {
+ return nil, int64(mxPermCnt), tElapsed, 0.0, fmt.Errorf("no best")
+ }
+ } else {
+ bestMx = mx
+ bestDistSec = 0.0
+ }
+
+ // Hierarchy
+ vnh.reuseRootSubtreeHierarchy(bestMx)
+
+ // X coord
+ vnh.PopulateNodeTotalWidth()
+ vnh.PopulateNodesXCoords()
+
+ // Y coord
+ vnh.PopulateEdgeLabelDimensions()
+ vnh.PopulateUpperLayerGapMap(edgeFo.SizeInPixels)
+ vnh.PopulateNodesYCoords()
+
+ // Edge label X and Y
+ vnh.PopulateEdgeLabelCoords()
+ vnh.RemoveDuplicateSecEdgeLabels()
+
+ return vnh.VizNodeMap, int64(mxPermCnt), tElapsed, bestDistSec, nil
+}
diff --git a/pkg/capigraph/viz_node_hierarchy_test.go b/pkg/capigraph/viz_node_hierarchy_test.go
new file mode 100644
index 00000000..df6cbeab
--- /dev/null
+++ b/pkg/capigraph/viz_node_hierarchy_test.go
@@ -0,0 +1,76 @@
+package capigraph
+
+import (
+ "math"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBasicMx(t *testing.T) {
+ mx := LayerMx{{1, 3}, {2}}
+
+ vnh := NewVizNodeHierarchy(testNodeDefsBasic, DefaultNodeFontOptions(), DefaultEdgeLabelFontOptions())
+
+ vnh.buildNewRootSubtreeHierarchy(mx)
+ vnh.reuseRootSubtreeHierarchy(mx)
+ vnh.PopulateNodeTotalWidth()
+
+ assert.Equal(t, 124.0, vnh.VizNodeMap[0].TotalW)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[0].NodeW)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[0].NodeH)
+
+ assert.Equal(t, 52.0, vnh.VizNodeMap[1].TotalW)
+ assert.Equal(t, 52.0, vnh.VizNodeMap[1].NodeW)
+ assert.Equal(t, 60.0, vnh.VizNodeMap[1].NodeH)
+
+ assert.Equal(t, 52.0, vnh.VizNodeMap[2].TotalW)
+ assert.Equal(t, 52.0, vnh.VizNodeMap[2].NodeW)
+ assert.Equal(t, 60.0, vnh.VizNodeMap[2].NodeH)
+
+ assert.Equal(t, 52.0, vnh.VizNodeMap[3].TotalW)
+ assert.Equal(t, 52.0, vnh.VizNodeMap[3].NodeW)
+ assert.Equal(t, 60.0, vnh.VizNodeMap[3].NodeH)
+
+ vnh.PopulateNodesXCoords()
+
+ assert.Equal(t, 0.0, vnh.VizNodeMap[0].X)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[1].X)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[2].X)
+ assert.Equal(t, 72.0, vnh.VizNodeMap[3].X)
+
+ horShift := vnh.CalculateTotalHorizontalShift()
+ assert.Equal(t, 72.0, math.Round(horShift*100)/100.0)
+
+ vnh.PopulateEdgeLabelDimensions()
+
+ assert.Equal(t, int16(1), vnh.VizNodeMap[2].IncomingVizEdges[0].Edge.SrcId)
+ assert.Equal(t, HierarchyPri, vnh.VizNodeMap[2].IncomingVizEdges[0].HierarchyType)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[2].IncomingVizEdges[0].W) // No label text
+ assert.Equal(t, 0.0, vnh.VizNodeMap[2].IncomingVizEdges[0].H) // No label text
+
+ assert.Equal(t, int16(3), vnh.VizNodeMap[2].IncomingVizEdges[1].Edge.SrcId)
+ assert.Equal(t, HierarchySec, vnh.VizNodeMap[2].IncomingVizEdges[1].HierarchyType)
+ assert.Equal(t, 69.12, vnh.VizNodeMap[2].IncomingVizEdges[1].W)
+ assert.Equal(t, 36.0, vnh.VizNodeMap[2].IncomingVizEdges[1].H)
+
+ vnh.PopulateUpperLayerGapMap(DefaultEdgeLabelFontOptions().SizeInPixels)
+
+ assert.Equal(t, 60.0, vnh.UpperLayerGapMap[0]) // Changed
+ assert.Equal(t, 216.0, vnh.UpperLayerGapMap[1])
+
+ vnh.PopulateNodesYCoords()
+
+ assert.Equal(t, 0.0, vnh.VizNodeMap[0].Y)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[1].Y)
+ assert.Equal(t, 276.0, vnh.VizNodeMap[2].Y)
+ assert.Equal(t, 0.0, vnh.VizNodeMap[3].Y)
+
+ vnh.PopulateEdgeLabelCoords()
+
+ assert.Equal(t, 26.0, vnh.VizNodeMap[2].IncomingVizEdges[0].X)
+ assert.Equal(t, 240.0, vnh.VizNodeMap[2].IncomingVizEdges[0].Y)
+
+ assert.Equal(t, 27.44, math.Round(vnh.VizNodeMap[2].IncomingVizEdges[1].X*100.0)/100.0)
+ assert.Equal(t, 150.0, vnh.VizNodeMap[2].IncomingVizEdges[1].Y)
+}
diff --git a/pkg/custom/tag_and_denormalize/tag_and_denormalize.go b/pkg/custom/tag_and_denormalize/tag_and_denormalize.go
index 666a9276..4037a8c6 100644
--- a/pkg/custom/tag_and_denormalize/tag_and_denormalize.go
+++ b/pkg/custom/tag_and_denormalize/tag_and_denormalize.go
@@ -20,7 +20,7 @@ const ProcessorTagAndDenormalizeName string = "tag_and_denormalize"
type TagAndDenormalizeProcessorDef struct {
TagFieldName string `json:"tag_field_name" env:"CAPI_TAGANDDENORMALIZE_TAG_FIELD_NAME, overwrite"`
RawTagCriteria map[string]string `json:"tag_criteria" env:"CAPI_TAGANDDENORMALIZE_TAG_CRITERIA, overwrite"`
- RawTagCriteriaUri string `json:"tag_criteria_uri" env:"CAPI_TAGANDDENORMALIZE_RAW_TAG_CRITERIA_URI, overwrite"`
+ RawTagCriteriaUrl string `json:"tag_criteria_url" env:"CAPI_TAGANDDENORMALIZE_RAW_TAG_CRITERIA_URL, overwrite"`
ParsedTagCriteria map[string]ast.Expr
UsedInCriteriaFields sc.FieldRefs
}
@@ -58,23 +58,23 @@ func (procDef *TagAndDenormalizeProcessorDef) Deserialize(raw json.RawMessage, _
errors := make([]string, 0)
procDef.ParsedTagCriteria = map[string]ast.Expr{}
- if len(procDef.RawTagCriteriaUri) > 0 {
+ if len(procDef.RawTagCriteriaUrl) > 0 {
if len(procDef.RawTagCriteria) > 0 {
return fmt.Errorf("cannot unmarshal both tag_criteria and tag_criteria_url - pick one")
}
- criteriaBytes, err := xfer.GetFileBytes(procDef.RawTagCriteriaUri, caPath, privateKeys)
+ criteriaBytes, err := xfer.GetFileBytes(procDef.RawTagCriteriaUrl, caPath, privateKeys)
if err != nil {
- return fmt.Errorf("cannot get criteria file [%s]: %s", procDef.RawTagCriteriaUri, err.Error())
+ return fmt.Errorf("cannot get criteria file [%s]: %s", procDef.RawTagCriteriaUrl, err.Error())
}
if len(criteriaBytes) == 0 {
- return fmt.Errorf("criteria file [%s] is empty", procDef.RawTagCriteriaUri)
+ return fmt.Errorf("criteria file [%s] is empty", procDef.RawTagCriteriaUrl)
}
if criteriaBytes != nil {
if err := json.Unmarshal(criteriaBytes, &procDef.RawTagCriteria); err != nil {
- return fmt.Errorf("cannot unmarshal tag criteria file [%s]: [%s]", procDef.RawTagCriteriaUri, err.Error())
+ return fmt.Errorf("cannot unmarshal tag criteria file [%s]: [%s]", procDef.RawTagCriteriaUrl, err.Error())
}
}
} else if len(procDef.RawTagCriteria) == 0 {
diff --git a/pkg/custom/tag_and_denormalize/tag_and_denormalize_test.go b/pkg/custom/tag_and_denormalize/tag_and_denormalize_test.go
index 48505672..a71eb65f 100644
--- a/pkg/custom/tag_and_denormalize/tag_and_denormalize_test.go
+++ b/pkg/custom/tag_and_denormalize/tag_and_denormalize_test.go
@@ -150,7 +150,7 @@ func TestTagAndDenormalizeDeserializeFileCriteria(t *testing.T) {
re := regexp.MustCompile(`"tag_criteria": \{[^\}]+\}`)
err := scriptDef.Deserialize(
- []byte(re.ReplaceAllString(scriptJson, `"tag_criteria_uri": "../../../test/data/cfg/tag_and_denormalize_quicktest/tag_criteria.json"`)), sc.ScriptJson,
+ []byte(re.ReplaceAllString(scriptJson, `"tag_criteria_url": "../../../test/data/cfg/tag_and_denormalize_quicktest/tag_criteria.json"`)), sc.ScriptJson,
&TagAndDenormalizeTestTestProcessorDefFactory{}, map[string]json.RawMessage{"tag_and_denormalize": {}}, "", nil)
assert.Nil(t, err)
@@ -282,12 +282,12 @@ func TestTagAndDenormalizeDeserializeFailures(t *testing.T) {
assert.Contains(t, err.Error(), "cannot unmarshal with tag_criteria and tag_criteria_url missing")
err = scriptDef.Deserialize(
- []byte(re.ReplaceAllString(scriptJson, `"tag_criteria":{"a":"b"},"tag_criteria_uri":"aaa"`)), sc.ScriptJson,
+ []byte(re.ReplaceAllString(scriptJson, `"tag_criteria":{"a":"b"},"tag_criteria_url":"aaa"`)), sc.ScriptJson,
&TagAndDenormalizeTestTestProcessorDefFactory{}, map[string]json.RawMessage{"tag_and_denormalize": {}}, "", nil)
assert.Contains(t, err.Error(), "cannot unmarshal both tag_criteria and tag_criteria_url - pick one")
err = scriptDef.Deserialize(
- []byte(re.ReplaceAllString(scriptJson, `"tag_criteria_uri":"aaa"`)), sc.ScriptJson,
+ []byte(re.ReplaceAllString(scriptJson, `"tag_criteria_url":"aaa"`)), sc.ScriptJson,
&TagAndDenormalizeTestTestProcessorDefFactory{}, map[string]json.RawMessage{"tag_and_denormalize": {}}, "", nil)
assert.Contains(t, err.Error(), "cannot get criteria file")
diff --git a/pkg/exe/daemon/capidaemon.json b/pkg/exe/daemon/capidaemon.json
index 803f3855..9e9948ea 100644
--- a/pkg/exe/daemon/capidaemon.json
+++ b/pkg/exe/daemon/capidaemon.json
@@ -25,7 +25,7 @@
},
"tag_and_denormalize":{}
},
- "thread_pool_size": 2,
+ "thread_pool_size": 1,
"dead_letter_ttl": 10000,
"log":{
"level": "debug"
diff --git a/pkg/exe/toolbelt/capitoolbelt.go b/pkg/exe/toolbelt/capitoolbelt.go
index 38179dbf..dcef5087 100644
--- a/pkg/exe/toolbelt/capitoolbelt.go
+++ b/pkg/exe/toolbelt/capitoolbelt.go
@@ -26,169 +26,6 @@ import (
"github.com/capillariesio/capillaries/pkg/wfmodel"
)
-type DotDiagramType string
-
-const (
- DotDiagramIndexes DotDiagramType = "indexes"
- DotDiagramFields DotDiagramType = "fields"
- DotDiagramRunStatus DotDiagramType = "run_status"
-)
-
-func nodeBatchStatusToColor(status wfmodel.NodeBatchStatusType) string {
- switch status {
- case wfmodel.NodeBatchNone:
- return "white"
- case wfmodel.NodeBatchStart:
- return "lightblue"
- case wfmodel.NodeBatchSuccess:
- return "green"
- case wfmodel.NodeBatchFail:
- return "red"
- case wfmodel.NodeBatchRunStopReceived:
- return "orangered"
- default:
- return "cyan"
- }
-}
-
-func drawFileReader(node *sc.ScriptNodeDef, dotDiagramType DotDiagramType, arrowFontSize int, recordFontSize int) string {
- var b strings.Builder
- arrowLabelBuilder := strings.Builder{}
- if dotDiagramType == DotDiagramType(DotDiagramFields) {
- for colName := range node.FileReader.Columns {
- arrowLabelBuilder.WriteString(colName)
- arrowLabelBuilder.WriteString("\\l")
- }
- }
- fileNames := make([]string, len(node.FileReader.SrcFileUrls))
- copy(fileNames, node.FileReader.SrcFileUrls)
-
- b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=dotted, fontsize=\"%d\", label=\"%s\"];\n", node.FileReader.SrcFileUrls[0], node.GetTargetName(), arrowFontSize, arrowLabelBuilder.String()))
- b.WriteString(fmt.Sprintf("\"%s\" [shape=folder, fontsize=\"%d\", label=\"%s\", tooltip=\"Source data file(s)\"];\n", node.FileReader.SrcFileUrls[0], recordFontSize, strings.Join(fileNames, "\\n")))
- return b.String()
-}
-
-func drawFileCreator(node *sc.ScriptNodeDef, dotDiagramType DotDiagramType, arrowFontSize int, recordFontSize int, allUsedFields sc.FieldRefs, penWidth string, fillColor string, urlEscaper *strings.Replacer, inSrcArrowLabel string) string {
- var b strings.Builder
- b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=solid, fontsize=\"%d\", label=\"%s\"];\n", node.TableReader.TableName, node.Name, arrowFontSize, inSrcArrowLabel))
-
- // Node (file)
- b.WriteString(fmt.Sprintf("\"%s\" [shape=record, penwidth=\"%s\", fontsize=\"%d\", fillcolor=\"%s\", style=\"filled\", label=\"{%s|creates file:\\n%s}\", tooltip=\"%s\"];\n", node.Name, penWidth, recordFontSize, fillColor, node.Name, urlEscaper.Replace(node.FileCreator.UrlTemplate), node.Desc))
-
- // Out (file)
- arrowLabelBuilder := strings.Builder{}
- if dotDiagramType == DotDiagramType(DotDiagramFields) {
- for i := 0; i < len(allUsedFields); i++ {
- arrowLabelBuilder.WriteString(allUsedFields[i].FieldName)
- arrowLabelBuilder.WriteString("\\l")
- }
- }
-
- b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=dotted, fontsize=\"%d\", label=\"%s\"];\n", node.Name, node.FileCreator.UrlTemplate, arrowFontSize, arrowLabelBuilder.String()))
- b.WriteString(fmt.Sprintf("\"%s\" [shape=note, fontsize=\"%d\", label=\"%s\", tooltip=\"Target data file(s)\"];\n", node.FileCreator.UrlTemplate, recordFontSize, node.FileCreator.UrlTemplate))
- return b.String()
-}
-
-func drawTableReader(node *sc.ScriptNodeDef, dotDiagramType DotDiagramType, arrowFontSize int, recordFontSize int, allUsedFields sc.FieldRefs, penWidth string, fillColor string, urlEscaper *strings.Replacer) string {
- var b strings.Builder
- var inSrcArrowLabel string
- if dotDiagramType == DotDiagramType(DotDiagramIndexes) || dotDiagramType == DotDiagramType(DotDiagramRunStatus) {
- if node.TableReader.ExpectedBatchesTotal > 1 {
- inSrcArrowLabel = fmt.Sprintf("%s (%d batches)", node.TableReader.TableName, node.TableReader.ExpectedBatchesTotal)
- } else {
- inSrcArrowLabel = fmt.Sprintf("%s (no parallelism)", node.TableReader.TableName)
- }
- } else if dotDiagramType == DotDiagramType(DotDiagramFields) {
- inSrcArrowLabelBuilder := strings.Builder{}
- for i := 0; i < len(allUsedFields); i++ {
- if allUsedFields[i].TableName == sc.ReaderAlias {
- inSrcArrowLabelBuilder.WriteString(allUsedFields[i].FieldName)
- inSrcArrowLabelBuilder.WriteString("\\l")
- }
- }
- inSrcArrowLabel = inSrcArrowLabelBuilder.String()
- }
- if node.HasFileCreator() {
- b.WriteString(drawFileCreator(node, dotDiagramType, arrowFontSize, recordFontSize, allUsedFields, penWidth, fillColor, urlEscaper, inSrcArrowLabel))
- } else {
- b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=solid, fontsize=\"%d\", label=\"%s\"];\n", node.TableReader.TableName, node.GetTargetName(), arrowFontSize, inSrcArrowLabel))
- }
-
- if node.HasLookup() {
- inLkpArrowLabel := fmt.Sprintf("%s (lookup)", node.Lookup.IndexName)
- if dotDiagramType == DotDiagramType(DotDiagramFields) {
- inLkpArrowLabelBuilder := strings.Builder{}
- for i := 0; i < len(allUsedFields); i++ {
- if allUsedFields[i].TableName == sc.LookupAlias {
- inLkpArrowLabelBuilder.WriteString(allUsedFields[i].FieldName)
- inLkpArrowLabelBuilder.WriteString("\\l")
- }
- }
- inLkpArrowLabel = inLkpArrowLabelBuilder.String()
- }
- // In (lookup)
- b.WriteString(fmt.Sprintf("\"%s\" -> \"%s\" [style=dashed, fontsize=\"%d\", label=\"%s\"];\n", node.Lookup.TableCreator.Name, node.GetTargetName(), arrowFontSize, inLkpArrowLabel))
- }
- return b.String()
-}
-
-func drawTableCreator(node *sc.ScriptNodeDef, recordFontSize int, penWidth string, fillColor string) string {
- if node.HasLookup() {
- return fmt.Sprintf("\"%s\" [shape=record, penwidth=\"%s\", fontsize=\"%d\", fillcolor=\"%s\", style=\"filled\", label=\"{%s|creates table:\\n%s|group:%t, join:%s}\", tooltip=\"%s\"];\n",
- node.TableCreator.Name, penWidth, recordFontSize, fillColor, node.Name, node.TableCreator.Name, node.Lookup.IsGroup, node.Lookup.LookupJoin, node.Desc)
- }
- return fmt.Sprintf("\"%s\" [shape=record, penwidth=\"%s\", fontsize=\"%d\", fillcolor=\"%s\", style=\"filled\", label=\"{%s|creates table:\\n%s}\", tooltip=\"%s\"];\n",
- node.TableCreator.Name, penWidth, recordFontSize, fillColor, node.Name, node.TableCreator.Name, node.Desc)
-}
-
-func getDotDiagram(scriptDef *sc.ScriptDef, dotDiagramType DotDiagramType, nodeColorMap map[string]string) string {
- var b strings.Builder
-
- const recordFontSize int = 20
- const arrowFontSize int = 18
-
- urlEscaper := strings.NewReplacer(`{`, `\{`, `}`, `\}`, `|`, `\|`)
- b.WriteString(fmt.Sprintf("\ndigraph %s {\nrankdir=\"TD\";\n node [fontname=\"Helvetica\"];\nedge [fontname=\"Helvetica\"];\ngraph [splines=true, pad=\"0.5\", ranksep=\"0.5\", nodesep=\"0.5\"];\n", dotDiagramType))
- for _, node := range scriptDef.ScriptNodes {
- penWidth := "1"
- if node.StartPolicy == sc.NodeStartManual {
- penWidth = "6"
- }
- fillColor := "white"
- var ok bool
- if nodeColorMap != nil {
- if fillColor, ok = nodeColorMap[node.Name]; !ok {
- fillColor = "white" // This run does not affect this node, or the node was not started
- }
- }
-
- if node.HasFileReader() {
- b.WriteString(drawFileReader(node, dotDiagramType, arrowFontSize, recordFontSize))
- }
-
- allUsedFields := sc.FieldRefs{}
-
- if node.HasFileCreator() {
- usedInAllTargetFileExpressions := node.FileCreator.GetFieldRefsUsedInAllTargetFileExpressions()
- allUsedFields.Append(usedInAllTargetFileExpressions)
- } else if node.HasTableCreator() {
- usedInAllTargetTableExpressions := sc.GetFieldRefsUsedInAllTargetExpressions(node.TableCreator.Fields)
- allUsedFields.Append(usedInAllTargetTableExpressions)
- }
-
- if node.HasTableReader() {
- b.WriteString(drawTableReader(node, dotDiagramType, arrowFontSize, recordFontSize, allUsedFields, penWidth, fillColor, urlEscaper))
- }
-
- if node.HasTableCreator() {
- b.WriteString(drawTableCreator(node, recordFontSize, penWidth, fillColor))
- }
- }
- b.WriteString("}\n")
-
- return b.String()
-}
-
const LogTsFormatUnquoted = `2006-01-02T15:04:05.000-0700`
type StandardToolbeltProcessorDefFactory struct {
@@ -232,7 +69,7 @@ const (
CmdGetRunHistory string = "get_run_history"
CmdGetNodeHistory string = "get_node_history"
CmdGetBatchHistory string = "get_batch_history"
- CmdGetRunStatusDiagram string = "drop_run_status_diagram"
+ CmdGetRunStatusDiagram string = "get_run_status_diagram"
CmdDropKeyspace string = "drop_keyspace"
CmdGetTableCql string = "get_table_cql"
CmdProtoFileReaderCreator string = "proto_file_reader_creator"
@@ -266,8 +103,8 @@ func validateScript(envConfig *env.EnvConfig) int {
validateScriptCmd := flag.NewFlagSet(CmdValidateScript, flag.ExitOnError)
scriptFilePath := validateScriptCmd.String("script_file", "", "Path to script file")
paramsFilePath := validateScriptCmd.String("params_file", "", "Path to script parameters map file")
- isIdxDag := validateScriptCmd.Bool("idx_dag", false, "Print index DAG")
- isFieldDag := validateScriptCmd.Bool("field_dag", false, "Print field DAG")
+ paramsFormat := validateScriptCmd.String("format", "capigraph", "capigraph (default) or dot")
+ paramsDetail := validateScriptCmd.String("detail", "idx,field", "idx or field or both (default)")
if err := validateScriptCmd.Parse(os.Args[2:]); err != nil || *scriptFilePath == "" {
usage(validateScriptCmd)
return 0
@@ -279,11 +116,10 @@ func validateScript(envConfig *env.EnvConfig) int {
return 1
}
- if *isIdxDag {
- fmt.Println(getDotDiagram(script, DotDiagramIndexes, nil))
- }
- if *isFieldDag {
- fmt.Println(getDotDiagram(script, DotDiagramFields, nil))
+ if *paramsFormat == "capigraph" {
+ fmt.Println(api.GetCapigraphDiagram(script, strings.Contains(*paramsDetail, "idx"), strings.Contains(*paramsDetail, "field"), true, nil))
+ } else {
+ fmt.Println(api.GetDotDiagram(script, strings.Contains(*paramsDetail, "idx"), strings.Contains(*paramsDetail, "field"), nil))
}
return 0
}
@@ -491,6 +327,7 @@ func getRunStatusDiagram(envConfig *env.EnvConfig, logger *l.CapiLogger) int {
paramsFilePath := getRunStatusDiagramCmd.String("params_file", "", "Path to script parameters map file")
keyspace := getRunStatusDiagramCmd.String("keyspace", "", "Keyspace (session id)")
runIdString := getRunStatusDiagramCmd.String("run_id", "", "Run id")
+ paramsFormat := getRunStatusDiagramCmd.String("format", "capigraph", "capigraph (default) or dot")
if err := getRunStatusDiagramCmd.Parse(os.Args[2:]); err != nil {
usage(getRunStatusDiagramCmd)
return 0
@@ -520,12 +357,20 @@ func getRunStatusDiagram(envConfig *env.EnvConfig, logger *l.CapiLogger) int {
return 1
}
- nodeColorMap := map[string]string{}
- for _, node := range nodes {
- nodeColorMap[node.ScriptNode] = nodeBatchStatusToColor(node.Status)
+ if *paramsFormat == "capigraph" {
+ nodeColorMap := map[string]int32{}
+ for _, node := range nodes {
+ nodeColorMap[node.ScriptNode] = api.NodeBatchStatusToCapigraphColor(node.Status)
+ }
+ fmt.Println(api.GetCapigraphDiagram(script, false, false, false, nodeColorMap))
+ } else {
+ nodeColorMap := map[string]string{}
+ for _, node := range nodes {
+ nodeColorMap[node.ScriptNode] = api.NodeBatchStatusToDotColor(node.Status)
+ }
+ fmt.Println(api.GetDotDiagram(script, true, false, nodeColorMap))
}
- fmt.Println(getDotDiagram(script, DotDiagramRunStatus, nodeColorMap))
return 0
}
diff --git a/pkg/exe/webapi/capiwebapi.go b/pkg/exe/webapi/capiwebapi.go
index c6cc7383..ed3ed23b 100644
--- a/pkg/exe/webapi/capiwebapi.go
+++ b/pkg/exe/webapi/capiwebapi.go
@@ -199,9 +199,9 @@ func (h *UrlHandler) getNodeDesc(scriptCache *expirable.LRU[string, string], log
return "", err
}
- // Now we have script URI, load it
+ // Now we have script URL, load it
- script, _, err := sc.NewScriptFromFiles(scriptCache, h.Env.CaPath, h.Env.PrivateKeys, runProps.ScriptUri, runProps.ScriptParamsUri, h.Env.CustomProcessorDefFactoryInstance, h.Env.CustomProcessorsSettings)
+ script, _, err := sc.NewScriptFromFiles(scriptCache, h.Env.CaPath, h.Env.PrivateKeys, runProps.ScriptUrl, runProps.ScriptParamsUrl, h.Env.CustomProcessorDefFactoryInstance, h.Env.CustomProcessorsSettings)
if err != nil {
return "", err
}
@@ -475,6 +475,121 @@ func (h *UrlHandler) ksRunNodeHistory(w http.ResponseWriter, r *http.Request) {
WriteApiSuccess(h.L, &h.Env.Webapi, r, w, result)
}
+func (h *UrlHandler) ksRunViz(w http.ResponseWriter, r *http.Request) {
+ var useRootPalette bool
+ useRootPaletteParam := r.URL.Query().Get("use_root_palette")
+ if useRootPaletteParam == "true" {
+ useRootPalette = true
+ }
+
+ var isStatus bool
+ isStatusParam := r.URL.Query().Get("is_status")
+ if isStatusParam == "true" {
+ isStatus = true
+ }
+
+ keyspace, err := getField(r, 0)
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ return
+ }
+
+ cqlSession, err := db.NewSession(h.Env, keyspace, db.DoNotCreateKeyspaceOnConnect)
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ return
+ }
+ defer cqlSession.Close()
+
+ runIdString, err := getField(r, 1)
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ return
+ }
+
+ runId, err := strconv.Atoi(runIdString)
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ return
+ }
+
+ // Extract script URL from run props
+ runProps, err := getRunProps(h.L, cqlSession, keyspace, int16(runId))
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ }
+
+ // Now we have script URL, load it
+ scriptDef, _, err := sc.NewScriptFromFiles(h.ScriptCache, h.Env.CaPath, h.Env.PrivateKeys, runProps.ScriptUrl, runProps.ScriptParamsUrl, h.Env.CustomProcessorDefFactoryInstance, h.Env.CustomProcessorsSettings)
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ }
+
+ var nodeColorMap map[string]int32
+ showIdx := true
+ showFields := true
+ if isStatus {
+ nodeColorMap = map[string]int32{}
+ nodes, err := api.GetNodeHistoryForRuns(h.L, cqlSession, keyspace, []int16{int16(runId)})
+ if err != nil {
+ WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+ }
+ for _, node := range nodes {
+ nodeColorMap[node.ScriptNode] = api.NodeBatchStatusToCapigraphColor(node.Status)
+ }
+ showIdx = false
+ showFields = false
+ useRootPalette = false
+ }
+
+ svg := api.GetCapigraphDiagram(scriptDef, showIdx, showFields, useRootPalette, nodeColorMap)
+
+ w.Header().Set("content-type", "image/svg+xml")
+ w.Header().Set("Access-Control-Allow-Origin", pickAccessControlAllowOrigin(&h.Env.Webapi, r))
+ if _, err := w.Write([]byte(svg)); err != nil {
+ h.L.Error("cannot write svg response, error %s", err.Error())
+ }
+}
+
+// func (h *UrlHandler) ksRunStatusViz(w http.ResponseWriter, r *http.Request) {
+// keyspace, err := getField(r, 0)
+// if err != nil {
+// WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+// return
+// }
+
+// cqlSession, err := db.NewSession(h.Env, keyspace, db.DoNotCreateKeyspaceOnConnect)
+// if err != nil {
+// WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+// return
+// }
+// defer cqlSession.Close()
+
+// runIdString, err := getField(r, 1)
+// if err != nil {
+// WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+// return
+// }
+
+// runId, err := strconv.Atoi(runIdString)
+// if err != nil {
+// WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+// return
+// }
+
+// svg, err := h.getViz(h.ScriptCache, h.L, cqlSession, keyspace, int16(runId), true)
+// if err != nil {
+// WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
+// return
+// }
+
+// w.Header().Set("content-type", "image/svg+xml")
+// w.Header().Set("Access-Control-Allow-Origin", pickAccessControlAllowOrigin(&h.Env.Webapi, r))
+// if _, err := w.Write([]byte(svg)); err != nil {
+// h.L.Error("cannot write svg response, error %s", err.Error())
+// }
+// }
+
type StartedRunInfo struct {
RunId int16 `json:"run_id"`
}
@@ -523,7 +638,7 @@ func (h *UrlHandler) ksStartRun(w http.ResponseWriter, r *http.Request) {
return
}
- runId, err := api.StartRun(h.Env, h.L, amqpChannel, runProps.ScriptUri, runProps.ScriptParamsUri, cqlSession, keyspace, strings.Split(runProps.StartNodes, ","), runProps.RunDescription)
+ runId, err := api.StartRun(h.Env, h.L, amqpChannel, runProps.ScriptUrl, runProps.ScriptParamsUrl, cqlSession, keyspace, strings.Split(runProps.StartNodes, ","), runProps.RunDescription)
if err != nil {
WriteApiError(h.L, &h.Env.Webapi, r, w, r.URL.Path, err, http.StatusInternalServerError)
return
@@ -685,6 +800,7 @@ func main() {
newRoute("GET", "/ks/([a-zA-Z0-9_]+)[/]*", h.ksMatrix),
newRoute("GET", "/ks/([a-zA-Z0-9_]+)/run/([0-9]+)/node/([a-zA-Z0-9_]+)/batch_history[/]*", h.ksRunNodeBatchHistory),
newRoute("GET", "/ks/([a-zA-Z0-9_]+)/run/([0-9]+)/node_history[/]*", h.ksRunNodeHistory),
+ newRoute("GET", "/ks/([a-zA-Z0-9_]+)/run/([0-9]+)/viz[/\\?&=a-zA-Z0-9_]*", h.ksRunViz),
newRoute("POST", "/ks/([a-zA-Z0-9_]+)/run[/]*", h.ksStartRun),
newRoute("OPTIONS", "/ks/([a-zA-Z0-9_]+)/run[/]*", h.ksStartRunOptions),
newRoute("DELETE", "/ks/([a-zA-Z0-9_]+)/run/([0-9]+)[/]*", h.ksStopRun),
diff --git a/pkg/proc/file_inserter_csv.go b/pkg/proc/file_inserter_csv.go
index 6549ec30..0905b9c9 100644
--- a/pkg/proc/file_inserter_csv.go
+++ b/pkg/proc/file_inserter_csv.go
@@ -18,7 +18,7 @@ func (instr *FileInserter) createCsvFileAndStartWorker(logger *l.CapiLogger, u *
var err error
var f *os.File
- if u.Scheme == xfer.UriSchemeSftp || u.Scheme == xfer.UriSchemeS3 {
+ if u.Scheme == xfer.UrlSchemeSftp || u.Scheme == xfer.UrlSchemeS3 {
f, err = os.CreateTemp("", "capi")
if err != nil {
return fmt.Errorf("cannot create temp file for %s: %s", instr.FinalFileUrl, err.Error())
diff --git a/pkg/proc/file_inserter_parquet.go b/pkg/proc/file_inserter_parquet.go
index 4f5377e0..31eb8a8a 100644
--- a/pkg/proc/file_inserter_parquet.go
+++ b/pkg/proc/file_inserter_parquet.go
@@ -20,7 +20,7 @@ func (instr *FileInserter) createParquetFileAndStartWorker(logger *l.CapiLogger,
var err error
var f *os.File
- if u.Scheme == xfer.UriSchemeSftp || u.Scheme == xfer.UriSchemeS3 {
+ if u.Scheme == xfer.UrlSchemeSftp || u.Scheme == xfer.UrlSchemeS3 {
f, err = os.CreateTemp("", "capi")
if err != nil {
return fmt.Errorf("cannot create temp file for %s: %s", instr.FinalFileUrl, err.Error())
diff --git a/pkg/proc/proc_file_creator.go b/pkg/proc/proc_file_creator.go
index 7b39a02e..8af3b432 100644
--- a/pkg/proc/proc_file_creator.go
+++ b/pkg/proc/proc_file_creator.go
@@ -171,7 +171,7 @@ func RunCreateFile(envConfig *env.EnvConfig,
u, err := url.Parse(instr.FinalFileUrl)
if err != nil {
- return BatchStats{RowsRead: 0, RowsWritten: 0}, fmt.Errorf("cannot parse file uri %s: %s", instr.FinalFileUrl, err.Error())
+ return BatchStats{RowsRead: 0, RowsWritten: 0}, fmt.Errorf("cannot parse file url %s: %s", instr.FinalFileUrl, err.Error())
}
if node.FileCreator.CreatorFileType == sc.CreatorFileTypeCsv {
@@ -228,11 +228,11 @@ func RunCreateFile(envConfig *env.EnvConfig,
logger.InfoCtx(pCtx, "uploading %s of size %d to %s...", instr.TempFilePath, st.Size(), instr.FinalFileUrl)
- if u.Scheme == xfer.UriSchemeSftp {
+ if u.Scheme == xfer.UrlSchemeSftp {
return bs, xfer.UploadSftpFile(instr.TempFilePath, instr.FinalFileUrl, envConfig.PrivateKeys)
- } else if u.Scheme == xfer.UriSchemeS3 {
+ } else if u.Scheme == xfer.UrlSchemeS3 {
return bs, xfer.UploadS3File(instr.TempFilePath, u)
}
- return bs, fmt.Errorf("unexpected URI scheme %s in %s", u.Scheme, instr.FinalFileUrl)
+ return bs, fmt.Errorf("unexpected URL scheme %s in %s", u.Scheme, instr.FinalFileUrl)
}
diff --git a/pkg/proc/proc_table_creator.go b/pkg/proc/proc_table_creator.go
index 63c67e86..8927f083 100644
--- a/pkg/proc/proc_table_creator.go
+++ b/pkg/proc/proc_table_creator.go
@@ -89,7 +89,7 @@ func RunReadFileForBatch(envConfig *env.EnvConfig, logger *l.CapiLogger, pCtx *c
u, err := url.Parse(filePath)
if err != nil {
- return bs, fmt.Errorf("cannot parse file uri %s: %s", filePath, err.Error())
+ return bs, fmt.Errorf("cannot parse file url %s: %s", filePath, err.Error())
}
bs.Src = filePath
@@ -98,7 +98,7 @@ func RunReadFileForBatch(envConfig *env.EnvConfig, logger *l.CapiLogger, pCtx *c
var localSrcFile *os.File
var fileReader io.Reader
var fileReadSeeker io.ReadSeeker
- if u.Scheme == xfer.UriSchemeFile || len(u.Scheme) == 0 {
+ if u.Scheme == xfer.UrlSchemeFile || len(u.Scheme) == 0 {
localSrcFile, err = os.Open(filePath)
if err != nil {
return bs, err
@@ -106,20 +106,20 @@ func RunReadFileForBatch(envConfig *env.EnvConfig, logger *l.CapiLogger, pCtx *c
defer localSrcFile.Close()
fileReader = bufio.NewReader(localSrcFile)
fileReadSeeker = localSrcFile
- } else if u.Scheme == xfer.UriSchemeHttp || u.Scheme == xfer.UriSchemeHttps || u.Scheme == xfer.UriSchemeS3 {
+ } else if u.Scheme == xfer.UrlSchemeHttp || u.Scheme == xfer.UrlSchemeHttps || u.Scheme == xfer.UrlSchemeS3 {
var readCloser io.ReadCloser
- if u.Scheme == xfer.UriSchemeHttp || u.Scheme == xfer.UriSchemeHttps {
+ if u.Scheme == xfer.UrlSchemeHttp || u.Scheme == xfer.UrlSchemeHttps {
readCloser, err = xfer.GetHttpReadCloser(filePath, u.Scheme, envConfig.CaPath)
if err != nil {
return bs, fmt.Errorf("cannot open http file %s: %s", filePath, err.Error())
}
- } else if u.Scheme == xfer.UriSchemeS3 {
+ } else if u.Scheme == xfer.UrlSchemeS3 {
readCloser, err = xfer.GetS3ReadCloser(filePath)
if err != nil {
return bs, fmt.Errorf("cannot open http file %s: %s", filePath, err.Error())
}
} else {
- return bs, fmt.Errorf("cannot open file %s: unknown uri scheme", filePath)
+ return bs, fmt.Errorf("cannot open file %s: unknown url scheme", filePath)
}
defer readCloser.Close()
@@ -150,7 +150,7 @@ func RunReadFileForBatch(envConfig *env.EnvConfig, logger *l.CapiLogger, pCtx *c
fileReader = readCloser
defer readCloser.Close()
}
- } else if u.Scheme == xfer.UriSchemeSftp {
+ } else if u.Scheme == xfer.UrlSchemeSftp {
// When dealing with sftp, we download the *whole* file, instead of providing a reader
dstFile, err := os.CreateTemp("", "capi")
if err != nil {
@@ -175,7 +175,7 @@ func RunReadFileForBatch(envConfig *env.EnvConfig, logger *l.CapiLogger, pCtx *c
fileReader = bufio.NewReader(localSrcFile)
fileReadSeeker = localSrcFile
} else {
- return bs, fmt.Errorf("uri scheme %s not supported: %s", u.Scheme, filePath)
+ return bs, fmt.Errorf("l scheme %s not supported: %s", u.Scheme, filePath)
}
if node.FileReader.ReaderFileType == sc.ReaderFileTypeCsv {
diff --git a/pkg/sc/script_def_loader.donotcover.go b/pkg/sc/script_def_loader.donotcover.go
index 4f7655b7..ae89879d 100644
--- a/pkg/sc/script_def_loader.donotcover.go
+++ b/pkg/sc/script_def_loader.donotcover.go
@@ -8,41 +8,41 @@ import (
"github.com/hashicorp/golang-lru/v2/expirable"
)
-func NewScriptFromFiles(scriptCache *expirable.LRU[string, string], caPath string, privateKeys map[string]string, scriptUri string, scriptParamsUri string, customProcessorDefFactoryInstance CustomProcessorDefFactory, customProcessorsSettings map[string]json.RawMessage) (*ScriptDef, ScriptInitProblemType, error) {
+func NewScriptFromFiles(scriptCache *expirable.LRU[string, string], caPath string, privateKeys map[string]string, scriptUrl string, scriptParamsUrl string, customProcessorDefFactoryInstance CustomProcessorDefFactory, customProcessorsSettings map[string]json.RawMessage) (*ScriptDef, ScriptInitProblemType, error) {
var err error
var jsonBytesScript []byte
var jsonBytesParams []byte
if scriptCache != nil {
- if jsonCachedScript, ok := scriptCache.Get(scriptUri); ok {
+ if jsonCachedScript, ok := scriptCache.Get(scriptUrl); ok {
jsonBytesScript = []byte(jsonCachedScript)
}
- if scriptParamsUri != "" {
- if jsonCachedParams, ok := scriptCache.Get(scriptParamsUri); ok {
+ if scriptParamsUrl != "" {
+ if jsonCachedParams, ok := scriptCache.Get(scriptParamsUrl); ok {
jsonBytesParams = []byte(jsonCachedParams)
}
}
}
if jsonBytesScript == nil {
- jsonBytesScript, err = xfer.GetFileBytes(scriptUri, caPath, privateKeys)
+ jsonBytesScript, err = xfer.GetFileBytes(scriptUrl, caPath, privateKeys)
if err != nil {
- return nil, ScriptInitConnectivityProblem, fmt.Errorf("cannot read script %s: %s", scriptUri, err.Error())
+ return nil, ScriptInitConnectivityProblem, fmt.Errorf("cannot read script %s: %s", scriptUrl, err.Error())
}
if scriptCache != nil {
- scriptCache.Add(scriptUri, string(jsonBytesScript))
+ scriptCache.Add(scriptUrl, string(jsonBytesScript))
}
}
- if jsonBytesParams == nil && scriptParamsUri != "" {
- jsonBytesParams, err = xfer.GetFileBytes(scriptParamsUri, caPath, privateKeys)
+ if jsonBytesParams == nil && scriptParamsUrl != "" {
+ jsonBytesParams, err = xfer.GetFileBytes(scriptParamsUrl, caPath, privateKeys)
if err != nil {
- return nil, ScriptInitConnectivityProblem, fmt.Errorf("cannot read script parameters %s: %s", scriptParamsUri, err.Error())
+ return nil, ScriptInitConnectivityProblem, fmt.Errorf("cannot read script parameters %s: %s", scriptParamsUrl, err.Error())
}
if scriptCache != nil {
- scriptCache.Add(scriptParamsUri, string(jsonBytesParams))
+ scriptCache.Add(scriptParamsUrl, string(jsonBytesParams))
}
}
- return NewScriptFromFileBytes(caPath, privateKeys, scriptUri, jsonBytesScript, scriptParamsUri, jsonBytesParams, customProcessorDefFactoryInstance, customProcessorsSettings)
+ return NewScriptFromFileBytes(caPath, privateKeys, scriptUrl, jsonBytesScript, scriptParamsUrl, jsonBytesParams, customProcessorDefFactoryInstance, customProcessorsSettings)
}
diff --git a/pkg/sc/script_def_loader.go b/pkg/sc/script_def_loader.go
index 12395a5e..d0bb41e3 100644
--- a/pkg/sc/script_def_loader.go
+++ b/pkg/sc/script_def_loader.go
@@ -26,9 +26,9 @@ func getFileType(url string) (ScriptType, error) {
func NewScriptFromFileBytes(
caPath string,
privateKeys map[string]string,
- scriptUri string,
+ scriptUrl string,
jsonOrYamlBytesScript []byte,
- scriptParamsUri string,
+ scriptParamsUrl string,
jsonOrYamlBytesParams []byte,
customProcessorDefFactoryInstance CustomProcessorDefFactory,
customProcessorsSettings map[string]json.RawMessage) (*ScriptDef, ScriptInitProblemType, error) {
@@ -50,19 +50,19 @@ func NewScriptFromFileBytes(
re = regexp.MustCompile(`([^"]{[a-zA-Z0-9_]+\|(number|bool)})|({[a-zA-Z0-9_]+\|(number|bool)}[^"])`)
invalidParamRefs := re.FindAllString(jsonOrYamlScriptString, -1)
if len(invalidParamRefs) > 0 {
- return nil, ScriptInitUrlProblem, fmt.Errorf("cannot parse number/bool script parameter references in [%s], the following parameter references should not have extra characters between curly braces and double quotes: [%s]", scriptUri, strings.Join(invalidParamRefs, ","))
+ return nil, ScriptInitUrlProblem, fmt.Errorf("cannot parse number/bool script parameter references in [%s], the following parameter references should not have extra characters between curly braces and double quotes: [%s]", scriptUrl, strings.Join(invalidParamRefs, ","))
}
// Apply template params here, script def should know nothing about them: they may tweak some 3d-party tfm config
paramsMap := map[string]any{}
if jsonOrYamlBytesParams != nil {
- scriptParamsType, err := getFileType(scriptParamsUri)
+ scriptParamsType, err := getFileType(scriptParamsUrl)
if err != nil {
return nil, ScriptInitContentProblem, err
}
if err := JsonOrYamlUnmarshal(scriptParamsType, jsonOrYamlBytesParams, ¶msMap); err != nil {
- return nil, ScriptInitContentProblem, fmt.Errorf("cannot unmarshal script params from [%s]: [%s]", scriptParamsUri, err.Error())
+ return nil, ScriptInitContentProblem, fmt.Errorf("cannot unmarshal script params from [%s]: [%s]", scriptParamsUrl, err.Error())
}
}
@@ -103,16 +103,16 @@ func NewScriptFromFileBytes(
for i, itemAny := range arrayParamVal {
itemStr, ok := itemAny.(string)
if !ok {
- return nil, ScriptInitContentProblem, fmt.Errorf("stringlist contains non-string value type %T from [%s]: %s", itemAny, scriptParamsUri, templateParam)
+ return nil, ScriptInitContentProblem, fmt.Errorf("stringlist contains non-string value type %T from [%s]: %s", itemAny, scriptParamsUrl, templateParam)
}
strArray[i] = fmt.Sprintf(`"%s"`, itemStr)
}
replacerStrings[i+1] = fmt.Sprintf("[%s]", strings.Join(strArray, ","))
default:
- return nil, ScriptInitContentProblem, fmt.Errorf("unsupported array parameter type %T from [%s]: %s", arrayParamVal, scriptParamsUri, templateParam)
+ return nil, ScriptInitContentProblem, fmt.Errorf("unsupported array parameter type %T from [%s]: %s", arrayParamVal, scriptParamsUrl, templateParam)
}
} else {
- return nil, ScriptInitContentProblem, fmt.Errorf("unsupported parameter type %T from [%s]: %s", templateParamVal, scriptParamsUri, templateParam)
+ return nil, ScriptInitContentProblem, fmt.Errorf("unsupported parameter type %T from [%s]: %s", templateParamVal, scriptParamsUrl, templateParam)
}
}
i += 2
@@ -130,17 +130,17 @@ func NewScriptFromFileBytes(
}
}
if len(unresolvedParamMap) > 0 {
- return nil, ScriptInitContentProblem, fmt.Errorf("unresolved parameter references in [%s]: %v; make sure that type in the script matches the type of the parameter value in the script parameters file", scriptUri, unresolvedParamMap)
+ return nil, ScriptInitContentProblem, fmt.Errorf("unresolved parameter references in [%s]: %v; make sure that type in the script matches the type of the parameter value in the script parameters file", scriptUrl, unresolvedParamMap)
}
- scriptType, err := getFileType(scriptUri)
+ scriptType, err := getFileType(scriptUrl)
if err != nil {
return nil, ScriptInitContentProblem, err
}
newScript := &ScriptDef{}
if err := newScript.Deserialize([]byte(jsonOrYamlScriptString), scriptType, customProcessorDefFactoryInstance, customProcessorsSettings, caPath, privateKeys); err != nil {
- return nil, ScriptInitContentProblem, fmt.Errorf("cannot deserialize script %s(%s): %s", scriptUri, scriptParamsUri, err.Error())
+ return nil, ScriptInitContentProblem, fmt.Errorf("cannot deserialize script %s(%s): %s", scriptUrl, scriptParamsUrl, err.Error())
}
return newScript, ScriptInitNoProblem, nil
diff --git a/pkg/sc/script_def_loader_test.go b/pkg/sc/script_def_loader_test.go
index eec92c37..c70771e5 100644
--- a/pkg/sc/script_def_loader_test.go
+++ b/pkg/sc/script_def_loader_test.go
@@ -238,7 +238,7 @@ func (f *SomeTestCustomProcessorDefFactory) Create(processorType string) (Custom
func TestNewScriptFromFileBytes(t *testing.T) {
// Test main script parsing function
scriptDef, initProblem, err := NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(parameterizedScriptJson),
+ "someScriptUrl.json", []byte(parameterizedScriptJson),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Nil(t, err)
@@ -255,68 +255,68 @@ func TestNewScriptFromFileBytes(t *testing.T) {
// Tweak paramater name and make sure templating engine catches it
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "source_table_for_test_custom_processor", "some_bad_param")),
+ "someScriptUrl.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "source_table_for_test_custom_processor", "some_bad_param")),
"someScriptParamsUrl.json", []byte(paramsJson),
nil, nil)
assert.Contains(t, err.Error(), "unresolved parameter references", err.Error())
// Bad-formed JSON
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(strings.TrimSuffix(parameterizedScriptJson, "}")),
+ "someScriptUrl.json", []byte(strings.TrimSuffix(parameterizedScriptJson, "}")),
"someScriptParamsUrl.json", []byte(paramsJson),
nil, nil)
assert.Contains(t, err.Error(), "unexpected end of JSON input", err.Error())
// Invalid field in custom processor (Python) formula
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "-r.field_int1*2", "r.bad_field")),
+ "someScriptUrl.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "-r.field_int1*2", "r.bad_field")),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "field usage error in custom processor creator")
// Invalid dependency policy
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "run_is_current(desc),node_start_ts(desc)", "some_bad_event_priority_order")),
+ "someScriptUrl.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "run_is_current(desc),node_start_ts(desc)", "some_bad_event_priority_order")),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "failed to deserialize dependency policy")
// Run (tweaked) dependency policy checker with some vanilla values and see if it works
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "e.run_final_status == wfmodel.RunStart", "e.run_final_status == true")),
+ "someScriptUrl.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "e.run_final_status == wfmodel.RunStart", "e.run_final_status == true")),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "failed to test dependency policy")
re := regexp.MustCompile(`"expression": "e\.run[^"]+"`)
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(re.ReplaceAllString(parameterizedScriptJson, `"expression": 1`)),
+ "someScriptUrl.json", []byte(re.ReplaceAllString(parameterizedScriptJson, `"expression": 1`)),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "cannot unmarshal dependency policy")
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(re.ReplaceAllString(parameterizedScriptJson, `"expression": "a.aaa"`)),
+ "someScriptUrl.json", []byte(re.ReplaceAllString(parameterizedScriptJson, `"expression": "a.aaa"`)),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "cannot parse rule expression 'a.aaa': all fields must be prefixed")
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(re.ReplaceAllString(parameterizedScriptJson, `"expression": "e.aaa"`)),
+ "someScriptUrl.json", []byte(re.ReplaceAllString(parameterizedScriptJson, `"expression": "e.aaa"`)),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "cannot parse rule expression 'e.aaa': field e.aaa not found")
// Tweak lookup isGroup = false and get error
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(parameterizedScriptJson),
+ "someScriptUrl.json", []byte(parameterizedScriptJson),
"someScriptParamsUrl.json", []byte(strings.ReplaceAll(paramsJson, "true", "false")),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "cannot use agg functions")
// Invalid rerun_policy
_, _, err = NewScriptFromFileBytes("", nil,
- "someScriptUri.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "\"rerun_policy\": \"fail\"", "\"rerun_policy\": \"bad_rerun_policy\"")),
+ "someScriptUrl.json", []byte(strings.ReplaceAll(parameterizedScriptJson, "\"rerun_policy\": \"fail\"", "\"rerun_policy\": \"bad_rerun_policy\"")),
"someScriptParamsUrl.json", []byte(paramsJson),
&SomeTestCustomProcessorDefFactory{}, map[string]json.RawMessage{"some_test_custom_proc": []byte("{}")})
assert.Contains(t, err.Error(), "invalid node rerun policy bad_rerun_policy")
diff --git a/pkg/wf/message_handler.go b/pkg/wf/message_handler.go
index 97518f52..4af92481 100644
--- a/pkg/wf/message_handler.go
+++ b/pkg/wf/message_handler.go
@@ -137,7 +137,7 @@ func SafeProcessBatch(envConfig *env.EnvConfig, logger *l.CapiLogger, pCtx *ctx.
if err != nil {
logger.DebugCtx(pCtx, "batch processed, error: %s", err.Error())
- return wfmodel.NodeBatchFail, bs, fmt.Errorf("error running node %s of type %s in the script [%s]: [%s]", pCtx.BatchInfo.TargetNodeName, pCtx.CurrentScriptNode.Type, pCtx.BatchInfo.ScriptURI, err.Error())
+ return wfmodel.NodeBatchFail, bs, fmt.Errorf("error running node %s of type %s in the script [%s]: [%s]", pCtx.BatchInfo.TargetNodeName, pCtx.CurrentScriptNode.Type, pCtx.BatchInfo.ScriptURL, err.Error())
}
logger.DebugCtx(pCtx, "batch processed ok")
@@ -227,14 +227,14 @@ func initCtxScript(logger *l.CapiLogger, scriptCache *expirable.LRU[string, stri
var initProblem sc.ScriptInitProblemType
var err error
- pCtx.Script, initProblem, err = sc.NewScriptFromFiles(scriptCache, caPath, privateKeys, dataBatchInfo.ScriptURI, dataBatchInfo.ScriptParamsURI, customProcFactory, customProcSettings)
+ pCtx.Script, initProblem, err = sc.NewScriptFromFiles(scriptCache, caPath, privateKeys, dataBatchInfo.ScriptURL, dataBatchInfo.ScriptParamsURL, customProcFactory, customProcSettings)
if initProblem == sc.ScriptInitNoProblem {
return DaemonCmdNone
}
switch initProblem {
case sc.ScriptInitUrlProblem:
- logger.Error("cannot init script because of URI problem, will not let other workers handle it, giving up with msg %s: %s", dataBatchInfo.ToString(), err.Error())
+ logger.Error("cannot init script because of URL problem, will not let other workers handle it, giving up with msg %s: %s", dataBatchInfo.ToString(), err.Error())
return DaemonCmdAckWithError
case sc.ScriptInitContentProblem:
logger.Error("cannot init script because of content problem, will not let other workers handle it, giving up with msg %s: %s", dataBatchInfo.ToString(), err.Error())
@@ -404,7 +404,7 @@ func ProcessDataBatchMsg(envConfig *env.EnvConfig, logger *l.CapiLogger, scriptC
var ok bool
pCtx.CurrentScriptNode, ok = pCtx.Script.ScriptNodes[dataBatchInfo.TargetNodeName]
if !ok {
- logger.Error("cannot find node %s in the script [%s], giving up with %s, returning DaemonCmdAckWithError, will not let other workers handle it", pCtx.BatchInfo.TargetNodeName, pCtx.BatchInfo.ScriptURI, dataBatchInfo.ToString())
+ logger.Error("cannot find node %s in the script [%s], giving up with %s, returning DaemonCmdAckWithError, will not let other workers handle it", pCtx.BatchInfo.TargetNodeName, pCtx.BatchInfo.ScriptURL, dataBatchInfo.ToString())
return DaemonCmdAckWithError
}
diff --git a/pkg/wfdb/dependency_node_event.go b/pkg/wfdb/dependency_node_event.go
index d488c303..a231b590 100644
--- a/pkg/wfdb/dependency_node_event.go
+++ b/pkg/wfdb/dependency_node_event.go
@@ -35,7 +35,7 @@ func BuildDependencyNodeEventLists(logger *l.CapiLogger, pCtx *ctx.MessageProces
for _, affectingRunId := range nodeAffectingRunIdsMap[nodeName] {
runLifespan, ok := runLifespanMap[affectingRunId]
if !ok {
- return nil, fmt.Errorf("unexpectedly, cannot find run lifespan map for run %d: %s", affectingRunId, runLifespanMap.ToString())
+ return nil, fmt.Errorf("unexpectedly, cannot find run lifespan map for run %d, was it ever started?", affectingRunId)
}
if runLifespan.StartTs == time.Unix(0, 0) || runLifespan.FinalStatus == wfmodel.RunNone {
return nil, fmt.Errorf("unexpectedly, run lifespan %d looks like the run never started: %s", affectingRunId, runLifespanMap.ToString())
diff --git a/pkg/wfdb/run_properties.go b/pkg/wfdb/run_properties.go
index 00c036ff..cd8e8bd6 100644
--- a/pkg/wfdb/run_properties.go
+++ b/pkg/wfdb/run_properties.go
@@ -100,14 +100,14 @@ func HarvestRunIdsByAffectedNodes(logger *l.CapiLogger, pCtx *ctx.MessageProcess
return runIds, nodeAffectingRunIdsMap, nil
}
-func WriteRunProperties(cqlSession *gocql.Session, keyspace string, runId int16, startNodes []string, affectedNodes []string, scriptUri string, scriptParamsUri string, runDescription string) error {
+func WriteRunProperties(cqlSession *gocql.Session, keyspace string, runId int16, startNodes []string, affectedNodes []string, scriptUrl string, scriptParamsUrl string, runDescription string) error {
q := (&cql.QueryBuilder{}).
Keyspace(keyspace).
Write("run_id", runId).
Write("start_nodes", strings.Join(startNodes, ",")).
Write("affected_nodes", strings.Join(affectedNodes, ",")).
- Write("script_uri", scriptUri).
- Write("script_params_uri", scriptParamsUri).
+ Write("script_url", scriptUrl).
+ Write("script_params_url", scriptParamsUrl).
Write("run_description", runDescription).
InsertUnpreparedQuery(wfmodel.TableNameRunAffectedNodes, cql.IgnoreIfExists) // If not exists. First one wins.
err := cqlSession.Query(q).Exec()
diff --git a/pkg/wfmodel/message.go b/pkg/wfmodel/message.go
index 9189f4c7..85021527 100644
--- a/pkg/wfmodel/message.go
+++ b/pkg/wfmodel/message.go
@@ -92,8 +92,8 @@ func (msg *Message) Deserialize(jsonBytes []byte) error {
// tgtMsg.Ts = time.Now().UnixMilli()
// tgtMsg.MessageType = MessageTypeDataBatch
// tgtMsg.Payload = MessagePayloadDataBatch{
-// ScriptURI: context.BatchInfo.ScriptURI,
-// ScriptParamsURI: context.BatchInfo.ScriptParamsURI,
+// ScriptURL: context.BatchInfo.ScriptURL,
+// ScriptParamsURL: context.BatchInfo.ScriptParamsURL,
// DataKeyspace: context.BatchInfo.DataKeyspace,
// RunId: context.BatchInfo.RunId,
// TargetNodeName: targetNodeName,
diff --git a/pkg/wfmodel/message_payload_data_batch.go b/pkg/wfmodel/message_payload_data_batch.go
index 0728bcae..650ee06b 100644
--- a/pkg/wfmodel/message_payload_data_batch.go
+++ b/pkg/wfmodel/message_payload_data_batch.go
@@ -6,8 +6,8 @@ import (
)
type MessagePayloadDataBatch struct {
- ScriptURI string `json:"script_uri"`
- ScriptParamsURI string `json:"script_params_uri"`
+ ScriptURL string `json:"script_url"`
+ ScriptParamsURL string `json:"script_params_url"`
DataKeyspace string `json:"data_keyspace"` // Instance/process id
RunId int16 `json:"run_id"`
TargetNodeName string `json:"target_node"`
@@ -22,8 +22,8 @@ func (dc *MessagePayloadDataBatch) FullBatchId() string {
}
func (dc *MessagePayloadDataBatch) ToString() string {
- return fmt.Sprintf("ScriptURI:%s,ScriptParamsURI:%s, DataKeyspace:%s, RunId:%d, TargetNodeName:%s, FirstToken:%d, LastToken:%d, BatchIdx:%d, BatchesTotal:%d. ",
- dc.ScriptURI, dc.ScriptParamsURI, dc.DataKeyspace, dc.RunId, dc.TargetNodeName, dc.FirstToken, dc.LastToken, dc.BatchIdx, dc.BatchesTotal)
+ return fmt.Sprintf("ScriptURL:%s,ScriptParamsURL:%s, DataKeyspace:%s, RunId:%d, TargetNodeName:%s, FirstToken:%d, LastToken:%d, BatchIdx:%d, BatchesTotal:%d. ",
+ dc.ScriptURL, dc.ScriptParamsURL, dc.DataKeyspace, dc.RunId, dc.TargetNodeName, dc.FirstToken, dc.LastToken, dc.BatchIdx, dc.BatchesTotal)
}
func (dc *MessagePayloadDataBatch) Deserialize(jsonBytes []byte) error {
diff --git a/pkg/wfmodel/run_properties.go b/pkg/wfmodel/run_properties.go
index a34410b9..3dfae1b5 100644
--- a/pkg/wfmodel/run_properties.go
+++ b/pkg/wfmodel/run_properties.go
@@ -11,13 +11,13 @@ type RunProperties struct {
RunId int16 `header:"run_id" format:"%6d" column:"run_id" type:"int" key:"true" json:"run_id"`
StartNodes string `header:"start_nodes" format:"%20v" column:"start_nodes" type:"text" json:"start_nodes"`
AffectedNodes string `header:"affected_nodes" format:"%20v" column:"affected_nodes" type:"text" json:"affected_nodes"`
- ScriptUri string `header:"script_uri" format:"%20v" column:"script_uri" type:"text" json:"script_uri"`
- ScriptParamsUri string `header:"script_params_uri" format:"%20v" column:"script_params_uri" type:"text" json:"script_params_uri"`
+ ScriptUrl string `header:"script_url" format:"%20v" column:"script_url" type:"text" json:"script_url"`
+ ScriptParamsUrl string `header:"script_params_url" format:"%20v" column:"script_params_url" type:"text" json:"script_params_url"`
RunDescription string `header:"run_desc" format:"%20v" column:"run_description" type:"text" json:"run_description"`
}
func RunPropertiesAllFields() []string {
- return []string{"run_id", "start_nodes", "affected_nodes", "script_uri", "script_params_uri", "run_description"}
+ return []string{"run_id", "start_nodes", "affected_nodes", "script_url", "script_params_url", "run_description"}
}
func NewRunPropertiesFromMap(r map[string]any, fields []string) (*RunProperties, error) {
@@ -31,10 +31,10 @@ func NewRunPropertiesFromMap(r map[string]any, fields []string) (*RunProperties,
res.StartNodes, err = ReadStringFromRow(fieldName, r)
case "affected_nodes":
res.AffectedNodes, err = ReadStringFromRow(fieldName, r)
- case "script_uri":
- res.ScriptUri, err = ReadStringFromRow(fieldName, r)
- case "script_params_uri":
- res.ScriptParamsUri, err = ReadStringFromRow(fieldName, r)
+ case "script_url":
+ res.ScriptUrl, err = ReadStringFromRow(fieldName, r)
+ case "script_params_url":
+ res.ScriptParamsUrl, err = ReadStringFromRow(fieldName, r)
case "run_description":
res.RunDescription, err = ReadStringFromRow(fieldName, r)
default:
diff --git a/pkg/xfer/get_file_bytes.go b/pkg/xfer/get_file_bytes.go
index fcaa2992..27a6cea8 100644
--- a/pkg/xfer/get_file_bytes.go
+++ b/pkg/xfer/get_file_bytes.go
@@ -6,37 +6,37 @@ import (
"os"
)
-func GetFileBytes(uri string, certPath string, privateKeys map[string]string) ([]byte, error) {
- u, err := url.Parse(uri)
+func GetFileBytes(fileUrl string, certPath string, privateKeys map[string]string) ([]byte, error) {
+ u, err := url.Parse(fileUrl)
if err != nil {
- return nil, fmt.Errorf("cannot parse uri %s: %s", uri, err.Error())
+ return nil, fmt.Errorf("cannot parse url %s: %s", fileUrl, err.Error())
}
var bytes []byte
- if u.Scheme == UriSchemeFile || len(u.Scheme) == 0 {
- bytes, err = os.ReadFile(uri)
+ if u.Scheme == UrlSchemeFile || len(u.Scheme) == 0 {
+ bytes, err = os.ReadFile(fileUrl)
if err != nil {
return nil, err
}
- } else if u.Scheme == UriSchemeHttp || u.Scheme == UriSchemeHttps {
- bytes, err = readHttpFile(uri, u.Scheme, certPath)
+ } else if u.Scheme == UrlSchemeHttp || u.Scheme == UrlSchemeHttps {
+ bytes, err = readHttpFile(fileUrl, u.Scheme, certPath)
if err != nil {
return nil, err
}
- } else if u.Scheme == UriSchemeS3 {
- bytes, err = readS3File(uri)
+ } else if u.Scheme == UrlSchemeS3 {
+ bytes, err = readS3File(fileUrl)
if err != nil {
return nil, err
}
- } else if u.Scheme == UriSchemeSftp {
+ } else if u.Scheme == UrlSchemeSftp {
// When dealing with sftp, we download the *whole* file, then we read all of it
dstFile, err := os.CreateTemp("", "capi")
if err != nil {
- return nil, fmt.Errorf("cannot creeate temp file for %s: %s", uri, err.Error())
+ return nil, fmt.Errorf("cannot creeate temp file for %s: %s", fileUrl, err.Error())
}
// Download and schedule delete
- if err = DownloadSftpFile(uri, privateKeys, dstFile); err != nil {
+ if err = DownloadSftpFile(fileUrl, privateKeys, dstFile); err != nil {
dstFile.Close()
return nil, err
}
@@ -46,10 +46,10 @@ func GetFileBytes(uri string, certPath string, privateKeys map[string]string) ([
// Read
bytes, err = os.ReadFile(dstFile.Name())
if err != nil {
- return nil, fmt.Errorf("cannot read from file %s downloaded from %s: %s", dstFile.Name(), uri, err.Error())
+ return nil, fmt.Errorf("cannot read from file %s downloaded from %s: %s", dstFile.Name(), fileUrl, err.Error())
}
} else {
- return nil, fmt.Errorf("uri scheme %s not supported: %s", u.Scheme, uri)
+ return nil, fmt.Errorf("url scheme %s not supported: %s", u.Scheme, fileUrl)
}
return bytes, nil
diff --git a/pkg/xfer/http.go b/pkg/xfer/http.go
index f009da25..d678e45a 100644
--- a/pkg/xfer/http.go
+++ b/pkg/xfer/http.go
@@ -11,18 +11,18 @@ import (
"time"
)
-const UriSchemeFile string = "file"
-const UriSchemeHttp string = "http"
-const UriSchemeHttps string = "https"
-const UriSchemeSftp string = "sftp"
-const UriSchemeS3 string = "s3"
+const UrlSchemeFile string = "file"
+const UrlSchemeHttp string = "http"
+const UrlSchemeHttps string = "https"
+const UrlSchemeSftp string = "sftp"
+const UrlSchemeS3 string = "s3"
-func GetHttpReadCloser(uri string, scheme string, certDir string) (io.ReadCloser, error) {
+func GetHttpReadCloser(fileUrl string, scheme string, certDir string) (io.ReadCloser, error) {
var caCertPool *x509.CertPool
// tls.Config doc: If RootCAs is nil, TLS uses the host's root CA set.
if certDir != "" {
caCertPool = x509.NewCertPool()
- if scheme == UriSchemeHttps {
+ if scheme == UrlSchemeHttps {
files, err := os.ReadDir(certDir)
if err != nil {
return nil, fmt.Errorf("cannot read ca dir with PEM certs %s: %s", certDir, err.Error())
@@ -40,21 +40,21 @@ func GetHttpReadCloser(uri string, scheme string, certDir string) (io.ReadCloser
t := &http.Transport{TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{}, RootCAs: caCertPool}}
client := http.Client{Transport: t, Timeout: 30 * time.Second}
- resp, err := client.Get(uri)
+ resp, err := client.Get(fileUrl)
if err != nil {
- return nil, fmt.Errorf("cannot get %s: %s", uri, err.Error())
+ return nil, fmt.Errorf("cannot get %s: %s", fileUrl, err.Error())
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
- return nil, fmt.Errorf("cannot get %s, bad status: %s", uri, resp.Status)
+ return nil, fmt.Errorf("cannot get %s, bad status: %s", fileUrl, resp.Status)
}
return resp.Body, nil
}
-func readHttpFile(uri string, scheme string, certDir string) ([]byte, error) {
- r, err := GetHttpReadCloser(uri, scheme, certDir)
+func readHttpFile(fileUrl string, scheme string, certDir string) ([]byte, error) {
+ r, err := GetHttpReadCloser(fileUrl, scheme, certDir)
if err != nil {
return nil, err
}
@@ -62,7 +62,7 @@ func readHttpFile(uri string, scheme string, certDir string) ([]byte, error) {
bytes, err := io.ReadAll(r)
if err != nil {
- return nil, fmt.Errorf("cannot read body of %s, bad status: %s", uri, err.Error())
+ return nil, fmt.Errorf("cannot read body of %s, bad status: %s", fileUrl, err.Error())
}
return bytes, nil
}
diff --git a/pkg/xfer/s3.go b/pkg/xfer/s3.go
index 37c589f6..852d68ec 100644
--- a/pkg/xfer/s3.go
+++ b/pkg/xfer/s3.go
@@ -14,8 +14,8 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3"
)
-func GetS3ReadCloser(uri string) (io.ReadCloser, error) {
- parsedUri, _ := url.Parse(uri)
+func GetS3ReadCloser(fileUrl string) (io.ReadCloser, error) {
+ parsedUrl, _ := url.Parse(fileUrl)
// Assuming ~/.aws/credentials:
// [default]
@@ -39,8 +39,8 @@ func GetS3ReadCloser(uri string) (io.ReadCloser, error) {
// })
resp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
- Bucket: aws.String(parsedUri.Host),
- Key: aws.String(strings.TrimLeft(parsedUri.Path, "/")),
+ Bucket: aws.String(parsedUrl.Host),
+ Key: aws.String(strings.TrimLeft(parsedUrl.Path, "/")),
})
if err != nil {
return nil, err
@@ -49,8 +49,8 @@ func GetS3ReadCloser(uri string) (io.ReadCloser, error) {
return resp.Body, nil
}
-func readS3File(uri string) ([]byte, error) {
- r, err := GetS3ReadCloser(uri)
+func readS3File(fileUrl string) ([]byte, error) {
+ r, err := GetS3ReadCloser(fileUrl)
if err != nil {
return nil, err
}
@@ -58,7 +58,7 @@ func readS3File(uri string) ([]byte, error) {
bytes, err := io.ReadAll(r)
if err != nil {
- return nil, fmt.Errorf("cannot read body of %s, bad status: %s", uri, err.Error())
+ return nil, fmt.Errorf("cannot read body of %s, bad status: %s", fileUrl, err.Error())
}
return bytes, nil
}
diff --git a/pkg/xfer/sftp.go b/pkg/xfer/sftp.go
index 76b5df97..68096372 100644
--- a/pkg/xfer/sftp.go
+++ b/pkg/xfer/sftp.go
@@ -12,7 +12,7 @@ import (
"golang.org/x/crypto/ssh"
)
-type ParsedSftpUri struct {
+type ParsedSftpUrl struct {
User string
Host string
Port int
@@ -22,104 +22,104 @@ type ParsedSftpUri struct {
const DefaultSshPort int = 22
-func parseSftpUri(uri string, privateKeys map[string]string) (*ParsedSftpUri, error) {
+func parseSftpUrl(fileUrl string, privateKeys map[string]string) (*ParsedSftpUrl, error) {
// sftp://user[:pass]@host[:port]/path/to/file
- u, err := url.Parse(uri)
+ u, err := url.Parse(fileUrl)
if err != nil {
return nil, err
}
if u.User == nil || u.User.Username() == "" {
- return nil, fmt.Errorf("empty username in sftp uri %s", uri)
+ return nil, fmt.Errorf("empty username in sftp url %s", fileUrl)
}
userName := u.User.Username()
privateKeyPath, ok := privateKeys[userName]
if !ok {
- return nil, fmt.Errorf("username %s in sftp uri %s not found in environment configuration", userName, uri)
+ return nil, fmt.Errorf("username %s in sftp url %s not found in environment configuration", userName, fileUrl)
}
hostParts := strings.Split(u.Host, ":")
if len(hostParts) < 2 {
- return &ParsedSftpUri{userName, hostParts[0], DefaultSshPort, privateKeyPath, u.Path}, nil
+ return &ParsedSftpUrl{userName, hostParts[0], DefaultSshPort, privateKeyPath, u.Path}, nil
}
port, err := strconv.ParseInt(hostParts[1], 10, 32)
if err != nil {
- return nil, fmt.Errorf("cannot parse port in sftp uri %s", uri)
+ return nil, fmt.Errorf("cannot parse port in sftp url %s", fileUrl)
}
- return &ParsedSftpUri{userName, hostParts[0], int(port), privateKeyPath, u.Path}, nil
+ return &ParsedSftpUrl{userName, hostParts[0], int(port), privateKeyPath, u.Path}, nil
}
-func DownloadSftpFile(uri string, privateKeys map[string]string, dstFile *os.File) error {
- parsedUri, err := parseSftpUri(uri, privateKeys)
+func DownloadSftpFile(fileUrl string, privateKeys map[string]string, dstFile *os.File) error {
+ parsedUrl, err := parseSftpUrl(fileUrl, privateKeys)
if err != nil {
return err
}
// Assume empty key password ""
- sshClientConfig, err := NewSshClientConfig(parsedUri.User, parsedUri.PrivateKeyPath, "")
+ sshClientConfig, err := NewSshClientConfig(parsedUrl.User, parsedUrl.PrivateKeyPath, "")
if err != nil {
return err
}
- sshUrl := fmt.Sprintf("%s:%d", parsedUri.Host, parsedUri.Port)
+ sshUrl := fmt.Sprintf("%s:%d", parsedUrl.Host, parsedUrl.Port)
sshClient, err := ssh.Dial("tcp", sshUrl, sshClientConfig)
if err != nil {
- return fmt.Errorf("dial to %s failed: %s", uri, err.Error())
+ return fmt.Errorf("dial to %s failed: %s", fileUrl, err.Error())
}
defer sshClient.Close()
sftpClient, err := sftp.NewClient(sshClient)
if err != nil {
- return fmt.Errorf("cannot create sftp client to %s: %s", uri, err.Error())
+ return fmt.Errorf("cannot create sftp client to %s: %s", fileUrl, err.Error())
}
defer sftpClient.Close()
- srcFile, err := sftpClient.Open(parsedUri.RemotePath)
+ srcFile, err := sftpClient.Open(parsedUrl.RemotePath)
if err != nil {
- return fmt.Errorf("cannot open target file for sftp download %s: %s", uri, err.Error())
+ return fmt.Errorf("cannot open target file for sftp download %s: %s", fileUrl, err.Error())
}
defer srcFile.Close()
_, err = dstFile.ReadFrom(srcFile)
if err != nil {
- return fmt.Errorf("cannot read for download %s: %s", uri, err.Error())
+ return fmt.Errorf("cannot read for download %s: %s", fileUrl, err.Error())
}
return nil
}
-func UploadSftpFile(srcPath string, uri string, privateKeys map[string]string) error {
- parsedUri, err := parseSftpUri(uri, privateKeys)
+func UploadSftpFile(srcPath string, fileUrl string, privateKeys map[string]string) error {
+ parsedUrl, err := parseSftpUrl(fileUrl, privateKeys)
if err != nil {
return err
}
// Assume empty key password ""
- sshClientConfig, err := NewSshClientConfig(parsedUri.User, parsedUri.PrivateKeyPath, "")
+ sshClientConfig, err := NewSshClientConfig(parsedUrl.User, parsedUrl.PrivateKeyPath, "")
if err != nil {
return err
}
- sshUrl := fmt.Sprintf("%s:%d", parsedUri.Host, parsedUri.Port)
+ sshUrl := fmt.Sprintf("%s:%d", parsedUrl.Host, parsedUrl.Port)
sshClient, err := ssh.Dial("tcp", sshUrl, sshClientConfig)
if err != nil {
- return fmt.Errorf("dial to %s failed: %s", uri, err.Error())
+ return fmt.Errorf("dial to %s failed: %s", fileUrl, err.Error())
}
defer sshClient.Close()
sftpClient, err := sftp.NewClient(sshClient)
if err != nil {
- return fmt.Errorf("cannot create sftp client to %s: %s", uri, err.Error())
+ return fmt.Errorf("cannot create sftp client to %s: %s", fileUrl, err.Error())
}
defer sftpClient.Close()
- if err := sftpClient.MkdirAll(filepath.Dir(parsedUri.RemotePath)); err != nil {
- return fmt.Errorf("cannot create target dir for %s: %s", uri, err.Error())
+ if err := sftpClient.MkdirAll(filepath.Dir(parsedUrl.RemotePath)); err != nil {
+ return fmt.Errorf("cannot create target dir for %s: %s", fileUrl, err.Error())
}
srcFile, err := os.Open(srcPath)
@@ -128,19 +128,19 @@ func UploadSftpFile(srcPath string, uri string, privateKeys map[string]string) e
}
defer srcFile.Close()
- dstFile, err := sftpClient.Create(parsedUri.RemotePath)
+ dstFile, err := sftpClient.Create(parsedUrl.RemotePath)
if err != nil {
- return fmt.Errorf("cannot create on upload %s: %s", uri, err.Error())
+ return fmt.Errorf("cannot create on upload %s: %s", fileUrl, err.Error())
}
_, err = dstFile.ReadFrom(srcFile)
if err != nil {
- return fmt.Errorf("cannot read for upload %s to %s : %s", srcPath, uri, err.Error())
+ return fmt.Errorf("cannot read for upload %s to %s : %s", srcPath, fileUrl, err.Error())
}
err = dstFile.Sync()
if err != nil {
- return fmt.Errorf("cannot flush for upload %s to %s : %s", srcPath, uri, err.Error())
+ return fmt.Errorf("cannot flush for upload %s to %s : %s", srcPath, fileUrl, err.Error())
}
return nil
diff --git a/test/code/common/util.sh b/test/code/common/util.sh
index 9cc1ca50..c8b9432a 100755
--- a/test/code/common/util.sh
+++ b/test/code/common/util.sh
@@ -233,7 +233,7 @@ one_daemon_run_webapi()
fi
echo Starting a run in $keyspace at $webapiUrl ...
- curl -s -w "\n" -d '{"script_uri":"'$scriptFile'", "script_params_uri":"'$paramsFile'", "start_nodes":"'$startNodes'"}' -H "Content-Type: application/json" -X POST $webapiUrl"/ks/$keyspace/run"
+ curl -s -w "\n" -d '{"script_url":"'$scriptFile'", "script_params_url":"'$paramsFile'", "start_nodes":"'$startNodes'"}' -H "Content-Type: application/json" -X POST $webapiUrl"/ks/$keyspace/run"
if [ "$?" != "0" ]; then
exit 1
fi
diff --git a/test/code/fannie_mae/README.md b/test/code/fannie_mae/README.md
index f9cd04a0..e5abe9a1 100644
--- a/test/code/fannie_mae/README.md
+++ b/test/code/fannie_mae/README.md
@@ -14,13 +14,11 @@ fannie_mae_bigtest is a variation that uses large number of payment records borr
## Workflow
-The [DOT diagram](../../../doc/glossary.md#dot-diagrams) generated with
+The diagram generated with
```
-go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/fannie_mae_quicktest/script.json -params_file=../../../test/data/cfg/fannie_mae_bigtest/script_params.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/fannie_mae_quicktest/script.json -params_file=../../../test/data/cfg/fannie_mae_quicktest/script_params.json -detail=idx
```
-and rendered in https://dreampuf.github.io/GraphvizOnline :
-
-
+
Full transcript of what the result of each script node looks like in Cassandra - [transcript_fannie_mae.md](../../../doc/transcript_fannie_mae.md).
diff --git a/test/code/lookup/README.md b/test/code/lookup/README.md
index 01f40618..6170a136 100644
--- a/test/code/lookup/README.md
+++ b/test/code/lookup/README.md
@@ -15,13 +15,12 @@ and requires test data to be generated - see [1_create_data.sh](./bigtest_cloud/
## Workflow
-The [DOT diagram](../../../doc/glossary.md#dot-diagrams) generated with
+The diagram generated with
```
-go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/lookup_quicktest/script.json -params_file=../../../test/data/cfg/lookup_quicktest/script_params_two_runs.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/lookup_quicktest/script.json -params_file=../../../test/data/cfg/lookup_quicktest/script_params_two_runs.json -detail=idx
```
-and rendered in https://dreampuf.github.io/GraphvizOnline :
-
+
## What's tested:
diff --git a/test/code/lookup/quicktest_s3/1_create_data.sh b/test/code/lookup/quicktest_s3/1_create_data.sh
index ee097b2b..fe325ebf 100755
--- a/test/code/lookup/quicktest_s3/1_create_data.sh
+++ b/test/code/lookup/quicktest_s3/1_create_data.sh
@@ -4,7 +4,7 @@ source ../../common/util.sh
check_s3
# Generate in and out-baseline files
-../quicktest/1_create_data.sh
+../quicktest_local_fs/1_create_data.sh
cfgS3=s3://$CAPILLARIES_AWS_TESTBUCKET/capi_cfg/lookup_quicktest
inS3=s3://$CAPILLARIES_AWS_TESTBUCKET/capi_in/lookup_quicktest
diff --git a/test/code/portfolio/README.md b/test/code/portfolio/README.md
index d320460c..5f9d209c 100644
--- a/test/code/portfolio/README.md
+++ b/test/code/portfolio/README.md
@@ -15,13 +15,12 @@ and requires test data to be generated - see [1_create_data.sh](./bigtest_cloud/
## Workflow
-The [DOT diagram](../../../doc/glossary.md#dot-diagrams) generated with
+The diagram generated with
```
-go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/portfolio_quicktest/script.json -params_file=../../../test/data/cfg/portfolio_quicktest/script_params.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/portfolio_quicktest/script.json -params_file=../../../test/data/cfg/portfolio_quicktest/script_params.json -detail=idx
```
-and rendered in https://dreampuf.github.io/GraphvizOnline :
-
+
Or, here is a bit more low-level walkthrough
diff --git a/test/code/portfolio/doc/dot-portfolio.svg b/test/code/portfolio/doc/dot-portfolio.svg
deleted file mode 100644
index 06ba1a3a..00000000
--- a/test/code/portfolio/doc/dot-portfolio.svg
+++ /dev/null
@@ -1,320 +0,0 @@
-
-
-indexes
-
-
-
-/tmp/capi_in/portfolio_quicktest/txns.csv
-
-
-/tmp/capi_in/portfolio_quicktest/txns.csv
-
-
-
-
-
-txns
-
-
-1_read_txns
-
-creates table:
-txns
-
-
-
-
-
-/tmp/capi_in/portfolio_quicktest/txns.csv->txns
-
-
-
-
-
-account_txns
-
-
-2_account_txns_outer
-
-creates table:
-account_txns
-
-group:true, join:left
-
-
-
-
-
-txns->account_txns
-
-
-idx_txns_account_id (lookup)
-
-
-
-accounts
-
-
-1_read_accounts
-
-creates table:
-accounts
-
-
-
-
-
-accounts->account_txns
-
-
-accounts (10 batches)
-
-
-
-account_period_holdings
-
-
-2_account_period_holdings_outer
-
-creates table:
-account_period_holdings
-
-group:true, join:left
-
-
-
-
-
-accounts->account_period_holdings
-
-
-accounts (10 batches)
-
-
-
-account_period_activity
-
-
-3_build_account_period_activity
-
-creates table:
-account_period_activity
-
-group:false, join:left
-
-
-
-
-
-account_txns->account_period_activity
-
-
-account_txns (10 batches)
-
-
-
-/tmp/capi_in/portfolio_quicktest/holdings.csv
-
-
-/tmp/capi_in/portfolio_quicktest/holdings.csv
-
-
-
-
-
-period_holdings
-
-
-1_read_period_holdings
-
-creates table:
-period_holdings
-
-
-
-
-
-/tmp/capi_in/portfolio_quicktest/holdings.csv->period_holdings
-
-
-
-
-
-period_holdings->account_period_holdings
-
-
-idx_period_holdings_account_id (lookup)
-
-
-
-account_period_holdings->account_period_activity
-
-
-idx_account_period_holdings_account_id (lookup)
-
-
-
-account_period_perf
-
-
-4_calc_account_period_perf
-
-creates table:
-account_period_perf
-
-
-
-
-
-account_period_activity->account_period_perf
-
-
-account_period_activity (10 batches)
-
-
-
-account_period_perf_by_period
-
-
-5_tag_by_period
-
-creates table:
-account_period_perf_by_period
-
-
-
-
-
-account_period_perf->account_period_perf_by_period
-
-
-account_period_perf (10 batches)
-
-
-
-account_period_perf_by_period_sector
-
-
-5_tag_by_sector
-
-creates table:
-account_period_perf_by_period_sector
-
-
-
-
-
-account_period_perf_by_period->account_period_perf_by_period_sector
-
-
-account_period_perf_by_period (10 batches)
-
-
-
-account_period_sector_twr_cagr
-
-
-6_perf_json_to_columns
-
-creates table:
-account_period_sector_twr_cagr
-
-
-
-
-
-account_period_perf_by_period_sector->account_period_sector_twr_cagr
-
-
-account_period_perf_by_period_sector (100 batches)
-
-
-
-/tmp/capi_in/portfolio_quicktest/accounts.csv
-
-
-/tmp/capi_in/portfolio_quicktest/accounts.csv
-
-
-
-
-
-/tmp/capi_in/portfolio_quicktest/accounts.csv->accounts
-
-
-
-
-
-7_file_account_period_sector_perf
-
-
-7_file_account_period_sector_perf
-
-creates file:
-/tmp/capi_out/portfolio_quicktest/account_period_sector_perf.csv
-
-
-
-
-
-account_period_sector_twr_cagr->7_file_account_period_sector_perf
-
-
-account_period_sector_twr_cagr (no parallelism)
-
-
-
-7_file_account_year_perf
-
-
-7_file_account_year_perf
-
-creates file:
-/tmp/capi_out/portfolio_quicktest/account_year_perf.csv
-
-
-
-
-
-account_period_sector_twr_cagr->7_file_account_year_perf
-
-
-account_period_sector_twr_cagr (no parallelism)
-
-
-
-/tmp/capi_out/portfolio_quicktest/account_period_sector_perf.csv
-
-
-
-
-/tmp/capi_out/portfolio_quicktest/account_period_sector_perf.csv
-
-
-
-
-
-7_file_account_period_sector_perf->/tmp/capi_out/portfolio_quicktest/account_period_sector_perf.csv
-
-
-
-
-
-/tmp/capi_out/portfolio_quicktest/account_year_perf.csv
-
-
-
-
-/tmp/capi_out/portfolio_quicktest/account_year_perf.csv
-
-
-
-
-
-7_file_account_year_perf->/tmp/capi_out/portfolio_quicktest/account_year_perf.csv
-
-
-
-
-
\ No newline at end of file
diff --git a/test/code/portfolio/doc/viz-portfolio.svg b/test/code/portfolio/doc/viz-portfolio.svg
new file mode 100644
index 00000000..f9c02472
--- /dev/null
+++ b/test/code/portfolio/doc/viz-portfolio.svg
@@ -0,0 +1,443 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1_read_period_holdings
+ Load holdings from csv
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../holdings.csv
+ Table created: period_holdings
+
+
+
+
+
+
+
+ 1_read_accounts
+ Load accounts from csv
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../accounts.csv
+ Table created: accounts
+
+
+
+
+
+
+
+ 2_account_period_holdings_outer
+ For each account, merge all holdings into single json string
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_period_holdings_account_id
+ Table created: account_period_holdings
+
+
+
+
+ accounts
+ (10 batches)
+
+
+
+
+ idx_period_holdings_account_id
+ (lookup)
+
+
+
+
+
+
+
+ 2_account_txns_outer
+ For each account, merge all txns into single json string
+ Processor: left join with lookup table, group: true
+ Lookup index: idx_txns_account_id
+ Table created: account_txns
+
+
+
+
+ accounts
+ (10 batches)
+
+
+
+
+ idx_txns_account_id
+ (lookup)
+
+
+
+
+
+
+
+ 3_build_account_period_activity
+ For each account, merge holdings and txns
+ Processor: left join with lookup table, group: false
+ Lookup index: idx_account_period_holdings_account_id
+ Table created: account_period_activity
+
+
+
+
+ account_txns
+ (10 batches)
+
+
+
+
+ idx_account_period_holdings_account_id
+ (lookup)
+
+
+
+
+
+
+
+ 4_calc_account_period_perf
+ Apply Python-based calculations to account holdings and txns
+ Processor: apply Python calculations
+ Python file(s): /tmp/capi_cfg/.../portfolio_test_company_info_provider.py
+ Table created: account_period_perf
+
+
+
+
+ account_period_activity
+ (10 batches)
+
+
+
+
+
+
+
+ 5_tag_by_period
+ Tag accounts by period name
+ Processor: tag and denormalize
+ Tag criteria URL: (inline)
+ Table created: account_period_perf_by_period
+
+
+
+
+ account_period_perf
+ (10 batches)
+
+
+
+
+
+
+
+ 5_tag_by_sector
+ Tag accounts by sector
+ Processor: tag and denormalize
+ Tag criteria URL: (inline)
+ Table created: account_period_perf_by_period_sector
+
+
+
+
+ account_period_perf_by_period
+ (10 batches)
+
+
+
+
+
+
+
+ 6_perf_json_to_columns
+ Use Python to read perf json and save stats as columns
+ Processor: apply Python calculations
+ Python file(s): /tmp/capi_cfg/.../json_to_columns.py
+ Table created: account_period_sector_twr_cagr
+
+
+
+
+ account_period_perf_by_period_sector
+ (100 batches)
+
+
+
+
+
+
+
+ 7_file_account_period_sector_perf
+ Write yearly/quarterly perf results by sector to CSV file
+ Processor: read from table into files
+ Source table: account_period_sector_twr_cagr
+ File(s): /tmp/capi_out/.../account_period_sector_perf.csv
+
+
+
+
+ account_period_sector_twr_cagr
+ (no parallelism)
+
+
+
+
+
+
+
+ 7_file_account_year_perf
+ Write yearly perf results for all sectors to CSV file
+ Processor: read from table into files
+ Source table: account_period_sector_twr_cagr
+ File(s): /tmp/capi_out/.../account_year_perf.csv
+
+
+
+
+ account_period_sector_twr_cagr
+ (no parallelism)
+
+
+
+
+
+
+
+ 1_read_txns
+ Load txns from csv
+ Processor: read from files into a table
+ File(s): /tmp/capi_in/.../txns.csv
+ Table created: txns
+
+Perms 24, elapsed 0.000s, dist 2864.6
+
diff --git a/test/code/proto_file_reader_creator/README.md b/test/code/proto_file_reader_creator/README.md
index 49678981..62a19cd3 100644
--- a/test/code/proto_file_reader_creator/README.md
+++ b/test/code/proto_file_reader_creator/README.md
@@ -4,14 +4,13 @@ Created using Ubuntu WSL. Other Linux flavors and MacOS may require edits.
## Workflow
-There are no Capillaries scripts for this test in the Capillaries codebase. The scripts are generated on the fly by [1_generate_scripts.sh](./1_generate_scripts.sh) and saved to `/tmp/capi_cfg/proto_file_reader_creator_quicktest`. After the scripts are generated, you can generate the [DOT diagram](../../../doc/glossary.md#dot-diagrams) diagram using this command:
+There are no Capillaries scripts for this test in the Capillaries codebase. The scripts are generated on the fly by [1_generate_scripts.sh](./1_generate_scripts.sh) and saved to `/tmp/capi_cfg/proto_file_reader_creator_quicktest`. After the scripts are generated, you can generate the diagram using this command:
```
-go run capitoolbelt.go validate_script -script_file=/tmp/capi_cfg/proto_file_reader_creator_quicktest/script_csv.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=/tmp/capi_cfg/proto_file_reader_creator_quicktest/script_csv.json -detail=idx
```
-and render it in https://dreampuf.github.io/GraphvizOnline :
-
+
## What's tested:
diff --git a/test/code/py_calc/README.md b/test/code/py_calc/README.md
index 0b280543..8a1c9411 100644
--- a/test/code/py_calc/README.md
+++ b/test/code/py_calc/README.md
@@ -8,13 +8,12 @@ User-supplied formulas are expected to reside in test/data/cfg/py_calc/py direct
## Workflow
-The [DOT diagram](../../../doc/glossary.md#dot-diagrams) generated with
+The diagram generated with
```
-go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/py_calc/script.json -params_file=../../../test/data/cfg/py_calc/script_params.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/py_calc_quicktest/script.json -params_file=../../../test/data/cfg/py_calc_quicktest/script_params.json -detail=idx
```
-and rendered in https://dreampuf.github.io/GraphvizOnline :
-
+
## What's tested:
diff --git a/test/code/tag_and_denormalize/README.md b/test/code/tag_and_denormalize/README.md
index eb96bd54..bdd2526f 100644
--- a/test/code/tag_and_denormalize/README.md
+++ b/test/code/tag_and_denormalize/README.md
@@ -4,13 +4,12 @@ Created using Ubuntu WSL. Other Linux flavors and MacOS may require edits.
## Workflow
-The [DOT diagram](../../../doc/glossary.md#dot-diagrams) generated with
+The diagram generated with
```
-go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/tag_and_denormalize_quicktest/script.json -params_file=../../../test/data/cfg/tag_and_denormalize/script_params_two_runs.json -idx_dag=true
+go run capitoolbelt.go validate_script -script_file=../../../test/data/cfg/tag_and_denormalize_quicktest/script.json -params_file=../../../test/data/cfg/tag_and_denormalize_quicktest/script_params_two_runs.json -detail=idx
```
-and rendered in https://dreampuf.github.io/GraphvizOnline :
-
+
## What's tested:
diff --git a/test/data/cfg/tag_and_denormalize_quicktest/script.json b/test/data/cfg/tag_and_denormalize_quicktest/script.json
index eb8f4a05..5da1193f 100644
--- a/test/data/cfg/tag_and_denormalize_quicktest/script.json
+++ b/test/data/cfg/tag_and_denormalize_quicktest/script.json
@@ -5,7 +5,7 @@
"desc": "Load tags from file",
"r": {
"urls": [
- "{dir_in}/tags.csv{uri_suffix}"
+ "{dir_in}/tags.csv{url_suffix}"
],
"csv": {
"first_data_line_idx": 0
@@ -35,7 +35,7 @@
"explicit_run_only": true,
"r": {
"urls": [
- "{dir_in}/flipcart_products.tsv{uri_suffix}"
+ "{dir_in}/flipcart_products.tsv{url_suffix}"
],
"csv": {
"separator": "\t",
@@ -113,7 +113,7 @@
},
"p": {
"tag_field_name": "tag",
- "tag_criteria_uri": "{dir_cfg}/tag_criteria.json{uri_suffix}"
+ "tag_criteria_url": "{dir_cfg}/tag_criteria.json{url_suffix}"
},
"w": {
"name": "tagged_products",
@@ -181,7 +181,7 @@
"tag_totals": {
"type": "table_lookup_table",
"start_policy": "{totals_start_policy|string}",
- "desc": "For each tag, look up produts and perform left outer join with grouping and aggregation",
+ "desc": "For each tag, look up products and perform left outer join with grouping and aggregation",
"r": {
"table": "tags",
"expected_batches_total": 10
diff --git a/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run.json b/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run.json
index e5a1bb97..948bb90c 100644
--- a/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run.json
+++ b/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run.json
@@ -1,6 +1,6 @@
{
"totals_start_policy": "auto",
- "uri_suffix": "",
+ "url_suffix": "",
"dir_cfg": "/tmp/capi_cfg/tag_and_denormalize_quicktest",
"dir_in": "/tmp/capi_in/tag_and_denormalize_quicktest",
"dir_out": "/tmp/capi_out/tag_and_denormalize_quicktest"
diff --git a/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run_input_https.json b/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run_input_https.json
index 86524d58..55de43fe 100644
--- a/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run_input_https.json
+++ b/test/data/cfg/tag_and_denormalize_quicktest/script_params_one_run_input_https.json
@@ -1,6 +1,6 @@
{
"totals_start_policy": "auto",
- "uri_suffix": "",
+ "url_suffix": "",
"dir_cfg": "https://raw.githubusercontent.com/capillariesio/capillaries/main/test/data/cfg/tag_and_denormalize_quicktest",
"dir_in": "https://raw.githubusercontent.com/capillariesio/capillaries/main/test/data/in/tag_and_denormalize_quicktest",
"dir_out": "/tmp/capi_out/tag_and_denormalize_quicktest"
diff --git a/test/data/cfg/tag_and_denormalize_quicktest/script_params_two_runs.json b/test/data/cfg/tag_and_denormalize_quicktest/script_params_two_runs.json
index 54ece208..3cf78ecf 100644
--- a/test/data/cfg/tag_and_denormalize_quicktest/script_params_two_runs.json
+++ b/test/data/cfg/tag_and_denormalize_quicktest/script_params_two_runs.json
@@ -1,6 +1,6 @@
{
"totals_start_policy": "manual",
- "uri_suffix": "",
+ "url_suffix": "",
"dir_cfg": "/tmp/capi_cfg/tag_and_denormalize_quicktest",
"dir_in": "/tmp/capi_in/tag_and_denormalize_quicktest",
"dir_out": "/tmp/capi_out/tag_and_denormalize_quicktest"
diff --git a/test/docker/cassandra/Dockerfile b/test/docker/cassandra/Dockerfile
index 98713dc1..5dcc3801 100755
--- a/test/docker/cassandra/Dockerfile
+++ b/test/docker/cassandra/Dockerfile
@@ -1,6 +1,6 @@
-FROM cassandra:4.1
+FROM cassandra:5.0.2
-ARG JMX_EXPORTER_VERSION=0.20.0
+ARG JMX_EXPORTER_VERSION=1.0.1
RUN mkdir /prometheus
ADD "https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/$JMX_EXPORTER_VERSION/jmx_prometheus_javaagent-$JMX_EXPORTER_VERSION.jar" /prometheus
diff --git a/test/docker/fluentd/Dockerfile b/test/docker/fluentd/Dockerfile
index 81d839ba..222ef783 100644
--- a/test/docker/fluentd/Dockerfile
+++ b/test/docker/fluentd/Dockerfile
@@ -1,3 +1,3 @@
-FROM fluent/fluentd:v1.17-1
+FROM fluent/fluentd:v1.18-1
RUN rm /fluentd/etc/fluent.conf
ADD ./test/docker/fluentd/fluent.conf /fluentd/etc
\ No newline at end of file
diff --git a/test/docker/fluentd/fluent.conf b/test/docker/fluentd/fluent.conf
index e244dc51..25748110 100755
--- a/test/docker/fluentd/fluent.conf
+++ b/test/docker/fluentd/fluent.conf
@@ -26,7 +26,7 @@
@type file
path /fluentd/log/webapi.*.log
- time_key fluentd_time # Add ts - we mau want to know when the msg hit fluentd
+ time_key fluentd_time # Add ts - we may want to know when the msg hit fluentd
time_type string
time_format %Y-%m-%dT%H:%M:%S.%NZ
@@ -40,7 +40,7 @@
@type file
path /fluentd/log/daemon.*.log
- time_key fluentd_time # Add ts - we mau want to know when the msg hit fluentd
+ time_key fluentd_time # Add ts - we may want to know when the msg hit fluentd
time_type string
time_format %Y-%m-%dT%H:%M:%S.%NZ
@@ -54,7 +54,7 @@
@type file
path /fluentd/log/cassandra.*.log
- time_key fluentd_time # Add ts - we mau want to know when the msg hit fluentd
+ time_key fluentd_time # Add ts - we may want to know when the msg hit fluentd
time_type string
time_format %Y-%m-%dT%H:%M:%S.%NZ
@@ -68,7 +68,7 @@
@type file
path /fluentd/log/rabbitmq.*.log
- time_key fluentd_time # Add ts - we mau want to know when the msg hit fluentd
+ time_key fluentd_time # Add ts - we may want to know when the msg hit fluentd
time_type string
time_format %Y-%m-%dT%H:%M:%S.%NZ
@@ -82,7 +82,7 @@
@type file
path /fluentd/log/prometheus.*.log
- time_key fluentd_time # Add ts - we mau want to know when the msg hit fluentd
+ time_key fluentd_time # Add ts - we may want to know when the msg hit fluentd
time_type string
time_format %Y-%m-%dT%H:%M:%S.%NZ
@@ -96,7 +96,7 @@
@type file
path /fluentd/log/ui.*.log
- time_key fluentd_time # Add ts - we mau want to know when the msg hit fluentd
+ time_key fluentd_time # Add ts - we may want to know when the msg hit fluentd
time_type string
time_format %Y-%m-%dT%H:%M:%S.%NZ
diff --git a/test/docker/prometheus/prometheus.yml b/test/docker/prometheus/prometheus.yml
index 06928c99..f0d5ad65 100755
--- a/test/docker/prometheus/prometheus.yml
+++ b/test/docker/prometheus/prometheus.yml
@@ -8,4 +8,4 @@ scrape_configs:
- job_name: 'node_exporter'
scrape_interval: 5s
static_configs:
- - targets: ['10.5.0.11:7070','10.5.0.12:7070']
+ - targets: ['10.5.0.11:7070'] #['10.5.0.11:7070','10.5.0.12:7070']
diff --git a/test/k8s/README.md b/test/k8s/README.md
index 64a5fb77..8adb0d1c 100755
--- a/test/k8s/README.md
+++ b/test/k8s/README.md
@@ -2,7 +2,7 @@ This is a POC demonstrating Capillaries running in a Kubernetes cluster.
It assumes that:
- S3 docker repositories are set up
- - S3 bucket that hold lookup_quicktest_s3 Capillaries script and input data files are provisioned
+ - S3 bucket that holds lookup_quicktest_s3 Capillaries script and input data files are provisioned
- Capillaries docker images were built and uploaded using binaries_build.sh, images_build.sh, images_upload.sh commands
- Minikube Kuberetes cluster is running
diff --git a/ui/.eslintignore b/ui/.eslintignore
deleted file mode 100644
index a6ca7295..00000000
--- a/ui/.eslintignore
+++ /dev/null
@@ -1,12 +0,0 @@
-.DS_Store
-node_modules
-/build
-/.svelte-kit
-/package
-.env.*
-!.env.example
-
-# Ignore files for PNPM, NPM and YARN
-pnpm-lock.yaml
-package-lock.json
-yarn.lock
diff --git a/ui/docker/Dockerfile b/ui/docker/Dockerfile
index 7232a871..d67c534a 100755
--- a/ui/docker/Dockerfile
+++ b/ui/docker/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:21.7.0-alpine
+FROM node:23.3.0-alpine
WORKDIR /usr/src/capillaries
diff --git a/ui/eslint.config.js b/ui/eslint.config.js
new file mode 100644
index 00000000..ce3c1f81
--- /dev/null
+++ b/ui/eslint.config.js
@@ -0,0 +1,38 @@
+import prettier from 'eslint-config-prettier';
+import js from '@eslint/js';
+import { includeIgnoreFile } from '@eslint/compat';
+import svelte from 'eslint-plugin-svelte';
+import globals from 'globals';
+import { fileURLToPath } from 'node:url';
+const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
+
+/** @type {import('eslint').Linter.Config[]} */
+export default [
+ includeIgnoreFile(gitignorePath),
+ js.configs.recommended,
+ ...svelte.configs['flat/recommended'],
+ prettier,
+ ...svelte.configs['flat/prettier'],
+ {
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node
+ }
+ }
+ },
+ {
+ // Note: there should be no other properties in this object
+ ignores: [
+ '**/temp.js',
+ 'config/*',
+ '.DS_Store',
+ 'node_modules',
+ '/build',
+ '/.svelte-kit',
+ 'package',
+ '.env.*',
+ '!.env.example'
+ ]
+ }
+];
diff --git a/ui/package-lock.json b/ui/package-lock.json
index a801f888..e489307b 100755
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -8,19 +8,22 @@
"name": "capillaries-ui",
"version": "1.0.0",
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.2.5",
- "@sveltejs/adapter-static": "^3.0.5",
- "@sveltejs/kit": "^2.5.28",
+ "@eslint/compat": "^1.2.4",
+ "@sveltejs/adapter-auto": "^3.3.1",
+ "@sveltejs/adapter-static": "^3.0.6",
+ "@sveltejs/kit": "^2.9.0",
+ "@sveltejs/vite-plugin-svelte": "^5.0.1",
"dayjs": "^1.11.13",
- "eslint": "^9.11.0",
+ "eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-svelte": "^2.44.0",
- "prettier": "^3.3.3",
- "prettier-plugin-svelte": "^3.2.6",
- "svelte": "^4.2.19",
- "svelte-modals": "^1.3.0",
+ "eslint-plugin-svelte": "^2.46.1",
+ "prettier": "^3.4.2",
+ "prettier-plugin-svelte": "^3.2.7",
+ "rollup-plugin-visualizer": "^5.12.0",
+ "svelte": "^5.9.0",
+ "svelte-modals": "^2.0.0",
"urlpattern-polyfill": "^10.0.0",
- "vite": "^5.4.7",
+ "vite": "^6.0.3",
"vite-plugin-filter-replace": "^0.1.13"
}
},
@@ -38,371 +41,411 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
+ "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
+ "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
+ "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
+ "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
+ "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
+ "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
+ "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
+ "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
+ "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
+ "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
+ "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
+ "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
"cpu": [
"loong64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
+ "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
"cpu": [
"mips64el"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
+ "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
+ "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
"cpu": [
"riscv64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
+ "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
"cpu": [
"s390x"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
+ "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
+ "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
+ "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
+ "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
+ "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
+ "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
+ "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
+ "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -421,21 +464,41 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.11.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
- "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
+ "node_modules/@eslint/compat": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.4.tgz",
+ "integrity": "sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": "^9.10.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@eslint/config-array": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
- "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
+ "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.4",
+ "@eslint/object-schema": "^2.1.5",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -443,11 +506,25 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@eslint/core": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz",
+ "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
"node_modules/@eslint/eslintrc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
- "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
+ "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@@ -467,28 +544,31 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.11.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.0.tgz",
- "integrity": "sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==",
+ "version": "9.16.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
+ "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz",
- "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==",
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
+ "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
- "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz",
+ "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==",
"dev": true,
+ "license": "Apache-2.0",
"dependencies": {
"levn": "^0.4.1"
},
@@ -496,6 +576,44 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -510,10 +628,11 @@
}
},
"node_modules/@humanwhocodes/retry": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
- "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+ "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
@@ -555,10 +674,11 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -570,260 +690,285 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/@polka/url": {
- "version": "1.0.0-next.25",
- "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
- "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==",
- "dev": true
+ "version": "1.0.0-next.28",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
+ "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
- "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
+ "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
- "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
+ "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
- "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
+ "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
- "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
+ "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
+ "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
+ "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
- "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
+ "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
- "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
+ "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
"cpu": [
"arm"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
- "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
+ "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
- "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
+ "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
- "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
+ "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
"cpu": [
"ppc64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
- "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
"cpu": [
"riscv64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
- "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
+ "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
"cpu": [
"s390x"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
- "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
+ "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
- "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
+ "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
- "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
+ "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
"cpu": [
"arm64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
- "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
"cpu": [
"ia32"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
- "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
"cpu": [
"x64"
],
"dev": true,
+ "license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sveltejs/adapter-auto": {
- "version": "3.2.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.5.tgz",
- "integrity": "sha512-27LR+uKccZ62lgq4N/hvyU2G+hTP9fxWEAfnZcl70HnyfAjMSsGk1z/SjAPXNCD1mVJIE7IFu3TQ8cQ/UH3c0A==",
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz",
+ "integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"import-meta-resolve": "^4.1.0"
},
@@ -832,32 +977,34 @@
}
},
"node_modules/@sveltejs/adapter-static": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.5.tgz",
- "integrity": "sha512-kFJR7RxeB6FBvrKZWAEzIALatgy11ISaaZbcPup8JdWUdrmmfUHHTJ738YHJTEfnCiiXi6aX8Q6ePY7tnSMD6Q==",
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz",
+ "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/kit": {
- "version": "2.5.28",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.28.tgz",
- "integrity": "sha512-/O7pvFGBsQPcFa9UrW8eUC5uHTOXLsUp3SN0dY6YmRAL9nfPSrJsSJk//j5vMpinSshzUjteAFcfQTU+04Ka1w==",
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.9.0.tgz",
+ "integrity": "sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0",
- "devalue": "^5.0.0",
- "esm-env": "^1.0.0",
+ "devalue": "^5.1.0",
+ "esm-env": "^1.2.1",
"import-meta-resolve": "^4.1.0",
"kleur": "^4.1.5",
"magic-string": "^0.30.5",
"mrmime": "^2.0.0",
"sade": "^1.8.1",
"set-cookie-parser": "^2.6.0",
- "sirv": "^2.0.4",
+ "sirv": "^3.0.0",
"tiny-glob": "^0.2.9"
},
"bin": {
@@ -867,50 +1014,49 @@
"node": ">=18.13"
},
"peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1",
+ "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0",
"svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.3"
+ "vite": "^5.0.3 || ^6.0.0"
}
},
"node_modules/@sveltejs/vite-plugin-svelte": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.0.tgz",
- "integrity": "sha512-sY6ncCvg+O3njnzbZexcVtUqOBE3iYmQPJ9y+yXSkOwG576QI/xJrBnQSRXFLGwJNBa0T78JEKg5cIR0WOAuUw==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.1.tgz",
+ "integrity": "sha512-D5l5+STmywGoLST07T9mrqqFFU+xgv5fqyTWM+VbxTvQ6jujNn4h3lQNCvlwVYs4Erov8i0K5Rwr3LQtmBYmBw==",
"dev": true,
- "peer": true,
+ "license": "MIT",
"dependencies": {
- "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0",
- "debug": "^4.3.4",
+ "@sveltejs/vite-plugin-svelte-inspector": "^4.0.0",
+ "debug": "^4.3.7",
"deepmerge": "^4.3.1",
"kleur": "^4.1.5",
- "magic-string": "^0.30.9",
- "svelte-hmr": "^0.16.0",
- "vitefu": "^0.2.5"
+ "magic-string": "^0.30.13",
+ "vitefu": "^1.0.3"
},
"engines": {
- "node": "^18.0.0 || >=20"
+ "node": "^18.0.0 || ^20.0.0 || >=22"
},
"peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.0"
+ "svelte": "^5.0.0",
+ "vite": "^6.0.0"
}
},
"node_modules/@sveltejs/vite-plugin-svelte-inspector": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
- "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz",
+ "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==",
"dev": true,
- "peer": true,
+ "license": "MIT",
"dependencies": {
- "debug": "^4.3.4"
+ "debug": "^4.3.7"
},
"engines": {
- "node": "^18.0.0 || >=20"
+ "node": "^18.0.0 || ^20.0.0 || >=22"
},
"peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
- "vite": "^5.0.0"
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
+ "svelte": "^5.0.0",
+ "vite": "^6.0.0"
}
},
"node_modules/@types/cookie": {
@@ -920,16 +1066,25 @@
"dev": true
},
"node_modules/@types/estree": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/acorn": {
- "version": "8.12.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
- "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
+ "license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -942,15 +1097,27 @@
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-typescript": {
+ "version": "1.4.13",
+ "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
+ "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": ">=8.9.0"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -967,6 +1134,7 @@
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
}
@@ -990,37 +1158,42 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
+ "dev": true,
+ "license": "Python-2.0"
},
"node_modules/aria-query": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
- "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
- "dependencies": {
- "dequal": "^2.0.3"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
}
},
"node_modules/axobject-query": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
- "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
- "dependencies": {
- "dequal": "^2.0.3"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -1031,6 +1204,7 @@
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -1051,17 +1225,19 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/code-red": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
- "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
+ "license": "ISC",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@types/estree": "^1.0.1",
- "acorn": "^8.10.0",
- "estree-walker": "^3.0.3",
- "periscopic": "^3.1.0"
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/color-convert": {
@@ -1086,7 +1262,8 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/cookie": {
"version": "0.6.0",
@@ -1098,10 +1275,11 @@
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -1111,19 +1289,6 @@
"node": ">= 8"
}
},
- "node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "dev": true,
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1143,12 +1308,13 @@
"dev": true
},
"node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "ms": "2.1.2"
+ "ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -1170,62 +1336,82 @@
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
"node_modules/devalue": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz",
- "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==",
- "dev": true
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
+ "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "version": "0.24.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
+ "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
+ "@esbuild/aix-ppc64": "0.24.0",
+ "@esbuild/android-arm": "0.24.0",
+ "@esbuild/android-arm64": "0.24.0",
+ "@esbuild/android-x64": "0.24.0",
+ "@esbuild/darwin-arm64": "0.24.0",
+ "@esbuild/darwin-x64": "0.24.0",
+ "@esbuild/freebsd-arm64": "0.24.0",
+ "@esbuild/freebsd-x64": "0.24.0",
+ "@esbuild/linux-arm": "0.24.0",
+ "@esbuild/linux-arm64": "0.24.0",
+ "@esbuild/linux-ia32": "0.24.0",
+ "@esbuild/linux-loong64": "0.24.0",
+ "@esbuild/linux-mips64el": "0.24.0",
+ "@esbuild/linux-ppc64": "0.24.0",
+ "@esbuild/linux-riscv64": "0.24.0",
+ "@esbuild/linux-s390x": "0.24.0",
+ "@esbuild/linux-x64": "0.24.0",
+ "@esbuild/netbsd-x64": "0.24.0",
+ "@esbuild/openbsd-arm64": "0.24.0",
+ "@esbuild/openbsd-x64": "0.24.0",
+ "@esbuild/sunos-x64": "0.24.0",
+ "@esbuild/win32-arm64": "0.24.0",
+ "@esbuild/win32-ia32": "0.24.0",
+ "@esbuild/win32-x64": "0.24.0"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/escape-string-regexp": {
@@ -1241,28 +1427,32 @@
}
},
"node_modules/eslint": {
- "version": "9.11.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.0.tgz",
- "integrity": "sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==",
+ "version": "9.16.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
+ "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.11.0",
- "@eslint/config-array": "^0.18.0",
- "@eslint/eslintrc": "^3.1.0",
- "@eslint/js": "9.11.0",
- "@eslint/plugin-kit": "^0.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.19.0",
+ "@eslint/core": "^0.9.0",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "9.16.0",
+ "@eslint/plugin-kit": "^0.2.3",
+ "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.3.0",
- "@nodelib/fs.walk": "^1.2.8",
+ "@humanwhocodes/retry": "^0.4.1",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
- "cross-spawn": "^7.0.2",
+ "cross-spawn": "^7.0.5",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.0.2",
- "eslint-visitor-keys": "^4.0.0",
- "espree": "^10.1.0",
+ "eslint-scope": "^8.2.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -1272,14 +1462,11 @@
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
- "is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3",
- "strip-ansi": "^6.0.1",
- "text-table": "^0.2.0"
+ "optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
@@ -1327,22 +1514,23 @@
}
},
"node_modules/eslint-plugin-svelte": {
- "version": "2.44.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.44.0.tgz",
- "integrity": "sha512-wav4MOs02vBb1WjvTCYItwJCxMkuk2Z4p+K/eyjL0N/z7ahXLP+0LtQQjiKc2ezuif7GnZLbD1F3o1VHzSvdVg==",
+ "version": "2.46.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz",
+ "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@jridgewell/sourcemap-codec": "^1.4.15",
"eslint-compat-utils": "^0.5.1",
"esutils": "^2.0.3",
- "known-css-properties": "^0.34.0",
+ "known-css-properties": "^0.35.0",
"postcss": "^8.4.38",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.1.0",
"semver": "^7.6.2",
- "svelte-eslint-parser": "^0.41.1"
+ "svelte-eslint-parser": "^0.43.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
@@ -1352,7 +1540,7 @@
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0",
- "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"svelte": {
@@ -1361,10 +1549,11 @@
}
},
"node_modules/eslint-scope": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
- "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+ "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
@@ -1389,10 +1578,11 @@
}
},
"node_modules/eslint/node_modules/eslint-visitor-keys": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
- "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -1401,20 +1591,22 @@
}
},
"node_modules/esm-env": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
- "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==",
- "dev": true
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
+ "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/espree": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
- "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.12.0",
+ "acorn": "^8.14.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.0.0"
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1424,10 +1616,11 @@
}
},
"node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
- "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"dev": true,
+ "license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -1447,11 +1640,23 @@
"node": ">=0.10"
}
},
+ "node_modules/esrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.3.tgz",
+ "integrity": "sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@types/estree": "^1.0.1"
+ }
+ },
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -1468,15 +1673,6 @@
"node": ">=4.0"
}
},
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -1490,13 +1686,15 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
@@ -1504,15 +1702,6 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
- "node_modules/fastq": {
- "version": "1.17.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
- "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
- "dev": true,
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -1566,6 +1755,7 @@
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -1574,6 +1764,16 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -1591,6 +1791,7 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -1624,6 +1825,7 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">= 4"
}
@@ -1633,6 +1835,7 @@
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -1663,6 +1866,22 @@
"node": ">=0.8.19"
}
},
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -1672,6 +1891,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -1684,35 +1913,42 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-path-inside": {
+ "node_modules/is-reference": {
"version": "3.0.3",
- "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
- "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dev": true,
- "engines": {
- "node": ">=8"
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.6"
}
},
- "node_modules/is-reference": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
- "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@types/estree": "*"
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true
+ "dev": true,
+ "license": "ISC"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
@@ -1730,7 +1966,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -1757,10 +1994,11 @@
}
},
"node_modules/known-css-properties": {
- "version": "0.34.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz",
- "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==",
- "dev": true
+ "version": "0.35.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
+ "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/levn": {
"version": "0.4.1",
@@ -1812,25 +2050,21 @@
"dev": true
},
"node_modules/magic-string": {
- "version": "0.30.10",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
- "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
+ "version": "0.30.14",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
+ "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
+ "@jridgewell/sourcemap-codec": "^1.5.0"
}
},
- "node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
- "dev": true
- },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -1852,15 +2086,17 @@
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.7",
@@ -1886,6 +2122,24 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -1938,6 +2192,7 @@
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
@@ -1959,31 +2214,35 @@
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
}
},
- "node_modules/periscopic": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
- "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^3.0.0",
- "is-reference": "^3.0.0"
- }
+ "license": "ISC"
},
- "node_modules/picocolors": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
- "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
- "dev": true
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
},
"node_modules/postcss": {
- "version": "8.4.47",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
- "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [
{
@@ -1999,9 +2258,10 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.1.0",
+ "picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
@@ -2037,6 +2297,16 @@
}
}
},
+ "node_modules/postcss-load-config/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/postcss-safe-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
@@ -2072,6 +2342,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"engines": {
"node": ">=12.0"
},
@@ -2102,10 +2373,11 @@
}
},
"node_modules/prettier": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
- "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
+ "license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -2117,10 +2389,11 @@
}
},
"node_modules/prettier-plugin-svelte": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.6.tgz",
- "integrity": "sha512-Y1XWLw7vXUQQZmgv1JAEiLcErqUniAF2wO7QJsw8BVMvpLET2dI5WpEIEJx1r11iHVdSMzQxivyfrH9On9t2IQ==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.2.tgz",
+ "integrity": "sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
"prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
@@ -2131,56 +2404,39 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
},
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=4"
}
},
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true,
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
"node_modules/rollup": {
- "version": "4.22.4",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
- "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
+ "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@types/estree": "1.0.5"
+ "@types/estree": "1.0.6"
},
"bin": {
"rollup": "dist/bin/rollup"
@@ -2190,46 +2446,53 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.22.4",
- "@rollup/rollup-android-arm64": "4.22.4",
- "@rollup/rollup-darwin-arm64": "4.22.4",
- "@rollup/rollup-darwin-x64": "4.22.4",
- "@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
- "@rollup/rollup-linux-arm-musleabihf": "4.22.4",
- "@rollup/rollup-linux-arm64-gnu": "4.22.4",
- "@rollup/rollup-linux-arm64-musl": "4.22.4",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
- "@rollup/rollup-linux-riscv64-gnu": "4.22.4",
- "@rollup/rollup-linux-s390x-gnu": "4.22.4",
- "@rollup/rollup-linux-x64-gnu": "4.22.4",
- "@rollup/rollup-linux-x64-musl": "4.22.4",
- "@rollup/rollup-win32-arm64-msvc": "4.22.4",
- "@rollup/rollup-win32-ia32-msvc": "4.22.4",
- "@rollup/rollup-win32-x64-msvc": "4.22.4",
+ "@rollup/rollup-android-arm-eabi": "4.28.1",
+ "@rollup/rollup-android-arm64": "4.28.1",
+ "@rollup/rollup-darwin-arm64": "4.28.1",
+ "@rollup/rollup-darwin-x64": "4.28.1",
+ "@rollup/rollup-freebsd-arm64": "4.28.1",
+ "@rollup/rollup-freebsd-x64": "4.28.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.28.1",
+ "@rollup/rollup-linux-arm64-musl": "4.28.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-musl": "4.28.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.28.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.28.1",
+ "@rollup/rollup-win32-x64-msvc": "4.28.1",
"fsevents": "~2.3.2"
}
},
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "node_modules/rollup-plugin-visualizer": {
+ "version": "5.12.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz",
+ "integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "license": "MIT",
"dependencies": {
- "queue-microtask": "^1.2.2"
+ "open": "^8.4.0",
+ "picomatch": "^2.3.1",
+ "source-map": "^0.7.4",
+ "yargs": "^17.5.1"
+ },
+ "bin": {
+ "rollup-plugin-visualizer": "dist/bin/cli.js"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "rollup": "2.x || 3.x || 4.x"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
}
},
"node_modules/sade": {
@@ -2267,6 +2530,7 @@
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -2279,22 +2543,34 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/sirv": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
- "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
+ "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
"mrmime": "^2.0.0",
"totalist": "^3.0.0"
},
"engines": {
- "node": ">= 10"
+ "node": ">=18"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 8"
}
},
"node_modules/source-map-js": {
@@ -2306,11 +2582,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -2323,6 +2615,7 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
},
@@ -2343,35 +2636,36 @@
}
},
"node_modules/svelte": {
- "version": "4.2.19",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
- "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
- "dev": true,
- "dependencies": {
- "@ampproject/remapping": "^2.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/estree": "^1.0.1",
- "acorn": "^8.9.0",
- "aria-query": "^5.3.0",
- "axobject-query": "^4.0.0",
- "code-red": "^1.0.3",
- "css-tree": "^2.3.1",
- "estree-walker": "^3.0.3",
- "is-reference": "^3.0.1",
+ "version": "5.9.0",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.9.0.tgz",
+ "integrity": "sha512-ZcC3BtjIDa4yfhAyAr94MxDQLD97zbpXmaUldFv2F5AkdZwYgQYB3BZVNRU5zEVaeeHoAns8ADiRMnre3QmpxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@types/estree": "^1.0.5",
+ "acorn": "^8.12.1",
+ "acorn-typescript": "^1.4.13",
+ "aria-query": "^5.3.1",
+ "axobject-query": "^4.1.0",
+ "esm-env": "^1.2.1",
+ "esrap": "^1.2.3",
+ "is-reference": "^3.0.3",
"locate-character": "^3.0.0",
- "magic-string": "^0.30.4",
- "periscopic": "^3.1.0"
+ "magic-string": "^0.30.11",
+ "zimmerframe": "^1.1.2"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
}
},
"node_modules/svelte-eslint-parser": {
- "version": "0.41.1",
- "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.41.1.tgz",
- "integrity": "sha512-08ndI6zTghzI8SuJAFpvMbA/haPSGn3xz19pjre19yYMw8Nw/wQJ2PrZBI/L8ijGTgtkWCQQiLLy+Z1tfaCwNA==",
+ "version": "0.43.0",
+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
+ "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
@@ -2386,7 +2680,7 @@
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
- "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"svelte": {
@@ -2399,6 +2693,7 @@
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
@@ -2415,6 +2710,7 @@
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
@@ -2427,34 +2723,16 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/svelte-hmr": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
- "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
- "dev": true,
- "peer": true,
- "engines": {
- "node": "^12.20 || ^14.13.1 || >= 16"
- },
- "peerDependencies": {
- "svelte": "^3.19.0 || ^4.0.0"
- }
- },
"node_modules/svelte-modals": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/svelte-modals/-/svelte-modals-1.3.0.tgz",
- "integrity": "sha512-b1Ylnyv9O6b7VYeWGJVToaVU2lw7GtErVwiEdojyfnOuZcrhNlQ5eDqbTrL3xyKz8j2VTy/QiGUl1lm/6SnQ2A==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/svelte-modals/-/svelte-modals-2.0.0.tgz",
+ "integrity": "sha512-wBClXmScNStF/rG75ZP7hEiKXRJ5H+DhA2dkMPQd+FX2Hc1XTj1n5cZGC7uTYCYOVmQwrsDOlL5a2V5E6q3f+A==",
"dev": true,
+ "license": "MIT",
"peerDependencies": {
- "svelte": "^3.0.0 || ^4.0.0"
+ "svelte": ">=5"
}
},
- "node_modules/text-table": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
- "dev": true
- },
"node_modules/tiny-glob": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
@@ -2470,6 +2748,7 @@
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=6"
}
@@ -2491,6 +2770,7 @@
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
@@ -2508,20 +2788,21 @@
"dev": true
},
"node_modules/vite": {
- "version": "5.4.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
- "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz",
+ "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
+ "esbuild": "^0.24.0",
+ "postcss": "^8.4.49",
+ "rollup": "^4.23.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -2530,19 +2811,25 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
- "terser": "^5.4.0"
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
+ "jiti": {
+ "optional": true
+ },
"less": {
"optional": true
},
@@ -2563,6 +2850,12 @@
},
"terser": {
"optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
}
}
},
@@ -2573,13 +2866,17 @@
"dev": true
},
"node_modules/vitefu": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
- "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.4.tgz",
+ "integrity": "sha512-y6zEE3PQf6uu/Mt6DTJ9ih+kyJLr4XcSgHR2zUkM8SWDhuixEJxfJ6CZGMHh1Ec3vPLoEA0IHU5oWzVqw8ulow==",
"dev": true,
- "peer": true,
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*"
+ ],
"peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
},
"peerDependenciesMeta": {
"vite": {
@@ -2592,6 +2889,7 @@
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
@@ -2611,13 +2909,76 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
+ "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"dev": true,
+ "license": "ISC",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "yaml": "bin.mjs"
+ },
"engines": {
- "node": ">= 6"
+ "node": ">= 14"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
}
},
"node_modules/yocto-queue": {
@@ -2631,6 +2992,13 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zimmerframe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
+ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+ "dev": true,
+ "license": "MIT"
}
}
}
diff --git a/ui/package.json b/ui/package.json
index 3a30e6c5..f7fc92a2 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -10,19 +10,22 @@
"format": "prettier --write ."
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.2.5",
- "@sveltejs/adapter-static": "^3.0.5",
- "@sveltejs/kit": "^2.5.28",
+ "@eslint/compat": "^1.2.4",
+ "@sveltejs/adapter-auto": "^3.3.1",
+ "@sveltejs/adapter-static": "^3.0.6",
+ "@sveltejs/kit": "^2.9.0",
+ "@sveltejs/vite-plugin-svelte": "^5.0.1",
"dayjs": "^1.11.13",
- "eslint": "^9.11.0",
+ "eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
- "eslint-plugin-svelte": "^2.44.0",
- "prettier": "^3.3.3",
- "prettier-plugin-svelte": "^3.2.6",
- "svelte": "^4.2.19",
- "svelte-modals": "^1.3.0",
+ "eslint-plugin-svelte": "^2.46.1",
+ "prettier": "^3.4.2",
+ "prettier-plugin-svelte": "^3.2.7",
+ "rollup-plugin-visualizer": "^5.12.0",
+ "svelte": "^5.9.0",
+ "svelte-modals": "^2.0.0",
"urlpattern-polyfill": "^10.0.0",
- "vite": "^5.4.7",
+ "vite": "^6.0.3",
"vite-plugin-filter-replace": "^0.1.13"
},
"type": "module"
diff --git a/ui/src/Tabs.svelte b/ui/src/Tabs.svelte
new file mode 100644
index 00000000..268d7d35
--- /dev/null
+++ b/ui/src/Tabs.svelte
@@ -0,0 +1,59 @@
+
+
+
+ {#each items as item}
+
+ {item.label}
+
+ {/each}
+
+ {#each items as item}
+ {#if activeTabValue == item.value}
+
+
+
+ {/if}
+ {/each}
+
\ No newline at end of file
diff --git a/ui/src/Util.svelte b/ui/src/Util.svelte
index 136158cb..bb15deed 100644
--- a/ui/src/Util.svelte
+++ b/ui/src/Util.svelte
@@ -1,11 +1,17 @@
-
-
diff --git a/ui/src/modals/ModalStartRun.svelte b/ui/src/modals/ModalStartRun.svelte
index 8fd55693..97cc449a 100644
--- a/ui/src/modals/ModalStartRun.svelte
+++ b/ui/src/modals/ModalStartRun.svelte
@@ -1,45 +1,42 @@
-
-
-
-
- Node batch execution summary
-
-
-
-
-
- {@html svgSummary}
-
-
- {#if executionDetailsVisible}
-
-
-
- Elapsed:
- {nodeElapsed}s
- (not very useful, skewed by other nodes)
-
-
- Elapsed average:
- {elapsedAverage}s
-
-
- Elapsed median:
- {elapsedMedian}s
-
-
- Batches not started:
- {nonStartedBatchesPercent.toFixed(0)}%
- {nonStartedBatchesRatio}
- {nonStartedBatches}
-
-
- Batches started:
- {startedBatchesPercent.toFixed(0)}%
- {startedBatchesRatio}
- {startedBatches}
-
-
- Batches running:
- {runningBatchesPercent.toFixed(0)}%
- {runningBatchesRatio}
- {runningBatches}
-
-
- Batches finished:
- {finishedBatchesPercent.toFixed(0)}%
- {finishedBatchesRatio}
- {finishedBatches}
-
-
-
- {/if}
-
-
-
-
-
-
- Timestamp
- Batch
- Status
- Elapsed
- First token
- Last token
- Host/Instance
- Thread
- Comment
-
-
- {#each batch_history as e}
-
- {dayjs(e.ts).format('MMM D, YYYY HH:mm:ss.SSS Z')}
- {e.batch_idx} / {e.batches_total}
-
- {#if e.elapsed > 0}
- {e.elapsed}
- {/if}
- {e.first_token}
- {e.last_token}
- {e.instance}
- {e.thread}
- {e.comment}
-
- {/each}
-
-
-
-
diff --git a/ui/src/panels/Breadcrumbs.svelte b/ui/src/panels/Breadcrumbs.svelte
index 732aa075..02b99f72 100644
--- a/ui/src/panels/Breadcrumbs.svelte
+++ b/ui/src/panels/Breadcrumbs.svelte
@@ -1,5 +1,5 @@
@@ -9,13 +9,13 @@
alt=""
title="Capillaries-UI"
/>
- {#each pathElements as e, i}
+ {#each path_elements as e, i}
{#if !!e.link}
{e.title}
{:else}
{e.title}
{/if}
- {#if i < pathElements.length - 1}
+ {#if i < path_elements.length - 1}
>
{/if}
{/each}
diff --git a/ui/src/panels/NodeHistory.svelte b/ui/src/panels/NodeHistory.svelte
deleted file mode 100644
index 8250619d..00000000
--- a/ui/src/panels/NodeHistory.svelte
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
-
-
-{@html svgSummary}
-
-
- Timestamp
- Node
- Status
- Elapsed
- Comment
-
-
- {#each node_history as e}
-
- {dayjs(e.ts).format('MMM D, YYYY HH:mm:ss.SSS Z')}
- {e.script_node}
-
- {#if e.elapsed > 0}
- {e.elapsed}
- {/if}
- {e.comment}
-
- {/each}
-
-
-
-
diff --git a/ui/src/panels/RunInfo.svelte b/ui/src/panels/RunInfo.svelte
index 0a35bb95..8b392c84 100644
--- a/ui/src/panels/RunInfo.svelte
+++ b/ui/src/panels/RunInfo.svelte
@@ -1,17 +1,13 @@
-
-
{#if Object.keys(run_lifespan).length > 0}
-
- Run summary
-
Run Id:
@@ -74,13 +65,13 @@
Status
- {util.runStatusToText(run_lifespan.final_status)}
+ {runStatusToText(run_lifespan.final_status)}
{#if run_lifespan.final_status != 3}
{/if}
-
+
+
{#if Object.keys(run_props).length > 0}
@@ -100,12 +92,12 @@
{run_props.run_description}
- Script URI:
- {run_props.script_uri}
+ Script URL:
+ {run_props.script_url}
- Script parameters URI:
- {run_props.script_params_uri}
+ Script parameters URL:
+ {run_props.script_params_url}
Start nodes:
diff --git a/ui/src/routes/+page.svelte b/ui/src/routes/+page.svelte
index 119710e8..c3a2e7b5 100644
--- a/ui/src/routes/+page.svelte
+++ b/ui/src/routes/+page.svelte
@@ -4,11 +4,10 @@
import Keyspaces from './Keyspaces.svelte';
import KsRunNodeHistory from './KsRunNodeHistory.svelte';
import KsRunNodeBatchHistory from './KsRunNodeBatchHistory.svelte';
- import { Modals, closeModal } from 'svelte-modals';
+ import { Modals, modals } from 'svelte-modals';
import { URLPattern } from 'urlpattern-polyfill';
- let params;
- let routed_page;
+ let routed_page, hg;
const patternKeyspaces = new URLPattern({ hash: '#/' });
const patternKsMatrix = new URLPattern({ hash: '#/ks/:ks_name/matrix' });
const patternKsRunNodeBatchHistory = new URLPattern({
@@ -24,20 +23,16 @@
function reload() {
if (patternKsMatrix.test($page.url)) {
- let hg = patternKsMatrix.exec($page.url).hash.groups;
- params = { ks_name: hg.ks_name };
+ hg = patternKsMatrix.exec($page.url).hash.groups;
routed_page = KsMatrix;
} else if (patternKsRunNodeHistory.test($page.url)) {
- let hg = patternKsRunNodeHistory.exec($page.url).hash.groups;
- params = { ks_name: hg.ks_name, run_id: hg.run_id };
+ hg = patternKsRunNodeHistory.exec($page.url).hash.groups;
routed_page = KsRunNodeHistory;
} else if (patternKsRunNodeBatchHistory.test($page.url)) {
- let hg = patternKsRunNodeBatchHistory.exec($page.url).hash.groups;
- params = { ks_name: hg.ks_name, run_id: hg.run_id, node_name: hg.node_name };
+ hg = patternKsRunNodeBatchHistory.exec($page.url).hash.groups;
routed_page = KsRunNodeBatchHistory;
} else if (patternKeyspaces.test($page.url)) {
routed_page = Keyspaces;
- params = {};
} else {
let url = new URL($page.url);
// Navigate to Keyspaces
@@ -48,16 +43,24 @@
reload();
-
+
- false}
- aria-hidden="true"
- />
+
+ {#snippet backdrop()}
+
modals.close()}
+ onkeypress={() => false}
+ aria-hidden="true"
+ >
+ {/snippet}
diff --git a/ui/src/routes/KsRunNodeHistory.svelte b/ui/src/routes/KsRunNodeHistory.svelte
index acc2bf07..7573e649 100644
--- a/ui/src/routes/KsRunNodeHistory.svelte
+++ b/ui/src/routes/KsRunNodeHistory.svelte
@@ -1,26 +1,112 @@
-
-
+
{responseError}
-
-
+
+{#snippet tabRunInfo()}
+
+{/snippet}
+
+{#snippet tabDiagram()}
+
+ This diagram is dynamic and evolves as nodes are processed. Color legend:
+ node started ,
+ node completed successfully ,
+ node failed ,
+ stop signal received
+ node not processed as part of this run (maybe yet) .
+ Nodes that require manual start are marked with a thicker border.
+ To see a static copy of it in a separate window,
+ click here .
+ To see detailed script diagram not reflecting run status,
+ click here for black and white,
+ or here for colored by root node.
+
+
+
{svgError}
+
+
+
+ {@html svgStatusViz}
+
+{/snippet}
+
+{#snippet tabHistory()}
+
+
+
+ Timestamp
+ Node
+ Status
+ Elapsed
+ Comment
+
+
+
+ {#each webapiData.node_history as e}
+
+ {dayjs(e.ts).format('MMM D, YYYY HH:mm:ss.SSS Z')}
+ {e.script_node}
+
+ {#if e.elapsed > 0}
+ {e.elapsed}
+ {/if}
+ {e.comment}
+
+ {/each}
+
+
+{/snippet}
+
+
+
+
diff --git a/ui/vite.config.js b/ui/vite.config.js
index 03aafb05..670b8d35 100644
--- a/ui/vite.config.js
+++ b/ui/vite.config.js
@@ -1,5 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
+import { visualizer } from 'rollup-plugin-visualizer';
console.log('In .env file VITE_WEBAPI_URL=$CAPI_WEBAPI_URL:', process.env.CAPI_WEBAPI_URL);
export default defineConfig({
@@ -7,11 +8,20 @@ export default defineConfig({
port: 8080,
host: true // In Docker containers, "--host 0.0.0.0" may not work. So, tell Vite to bind to 0.0.0.0 here.
},
- plugins: [sveltekit()],
+ plugins: [
+ sveltekit(),
+ visualizer({
+ emitFile: true,
+ filename: 'stats.html'
+ })
+ ],
build: {
- minify: true
- },
- esbuild: {
- minifyIdentifiers: true
+ rollupOptions: {
+ output: {
+ manualChunks: (id) => {
+ return 'capillaries-ui';
+ }
+ }
+ }
}
});