@shortlink-org/portolan 0.1.0 → 0.2.1
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 +27 -10
- package/scripts/local-api.test.mjs +85 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +24 -3
- 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 +17 -9
- 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,195 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// The closed sets an aggregate's fields take values from.
|
|
4
|
-
//
|
|
5
|
-
// Go has no enum. What it has is a convention, and the convention is what is
|
|
6
|
-
// read: a named type over a basic one - `type Reason string` - and a const
|
|
7
|
-
// block whose constants are of that type. The set is the constants, in the
|
|
8
|
-
// order they are written; a value's name is the constant's literal, because
|
|
9
|
-
// that is what a consumer sees on the wire, and the constant's own name only
|
|
10
|
-
// when the literal is not a string - an iota.
|
|
11
|
-
//
|
|
12
|
-
// Looked for in the aggregate's package, under vo/, and under event/: a
|
|
13
|
-
// reason is declared beside the event that carries it, and a status beside
|
|
14
|
-
// the root that holds it.
|
|
15
|
-
|
|
16
|
-
import (
|
|
17
|
-
"go/ast"
|
|
18
|
-
"go/token"
|
|
19
|
-
"path"
|
|
20
|
-
"strings"
|
|
21
|
-
|
|
22
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
23
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
func extractEnums(root, domainPath, aggID string, main *pkg, b *plugin.Builder) []catalog.Enum {
|
|
27
|
-
var out []catalog.Enum
|
|
28
|
-
|
|
29
|
-
out = append(out, enumsIn(main, aggID, "")...)
|
|
30
|
-
|
|
31
|
-
for _, name := range subdirs(root, path.Join(domainPath, "vo")) {
|
|
32
|
-
if name == "rules" {
|
|
33
|
-
continue
|
|
34
|
-
}
|
|
35
|
-
p, err := parsePkg(root, path.Join(domainPath, "vo", name))
|
|
36
|
-
if err != nil {
|
|
37
|
-
continue
|
|
38
|
-
}
|
|
39
|
-
// Qualified the way a value object is, for the same reason: the
|
|
40
|
-
// field that holds it says `password.Policy`, not `Policy`.
|
|
41
|
-
out = append(out, enumsIn(p, aggID, p.name)...)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if p, err := parsePkg(root, path.Join(domainPath, "event")); err == nil {
|
|
45
|
-
out = append(out, enumsIn(p, aggID, "")...)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
_ = b
|
|
49
|
-
|
|
50
|
-
return out
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// enumDecl is a named basic type and the constants declared of it.
|
|
54
|
-
type enumDecl struct {
|
|
55
|
-
name string
|
|
56
|
-
doc string
|
|
57
|
-
values []catalog.EnumValue
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
func enumsIn(p *pkg, aggID, qualifier string) []catalog.Enum {
|
|
61
|
-
var out []catalog.Enum
|
|
62
|
-
|
|
63
|
-
for _, decl := range p.enums() {
|
|
64
|
-
label := decl.name
|
|
65
|
-
if qualifier != "" {
|
|
66
|
-
label = qualified(qualifier, decl.name)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
out = append(out, catalog.Enum{
|
|
70
|
-
ID: blockID(aggID, slug(label)),
|
|
71
|
-
Slug: slug(label),
|
|
72
|
-
Name: label,
|
|
73
|
-
Doc: firstSentenceOrAll(decl.doc),
|
|
74
|
-
Deprecated: deprecatedByDoc(decl.doc),
|
|
75
|
-
Values: decl.values,
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return out
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// enums pairs every exported `type X <basic>` with the constants of type X,
|
|
83
|
-
// and keeps the pairs that have any. A type nothing is declared of is not a
|
|
84
|
-
// set; it is a name for a string.
|
|
85
|
-
func (p *pkg) enums() []enumDecl {
|
|
86
|
-
var out []enumDecl
|
|
87
|
-
byName := map[string]int{}
|
|
88
|
-
|
|
89
|
-
for _, file := range p.files {
|
|
90
|
-
for _, decl := range file.Decls {
|
|
91
|
-
gen, ok := decl.(*ast.GenDecl)
|
|
92
|
-
if !ok || gen.Tok != token.TYPE {
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
for _, spec := range gen.Specs {
|
|
96
|
-
typeSpec, ok := spec.(*ast.TypeSpec)
|
|
97
|
-
if !ok || !exported(typeSpec.Name.Name) || !isBasic(typeSpec.Type) {
|
|
98
|
-
continue
|
|
99
|
-
}
|
|
100
|
-
comment := typeSpec.Doc
|
|
101
|
-
if comment == nil && len(gen.Specs) == 1 {
|
|
102
|
-
comment = gen.Doc
|
|
103
|
-
}
|
|
104
|
-
byName[typeSpec.Name.Name] = len(out)
|
|
105
|
-
out = append(out, enumDecl{name: typeSpec.Name.Name, doc: comment.Text()})
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
for _, file := range p.files {
|
|
111
|
-
for _, decl := range file.Decls {
|
|
112
|
-
gen, ok := decl.(*ast.GenDecl)
|
|
113
|
-
if !ok || gen.Tok != token.CONST {
|
|
114
|
-
continue
|
|
115
|
-
}
|
|
116
|
-
// Inside one block the type carries over: `A Status = iota` then
|
|
117
|
-
// bare `B` and `C` are Status too. A new explicit type resets it.
|
|
118
|
-
current := ""
|
|
119
|
-
for _, spec := range gen.Specs {
|
|
120
|
-
value, ok := spec.(*ast.ValueSpec)
|
|
121
|
-
if !ok {
|
|
122
|
-
continue
|
|
123
|
-
}
|
|
124
|
-
if value.Type != nil {
|
|
125
|
-
current = ""
|
|
126
|
-
if ident, ok := value.Type.(*ast.Ident); ok {
|
|
127
|
-
current = ident.Name
|
|
128
|
-
}
|
|
129
|
-
} else if len(value.Values) > 0 {
|
|
130
|
-
// An untyped constant: `X = "x"` belongs to no set.
|
|
131
|
-
current = ""
|
|
132
|
-
}
|
|
133
|
-
at, known := byName[current]
|
|
134
|
-
if !known {
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
doc := value.Doc.Text()
|
|
138
|
-
if doc == "" && value.Comment != nil {
|
|
139
|
-
doc = value.Comment.Text()
|
|
140
|
-
}
|
|
141
|
-
for i, name := range value.Names {
|
|
142
|
-
if !exported(name.Name) {
|
|
143
|
-
continue
|
|
144
|
-
}
|
|
145
|
-
literal := name.Name
|
|
146
|
-
if i < len(value.Values) {
|
|
147
|
-
if lit, ok := value.Values[i].(*ast.BasicLit); ok && lit.Kind == token.STRING {
|
|
148
|
-
literal = strings.Trim(lit.Value, "`\"")
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
out[at].values = append(out[at].values, catalog.EnumValue{
|
|
152
|
-
Name: literal,
|
|
153
|
-
Doc: firstSentenceOrAll(strings.TrimSpace(doc)),
|
|
154
|
-
Deprecated: deprecatedByDoc(doc),
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
kept := out[:0]
|
|
162
|
-
for _, decl := range out {
|
|
163
|
-
if len(decl.values) > 0 {
|
|
164
|
-
kept = append(kept, decl)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return kept
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// isBasic is `string`, `int`, `uint8` and the like - a named type over one of
|
|
172
|
-
// those is what a const block can be declared of.
|
|
173
|
-
func isBasic(expr ast.Expr) bool {
|
|
174
|
-
ident, ok := expr.(*ast.Ident)
|
|
175
|
-
if !ok {
|
|
176
|
-
return false
|
|
177
|
-
}
|
|
178
|
-
switch ident.Name {
|
|
179
|
-
case "string", "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "byte", "rune":
|
|
180
|
-
return true
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return false
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// deprecatedByDoc is the Go convention: a paragraph starting "Deprecated:".
|
|
187
|
-
func deprecatedByDoc(doc string) bool {
|
|
188
|
-
for _, para := range strings.Split(doc, "\n\n") {
|
|
189
|
-
if strings.HasPrefix(strings.TrimSpace(para), "Deprecated:") {
|
|
190
|
-
return true
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return false
|
|
195
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
const enumSrc = `package event
|
|
11
|
-
|
|
12
|
-
// Reason says why a session stopped being usable. It is a closed set.
|
|
13
|
-
type Reason string
|
|
14
|
-
|
|
15
|
-
const (
|
|
16
|
-
// ReasonLogout - the user asked.
|
|
17
|
-
ReasonLogout Reason = "logout"
|
|
18
|
-
// ReasonRevoked - somebody else ended it.
|
|
19
|
-
//
|
|
20
|
-
// Deprecated: support ends sessions through ReasonRiskBlocked now.
|
|
21
|
-
ReasonRevoked Reason = "revoked"
|
|
22
|
-
reasonHidden Reason = "hidden"
|
|
23
|
-
Unrelated = "loose"
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
// Level is how loud the end was.
|
|
27
|
-
type Level int
|
|
28
|
-
|
|
29
|
-
const (
|
|
30
|
-
Quiet Level = iota // barely
|
|
31
|
-
Loud
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
// Alias names a string and nothing is declared of it.
|
|
35
|
-
type Alias string
|
|
36
|
-
|
|
37
|
-
type Point struct{ X int }
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
func TestEnumsAreTheConstantsOfANamedBasicType(t *testing.T) {
|
|
41
|
-
p, err := parseSource("session_ended.go", enumSrc)
|
|
42
|
-
if err != nil {
|
|
43
|
-
t.Fatal(err)
|
|
44
|
-
}
|
|
45
|
-
enums := enumsIn(p, "auth.auth.session", "")
|
|
46
|
-
if len(enums) != 2 {
|
|
47
|
-
t.Fatalf("enums = %+v", enums)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
reason := enums[0]
|
|
51
|
-
if reason.ID != "auth.auth.session.reason" || reason.Doc != "Reason says why a session stopped being usable. It is a closed set." {
|
|
52
|
-
t.Errorf("reason = %+v", reason)
|
|
53
|
-
}
|
|
54
|
-
// The literal, not the constant's name: it is what the wire carries.
|
|
55
|
-
if got := valueNames(reason.Values); got != "logout,revoked" {
|
|
56
|
-
t.Errorf("values = %s", got)
|
|
57
|
-
}
|
|
58
|
-
if !reason.Values[1].Deprecated || reason.Values[0].Deprecated {
|
|
59
|
-
t.Errorf("deprecation not read off the doc: %+v", reason.Values)
|
|
60
|
-
}
|
|
61
|
-
if reason.Values[0].Doc != "ReasonLogout - the user asked." {
|
|
62
|
-
t.Errorf("doc = %q", reason.Values[0].Doc)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// An iota has no literal to show, so the constant's name stands; the
|
|
66
|
-
// trailing comment is its doc.
|
|
67
|
-
level := enums[1]
|
|
68
|
-
if got := valueNames(level.Values); got != "Quiet,Loud" {
|
|
69
|
-
t.Errorf("level values = %s", got)
|
|
70
|
-
}
|
|
71
|
-
if level.Values[0].Doc != "barely" {
|
|
72
|
-
t.Errorf("trailing comment not read: %q", level.Values[0].Doc)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
func valueNames(values []catalog.EnumValue) string {
|
|
77
|
-
out := make([]string, 0, len(values))
|
|
78
|
-
for _, v := range values {
|
|
79
|
-
out = append(out, v.Name)
|
|
80
|
-
}
|
|
81
|
-
return strings.Join(out, ",")
|
|
82
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"path"
|
|
5
|
-
|
|
6
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
7
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
// extractEvents reads event below a discovered aggregate domain package.
|
|
11
|
-
//
|
|
12
|
-
// An event is a struct with a `Name() string` that returns a literal - which is
|
|
13
|
-
// both how the domain declares the event's name on the wire and how this tells
|
|
14
|
-
// an event apart from the helper types that live beside it.
|
|
15
|
-
func extractEvents(root, aggregateName, domainPath string, layout sourceLayout, aggID string, b *plugin.Builder) []catalog.Event {
|
|
16
|
-
out := []catalog.Event{}
|
|
17
|
-
|
|
18
|
-
pkg, err := parsePkg(root, path.Join(domainPath, "event"))
|
|
19
|
-
if err != nil {
|
|
20
|
-
return out
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
out = eventsIn(pkg, aggID, channelOf(root, aggregateName, layout))
|
|
24
|
-
if len(out) == 0 {
|
|
25
|
-
b.Warn(aggID, path.Join(domainPath, "event")+" declares no struct with a Name() method; the aggregate publishes nothing")
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return out
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// channelOf reads where an aggregate's events go: the `Topic` constant of
|
|
32
|
-
// the discovered integration event DTO package, which may live beside a
|
|
33
|
-
// feature or beside a legacy repository adapter. The domain names the event and the adapter
|
|
34
|
-
// names the channel, because the channel is a fact about the transport, not
|
|
35
|
-
// about what happened. Empty when the package or the constant is missing: a
|
|
36
|
-
// domain nobody publishes has no channel to name, and saying so is better
|
|
37
|
-
// than guessing one from the aggregate's name.
|
|
38
|
-
func channelOf(root, aggregateName string, layout sourceLayout) string {
|
|
39
|
-
dir := layout.integrationEvents[aggregateName]
|
|
40
|
-
if dir == "" {
|
|
41
|
-
return ""
|
|
42
|
-
}
|
|
43
|
-
pkg, err := parsePkg(root, dir)
|
|
44
|
-
if err != nil {
|
|
45
|
-
return ""
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return stringConsts(pkg)["Topic"]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// eventsIn is the reading itself, kept apart from where the package was found
|
|
52
|
-
// so it can be exercised on a package built in a test. channel is where every
|
|
53
|
-
// event of the aggregate is published, or empty.
|
|
54
|
-
func eventsIn(pkg *pkg, aggID, channel string) []catalog.Event {
|
|
55
|
-
out := []catalog.Event{}
|
|
56
|
-
|
|
57
|
-
for _, decl := range pkg.structs() {
|
|
58
|
-
if !exported(decl.name) {
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
wire, named := returnedString(pkg, pkg.methods(decl.name)["Name"])
|
|
63
|
-
if !named {
|
|
64
|
-
// Not every struct in the package is an event. One without a Name
|
|
65
|
-
// is a payload or a helper, and quietly documenting it as a
|
|
66
|
-
// published fact would be worse than missing it.
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// A Name that is not a literal names the event in a way this reader
|
|
71
|
-
// cannot follow, and a wire with no name is no wire at all.
|
|
72
|
-
var onWire *catalog.EventWire
|
|
73
|
-
if wire != "" {
|
|
74
|
-
onWire = &catalog.EventWire{Name: wire, Channel: channel}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
out = append(out, catalog.Event{
|
|
78
|
-
ID: eventID(aggID, decl.name),
|
|
79
|
-
Slug: slug(decl.name),
|
|
80
|
-
Name: decl.name,
|
|
81
|
-
Wire: onWire,
|
|
82
|
-
// One version, and it is declared rather than discovered: nothing
|
|
83
|
-
// in the source carries a version, so saying v1 is saying "this is
|
|
84
|
-
// what it looks like today", not "this is the first of several".
|
|
85
|
-
Versions: []catalog.EventVersion{{
|
|
86
|
-
Version: "v1",
|
|
87
|
-
Doc: decl.doc,
|
|
88
|
-
Source: decl.source,
|
|
89
|
-
Fields: fields(decl.fields),
|
|
90
|
-
}},
|
|
91
|
-
// Consumers live in other services, and this extractor is reading
|
|
92
|
-
// one. An empty list is the honest answer; the merge is where the
|
|
93
|
-
// other side of the arrow arrives.
|
|
94
|
-
Consumers: []catalog.EventConsumer{},
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return out
|
|
99
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"os"
|
|
6
|
-
"path/filepath"
|
|
7
|
-
"strconv"
|
|
8
|
-
"strings"
|
|
9
|
-
|
|
10
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
11
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
// extract reads a service and answers with one catalog fragment.
|
|
15
|
-
//
|
|
16
|
-
// A fragment, not a catalog: it carries one context and one service, names
|
|
17
|
-
// peers it does not own, and is merged with everything else before anything
|
|
18
|
-
// validates it. That is the whole reason the estate does not need a file
|
|
19
|
-
// somebody has to own.
|
|
20
|
-
func extract(in plugin.Input, opts Options) (plugin.Response, error) {
|
|
21
|
-
b := &plugin.Builder{}
|
|
22
|
-
root := in.Root
|
|
23
|
-
|
|
24
|
-
if opts.Context == "" {
|
|
25
|
-
opts.Context = filepath.Base(root)
|
|
26
|
-
}
|
|
27
|
-
if opts.Service == "" {
|
|
28
|
-
opts.Service = filepath.Base(root)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
svcID := serviceID(opts.Context, opts.Service)
|
|
32
|
-
layout := discoverLayout(root)
|
|
33
|
-
|
|
34
|
-
readme := readFile(filepath.Join(root, "README.md"))
|
|
35
|
-
service := catalog.Service{
|
|
36
|
-
ID: svcID,
|
|
37
|
-
Slug: opts.Service,
|
|
38
|
-
Name: firstNonEmpty(opts.ServiceName, markdownTitle(readme), title(opts.Service)),
|
|
39
|
-
Repo: firstNonEmpty(opts.Repo, modulePath(root)),
|
|
40
|
-
Path: filepath.ToSlash(root),
|
|
41
|
-
Readme: readme,
|
|
42
|
-
Provides: []catalog.RpcService{},
|
|
43
|
-
Consumes: []catalog.RpcCall{},
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Operations live in the application layer and belong to the aggregate the
|
|
47
|
-
// use case sits under, so they are gathered first and handed out below.
|
|
48
|
-
// What exposes each one is read from the transport layer beside it, and the
|
|
49
|
-
// endpoints that came back are where the flows start.
|
|
50
|
-
exposures, endpoints := extractTransport(root, layout, b)
|
|
51
|
-
operations := extractOperations(root, layout, exposures, b)
|
|
52
|
-
|
|
53
|
-
for _, aggregateName := range sortedKeys(layout.domains) {
|
|
54
|
-
aggregate, ok := extractAggregate(root, aggregateName, layout.domains[aggregateName], layout, svcID, b)
|
|
55
|
-
if !ok {
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
aggregate.Operations = operations[aggregateName]
|
|
59
|
-
if aggregate.Operations == nil {
|
|
60
|
-
aggregate.Operations = []catalog.Operation{}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
service.Aggregates = append(service.Aggregates, aggregate)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if len(service.Aggregates) == 0 {
|
|
67
|
-
b.Warn(svcID, "no aggregate domain packages found; supported layouts include internal/domain/<aggregate> and internal/<aggregate>/domain")
|
|
68
|
-
service.Aggregates = []catalog.Aggregate{}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for aggregate, ops := range operations {
|
|
72
|
-
if !hasAggregate(service.Aggregates, aggregateID(svcID, slug(aggregate))) {
|
|
73
|
-
b.Warn(svcID, "application slice "+aggregate+" has "+plural(len(ops))+" but there is no matching aggregate domain package")
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Flows are read last, because a flow names the events the aggregates
|
|
78
|
-
// declare and an event nothing reaches is worth reporting.
|
|
79
|
-
flows, calls := extractFlows(root, flowOptions{
|
|
80
|
-
context: opts.Context,
|
|
81
|
-
svcID: svcID,
|
|
82
|
-
service: opts.Service,
|
|
83
|
-
store: opts.Store,
|
|
84
|
-
peers: opts.Peers,
|
|
85
|
-
externals: opts.Externals,
|
|
86
|
-
events: opts.Events,
|
|
87
|
-
}, layout, endpoints, eventIDs(service.Aggregates), b)
|
|
88
|
-
// What the service calls is read off its flows: the generated client in
|
|
89
|
-
// the tree names the rpc, and a step on it is the call being made.
|
|
90
|
-
service.Consumes = calls
|
|
91
|
-
|
|
92
|
-
fragment := catalog.Catalog{
|
|
93
|
-
GeneratedAt: in.GeneratedAt,
|
|
94
|
-
Commit: in.Commit,
|
|
95
|
-
Contexts: []catalog.BoundedContext{{
|
|
96
|
-
ID: opts.Context,
|
|
97
|
-
Slug: opts.Context,
|
|
98
|
-
Name: firstNonEmpty(opts.ContextName, title(opts.Context)),
|
|
99
|
-
Summary: opts.ContextSummary,
|
|
100
|
-
Classification: catalog.Classification(opts.Classification),
|
|
101
|
-
Services: []catalog.Service{service},
|
|
102
|
-
}},
|
|
103
|
-
Defs: map[string]catalog.TypeDef{},
|
|
104
|
-
Flows: flows,
|
|
105
|
-
Adrs: []catalog.Adr{},
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
encoded, err := json.MarshalIndent(fragment, "", " ")
|
|
109
|
-
if err != nil {
|
|
110
|
-
return plugin.Response{}, err
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
b.File(firstNonEmpty(opts.Out, "domain.json"), string(encoded)+"\n")
|
|
114
|
-
|
|
115
|
-
return b.Response(), nil
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// eventIDs is every fact the service publishes, which is what a flow reader is
|
|
119
|
-
// held to: an event no flow reaches is an event whose publisher this could not
|
|
120
|
-
// follow.
|
|
121
|
-
func eventIDs(aggregates []catalog.Aggregate) []string {
|
|
122
|
-
var out []string
|
|
123
|
-
for i := range aggregates {
|
|
124
|
-
for j := range aggregates[i].Events {
|
|
125
|
-
out = append(out, aggregates[i].Events[j].ID)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return out
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
func hasAggregate(aggregates []catalog.Aggregate, id string) bool {
|
|
133
|
-
for i := range aggregates {
|
|
134
|
-
if aggregates[i].ID == id {
|
|
135
|
-
return true
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return false
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
func readFile(path string) string {
|
|
143
|
-
contents, err := os.ReadFile(path)
|
|
144
|
-
if err != nil {
|
|
145
|
-
return ""
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return strings.TrimSpace(string(contents))
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// modulePath is the module line of go.mod, which is the closest thing the
|
|
152
|
-
// source has to a repository name.
|
|
153
|
-
func modulePath(root string) string {
|
|
154
|
-
for _, line := range strings.Split(readFile(filepath.Join(root, "go.mod")), "\n") {
|
|
155
|
-
if rest, ok := strings.CutPrefix(strings.TrimSpace(line), "module "); ok {
|
|
156
|
-
return strings.TrimSpace(rest)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return ""
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// markdownTitle is the first `# ` heading of a readme, which is what the
|
|
164
|
-
// service is actually called.
|
|
165
|
-
func markdownTitle(md string) string {
|
|
166
|
-
for _, line := range strings.Split(md, "\n") {
|
|
167
|
-
if heading, ok := strings.CutPrefix(strings.TrimSpace(line), "# "); ok {
|
|
168
|
-
return strings.TrimSpace(heading)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return ""
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
func firstNonEmpty(values ...string) string {
|
|
176
|
-
for _, value := range values {
|
|
177
|
-
if strings.TrimSpace(value) != "" {
|
|
178
|
-
return value
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return ""
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
func plural(n int) string {
|
|
186
|
-
if n == 1 {
|
|
187
|
-
return "1 use case"
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return strconv.Itoa(n) + " use cases"
|
|
191
|
-
}
|