@shortlink-org/portolan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// Command extract-flows reads flows written by hand, in a text form that reads
|
|
2
|
-
// like the sequence diagram it becomes, and answers with a catalog fragment.
|
|
3
|
-
//
|
|
4
|
-
// Some flows will always be written by people: the design doc for something
|
|
5
|
-
// not built yet, the reconstruction after an incident, the path a test does
|
|
6
|
-
// not pin. The catalog's JSON is the wrong place to write them - a tree of
|
|
7
|
-
// nodes, a unique id per step, every lane declared twice - so this is the
|
|
8
|
-
// right one: one file per flow, one line per hop, frames closed by `end`. The
|
|
9
|
-
// format is described in plugins/README.md and held to by parse_test.go.
|
|
10
|
-
package main
|
|
11
|
-
|
|
12
|
-
import (
|
|
13
|
-
"fmt"
|
|
14
|
-
"io"
|
|
15
|
-
"os"
|
|
16
|
-
|
|
17
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
// Options are what the manifest tells the extractor.
|
|
21
|
-
type Options struct {
|
|
22
|
-
// Files are the flow files to read, as globs relative to the input root.
|
|
23
|
-
// Left out, every `*.flow.md` directly under the root.
|
|
24
|
-
Files []string `json:"files,omitempty"`
|
|
25
|
-
|
|
26
|
-
// Out names the fragment file.
|
|
27
|
-
Out string `json:"out,omitempty"`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
func main() {
|
|
31
|
-
if err := run(os.Stdin, os.Stdout); err != nil {
|
|
32
|
-
fmt.Fprintln(os.Stderr, "portolan-extract-flows:", err)
|
|
33
|
-
os.Exit(1)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
func run(stdin io.Reader, stdout io.Writer) error {
|
|
38
|
-
return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
|
|
39
|
-
if req.Input.Root == "" {
|
|
40
|
-
return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return extract(req.Input, opts)
|
|
44
|
-
})
|
|
45
|
-
}
|
|
@@ -1,593 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"path"
|
|
5
|
-
"regexp"
|
|
6
|
-
"strconv"
|
|
7
|
-
"strings"
|
|
8
|
-
|
|
9
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
// The format, in one place.
|
|
13
|
-
//
|
|
14
|
-
// # Order accepted the name; the slug is the file's
|
|
15
|
-
// owner: shop the context that owns the flow
|
|
16
|
-
// source: services/oms/test/x_test.go where it was read from; the file itself when left out
|
|
17
|
-
// slug: order-accepted only when the file's name is not it
|
|
18
|
-
//
|
|
19
|
-
// One or more paragraphs of summary.
|
|
20
|
-
//
|
|
21
|
-
// ## Participants
|
|
22
|
-
// - oms-db: store in shop "oms-db (postgres)"
|
|
23
|
-
// - psp-gateway: external "psp-gateway (external)"
|
|
24
|
-
//
|
|
25
|
-
// ## Steps
|
|
26
|
-
// shop.oms -> oms-db: insertOrderAndOutboxRow [verified] @order_repo.go:141 #a1
|
|
27
|
-
// > A note, as many lines as it takes.
|
|
28
|
-
// shop.oms -> bus: event shop.oms.order.OrderPlaced [verified]
|
|
29
|
-
// shop.oms -> shop.pricing: rpc shop.v1.Pricing/GetQuote as "GetQuote (250 ms)"
|
|
30
|
-
// alt score below 40 #alt-risk
|
|
31
|
-
// ...
|
|
32
|
-
// else score at or above 40
|
|
33
|
-
// ...
|
|
34
|
-
// stop
|
|
35
|
-
// else
|
|
36
|
-
// end
|
|
37
|
-
// par OrderPlaced fan-out
|
|
38
|
-
// ...
|
|
39
|
-
// and
|
|
40
|
-
// ...
|
|
41
|
-
// end
|
|
42
|
-
// loop until the batch is empty
|
|
43
|
-
// ...
|
|
44
|
-
// end
|
|
45
|
-
//
|
|
46
|
-
// A hop is `from -> to: [call|rpc|event] label-or-ref`, `call` when no kind is
|
|
47
|
-
// written, and after it in any order: `as "label"`, `[status]`, `@file:line`,
|
|
48
|
-
// `#id`. An event's or rpc's label is the last segment of its ref unless `as`
|
|
49
|
-
// says otherwise. `stop` ends the alt branch it is in - the flow does not
|
|
50
|
-
// continue past the frame on that path. `else` with no condition is
|
|
51
|
-
// "otherwise". Services are known by their `context.service` id, `bus` and
|
|
52
|
-
// `client` by their names; anything else is declared under Participants, in
|
|
53
|
-
// the order the lanes should be drawn. Lines starting with `//` are comments.
|
|
54
|
-
|
|
55
|
-
type parser struct {
|
|
56
|
-
file string
|
|
57
|
-
lines []string
|
|
58
|
-
errs []string
|
|
59
|
-
|
|
60
|
-
flow catalog.Flow
|
|
61
|
-
declared []catalog.Participant
|
|
62
|
-
lanes map[string]catalog.Participant
|
|
63
|
-
used []catalog.Participant
|
|
64
|
-
ids map[string]bool
|
|
65
|
-
n int
|
|
66
|
-
stack []*frame
|
|
67
|
-
last *catalog.Step // for notes
|
|
68
|
-
stopped bool // the current alt branch has ended
|
|
69
|
-
hasSteps bool
|
|
70
|
-
sawTitle bool
|
|
71
|
-
sawOwner bool
|
|
72
|
-
sawSource bool
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// frame is an open alt, par or loop and the branch of it being read.
|
|
76
|
-
type frame struct {
|
|
77
|
-
kind string
|
|
78
|
-
id string
|
|
79
|
-
// title is the frame's own, for a par or a loop; current is the branch
|
|
80
|
-
// being read, for an alt, where every branch has a condition.
|
|
81
|
-
title string
|
|
82
|
-
current string
|
|
83
|
-
alt []catalog.AltBranch
|
|
84
|
-
par []catalog.FlowNodes
|
|
85
|
-
steps catalog.FlowNodes
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
var (
|
|
89
|
-
titleLine = regexp.MustCompile(`^#\s+(.+?)\s*$`)
|
|
90
|
-
metaLine = regexp.MustCompile(`^(owner|source|slug):\s*(.*?)\s*$`)
|
|
91
|
-
partLine = regexp.MustCompile(`^-\s+([^\s:]+):\s*(actor|service|broker|store|external|unknown)(?:\s+in\s+([^\s"]+))?(?:\s+"([^"]*)")?\s*$`)
|
|
92
|
-
stepLine = regexp.MustCompile(`^([^\s>]+)\s*->\s*([^\s:]+):\s*(.*)$`)
|
|
93
|
-
idSuffix = regexp.MustCompile(`\s+#(\S+)\s*$`)
|
|
94
|
-
kindPrefix = regexp.MustCompile(`^(call|rpc|event)\s+(.*)$`)
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
func parseFlow(file, src string) (catalog.Flow, []string) {
|
|
98
|
-
p := &parser{
|
|
99
|
-
file: file,
|
|
100
|
-
lines: strings.Split(strings.ReplaceAll(src, "\r\n", "\n"), "\n"),
|
|
101
|
-
lanes: map[string]catalog.Participant{},
|
|
102
|
-
ids: map[string]bool{},
|
|
103
|
-
}
|
|
104
|
-
p.read()
|
|
105
|
-
if len(p.errs) > 0 {
|
|
106
|
-
return catalog.Flow{}, p.errs
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return p.flow, nil
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
func (p *parser) fail(line int, msg string) {
|
|
113
|
-
p.errs = append(p.errs, p.file+":"+strconv.Itoa(line+1)+": "+msg)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
func (p *parser) read() {
|
|
117
|
-
section := "head"
|
|
118
|
-
var summary []string
|
|
119
|
-
var paragraph []string
|
|
120
|
-
flush := func() {
|
|
121
|
-
if len(paragraph) > 0 {
|
|
122
|
-
summary = append(summary, strings.Join(paragraph, " "))
|
|
123
|
-
paragraph = nil
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
for i, raw := range p.lines {
|
|
128
|
-
line := strings.TrimSpace(raw)
|
|
129
|
-
if strings.HasPrefix(line, "//") {
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
switch {
|
|
134
|
-
case strings.HasPrefix(line, "## "):
|
|
135
|
-
flush()
|
|
136
|
-
switch strings.ToLower(strings.TrimSpace(line[3:])) {
|
|
137
|
-
case "participants":
|
|
138
|
-
section = "participants"
|
|
139
|
-
case "steps":
|
|
140
|
-
section = "steps"
|
|
141
|
-
default:
|
|
142
|
-
p.fail(i, "unknown section "+strconv.Quote(line[3:])+"; the sections are Participants and Steps")
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
continue
|
|
146
|
-
case section == "head":
|
|
147
|
-
p.head(i, line, ¶graph, flush)
|
|
148
|
-
case section == "participants":
|
|
149
|
-
p.participant(i, line)
|
|
150
|
-
case section == "steps":
|
|
151
|
-
p.step(i, line)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
flush()
|
|
155
|
-
|
|
156
|
-
if !p.sawTitle {
|
|
157
|
-
p.fail(0, "no title: the first line names the flow, as `# Name`")
|
|
158
|
-
}
|
|
159
|
-
if !p.sawOwner {
|
|
160
|
-
p.fail(0, "no owner: say which context the flow belongs to, as `owner: <context>`")
|
|
161
|
-
}
|
|
162
|
-
if !p.hasSteps {
|
|
163
|
-
p.fail(0, "no steps: a flow is at least one hop under `## Steps`")
|
|
164
|
-
}
|
|
165
|
-
for _, f := range p.stack {
|
|
166
|
-
p.fail(len(p.lines)-1, f.kind+" is never closed; every frame ends with `end`")
|
|
167
|
-
}
|
|
168
|
-
if len(p.errs) > 0 {
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
p.flow.Summary = strings.Join(summary, "\n\n")
|
|
173
|
-
if p.flow.Slug == "" {
|
|
174
|
-
base := path.Base(p.file)
|
|
175
|
-
p.flow.Slug = strings.TrimSuffix(strings.TrimSuffix(base, ".md"), ".flow")
|
|
176
|
-
}
|
|
177
|
-
p.flow.ID = "flow." + p.flow.Slug
|
|
178
|
-
if !p.sawSource {
|
|
179
|
-
p.flow.Source = p.file
|
|
180
|
-
}
|
|
181
|
-
p.flow.Participants = append(append([]catalog.Participant{}, p.declared...), p.used...)
|
|
182
|
-
if p.flow.Participants == nil {
|
|
183
|
-
p.flow.Participants = []catalog.Participant{}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
func (p *parser) head(i int, line string, paragraph *[]string, flush func()) {
|
|
188
|
-
if line == "" {
|
|
189
|
-
flush()
|
|
190
|
-
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
if m := titleLine.FindStringSubmatch(line); m != nil && !p.sawTitle {
|
|
194
|
-
p.flow.Name = m[1]
|
|
195
|
-
p.sawTitle = true
|
|
196
|
-
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
if m := metaLine.FindStringSubmatch(line); m != nil && len(*paragraph) == 0 {
|
|
200
|
-
switch m[1] {
|
|
201
|
-
case "owner":
|
|
202
|
-
p.flow.Owner = m[2]
|
|
203
|
-
p.sawOwner = true
|
|
204
|
-
case "source":
|
|
205
|
-
p.flow.Source = m[2]
|
|
206
|
-
p.sawSource = true
|
|
207
|
-
case "slug":
|
|
208
|
-
p.flow.Slug = m[2]
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return
|
|
212
|
-
}
|
|
213
|
-
*paragraph = append(*paragraph, line)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
func (p *parser) participant(i int, line string) {
|
|
217
|
-
if line == "" {
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
m := partLine.FindStringSubmatch(line)
|
|
221
|
-
if m == nil {
|
|
222
|
-
p.fail(i, "not a participant: write `- <id>: <kind> [in <context>] [\"label\"]`, with kind one of actor, service, broker, store, external, unknown")
|
|
223
|
-
|
|
224
|
-
return
|
|
225
|
-
}
|
|
226
|
-
id, kind, context, label := m[1], m[2], m[3], m[4]
|
|
227
|
-
if _, dup := p.lanes[id]; dup {
|
|
228
|
-
p.fail(i, "participant "+id+" is declared twice")
|
|
229
|
-
|
|
230
|
-
return
|
|
231
|
-
}
|
|
232
|
-
lane := catalog.Participant{ID: id, Kind: catalog.ParticipantKind(kind), Label: label}
|
|
233
|
-
if kind == "service" && context == "" {
|
|
234
|
-
context, _, _ = strings.Cut(id, ".")
|
|
235
|
-
}
|
|
236
|
-
if context != "" {
|
|
237
|
-
lane.Context = &context
|
|
238
|
-
}
|
|
239
|
-
p.lanes[id] = lane
|
|
240
|
-
p.declared = append(p.declared, lane)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// lane resolves a name in a step to a participant: declared, or inferable -
|
|
244
|
-
// `bus` is the broker, `context.service` is a service.
|
|
245
|
-
func (p *parser) lane(i int, id string) (string, bool) {
|
|
246
|
-
if _, ok := p.lanes[id]; ok {
|
|
247
|
-
return id, true
|
|
248
|
-
}
|
|
249
|
-
var lane catalog.Participant
|
|
250
|
-
switch {
|
|
251
|
-
case id == "bus":
|
|
252
|
-
lane = catalog.Participant{ID: id, Kind: catalog.ParticipantBroker}
|
|
253
|
-
case id == "client":
|
|
254
|
-
lane = catalog.Participant{ID: id, Kind: catalog.ParticipantActor}
|
|
255
|
-
case strings.Count(id, ".") == 1 && !strings.HasPrefix(id, ".") && !strings.HasSuffix(id, "."):
|
|
256
|
-
context, _, _ := strings.Cut(id, ".")
|
|
257
|
-
lane = catalog.Participant{ID: id, Kind: catalog.ParticipantService, Context: &context}
|
|
258
|
-
default:
|
|
259
|
-
p.fail(i, "participant "+strconv.Quote(id)+" is not declared, and is neither a service (context.service) nor the bus")
|
|
260
|
-
|
|
261
|
-
return "", false
|
|
262
|
-
}
|
|
263
|
-
p.lanes[id] = lane
|
|
264
|
-
p.used = append(p.used, lane)
|
|
265
|
-
|
|
266
|
-
return id, true
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// sink is where the next node goes: the open frame's branch, or the flow.
|
|
270
|
-
func (p *parser) sink() *catalog.FlowNodes {
|
|
271
|
-
if len(p.stack) == 0 {
|
|
272
|
-
return &p.flow.Steps
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return &p.stack[len(p.stack)-1].steps
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
func (p *parser) nextID(prefix string, explicit string, i int) string {
|
|
279
|
-
id := explicit
|
|
280
|
-
if id == "" {
|
|
281
|
-
p.n++
|
|
282
|
-
id = prefix + strconv.Itoa(p.n)
|
|
283
|
-
}
|
|
284
|
-
if p.ids[id] {
|
|
285
|
-
p.fail(i, "id "+id+" is used twice")
|
|
286
|
-
}
|
|
287
|
-
p.ids[id] = true
|
|
288
|
-
|
|
289
|
-
return id
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
func (p *parser) step(i int, line string) {
|
|
293
|
-
if line == "" {
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// A note continues the step above it.
|
|
298
|
-
if strings.HasPrefix(line, ">") {
|
|
299
|
-
text := strings.TrimSpace(strings.TrimPrefix(line, ">"))
|
|
300
|
-
if p.last == nil {
|
|
301
|
-
p.fail(i, "a note (`> ...`) has to follow the step it is about")
|
|
302
|
-
|
|
303
|
-
return
|
|
304
|
-
}
|
|
305
|
-
if text == "" {
|
|
306
|
-
return
|
|
307
|
-
}
|
|
308
|
-
if p.last.Note == "" {
|
|
309
|
-
p.last.Note = text
|
|
310
|
-
} else {
|
|
311
|
-
p.last.Note += " " + text
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
word, rest, _ := strings.Cut(line, " ")
|
|
318
|
-
rest = strings.TrimSpace(rest)
|
|
319
|
-
switch word {
|
|
320
|
-
case "alt", "par", "loop":
|
|
321
|
-
title, id := splitID(rest)
|
|
322
|
-
if word == "loop" && title == "" {
|
|
323
|
-
p.fail(i, "a loop needs a title saying what it repeats until")
|
|
324
|
-
}
|
|
325
|
-
p.last = nil
|
|
326
|
-
p.stopped = false
|
|
327
|
-
p.stack = append(p.stack, &frame{kind: word, id: p.nextID(word, id, i), title: title, current: title})
|
|
328
|
-
|
|
329
|
-
return
|
|
330
|
-
case "else", "and":
|
|
331
|
-
f := p.open(i, word)
|
|
332
|
-
if f == nil {
|
|
333
|
-
return
|
|
334
|
-
}
|
|
335
|
-
p.branch(f)
|
|
336
|
-
f.current = rest
|
|
337
|
-
p.last = nil
|
|
338
|
-
p.stopped = false
|
|
339
|
-
|
|
340
|
-
return
|
|
341
|
-
case "stop":
|
|
342
|
-
f := p.open(i, "stop")
|
|
343
|
-
if f == nil {
|
|
344
|
-
return
|
|
345
|
-
}
|
|
346
|
-
p.stopped = true
|
|
347
|
-
p.last = nil
|
|
348
|
-
|
|
349
|
-
return
|
|
350
|
-
case "end":
|
|
351
|
-
if len(p.stack) == 0 {
|
|
352
|
-
p.fail(i, "`end` with no frame open")
|
|
353
|
-
|
|
354
|
-
return
|
|
355
|
-
}
|
|
356
|
-
f := p.stack[len(p.stack)-1]
|
|
357
|
-
p.branch(f)
|
|
358
|
-
p.stack = p.stack[:len(p.stack)-1]
|
|
359
|
-
p.close(i, f)
|
|
360
|
-
p.last = nil
|
|
361
|
-
p.stopped = false
|
|
362
|
-
|
|
363
|
-
return
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if p.stopped {
|
|
367
|
-
p.fail(i, "nothing runs after `stop`; it ends the branch")
|
|
368
|
-
|
|
369
|
-
return
|
|
370
|
-
}
|
|
371
|
-
m := stepLine.FindStringSubmatch(line)
|
|
372
|
-
if m == nil {
|
|
373
|
-
p.fail(i, "not a step: write `from -> to: [call|rpc|event] label`, or alt/else/par/and/loop/stop/end")
|
|
374
|
-
|
|
375
|
-
return
|
|
376
|
-
}
|
|
377
|
-
from, okFrom := p.lane(i, m[1])
|
|
378
|
-
to, okTo := p.lane(i, m[2])
|
|
379
|
-
if !okFrom || !okTo {
|
|
380
|
-
return
|
|
381
|
-
}
|
|
382
|
-
step := p.hop(i, m[3])
|
|
383
|
-
if step == nil {
|
|
384
|
-
return
|
|
385
|
-
}
|
|
386
|
-
step.From, step.To = from, to
|
|
387
|
-
*p.sink() = append(*p.sink(), step)
|
|
388
|
-
p.last = step
|
|
389
|
-
p.hasSteps = true
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// open is the innermost frame `else`, `and` or `stop` belongs to, checked to
|
|
393
|
-
// be the right kind.
|
|
394
|
-
func (p *parser) open(i int, word string) *frame {
|
|
395
|
-
if len(p.stack) == 0 {
|
|
396
|
-
p.fail(i, "`"+word+"` outside any frame")
|
|
397
|
-
|
|
398
|
-
return nil
|
|
399
|
-
}
|
|
400
|
-
f := p.stack[len(p.stack)-1]
|
|
401
|
-
want := map[string]string{"else": "alt", "and": "par", "stop": "alt"}[word]
|
|
402
|
-
if f.kind != want {
|
|
403
|
-
p.fail(i, "`"+word+"` inside a "+f.kind+"; it belongs to an "+want)
|
|
404
|
-
|
|
405
|
-
return nil
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return f
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// branch closes the branch being read and starts the next.
|
|
412
|
-
func (p *parser) branch(f *frame) {
|
|
413
|
-
steps := f.steps
|
|
414
|
-
if steps == nil {
|
|
415
|
-
steps = catalog.FlowNodes{}
|
|
416
|
-
}
|
|
417
|
-
switch f.kind {
|
|
418
|
-
case "alt":
|
|
419
|
-
title := f.current
|
|
420
|
-
if len(f.alt) > 0 && title == "" {
|
|
421
|
-
title = "otherwise"
|
|
422
|
-
}
|
|
423
|
-
f.alt = append(f.alt, catalog.AltBranch{Title: title, Steps: steps, Terminal: p.stopped})
|
|
424
|
-
case "par":
|
|
425
|
-
f.par = append(f.par, steps)
|
|
426
|
-
case "loop":
|
|
427
|
-
f.steps = steps
|
|
428
|
-
|
|
429
|
-
return
|
|
430
|
-
}
|
|
431
|
-
f.steps = nil
|
|
432
|
-
f.current = ""
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// close turns a finished frame into its node and appends it where it belongs.
|
|
436
|
-
func (p *parser) close(i int, f *frame) {
|
|
437
|
-
var node catalog.FlowNode
|
|
438
|
-
switch f.kind {
|
|
439
|
-
case "alt":
|
|
440
|
-
if len(f.alt) < 2 {
|
|
441
|
-
p.fail(i, "an alt states a choice and needs an `else`; a branch with nothing in it is fine")
|
|
442
|
-
}
|
|
443
|
-
if f.alt[0].Title == "" {
|
|
444
|
-
p.fail(i, "the first branch of an alt needs a condition: `alt <when>`")
|
|
445
|
-
}
|
|
446
|
-
all := len(f.alt) > 0
|
|
447
|
-
for _, b := range f.alt {
|
|
448
|
-
if !b.Terminal {
|
|
449
|
-
all = false
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if all {
|
|
453
|
-
p.fail(i, "every branch of this alt stops; the flow cannot end on every path of a choice about what comes next")
|
|
454
|
-
}
|
|
455
|
-
node = &catalog.Alt{Type: "alt", ID: f.id, Branches: f.alt}
|
|
456
|
-
case "par":
|
|
457
|
-
if len(f.par) < 2 {
|
|
458
|
-
p.fail(i, "a par runs branches side by side and needs an `and`")
|
|
459
|
-
}
|
|
460
|
-
node = &catalog.Parallel{Type: "parallel", ID: f.id, Title: f.title, Branches: f.par}
|
|
461
|
-
case "loop":
|
|
462
|
-
node = &catalog.Loop{Type: "loop", ID: f.id, Title: f.title, Steps: f.steps}
|
|
463
|
-
}
|
|
464
|
-
*p.sink() = append(*p.sink(), node)
|
|
465
|
-
p.hasSteps = p.hasSteps || f.kind == "loop"
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// hop reads what follows `from -> to:`.
|
|
469
|
-
func (p *parser) hop(i int, rest string) *catalog.Step {
|
|
470
|
-
// The label runs up to the first trailer: ` as "`, ` [`, ` @`, ` #`.
|
|
471
|
-
head, tail := rest, ""
|
|
472
|
-
cut := len(rest)
|
|
473
|
-
for _, marker := range []string{` as "`, ` [`, ` @`, ` #`} {
|
|
474
|
-
if at := strings.Index(rest, marker); at >= 0 && at < cut {
|
|
475
|
-
cut = at
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if cut < len(rest) {
|
|
479
|
-
head, tail = rest[:cut], rest[cut+1:]
|
|
480
|
-
}
|
|
481
|
-
head = strings.TrimSpace(head)
|
|
482
|
-
|
|
483
|
-
kind := catalog.StepCall
|
|
484
|
-
if m := kindPrefix.FindStringSubmatch(head); m != nil {
|
|
485
|
-
kind = catalog.StepKind(m[1])
|
|
486
|
-
head = strings.TrimSpace(m[2])
|
|
487
|
-
}
|
|
488
|
-
if head == "" {
|
|
489
|
-
p.fail(i, "a step needs a label, or a ref for an event or rpc")
|
|
490
|
-
|
|
491
|
-
return nil
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
step := &catalog.Step{Type: "step", Kind: kind, Status: catalog.StatusDeclared}
|
|
495
|
-
switch kind {
|
|
496
|
-
case catalog.StepEvent:
|
|
497
|
-
step.Ref = head
|
|
498
|
-
step.Label = head[strings.LastIndex(head, ".")+1:]
|
|
499
|
-
case catalog.StepRPC:
|
|
500
|
-
// A ref is `<package.Service>/<Method>`, one word with a dot before
|
|
501
|
-
// the slash. `POST /webhooks/psp/v2` is a label: a route a webhook
|
|
502
|
-
// arrives on, which no interface in the catalog declares.
|
|
503
|
-
if isCallID(head) {
|
|
504
|
-
step.Ref = head
|
|
505
|
-
step.Label = head[strings.LastIndex(head, "/")+1:]
|
|
506
|
-
} else {
|
|
507
|
-
step.Label = head
|
|
508
|
-
}
|
|
509
|
-
default:
|
|
510
|
-
step.Label = head
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Each trailer runs to the next marker, so a source location may have
|
|
514
|
-
// spaces in it: `@trace 9f2c1a../span 04` is where a step read from a
|
|
515
|
-
// trace was seen, and it is not a file:line.
|
|
516
|
-
var explicit string
|
|
517
|
-
for _, part := range trailers(tail) {
|
|
518
|
-
switch {
|
|
519
|
-
case strings.HasPrefix(part, "as \""):
|
|
520
|
-
label, after, closed := strings.Cut(part[4:], "\"")
|
|
521
|
-
if !closed || strings.TrimSpace(after) != "" {
|
|
522
|
-
p.fail(i, "cannot read "+strconv.Quote(part)+"; a label after `as` is closed by its quote and followed by nothing")
|
|
523
|
-
|
|
524
|
-
return nil
|
|
525
|
-
}
|
|
526
|
-
step.Label = label
|
|
527
|
-
case strings.HasPrefix(part, "["):
|
|
528
|
-
status, after, closed := strings.Cut(part[1:], "]")
|
|
529
|
-
if !closed || strings.TrimSpace(after) != "" {
|
|
530
|
-
p.fail(i, "cannot read "+strconv.Quote(part)+"; a status is one word in brackets and followed by nothing")
|
|
531
|
-
|
|
532
|
-
return nil
|
|
533
|
-
}
|
|
534
|
-
switch catalog.Status(status) {
|
|
535
|
-
case catalog.StatusVerified, catalog.StatusDeclared, catalog.StatusUnresolved:
|
|
536
|
-
step.Status = catalog.Status(status)
|
|
537
|
-
default:
|
|
538
|
-
p.fail(i, "status ["+status+"] is not one of verified, declared, unresolved")
|
|
539
|
-
}
|
|
540
|
-
case strings.HasPrefix(part, "@"):
|
|
541
|
-
step.Line = strings.TrimSpace(part[1:])
|
|
542
|
-
case strings.HasPrefix(part, "#"):
|
|
543
|
-
explicit = strings.TrimSpace(part[1:])
|
|
544
|
-
default:
|
|
545
|
-
p.fail(i, "cannot read "+strconv.Quote(part)+" after the label; what may follow is as \"label\", [status], @file:line and #id")
|
|
546
|
-
|
|
547
|
-
return nil
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
step.ID = p.nextID("s", explicit, i)
|
|
551
|
-
|
|
552
|
-
return step
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
func isCallID(s string) bool {
|
|
556
|
-
slash := strings.Index(s, "/")
|
|
557
|
-
|
|
558
|
-
return slash > 0 && !strings.ContainsAny(s, " \t") && strings.Contains(s[:slash], ".") && slash < len(s)-1
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// trailers splits what follows a label at its markers, keeping each marker
|
|
562
|
-
// on the piece it starts.
|
|
563
|
-
func trailers(tail string) []string {
|
|
564
|
-
tail = strings.TrimSpace(tail)
|
|
565
|
-
if tail == "" {
|
|
566
|
-
return nil
|
|
567
|
-
}
|
|
568
|
-
var out []string
|
|
569
|
-
for tail != "" {
|
|
570
|
-
cut := len(tail)
|
|
571
|
-
for _, marker := range []string{` as "`, ` [`, ` @`, ` #`} {
|
|
572
|
-
if at := strings.Index(tail, marker); at >= 0 && at < cut {
|
|
573
|
-
cut = at
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
out = append(out, strings.TrimSpace(tail[:cut]))
|
|
577
|
-
if cut == len(tail) {
|
|
578
|
-
break
|
|
579
|
-
}
|
|
580
|
-
tail = strings.TrimSpace(tail[cut:])
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
return out
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// splitID takes a trailing `#id` off a frame's header.
|
|
587
|
-
func splitID(rest string) (string, string) {
|
|
588
|
-
if m := idSuffix.FindStringSubmatch(" " + rest); m != nil {
|
|
589
|
-
return strings.TrimSpace(strings.TrimSuffix(rest, m[0][1:])), m[1]
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return strings.TrimSpace(rest), ""
|
|
593
|
-
}
|