@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,41 +0,0 @@
|
|
|
1
|
-
// Package main is portolan-extract-project: a repository component in, a
|
|
2
|
-
// neutral catalog fragment out. It deliberately makes no DDD assumptions.
|
|
3
|
-
package main
|
|
4
|
-
|
|
5
|
-
import (
|
|
6
|
-
"fmt"
|
|
7
|
-
"io"
|
|
8
|
-
"os"
|
|
9
|
-
|
|
10
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
type Options struct {
|
|
14
|
-
Group string `json:"group,omitempty"`
|
|
15
|
-
GroupName string `json:"groupName,omitempty"`
|
|
16
|
-
GroupSummary string `json:"groupSummary,omitempty"`
|
|
17
|
-
GroupKind string `json:"groupKind,omitempty"`
|
|
18
|
-
|
|
19
|
-
Component string `json:"component,omitempty"`
|
|
20
|
-
ComponentName string `json:"componentName,omitempty"`
|
|
21
|
-
ComponentKind string `json:"componentKind,omitempty"`
|
|
22
|
-
Technologies []string `json:"technologies,omitempty"`
|
|
23
|
-
Repo string `json:"repo,omitempty"`
|
|
24
|
-
Out string `json:"out,omitempty"`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
func main() {
|
|
28
|
-
if err := run(os.Stdin, os.Stdout); err != nil {
|
|
29
|
-
fmt.Fprintln(os.Stderr, "portolan-extract-project:", err)
|
|
30
|
-
os.Exit(1)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func run(stdin io.Reader, stdout io.Writer) error {
|
|
35
|
-
return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
|
|
36
|
-
if req.Input.Root == "" {
|
|
37
|
-
return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
|
|
38
|
-
}
|
|
39
|
-
return extract(req.Input, opts)
|
|
40
|
-
})
|
|
41
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// What a .proto file says, as it says it.
|
|
4
|
-
//
|
|
5
|
-
// Types are kept AS WRITTEN - `repeated Money`, `map<string, LineItem>`, the
|
|
6
|
-
// `optional` keyword, the author's declaration order. The catalog already
|
|
7
|
-
// takes this stance for schemas ("the db type as declared - uuid, timestamptz,
|
|
8
|
-
// jsonb - never normalised"), and a reader comparing a page against the file it
|
|
9
|
-
// came from should recognise what they are looking at.
|
|
10
|
-
|
|
11
|
-
// File is one parsed .proto.
|
|
12
|
-
type File struct {
|
|
13
|
-
// Path is the file as a reader would type it, relative to the repo root.
|
|
14
|
-
Path string
|
|
15
|
-
|
|
16
|
-
// Doc is the comment at the very top of the file, above `syntax`.
|
|
17
|
-
//
|
|
18
|
-
// On a vendored copy this is where the header lives that says which module
|
|
19
|
-
// it came from - docs/adr/org.0001.md requires one - so it is worth keeping
|
|
20
|
-
// apart from the documentation of any declaration inside.
|
|
21
|
-
Doc string
|
|
22
|
-
|
|
23
|
-
Syntax string // "proto2", "proto3", "editions", or "" when unstated
|
|
24
|
-
Package string
|
|
25
|
-
Imports []Import
|
|
26
|
-
Options []Option
|
|
27
|
-
Messages []*Message
|
|
28
|
-
Enums []*Enum
|
|
29
|
-
Services []*Service
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type Import struct {
|
|
33
|
-
Path string
|
|
34
|
-
Public bool
|
|
35
|
-
Weak bool
|
|
36
|
-
Line int
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type Option struct {
|
|
40
|
-
Name string
|
|
41
|
-
Value string
|
|
42
|
-
Line int
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type Message struct {
|
|
46
|
-
Name string
|
|
47
|
-
Doc string
|
|
48
|
-
Line int
|
|
49
|
-
|
|
50
|
-
// Fields holds every field of the message, including the members of its
|
|
51
|
-
// oneofs, in declaration order. A oneof is a constraint on which of them may
|
|
52
|
-
// be set, not a separate place a field lives, so a reader asking "what is in
|
|
53
|
-
// this message" gets one list.
|
|
54
|
-
Fields []*Field
|
|
55
|
-
Oneofs []*Oneof
|
|
56
|
-
Messages []*Message // nested
|
|
57
|
-
Enums []*Enum
|
|
58
|
-
Reserved []string
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type Field struct {
|
|
62
|
-
Name string
|
|
63
|
-
Doc string
|
|
64
|
-
Line int
|
|
65
|
-
|
|
66
|
-
// Type as written: "string", "Money", "shop.v1.Money", "map<string, Money>".
|
|
67
|
-
Type string
|
|
68
|
-
Label string // "", "repeated", "optional", "required"
|
|
69
|
-
Number int
|
|
70
|
-
|
|
71
|
-
// Oneof names the oneof this field belongs to, empty when it is not in one.
|
|
72
|
-
Oneof string
|
|
73
|
-
Deprecated bool
|
|
74
|
-
Default string
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
type Oneof struct {
|
|
78
|
-
Name string
|
|
79
|
-
Doc string
|
|
80
|
-
Line int
|
|
81
|
-
Fields []*Field
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
type Enum struct {
|
|
85
|
-
Name string
|
|
86
|
-
Doc string
|
|
87
|
-
Line int
|
|
88
|
-
Values []*EnumValue
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
type EnumValue struct {
|
|
92
|
-
Name string
|
|
93
|
-
Doc string
|
|
94
|
-
Number int
|
|
95
|
-
Line int
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
type Service struct {
|
|
99
|
-
Name string
|
|
100
|
-
Doc string
|
|
101
|
-
Line int
|
|
102
|
-
Methods []*Method
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
type Method struct {
|
|
106
|
-
Name string
|
|
107
|
-
Doc string
|
|
108
|
-
Line int
|
|
109
|
-
|
|
110
|
-
Request string
|
|
111
|
-
Response string
|
|
112
|
-
ClientStreaming bool
|
|
113
|
-
ServerStreaming bool
|
|
114
|
-
Deprecated bool
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Note is something the parser declined to model, named rather than dropped.
|
|
118
|
-
//
|
|
119
|
-
// A construct the parser skips silently is a construct nobody knows is missing.
|
|
120
|
-
// Every one of these becomes a plugin diagnostic, so an estate using `extend`
|
|
121
|
-
// or a proto2 `group` finds out that portolan read past it.
|
|
122
|
-
type Note struct {
|
|
123
|
-
Line int
|
|
124
|
-
Message string
|
|
125
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// What a service CALLS, read from the client copies it vendors.
|
|
4
|
-
//
|
|
5
|
-
// docs/adr/org.0001.md puts a narrowed copy of the producer's proto in the
|
|
6
|
-
// consumer's infrastructure layer, with a header comment saying where it came
|
|
7
|
-
// from. That copy is a statement about what this service intends to call, and
|
|
8
|
-
// reading it is how a call edge gets into the catalog from the calling end.
|
|
9
|
-
|
|
10
|
-
import (
|
|
11
|
-
"sort"
|
|
12
|
-
|
|
13
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
14
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
// calls turns every method in the vendored files into a catalog call.
|
|
18
|
-
//
|
|
19
|
-
// STATUS IS ALWAYS "declared", NEVER "verified". Three reasons, and the test
|
|
20
|
-
// that holds this is deliberate:
|
|
21
|
-
//
|
|
22
|
-
// 1. `problems()` treats status as authored, not derived.
|
|
23
|
-
// 2. Verification is a property of the UNION, and every extractor runs before
|
|
24
|
-
// one exists - `plugin.Request.Catalog` is the zero value for an extractor.
|
|
25
|
-
// 3. In the shipped catalog `verified` already means something a proto cannot
|
|
26
|
-
// supply: it sits next to a note naming the integration test that covers
|
|
27
|
-
// the call end to end.
|
|
28
|
-
//
|
|
29
|
-
// Reading a .proto proves the call was WRITTEN DOWN, which is what `declared`
|
|
30
|
-
// means. Whether it is exercised is not a question this file can answer.
|
|
31
|
-
func calls(files []*File, peers map[string]string, moduleID string, b *plugin.Builder) []catalog.RpcCall {
|
|
32
|
-
var out []catalog.RpcCall
|
|
33
|
-
|
|
34
|
-
for _, file := range files {
|
|
35
|
-
peer, named := peers[file.Package]
|
|
36
|
-
if !named {
|
|
37
|
-
// The raw package name, which is exactly what `RpcCall.peer`
|
|
38
|
-
// documents: "service id if resolved, else raw name". Guessing a
|
|
39
|
-
// service id from a proto package would be inventing an edge.
|
|
40
|
-
peer = file.Package
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for _, svc := range file.Services {
|
|
44
|
-
iface := interfaceID(file.Package, svc.Name)
|
|
45
|
-
if !named {
|
|
46
|
-
b.Warn(iface, "no peer is mapped for proto package "+file.Package+
|
|
47
|
-
"; the call is listed against the package name")
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
for _, m := range svc.Methods {
|
|
51
|
-
out = append(out, catalog.RpcCall{
|
|
52
|
-
ID: callID(iface, m.Name),
|
|
53
|
-
Peer: peer,
|
|
54
|
-
Status: catalog.StatusDeclared,
|
|
55
|
-
Source: sourceRef(file.Path, m.Line),
|
|
56
|
-
Note: file.headerNote(),
|
|
57
|
-
Module: moduleID,
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
|
|
64
|
-
|
|
65
|
-
return out
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// headerNote is the comment at the top of a vendored copy.
|
|
69
|
-
//
|
|
70
|
-
// The ADR already requires that header - "the header comment on every vendored
|
|
71
|
-
// copy says so" - so surfacing it costs nothing and makes the boundary
|
|
72
|
-
// self-documenting on the page rather than only in the file. It sits above
|
|
73
|
-
// `syntax` rather than above a declaration, which is why the parser keeps it
|
|
74
|
-
// on the file instead of on whatever happens to come first.
|
|
75
|
-
func (f *File) headerNote() string {
|
|
76
|
-
return f.Doc
|
|
77
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
_ "embed"
|
|
5
|
-
|
|
6
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
//go:embed options.schema.json
|
|
10
|
-
var optionsSchema []byte
|
|
11
|
-
|
|
12
|
-
func descriptor() plugin.Descriptor {
|
|
13
|
-
return plugin.Descriptor{
|
|
14
|
-
Name: "extract-proto",
|
|
15
|
-
Summary: "Reads .proto files and answers with what a service provides from the modules it publishes and what it calls from the copies it vendors.",
|
|
16
|
-
Phases: []string{plugin.PhaseExtract},
|
|
17
|
-
Options: optionsSchema,
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// Protos in, a catalog fragment out.
|
|
4
|
-
//
|
|
5
|
-
// This extractor describes ONE aspect of a service - what it answers and what
|
|
6
|
-
// it calls - and nothing else. The aggregates come from a different extractor
|
|
7
|
-
// reading a different part of the tree, and the two meet in the merge. Neither
|
|
8
|
-
// knows the other exists, which is the whole reason each of them stays small.
|
|
9
|
-
//
|
|
10
|
-
// It reads no clock, opens no socket and looks at no environment. Fetching a
|
|
11
|
-
// module from a registry is the other plugin's job, and keeping the two apart
|
|
12
|
-
// is what lets this one be replayed byte-for-byte from a checkout.
|
|
13
|
-
//
|
|
14
|
-
// A note on proto event schemas: a message named `OrderPlaced` in an
|
|
15
|
-
// `events.proto` is the WIRE SCHEMA OF a domain event, not the event. It stays
|
|
16
|
-
// an RpcMessage rather than becoming a catalog Event, because `Event.id` is
|
|
17
|
-
// `<service>.<aggregate>.<Name>` and this extractor knows the package, not the
|
|
18
|
-
// aggregate. A guess would either collide with the event extract-go already
|
|
19
|
-
// emits from the domain package, or create a ghost under an invented aggregate
|
|
20
|
-
// - and since aggregates merge whole-object by id, that ghost would sit beside
|
|
21
|
-
// the real one forever.
|
|
22
|
-
|
|
23
|
-
import (
|
|
24
|
-
"encoding/json"
|
|
25
|
-
"fmt"
|
|
26
|
-
"os"
|
|
27
|
-
"path/filepath"
|
|
28
|
-
"sort"
|
|
29
|
-
"strings"
|
|
30
|
-
|
|
31
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
32
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
func extract(in plugin.Input, opts Options) (plugin.Response, error) {
|
|
36
|
-
b := &plugin.Builder{}
|
|
37
|
-
root := in.Root
|
|
38
|
-
|
|
39
|
-
if opts.Context == "" {
|
|
40
|
-
opts.Context = filepath.Base(root)
|
|
41
|
-
}
|
|
42
|
-
if opts.Service == "" {
|
|
43
|
-
opts.Service = filepath.Base(root)
|
|
44
|
-
}
|
|
45
|
-
serviceID := opts.Context + "." + opts.Service
|
|
46
|
-
|
|
47
|
-
owned, err := readDirs(root, opts.Paths, b)
|
|
48
|
-
if err != nil {
|
|
49
|
-
return plugin.Response{}, err
|
|
50
|
-
}
|
|
51
|
-
vendored, err := readDirs(root, opts.Vendored, b)
|
|
52
|
-
if err != nil {
|
|
53
|
-
return plugin.Response{}, err
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if len(owned) == 0 && len(vendored) == 0 {
|
|
57
|
-
b.Warn(serviceID, "no .proto files were found under "+root)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// One index over everything read. A vendored copy may well refer to a type
|
|
61
|
-
// the owned protos declare, and refusing to look would report an unresolved
|
|
62
|
-
// name the tree can plainly answer.
|
|
63
|
-
all := append(append([]*File{}, filesOf(owned)...), filesOf(vendored)...)
|
|
64
|
-
ix := NewIndex(all)
|
|
65
|
-
|
|
66
|
-
sh := newShared(opts.Defs != "off")
|
|
67
|
-
|
|
68
|
-
var provides []catalog.RpcService
|
|
69
|
-
var consumes []catalog.RpcCall
|
|
70
|
-
var modules []catalog.ProtoModule
|
|
71
|
-
moduleIDs := map[string]bool{}
|
|
72
|
-
|
|
73
|
-
for _, dir := range owned {
|
|
74
|
-
module := describe(root, dir, opts, b)
|
|
75
|
-
if module.ID != "" && !moduleIDs[module.ID] {
|
|
76
|
-
// Owned: this service publishes it, so the estate knows the owner.
|
|
77
|
-
module.Owner = serviceID
|
|
78
|
-
modules = append(modules, module)
|
|
79
|
-
moduleIDs[module.ID] = true
|
|
80
|
-
}
|
|
81
|
-
provides = append(provides, interfaces(ix, dir.files, module.ID, sh, b)...)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for _, dir := range vendored {
|
|
85
|
-
module := describe(root, dir, opts, b)
|
|
86
|
-
if module.ID != "" && !moduleIDs[module.ID] {
|
|
87
|
-
// Vendored: somebody else publishes it. Owner is left empty rather
|
|
88
|
-
// than guessed - a module whose publisher is outside the estate is
|
|
89
|
-
// the ordinary case, not a defect.
|
|
90
|
-
modules = append(modules, module)
|
|
91
|
-
moduleIDs[module.ID] = true
|
|
92
|
-
}
|
|
93
|
-
consumes = append(consumes, calls(dir.files, opts.Peers, module.ID, b)...)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
sort.Slice(modules, func(i, j int) bool { return modules[i].ID < modules[j].ID })
|
|
97
|
-
|
|
98
|
-
service := catalog.Service{
|
|
99
|
-
ID: serviceID,
|
|
100
|
-
Slug: opts.Service,
|
|
101
|
-
// Named by whichever source knows the name. This one describes what the
|
|
102
|
-
// service answers and calls, not what it is called, and a fragment that
|
|
103
|
-
// filled these in from a proto package would be inventing.
|
|
104
|
-
Provides: nonNilProvides(provides),
|
|
105
|
-
Consumes: nonNilCalls(consumes),
|
|
106
|
-
Aggregates: []catalog.Aggregate{},
|
|
107
|
-
Modules: sortedCopy(keysOf(moduleIDs)),
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
fragment := catalog.Catalog{
|
|
111
|
-
GeneratedAt: in.GeneratedAt,
|
|
112
|
-
Commit: in.Commit,
|
|
113
|
-
Contexts: []catalog.BoundedContext{{
|
|
114
|
-
ID: opts.Context,
|
|
115
|
-
Slug: opts.Context,
|
|
116
|
-
Services: []catalog.Service{service},
|
|
117
|
-
}},
|
|
118
|
-
Defs: sh.defs,
|
|
119
|
-
Flows: []catalog.Flow{},
|
|
120
|
-
Adrs: []catalog.Adr{},
|
|
121
|
-
Modules: modules,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
encoded, err := json.MarshalIndent(fragment, "", " ")
|
|
125
|
-
if err != nil {
|
|
126
|
-
return plugin.Response{}, err
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
b.File(firstNonEmpty(opts.Out, "proto.json"), string(encoded)+"\n")
|
|
130
|
-
|
|
131
|
-
return b.Response(), nil
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// dir is one directory of protos and everything parsed out of it.
|
|
135
|
-
type dir struct {
|
|
136
|
-
path string
|
|
137
|
-
files []*File
|
|
138
|
-
lock *lockFile
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
func filesOf(dirs []dir) []*File {
|
|
142
|
-
var out []*File
|
|
143
|
-
for _, d := range dirs {
|
|
144
|
-
out = append(out, d.files...)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return out
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// readDirs walks each directory for .proto files and parses them.
|
|
151
|
-
//
|
|
152
|
-
// Files are visited in sorted order and parsed in that order, because the
|
|
153
|
-
// fragment is committed and compared by `gen:check`: a filesystem walk that
|
|
154
|
-
// came back differently would rewrite the file for no reason.
|
|
155
|
-
func readDirs(root string, paths []string, b *plugin.Builder) ([]dir, error) {
|
|
156
|
-
var out []dir
|
|
157
|
-
|
|
158
|
-
for _, rel := range paths {
|
|
159
|
-
at := filepath.Join(root, filepath.FromSlash(rel))
|
|
160
|
-
info, err := os.Stat(at)
|
|
161
|
-
if os.IsNotExist(err) {
|
|
162
|
-
b.Warn(rel, "no such directory: "+filepath.ToSlash(at))
|
|
163
|
-
|
|
164
|
-
continue
|
|
165
|
-
}
|
|
166
|
-
if err != nil {
|
|
167
|
-
return nil, err
|
|
168
|
-
}
|
|
169
|
-
if !info.IsDir() {
|
|
170
|
-
return nil, fmt.Errorf("%s is not a directory", filepath.ToSlash(at))
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
var found []string
|
|
174
|
-
err = filepath.Walk(at, func(path string, info os.FileInfo, err error) error {
|
|
175
|
-
if err != nil {
|
|
176
|
-
return err
|
|
177
|
-
}
|
|
178
|
-
if !info.IsDir() && strings.HasSuffix(path, ".proto") {
|
|
179
|
-
found = append(found, path)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return nil
|
|
183
|
-
})
|
|
184
|
-
if err != nil {
|
|
185
|
-
return nil, err
|
|
186
|
-
}
|
|
187
|
-
sort.Strings(found)
|
|
188
|
-
|
|
189
|
-
d := dir{path: trimSlash(at)}
|
|
190
|
-
for _, path := range found {
|
|
191
|
-
src, err := os.ReadFile(path)
|
|
192
|
-
if err != nil {
|
|
193
|
-
return nil, err
|
|
194
|
-
}
|
|
195
|
-
file, notes, err := Parse(filepath.ToSlash(path), string(src))
|
|
196
|
-
if err != nil {
|
|
197
|
-
// The file could not be tokenised. Past that the parser is not
|
|
198
|
-
// reading proto any more, so it is reported and skipped rather
|
|
199
|
-
// than half-described.
|
|
200
|
-
b.Warn(filepath.ToSlash(path), err.Error())
|
|
201
|
-
|
|
202
|
-
continue
|
|
203
|
-
}
|
|
204
|
-
for _, note := range notes {
|
|
205
|
-
b.Warn(fmt.Sprintf("%s:%d", filepath.ToSlash(path), note.Line), note.Message)
|
|
206
|
-
}
|
|
207
|
-
d.files = append(d.files, file)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
lock, err := readLock(at)
|
|
211
|
-
if err != nil {
|
|
212
|
-
b.Warn(rel, "the lock beside these protos could not be read: "+err.Error())
|
|
213
|
-
}
|
|
214
|
-
d.lock = lock
|
|
215
|
-
|
|
216
|
-
out = append(out, d)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return out, nil
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// describe names the module a directory of protos belongs to.
|
|
223
|
-
//
|
|
224
|
-
// A lock written by fetch-bsr wins, because it is the only thing that knows the
|
|
225
|
-
// commit. Failing that the manifest may say, and failing that the directory
|
|
226
|
-
// gets a `local:` id so nobody mistakes the path for something fetchable.
|
|
227
|
-
func describe(root string, d dir, opts Options, b *plugin.Builder) catalog.ProtoModule {
|
|
228
|
-
rel := relativeTo(root, d.path)
|
|
229
|
-
|
|
230
|
-
id := ""
|
|
231
|
-
var lock *lockModule
|
|
232
|
-
if d.lock != nil && len(d.lock.Modules) > 0 {
|
|
233
|
-
lock = &d.lock.Modules[0]
|
|
234
|
-
id = lock.Module
|
|
235
|
-
if len(d.lock.Modules) > 1 {
|
|
236
|
-
b.Warn(rel, "the lock names more than one module for this directory; the first is used")
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if id == "" {
|
|
240
|
-
id = opts.Modules[rel]
|
|
241
|
-
}
|
|
242
|
-
if id == "" {
|
|
243
|
-
id = localModuleID(relativeTo(root, d.path))
|
|
244
|
-
b.Warn(rel, "these protos are not pinned to a published module; listed as "+id)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
module := moduleFor(id, d.path, d.files)
|
|
248
|
-
module.Source = filepath.ToSlash(d.path)
|
|
249
|
-
if lock != nil {
|
|
250
|
-
module.Commit = lock.Commit
|
|
251
|
-
module.Digest = lock.Digest
|
|
252
|
-
module.Deps = sortedCopy(lock.Deps)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return module
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
func keysOf(set map[string]bool) []string {
|
|
259
|
-
out := make([]string, 0, len(set))
|
|
260
|
-
for k := range set {
|
|
261
|
-
out = append(out, k)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return out
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// A fragment states what it knows and nothing else, but a list the schema calls
|
|
268
|
-
// required has to be present even when it is empty.
|
|
269
|
-
func nonNilProvides(in []catalog.RpcService) []catalog.RpcService {
|
|
270
|
-
if in == nil {
|
|
271
|
-
return []catalog.RpcService{}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return in
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
func nonNilCalls(in []catalog.RpcCall) []catalog.RpcCall {
|
|
278
|
-
if in == nil {
|
|
279
|
-
return []catalog.RpcCall{}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return in
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
func firstNonEmpty(values ...string) string {
|
|
286
|
-
for _, v := range values {
|
|
287
|
-
if v != "" {
|
|
288
|
-
return v
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return ""
|
|
293
|
-
}
|