@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,108 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
8
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
const lifecycleSrc = `package session
|
|
12
|
-
|
|
13
|
-
import "github.com/shortlink-org/go-sdk/fsm"
|
|
14
|
-
|
|
15
|
-
const (
|
|
16
|
-
StateLive fsm.State = "live"
|
|
17
|
-
StateRevoked fsm.State = "revoked"
|
|
18
|
-
EventRevoke fsm.Event = "revoke"
|
|
19
|
-
EventExpire fsm.Event = "expire"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
var Rules = fsm.TransitionRuleSet{
|
|
23
|
-
StateLive: {EventRevoke: StateRevoked, EventExpire: StateExpired},
|
|
24
|
-
StateRevoked: {EventRevive: StateLive},
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const StateExpired fsm.State = "expired"
|
|
28
|
-
|
|
29
|
-
type Session struct{ RevokedAt int }
|
|
30
|
-
|
|
31
|
-
func (s *Session) trigger(ev fsm.Event) bool {
|
|
32
|
-
m := &fsm.FSM{CurrentState: StateLive, TransitionRules: Rules}
|
|
33
|
-
return m.TriggerEvent(nil, ev) == nil
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
func (s *Session) Revoke() (event.SessionEnded, bool) {
|
|
37
|
-
if !s.trigger(EventRevoke) {
|
|
38
|
-
return event.SessionEnded{}, false
|
|
39
|
-
}
|
|
40
|
-
return event.SessionEnded{}, true
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
func (s *Session) Poke(ev fsm.Event) { s.trigger(ev) }
|
|
44
|
-
|
|
45
|
-
// Two moves, one event: the event is the last move's.
|
|
46
|
-
func (s *Session) Bounce() (event.SessionEnded, bool) {
|
|
47
|
-
s.trigger(EventRevive)
|
|
48
|
-
s.trigger(EventRevoke)
|
|
49
|
-
return event.SessionEnded{}, true
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const EventRevive fsm.Event = "revive"
|
|
53
|
-
`
|
|
54
|
-
|
|
55
|
-
func TestLifecycleIsReadOffTheRuleSetAndTheMover(t *testing.T) {
|
|
56
|
-
p, err := parseSource("session.go", lifecycleSrc)
|
|
57
|
-
if err != nil {
|
|
58
|
-
t.Fatal(err)
|
|
59
|
-
}
|
|
60
|
-
b := &plugin.Builder{}
|
|
61
|
-
events := []catalog.Event{{ID: "auth.auth.session.SessionEnded", Name: "SessionEnded"}}
|
|
62
|
-
lc := readLifecycle(p, "Session", events, "auth.auth.session", b)
|
|
63
|
-
if lc == nil {
|
|
64
|
-
t.Fatal("no lifecycle read")
|
|
65
|
-
}
|
|
66
|
-
// The literal's order, then the state only a target names.
|
|
67
|
-
if got := strings.Join(lc.States, ","); got != "live,revoked,expired" {
|
|
68
|
-
t.Fatalf("states = %s", got)
|
|
69
|
-
}
|
|
70
|
-
if len(lc.Transitions) != 3 {
|
|
71
|
-
t.Fatalf("transitions = %+v", lc.Transitions)
|
|
72
|
-
}
|
|
73
|
-
// In table order: live → revoked twice (Bounce, Revoke), then revoked → live.
|
|
74
|
-
tr := lc.Transitions[1]
|
|
75
|
-
if tr.From != "live" || tr.To != "revoked" || tr.On != "Revoke" || tr.Emits != "auth.auth.session.SessionEnded" || !strings.HasSuffix(tr.Source, "session.go:27") {
|
|
76
|
-
t.Fatalf("transition = %+v", tr)
|
|
77
|
-
}
|
|
78
|
-
// Bounce revives and then revokes: the event it returns is the revoke's.
|
|
79
|
-
if b1 := lc.Transitions[0]; b1.On != "Bounce" || b1.To != "revoked" || b1.Emits == "" {
|
|
80
|
-
t.Fatalf("Bounce's last move = %+v", b1)
|
|
81
|
-
}
|
|
82
|
-
if b0 := lc.Transitions[2]; b0.On != "Bounce" || b0.To != "live" || b0.Emits != "" {
|
|
83
|
-
t.Fatalf("Bounce's first move = %+v", b0)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
var messages []string
|
|
87
|
-
for _, d := range b.Warnings {
|
|
88
|
-
messages = append(messages, d.Message)
|
|
89
|
-
}
|
|
90
|
-
joined := strings.Join(messages, "\n")
|
|
91
|
-
// The rule nobody takes, and the method that hands the mover a variable.
|
|
92
|
-
if !strings.Contains(joined, `live → expired on "expire", and no method of Session makes that move`) {
|
|
93
|
-
t.Fatalf("missing the untaken rule: %s", joined)
|
|
94
|
-
}
|
|
95
|
-
if !strings.Contains(joined, "Poke hands trigger something that is not a constant") {
|
|
96
|
-
t.Fatalf("missing the non-constant warning: %s", joined)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
func TestNoRuleSetMeansNoLifecycle(t *testing.T) {
|
|
101
|
-
p, err := parseSource("user.go", "package user\n\ntype User struct{}\n")
|
|
102
|
-
if err != nil {
|
|
103
|
-
t.Fatal(err)
|
|
104
|
-
}
|
|
105
|
-
if lc := readLifecycle(p, "User", nil, "auth.auth.user", &plugin.Builder{}); lc != nil {
|
|
106
|
-
t.Fatalf("read a lifecycle off nothing: %+v", lc)
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// Package main is portolan-extract-go: a Go service in, a catalog fragment out.
|
|
2
|
-
//
|
|
3
|
-
// It reads the source with go/parser alone - no go/packages, no `go list`, no
|
|
4
|
-
// module download. The domain layer of a service laid out this way is regular
|
|
5
|
-
// enough that the syntax carries the answer, and staying out of the toolchain
|
|
6
|
-
// means the extractor runs on a checkout that has never been built.
|
|
7
|
-
//
|
|
8
|
-
// What it does not know, it says. An aggregate whose root it cannot identify,
|
|
9
|
-
// an event with no name, a use case it cannot classify - each is a diagnostic
|
|
10
|
-
// beside the fragment rather than a guess inside it.
|
|
11
|
-
package main
|
|
12
|
-
|
|
13
|
-
import (
|
|
14
|
-
"fmt"
|
|
15
|
-
"io"
|
|
16
|
-
"os"
|
|
17
|
-
|
|
18
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
// Options are what the manifest tells the extractor. Everything here is a fact
|
|
22
|
-
// about the estate that the source does not carry: a Go module knows its own
|
|
23
|
-
// import path, not which bounded context it belongs to.
|
|
24
|
-
type Options struct {
|
|
25
|
-
Context string `json:"context"`
|
|
26
|
-
ContextName string `json:"contextName,omitempty"`
|
|
27
|
-
ContextSummary string `json:"contextSummary,omitempty"`
|
|
28
|
-
Classification string `json:"classification,omitempty"`
|
|
29
|
-
|
|
30
|
-
Service string `json:"service"`
|
|
31
|
-
ServiceName string `json:"serviceName,omitempty"`
|
|
32
|
-
Repo string `json:"repo,omitempty"`
|
|
33
|
-
|
|
34
|
-
// Store is the slug of the database this service keeps its state in - the
|
|
35
|
-
// same one the SQL extractor is given. It is here for the flows: a call on
|
|
36
|
-
// a repository lands somewhere, and only the manifest knows where.
|
|
37
|
-
Store string `json:"store,omitempty"`
|
|
38
|
-
|
|
39
|
-
// Peers says which service answers to a proto package this service calls:
|
|
40
|
-
// {"risk.v1": "shop.risk"}. The generated client in the tree names the
|
|
41
|
-
// package and the rpc; only the manifest knows whose it is. A package
|
|
42
|
-
// with no line here is called as `unknown`, and the steps are unresolved.
|
|
43
|
-
Peers map[string]string `json:"peers,omitempty"`
|
|
44
|
-
|
|
45
|
-
// Externals says which system outside the estate answers to an api this
|
|
46
|
-
// service calls, as the api id to the external's bare id: {"stripe.v1":
|
|
47
|
-
// "stripe"}. Written when the name the document would give the system is
|
|
48
|
-
// not the one wanted; left out, a generated HTTP client whose package no
|
|
49
|
-
// peers line claims is read as calling the system its vendored document
|
|
50
|
-
// is titled after.
|
|
51
|
-
Externals map[string]string `json:"externals,omitempty"`
|
|
52
|
-
|
|
53
|
-
// Events says which aggregate another service's events belong to, as the
|
|
54
|
-
// import path a policy reads them from to the aggregate id. A service that
|
|
55
|
-
// vendors the shape of somebody else's event has the type and nothing else;
|
|
56
|
-
// only the manifest knows whose aggregate raised it.
|
|
57
|
-
Events map[string]string `json:"events,omitempty"`
|
|
58
|
-
|
|
59
|
-
// Out names the fragment file. One extractor, one file, so that a fragment
|
|
60
|
-
// carries the provenance of the run that produced it.
|
|
61
|
-
Out string `json:"out,omitempty"`
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
func main() {
|
|
65
|
-
if err := run(os.Stdin, os.Stdout); err != nil {
|
|
66
|
-
fmt.Fprintln(os.Stderr, "portolan-extract-go:", err)
|
|
67
|
-
os.Exit(1)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
func run(stdin io.Reader, stdout io.Writer) error {
|
|
72
|
-
return plugin.Serve(stdin, stdout, descriptor(), func(req plugin.Request, opts Options) (plugin.Response, error) {
|
|
73
|
-
if req.Input.Root == "" {
|
|
74
|
-
return plugin.Response{}, fmt.Errorf("no input root: an extractor has nothing to read")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return extract(req.Input, opts)
|
|
78
|
-
})
|
|
79
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"go/ast"
|
|
5
|
-
"path/filepath"
|
|
6
|
-
"strings"
|
|
7
|
-
|
|
8
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
9
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
// extractOperations reads every discovered application use case, keyed by the
|
|
13
|
-
// aggregate or feature the use case sits under.
|
|
14
|
-
//
|
|
15
|
-
// The application layer is where the commands and queries actually are. The
|
|
16
|
-
// aggregate's own methods are the mechanics of one - Register, ChangePassword -
|
|
17
|
-
// but a use case is the whole operation, it already has a name a reader would
|
|
18
|
-
// recognise, and in this codebase it has a README of its own.
|
|
19
|
-
func extractOperations(root string, layout sourceLayout, exposures map[string][]string, b *plugin.Builder) map[string][]catalog.Operation {
|
|
20
|
-
out := map[string][]catalog.Operation{}
|
|
21
|
-
|
|
22
|
-
for _, key := range sortedKeys(layout.useCases) {
|
|
23
|
-
aggregate, name, _ := strings.Cut(key, "/")
|
|
24
|
-
dir := layout.useCases[key]
|
|
25
|
-
pkg, err := parsePkg(root, dir)
|
|
26
|
-
if err != nil {
|
|
27
|
-
b.Warn(aggregate, dir+" could not be parsed; skipped")
|
|
28
|
-
|
|
29
|
-
continue
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
out[aggregate] = append(out[aggregate], catalog.Operation{
|
|
33
|
-
ID: camel(name),
|
|
34
|
-
Kind: operationKind(pkg),
|
|
35
|
-
Doc: operationDoc(root, dir, pkg),
|
|
36
|
-
// Which endpoints run it, read from the transport layer. Absent
|
|
37
|
-
// is the honest answer for a use case nothing outside can
|
|
38
|
-
// reach - and in this service that is a deliberate design, not
|
|
39
|
-
// an oversight.
|
|
40
|
-
ExposedBy: exposures[key],
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return out
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// operationKind asks whether the use case writes.
|
|
48
|
-
//
|
|
49
|
-
// A use case that reaches a repository's Save or Delete changes state, and one
|
|
50
|
-
// that does not is answering a question. Every method on UseCase is looked at,
|
|
51
|
-
// not just Handle: a use case that revokes a list of sessions does the writing
|
|
52
|
-
// in a helper, and reading only the entry point would file it as a query.
|
|
53
|
-
//
|
|
54
|
-
// This is a reading of the code rather than a declaration in it, so it is wrong
|
|
55
|
-
// exactly when a use case mutates through a name this does not know - which is
|
|
56
|
-
// a rule that can be read here, over an annotation nobody would keep current.
|
|
57
|
-
func operationKind(pkg *pkg) catalog.OperationKind {
|
|
58
|
-
ports := map[string]bool{}
|
|
59
|
-
for _, held := range pkg.structs() {
|
|
60
|
-
if held.name != "UseCase" || held.fields == nil || held.fields.Fields == nil {
|
|
61
|
-
continue
|
|
62
|
-
}
|
|
63
|
-
for _, field := range held.fields.Fields.List {
|
|
64
|
-
for _, name := range field.Names {
|
|
65
|
-
ports[name.Name] = true
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
for _, fn := range pkg.methods("UseCase") {
|
|
70
|
-
if callsWritePort(fn, ports) {
|
|
71
|
-
return catalog.OperationCommand
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return catalog.OperationQuery
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// All language extractors share this core vocabulary. Framework extractors
|
|
79
|
-
// may add native write verbs (Django's bulk_update, for example), but a helper
|
|
80
|
-
// method or an unrelated value called Save is not enough: the call has to be
|
|
81
|
-
// made through a dependency held by the use case.
|
|
82
|
-
var writeMethods = map[string]bool{
|
|
83
|
-
"save": true, "delete": true, "create": true, "update": true,
|
|
84
|
-
"publish": true, "remove": true, "insert": true, "upsert": true,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
func callsWritePort(fn *ast.FuncDecl, ports map[string]bool) bool {
|
|
88
|
-
if fn == nil || fn.Body == nil {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
receiver := receiverIdent(fn)
|
|
92
|
-
found := false
|
|
93
|
-
ast.Inspect(fn.Body, func(node ast.Node) bool {
|
|
94
|
-
call, ok := node.(*ast.CallExpr)
|
|
95
|
-
if !ok {
|
|
96
|
-
return true
|
|
97
|
-
}
|
|
98
|
-
method, ok := call.Fun.(*ast.SelectorExpr)
|
|
99
|
-
if !ok || !writeMethods[strings.ToLower(method.Sel.Name)] {
|
|
100
|
-
return true
|
|
101
|
-
}
|
|
102
|
-
field, ok := method.X.(*ast.SelectorExpr)
|
|
103
|
-
if !ok || !ports[field.Sel.Name] {
|
|
104
|
-
return true
|
|
105
|
-
}
|
|
106
|
-
base, ok := field.X.(*ast.Ident)
|
|
107
|
-
if ok && base.Name == receiver {
|
|
108
|
-
found = true
|
|
109
|
-
return false
|
|
110
|
-
}
|
|
111
|
-
return true
|
|
112
|
-
})
|
|
113
|
-
return found
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// operationDoc prefers the use case's README, which is written for a reader,
|
|
117
|
-
// over its package comment, which is written for whoever opens the file next.
|
|
118
|
-
func operationDoc(root, dir string, pkg *pkg) string {
|
|
119
|
-
readme := readFile(filepath.Join(root, filepath.FromSlash(dir), "README.md"))
|
|
120
|
-
if readme != "" {
|
|
121
|
-
if summary := firstParagraphAfterTitle(readme); summary != "" {
|
|
122
|
-
return summary
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return firstSentenceOrAll(pkg.doc())
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// firstParagraphAfterTitle is the prose under a readme's heading and before its
|
|
130
|
-
// first section: the one paragraph that says what the thing does.
|
|
131
|
-
func firstParagraphAfterTitle(md string) string {
|
|
132
|
-
var lines []string
|
|
133
|
-
|
|
134
|
-
started := false
|
|
135
|
-
for _, line := range strings.Split(md, "\n") {
|
|
136
|
-
trimmed := strings.TrimSpace(line)
|
|
137
|
-
|
|
138
|
-
if strings.HasPrefix(trimmed, "#") {
|
|
139
|
-
if started {
|
|
140
|
-
break
|
|
141
|
-
}
|
|
142
|
-
started = true
|
|
143
|
-
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
146
|
-
if trimmed == "" {
|
|
147
|
-
if len(lines) > 0 {
|
|
148
|
-
break
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
continue
|
|
152
|
-
}
|
|
153
|
-
lines = append(lines, trimmed)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return strings.Join(lines, " ")
|
|
157
|
-
}
|
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"go/ast"
|
|
5
|
-
"go/parser"
|
|
6
|
-
"go/token"
|
|
7
|
-
"go/types"
|
|
8
|
-
"os"
|
|
9
|
-
"path"
|
|
10
|
-
"path/filepath"
|
|
11
|
-
"strings"
|
|
12
|
-
|
|
13
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
// A parsed package: the files that make it up, and where they came from.
|
|
17
|
-
//
|
|
18
|
-
// Tests are left out. A _test.go file is about how the code is exercised, and
|
|
19
|
-
// a helper struct in one is not part of the domain however much it looks like
|
|
20
|
-
// it.
|
|
21
|
-
type pkg struct {
|
|
22
|
-
dir string // as a reader would open it, relative to the repository
|
|
23
|
-
name string
|
|
24
|
-
files []*ast.File
|
|
25
|
-
fset *token.FileSet
|
|
26
|
-
paths map[*ast.File]string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
func parsePkg(root, rel string) (*pkg, error) {
|
|
30
|
-
dir := filepath.Join(root, rel)
|
|
31
|
-
|
|
32
|
-
entries, err := os.ReadDir(dir)
|
|
33
|
-
if err != nil {
|
|
34
|
-
return nil, err
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
p := &pkg{
|
|
38
|
-
dir: path.Join(filepath.ToSlash(root), rel),
|
|
39
|
-
fset: token.NewFileSet(),
|
|
40
|
-
paths: map[*ast.File]string{},
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
for _, entry := range entries {
|
|
44
|
-
name := entry.Name()
|
|
45
|
-
if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
|
46
|
-
continue
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
file, err := parser.ParseFile(p.fset, filepath.Join(dir, name), nil, parser.ParseComments)
|
|
50
|
-
if err != nil {
|
|
51
|
-
return nil, err
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
p.name = file.Name.Name
|
|
55
|
-
p.files = append(p.files, file)
|
|
56
|
-
p.paths[file] = path.Join(p.dir, name)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if len(p.files) == 0 {
|
|
60
|
-
return nil, os.ErrNotExist
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return p, nil
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// doc is the package comment: the block immediately above `package x`, in
|
|
67
|
-
// whichever file carries it.
|
|
68
|
-
//
|
|
69
|
-
// It is the aggregate's readme, and it is worth saying why that is not a
|
|
70
|
-
// fallback. A package comment is where a Go author already writes down what the
|
|
71
|
-
// package is for and what it refuses to do; asking them to write it a second
|
|
72
|
-
// time somewhere a generator can find it is how documentation goes stale.
|
|
73
|
-
func (p *pkg) doc() string {
|
|
74
|
-
for _, file := range p.files {
|
|
75
|
-
if file.Doc == nil {
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
if text := strings.TrimSpace(file.Doc.Text()); text != "" {
|
|
79
|
-
return text
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return ""
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// position is where a node sits, as a reader would write it down: the file
|
|
87
|
-
// path relative to the repository, and the line. It is what a flow step points
|
|
88
|
-
// at, and it is read from the file set rather than remembered per declaration.
|
|
89
|
-
func (p *pkg) position(pos token.Pos) (string, int) {
|
|
90
|
-
at := p.fset.Position(pos)
|
|
91
|
-
|
|
92
|
-
return filepath.ToSlash(at.Filename), at.Line
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// structDecl is a named struct type with the comment above it.
|
|
96
|
-
type structDecl struct {
|
|
97
|
-
name string
|
|
98
|
-
doc string
|
|
99
|
-
fields *ast.StructType
|
|
100
|
-
source string
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
func (p *pkg) structs() []structDecl {
|
|
104
|
-
var out []structDecl
|
|
105
|
-
|
|
106
|
-
for _, file := range p.files {
|
|
107
|
-
for _, decl := range file.Decls {
|
|
108
|
-
gen, ok := decl.(*ast.GenDecl)
|
|
109
|
-
if !ok || gen.Tok != token.TYPE {
|
|
110
|
-
continue
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
for _, spec := range gen.Specs {
|
|
114
|
-
typeSpec, ok := spec.(*ast.TypeSpec)
|
|
115
|
-
if !ok {
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
structType, ok := typeSpec.Type.(*ast.StructType)
|
|
119
|
-
if !ok {
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// A single-spec declaration carries its comment on the
|
|
124
|
-
// GenDecl; one inside a `type (...)` block carries its own.
|
|
125
|
-
comment := typeSpec.Doc
|
|
126
|
-
if comment == nil && len(gen.Specs) == 1 {
|
|
127
|
-
comment = gen.Doc
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
out = append(out, structDecl{
|
|
131
|
-
name: typeSpec.Name.Name,
|
|
132
|
-
doc: firstSentenceOrAll(comment.Text()),
|
|
133
|
-
fields: structType,
|
|
134
|
-
source: p.paths[file],
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return out
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// methods returns the methods declared on a receiver type, by name.
|
|
144
|
-
func (p *pkg) methods(recv string) map[string]*ast.FuncDecl {
|
|
145
|
-
out := map[string]*ast.FuncDecl{}
|
|
146
|
-
|
|
147
|
-
for _, file := range p.files {
|
|
148
|
-
for _, decl := range file.Decls {
|
|
149
|
-
fn, ok := decl.(*ast.FuncDecl)
|
|
150
|
-
if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 {
|
|
151
|
-
continue
|
|
152
|
-
}
|
|
153
|
-
if receiverName(fn.Recv.List[0].Type) == recv {
|
|
154
|
-
out[fn.Name.Name] = fn
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return out
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
func receiverName(expr ast.Expr) string {
|
|
163
|
-
if star, ok := expr.(*ast.StarExpr); ok {
|
|
164
|
-
expr = star.X
|
|
165
|
-
}
|
|
166
|
-
if ident, ok := expr.(*ast.Ident); ok {
|
|
167
|
-
return ident.Name
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return ""
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// fields turns a struct's members into catalog fields.
|
|
174
|
-
//
|
|
175
|
-
// The type is rendered from the syntax rather than resolved: `email.Address`
|
|
176
|
-
// is written down as `email.Address`, which is what a reader of the catalog
|
|
177
|
-
// wants to see and what the shape of the schema asks for. Resolving it would
|
|
178
|
-
// need the whole module built, and would answer a question nobody asked.
|
|
179
|
-
func fields(st *ast.StructType) []catalog.Field {
|
|
180
|
-
if st == nil || st.Fields == nil {
|
|
181
|
-
return nil
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
var out []catalog.Field
|
|
185
|
-
for _, field := range st.Fields.List {
|
|
186
|
-
typeName := types.ExprString(field.Type)
|
|
187
|
-
|
|
188
|
-
doc := strings.TrimSpace(field.Doc.Text())
|
|
189
|
-
if doc == "" {
|
|
190
|
-
doc = strings.TrimSpace(field.Comment.Text())
|
|
191
|
-
}
|
|
192
|
-
doc = firstSentenceOrAll(doc)
|
|
193
|
-
|
|
194
|
-
if len(field.Names) == 0 {
|
|
195
|
-
// An embedded field: named by its type.
|
|
196
|
-
out = append(out, catalog.Field{Name: typeName, Type: typeName, Doc: doc})
|
|
197
|
-
|
|
198
|
-
continue
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for _, name := range field.Names {
|
|
202
|
-
out = append(out, catalog.Field{Name: name.Name, Type: typeName, Doc: doc})
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return out
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// returnedString is the string a one-line method returns, which is how an
|
|
210
|
-
// event's name on the bus is read out of `func (E) Name() string`: a literal,
|
|
211
|
-
// or a constant the package declares - `return TopicAccountLocked` - since a
|
|
212
|
-
// topic is routinely named once and returned by name.
|
|
213
|
-
func returnedString(p *pkg, fn *ast.FuncDecl) (string, bool) {
|
|
214
|
-
if fn == nil || fn.Body == nil {
|
|
215
|
-
return "", false
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
for _, stmt := range fn.Body.List {
|
|
219
|
-
ret, ok := stmt.(*ast.ReturnStmt)
|
|
220
|
-
if !ok || len(ret.Results) != 1 {
|
|
221
|
-
continue
|
|
222
|
-
}
|
|
223
|
-
if value, ok := constString(ret.Results[0], stringConsts(p)); ok {
|
|
224
|
-
return value, true
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return "", false
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// calls reports whether a function's body calls a method with one of these
|
|
232
|
-
// names on anything at all.
|
|
233
|
-
func calls(fn *ast.FuncDecl, names ...string) bool {
|
|
234
|
-
if fn == nil || fn.Body == nil {
|
|
235
|
-
return false
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
wanted := map[string]bool{}
|
|
239
|
-
for _, name := range names {
|
|
240
|
-
wanted[name] = true
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
found := false
|
|
244
|
-
ast.Inspect(fn.Body, func(node ast.Node) bool {
|
|
245
|
-
call, ok := node.(*ast.CallExpr)
|
|
246
|
-
if !ok {
|
|
247
|
-
return true
|
|
248
|
-
}
|
|
249
|
-
if selector, ok := call.Fun.(*ast.SelectorExpr); ok && wanted[selector.Sel.Name] {
|
|
250
|
-
found = true
|
|
251
|
-
|
|
252
|
-
return false
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return true
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
return found
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// subdirs lists the immediate subdirectories of a path, sorted by os.ReadDir,
|
|
262
|
-
// which reads them in filename order - so the fragment comes out the same way
|
|
263
|
-
// every time.
|
|
264
|
-
func subdirs(root, rel string) []string {
|
|
265
|
-
entries, err := os.ReadDir(filepath.Join(root, rel))
|
|
266
|
-
if err != nil {
|
|
267
|
-
return nil
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
var out []string
|
|
271
|
-
for _, entry := range entries {
|
|
272
|
-
if entry.IsDir() {
|
|
273
|
-
out = append(out, entry.Name())
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return out
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// firstSentenceOrAll trims a doc comment down to something that fits in a
|
|
281
|
-
// table cell, and leaves it alone when it is already short.
|
|
282
|
-
//
|
|
283
|
-
// Go doc comments open with a sentence naming the thing, then explain. The
|
|
284
|
-
// first sentence is the label; the rest belongs on the page, not in a column.
|
|
285
|
-
func firstSentenceOrAll(doc string) string {
|
|
286
|
-
doc = strings.TrimSpace(doc)
|
|
287
|
-
if doc == "" {
|
|
288
|
-
return ""
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Everything up to the first blank line is the opening paragraph.
|
|
292
|
-
if i := strings.Index(doc, "\n\n"); i >= 0 {
|
|
293
|
-
doc = doc[:i]
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return strings.Join(strings.Fields(doc), " ")
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// parseSource builds a package out of one file of source. It exists for tests:
|
|
300
|
-
// every other caller has a directory.
|
|
301
|
-
func parseSource(name, src string) (*pkg, error) {
|
|
302
|
-
fset := token.NewFileSet()
|
|
303
|
-
|
|
304
|
-
file, err := parser.ParseFile(fset, name, src, parser.ParseComments)
|
|
305
|
-
if err != nil {
|
|
306
|
-
return nil, err
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return &pkg{
|
|
310
|
-
dir: ".",
|
|
311
|
-
name: file.Name.Name,
|
|
312
|
-
files: []*ast.File{file},
|
|
313
|
-
fset: fset,
|
|
314
|
-
paths: map[*ast.File]string{file: name},
|
|
315
|
-
}, nil
|
|
316
|
-
}
|