Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
432 lines
24 KiB
Go
432 lines
24 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/importer"
|
|
"go/parser"
|
|
"go/token"
|
|
"go/types"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/ory/kratos/cmd"
|
|
"github.com/ory/kratos/text"
|
|
|
|
"github.com/ory/x/clidoc"
|
|
"github.com/ory/x/clock"
|
|
)
|
|
|
|
var (
|
|
aSecondAgo = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(-time.Second)
|
|
inAMinute = time.Date(2020, 1, 1, 1, 0, 0, 0, time.UTC).Add(time.Minute)
|
|
|
|
// Fixed clocks make the rendered "expired N minutes ago" / "within the next
|
|
// N minutes" example messages deterministic for doc generation. They are
|
|
// anchored so the durations come out as exactly one minute.
|
|
docExpiredClock = clock.NewMock(aSecondAgo.Add(time.Minute))
|
|
docUntilClock = clock.NewMock(inAMinute.Add(-time.Minute))
|
|
)
|
|
|
|
var messages map[string]*text.Message
|
|
|
|
func init() {
|
|
messages = map[string]*text.Message{
|
|
"NewInfoNodeLabelVerifyOTP": text.NewInfoNodeLabelVerifyOTP(),
|
|
"NewInfoNodeLabelVerificationCode": text.NewInfoNodeLabelVerificationCode(),
|
|
"NewInfoNodeLabelRecoveryCode": text.NewInfoNodeLabelRecoveryCode(),
|
|
"NewInfoNodeInputPassword": text.NewInfoNodeInputPassword(),
|
|
"NewInfoNodeInputPhoneNumber": text.NewInfoNodeInputPhoneNumber(),
|
|
"NewInfoNodeLabelGenerated": text.NewInfoNodeLabelGenerated("{title}", "{name}"),
|
|
"NewInfoNodeLabelSave": text.NewInfoNodeLabelSave(),
|
|
"NewInfoNodeLabelSubmit": text.NewInfoNodeLabelSubmit(),
|
|
"NewInfoNodeLabelID": text.NewInfoNodeLabelID(),
|
|
"NewErrorValidationSettingsFlowExpired": text.NewErrorValidationSettingsFlowExpired(docExpiredClock, aSecondAgo),
|
|
"NewInfoSelfServiceSettingsTOTPQRCode": text.NewInfoSelfServiceSettingsTOTPQRCode(),
|
|
"NewInfoSelfServiceSettingsTOTPSecret": text.NewInfoSelfServiceSettingsTOTPSecret("{secret}"),
|
|
"NewInfoSelfServiceSettingsTOTPSecretLabel": text.NewInfoSelfServiceSettingsTOTPSecretLabel(),
|
|
"NewInfoSelfServiceSettingsUpdateSuccess": text.NewInfoSelfServiceSettingsUpdateSuccess(),
|
|
"NewInfoSelfServiceSettingsUpdateUnlinkTOTP": text.NewInfoSelfServiceSettingsUpdateUnlinkTOTP(),
|
|
"NewInfoSelfServiceSettingsRevealLookup": text.NewInfoSelfServiceSettingsRevealLookup(),
|
|
"NewInfoSelfServiceSettingsRegenerateLookup": text.NewInfoSelfServiceSettingsRegenerateLookup(),
|
|
"NewInfoSelfServiceSettingsDisableLookup": text.NewInfoSelfServiceSettingsDisableLookup(),
|
|
"NewInfoSelfServiceSettingsLookupConfirm": text.NewInfoSelfServiceSettingsLookupConfirm(),
|
|
"NewInfoSelfServiceSettingsManagedByOrganization": text.NewInfoSelfServiceSettingsManagedByOrganization(),
|
|
"NewInfoSelfServiceSettingsLookupSecretList": text.NewInfoSelfServiceSettingsLookupSecretList([]string{"{secrets_list}"}, []interface{}{
|
|
text.NewInfoSelfServiceSettingsLookupSecret("{secret}"),
|
|
text.NewInfoSelfServiceSettingsLookupSecretUsed(aSecondAgo),
|
|
}),
|
|
"NewInfoSelfServiceSettingsLookupSecret": text.NewInfoSelfServiceSettingsLookupSecret("{secret}"),
|
|
"NewInfoSelfServiceSettingsLookupSecretUsed": text.NewInfoSelfServiceSettingsLookupSecretUsed(aSecondAgo),
|
|
"NewInfoSelfServiceSettingsLookupSecretsLabel": text.NewInfoSelfServiceSettingsLookupSecretsLabel(),
|
|
"NewInfoSelfServiceSettingsUpdateLinkOIDC": text.NewInfoSelfServiceSettingsUpdateLinkOIDC("{provider}"),
|
|
"NewInfoSelfServiceSettingsUpdateUnlinkOIDC": text.NewInfoSelfServiceSettingsUpdateUnlinkOIDC("{provider}"),
|
|
"NewInfoSelfServiceRegisterWebAuthnDisplayName": text.NewInfoSelfServiceRegisterWebAuthnDisplayName(),
|
|
"NewInfoSelfServiceRemoveWebAuthn": text.NewInfoSelfServiceRemoveWebAuthn("{display_name}", aSecondAgo),
|
|
"NewInfoSelfServiceRemovePasskey": text.NewInfoSelfServiceRemovePasskey("{display_name}", aSecondAgo),
|
|
"NewErrorValidationVerificationFlowExpired": text.NewErrorValidationVerificationFlowExpired(docExpiredClock, aSecondAgo),
|
|
"NewInfoSelfServiceVerificationSuccessful": text.NewInfoSelfServiceVerificationSuccessful(),
|
|
"NewVerificationEmailSent": text.NewVerificationEmailSent(),
|
|
"NewVerificationEmailWithCodeSent": text.NewVerificationEmailWithCodeSent(),
|
|
"NewVerificationPhoneWithCodeSent": text.NewVerificationPhoneWithCodeSent(),
|
|
"NewInfoSelfServiceVerificationPhoneSuccessful": text.NewInfoSelfServiceVerificationPhoneSuccessful(),
|
|
"NewErrorValidationVerificationTokenInvalidOrAlreadyUsed": text.NewErrorValidationVerificationTokenInvalidOrAlreadyUsed(),
|
|
"NewErrorValidationVerificationRetrySuccess": text.NewErrorValidationVerificationRetrySuccess(),
|
|
"NewErrorValidationVerificationStateFailure": text.NewErrorValidationVerificationStateFailure(),
|
|
"NewErrorValidationVerificationCodeInvalidOrAlreadyUsed": text.NewErrorValidationVerificationCodeInvalidOrAlreadyUsed(),
|
|
"NewErrorSystemGeneric": text.NewErrorSystemGeneric("{reason}"),
|
|
"NewErrorSystemNoAuthenticationMethodsAvailable": text.NewErrorSystemNoAuthenticationMethodsAvailable(),
|
|
"NewErrorSystemOrganizationNoSSOProvidersAvailable": text.NewErrorSystemOrganizationNoSSOProvidersAvailable(),
|
|
"NewValidationErrorGeneric": text.NewValidationErrorGeneric("{reason}"),
|
|
"NewValidationErrorRequired": text.NewValidationErrorRequired("{property}"),
|
|
"NewErrorValidationMinLength": text.NewErrorValidationMinLength(5, 3),
|
|
"NewErrorValidationMaxLength": text.NewErrorValidationMaxLength(5, 6),
|
|
"NewErrorValidationInvalidFormat": text.NewErrorValidationInvalidFormat("{pattern}"),
|
|
"NewErrorValidationMinimum": text.NewErrorValidationMinimum(5, 3),
|
|
"NewErrorValidationExclusiveMinimum": text.NewErrorValidationExclusiveMinimum(5, 5),
|
|
"NewErrorValidationMaximum": text.NewErrorValidationMaximum(5, 6),
|
|
"NewErrorValidationExclusiveMaximum": text.NewErrorValidationExclusiveMaximum(5, 5),
|
|
"NewErrorValidationMultipleOf": text.NewErrorValidationMultipleOf(7, 3),
|
|
"NewErrorValidationMaxItems": text.NewErrorValidationMaxItems(3, 4),
|
|
"NewErrorValidationMinItems": text.NewErrorValidationMinItems(3, 2),
|
|
"NewErrorValidationUniqueItems": text.NewErrorValidationUniqueItems(0, 2),
|
|
"NewErrorValidationWrongType": text.NewErrorValidationWrongType([]string{"{allowed_types_list}"}, "{actual_type}"),
|
|
"NewErrorValidationConst": text.NewErrorValidationConst("{expected}"),
|
|
"NewErrorValidationConstGeneric": text.NewErrorValidationConstGeneric(),
|
|
"NewErrorValidationPasswordPolicyViolationGeneric": text.NewErrorValidationPasswordPolicyViolationGeneric("{reason}"),
|
|
"NewErrorValidationPasswordIdentifierTooSimilar": text.NewErrorValidationPasswordIdentifierTooSimilar(),
|
|
"NewErrorValidationPasswordMinLength": text.NewErrorValidationPasswordMinLength(6, 5),
|
|
"NewErrorValidationPasswordMaxLength": text.NewErrorValidationPasswordMaxLength(72, 80),
|
|
"NewErrorValidationPasswordTooManyBreaches": text.NewErrorValidationPasswordTooManyBreaches(101),
|
|
"NewErrorValidationPasswordNewSameAsOld": text.NewErrorValidationPasswordNewSameAsOld(),
|
|
"NewErrorValidationInvalidCredentials": text.NewErrorValidationInvalidCredentials(),
|
|
"NewErrorValidationDuplicateCredentials": text.NewErrorValidationDuplicateCredentials(),
|
|
"NewErrorValidationDuplicateCredentialsWithHints": text.NewErrorValidationDuplicateCredentialsWithHints([]string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}, "{credential_identifier_hint}"),
|
|
"NewErrorValidationDuplicateCredentialsOnOIDCLink": text.NewErrorValidationDuplicateCredentialsOnOIDCLink(),
|
|
"NewErrorValidationTOTPVerifierWrong": text.NewErrorValidationTOTPVerifierWrong(),
|
|
"NewErrorValidationWebAuthnVerifierWrong": text.NewErrorValidationWebAuthnVerifierWrong(),
|
|
"NewErrorValidationDeviceAuthnVerifierWrong": text.NewErrorValidationDeviceAuthnVerifierWrong(),
|
|
"NewErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid": text.NewErrorValidationDeviceAuthnRelaxedAttestationNoLongerValid(),
|
|
"NewErrorValidationDeviceAuthnKeyReenrollmentRequired": text.NewErrorValidationDeviceAuthnKeyReenrollmentRequired(),
|
|
"NewErrorValidationLookupAlreadyUsed": text.NewErrorValidationLookupAlreadyUsed(),
|
|
"NewErrorValidationLookupInvalid": text.NewErrorValidationLookupInvalid(),
|
|
"NewErrorValidationIdentifierMissing": text.NewErrorValidationIdentifierMissing(),
|
|
"NewErrorValidationAddressNotVerified": text.NewErrorValidationAddressNotVerified(),
|
|
"NewErrorValidationNoTOTPDevice": text.NewErrorValidationNoTOTPDevice(),
|
|
"NewErrorValidationNoLookup": text.NewErrorValidationNoLookup(),
|
|
"NewErrorValidationNoWebAuthnDevice": text.NewErrorValidationNoWebAuthnDevice(),
|
|
"NewInfoLoginReAuth": text.NewInfoLoginReAuth(),
|
|
"NewInfoLoginMFA": text.NewInfoLoginMFA(),
|
|
"NewInfoLoginTOTPLabel": text.NewInfoLoginTOTPLabel(),
|
|
"NewInfoLoginLookupLabel": text.NewInfoLoginLookupLabel(),
|
|
"NewInfoLogin": text.NewInfoLogin(),
|
|
"NewInfoLoginAndLink": text.NewInfoLoginAndLink(),
|
|
"NewInfoLoginLinkMessage": text.NewInfoLoginLinkMessage("{duplicateIdentifier}", "{provider}", "{newLoginUrl}", []string{"{available_credential_types_list}"}, []string{"{available_oidc_providers_list}"}),
|
|
"NewInfoLoginTOTP": text.NewInfoLoginTOTP(),
|
|
"NewInfoLoginLookup": text.NewInfoLoginLookup(),
|
|
"NewInfoLoginVerify": text.NewInfoLoginVerify(),
|
|
"NewInfoLoginWith": text.NewInfoLoginWith("{provider}", "{providerID}"),
|
|
"NewInfoLoginWithAndLink": text.NewInfoLoginWithAndLink("{provider}"),
|
|
"NewErrorValidationNoDeviceAuthnDevice": text.NewErrorValidationNoDeviceAuthnDevice(),
|
|
"NewErrorValidationLoginFlowExpired": text.NewErrorValidationLoginFlowExpired(docExpiredClock, aSecondAgo),
|
|
"NewErrorValidationLoginNoStrategyFound": text.NewErrorValidationLoginNoStrategyFound(),
|
|
"NewErrorValidationRegistrationNoStrategyFound": text.NewErrorValidationRegistrationNoStrategyFound(),
|
|
"NewErrorValidationSettingsNoStrategyFound": text.NewErrorValidationSettingsNoStrategyFound(),
|
|
"NewErrorValidationRecoveryNoStrategyFound": text.NewErrorValidationRecoveryNoStrategyFound(),
|
|
"NewErrorValidationVerificationNoStrategyFound": text.NewErrorValidationVerificationNoStrategyFound(),
|
|
"NewInfoSelfServiceLoginWebAuthn": text.NewInfoSelfServiceLoginWebAuthn(),
|
|
"NewInfoSelfServiceLoginDeviceAuthn": text.NewInfoSelfServiceLoginDeviceAuthn(),
|
|
"NewInfoRegistration": text.NewInfoRegistration(),
|
|
"NewInfoRegistrationWith": text.NewInfoRegistrationWith("{provider}", "{providerID}"),
|
|
"NewInfoRegistrationContinue": text.NewInfoRegistrationContinue(),
|
|
"NewInfoRegistrationBack": text.NewInfoRegistrationBack(),
|
|
"NewInfoSelfServiceChooseCredentials": text.NewInfoSelfServiceChooseCredentials(),
|
|
"NewErrorValidationRegistrationFlowExpired": text.NewErrorValidationRegistrationFlowExpired(docExpiredClock, aSecondAgo),
|
|
"NewErrorValidationRecoveryFlowExpired": text.NewErrorValidationRecoveryFlowExpired(docExpiredClock, aSecondAgo),
|
|
"NewRecoverySuccessful": text.NewRecoverySuccessful(docUntilClock, inAMinute),
|
|
"NewRecoveryEmailSent": text.NewRecoveryEmailSent(),
|
|
"NewRecoveryEmailWithCodeSent": text.NewRecoveryEmailWithCodeSent(),
|
|
"NewRecoveryCodeRecoverySelectAddressSent": text.NewRecoveryCodeRecoverySelectAddressSent("{masked_address}"),
|
|
"NewRecoveryAskAnyRecoveryAddress": text.NewRecoveryAskAnyRecoveryAddress(),
|
|
"NewRecoveryAskForFullAddress": text.NewRecoveryAskForFullAddress(),
|
|
"NewRecoveryAskToChooseAddress": text.NewRecoveryAskToChooseAddress(),
|
|
"NewRecoveryBack": text.NewRecoveryBack(),
|
|
"NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed": text.NewErrorValidationRecoveryTokenInvalidOrAlreadyUsed(),
|
|
"NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed": text.NewErrorValidationRecoveryCodeInvalidOrAlreadyUsed(),
|
|
"NewErrorValidationRecoveryRetrySuccess": text.NewErrorValidationRecoveryRetrySuccess(),
|
|
"NewErrorValidationRecoveryStateFailure": text.NewErrorValidationRecoveryStateFailure(),
|
|
"NewInfoNodeInputEmail": text.NewInfoNodeInputEmail(),
|
|
"NewInfoNodeInputEmailOrPhone": text.NewInfoNodeInputEmailOrPhone(),
|
|
"NewInfoNodeResendOTP": text.NewInfoNodeResendOTP(),
|
|
"NewInfoNodeLoginAndLinkCredential": text.NewInfoNodeLoginAndLinkCredential(),
|
|
"NewInfoNodeLabelContinue": text.NewInfoNodeLabelContinue(),
|
|
"NewInfoSelfServiceSettingsRegisterWebAuthn": text.NewInfoSelfServiceSettingsRegisterWebAuthn(),
|
|
"NewInfoSelfServiceSettingsRegisterPasskey": text.NewInfoSelfServiceSettingsRegisterPasskey(),
|
|
"NewInfoLoginWebAuthnPasswordless": text.NewInfoLoginWebAuthnPasswordless(),
|
|
"NewInfoSelfServiceRegistrationRegisterWebAuthn": text.NewInfoSelfServiceRegistrationRegisterWebAuthn(),
|
|
"NewInfoSelfServiceContinueLoginWebAuthn": text.NewInfoSelfServiceContinueLoginWebAuthn(),
|
|
"NewInfoSelfServiceLoginPasskey": text.NewInfoSelfServiceLoginPasskey(),
|
|
"NewInfoSelfServiceRegistrationRegisterPasskey": text.NewInfoSelfServiceRegistrationRegisterPasskey(),
|
|
"NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(),
|
|
"NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(),
|
|
"NewRegistrationEmailWithCodeSent": text.NewRegistrationEmailWithCodeSent(),
|
|
"NewLoginCodeSent": text.NewLoginCodeSent(),
|
|
"NewLoginCodeSentForAuthenticatedUser": text.NewLoginCodeSentForAuthenticatedUser(),
|
|
"NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed": text.NewErrorValidationRegistrationCodeInvalidOrAlreadyUsed(),
|
|
"NewErrorValidationLoginCodeInvalidOrAlreadyUsed": text.NewErrorValidationLoginCodeInvalidOrAlreadyUsed(),
|
|
"NewErrorValidationNoCodeUser": text.NewErrorValidationNoCodeUser(),
|
|
"NewInfoNodeLabelRegistrationCode": text.NewInfoNodeLabelRegistrationCode(),
|
|
"NewInfoNodeLabelLoginCode": text.NewInfoNodeLabelLoginCode(),
|
|
"NewErrorValidationLoginRetrySuccessful": text.NewErrorValidationLoginRetrySuccessful(),
|
|
"NewErrorValidationTraitsMismatch": text.NewErrorValidationTraitsMismatch(),
|
|
"NewInfoSelfServiceLoginCode": text.NewInfoSelfServiceLoginCode(),
|
|
"NewErrorValidationRegistrationRetrySuccessful": text.NewErrorValidationRegistrationRetrySuccessful(),
|
|
"NewInfoSelfServiceRegistrationRegisterCode": text.NewInfoSelfServiceRegistrationRegisterCode(),
|
|
"NewErrorValidationLoginLinkedCredentialsDoNotMatch": text.NewErrorValidationLoginLinkedCredentialsDoNotMatch(),
|
|
"NewErrorValidationAddressUnknown": text.NewErrorValidationAddressUnknown(),
|
|
"NewInfoSelfServiceLoginCodeMFA": text.NewInfoSelfServiceLoginCodeMFA(),
|
|
"NewInfoLoginPassword": text.NewInfoLoginPassword(),
|
|
"NewErrorValidationAccountNotFound": text.NewErrorValidationAccountNotFound(),
|
|
"NewInfoSelfServiceLoginAAL2CodeAddress": text.NewInfoSelfServiceLoginAAL2CodeAddress("{channel}", "{address}"),
|
|
"NewErrorCaptchaFailed": text.NewErrorCaptchaFailed(),
|
|
"NewCaptchaContainerMessage": text.NewCaptchaContainerMessage(),
|
|
"NewErrorValidationEmail": text.NewErrorValidationEmail("{value}"),
|
|
"NewErrorValidationPhone": text.NewErrorValidationPhone("{value}"),
|
|
"NewErrorValidationIdentityDisabled": text.NewErrorValidationIdentityDisabled(),
|
|
"NewErrorValidationSettingsTooManyAddressChanges": text.NewErrorValidationSettingsTooManyAddressChanges(),
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
if os.Args[1] == "elements" {
|
|
path, err := filepath.Abs(os.Args[2])
|
|
if err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to determine absolute path for elements generation: %+v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := generateElements(path); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate locales for elements: %+v", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := clidoc.Generate(cmd.NewRootCmd(nil, nil), []string{filepath.Join(os.Args[2], "cli")}); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate CLI docs: %+v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := validateAllMessages(filepath.Join(os.Args[1], "text")); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to validate messages: %+v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
sortedMessages := sortMessages()
|
|
for i := 1; i < len(sortedMessages); i++ {
|
|
if sortedMessages[i].ID == sortedMessages[i-1].ID {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Message ID %d is used more than once: %q %q\n", sortedMessages[i].ID, sortedMessages[i].Text, sortedMessages[i-1].Text)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
if err := writeMessages(filepath.Join(os.Args[2], "concepts/ui-messages.md"), sortedMessages); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate message table: %+v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err := writeMessagesJson(filepath.Join(os.Args[2], "concepts/messages.json"), sortedMessages); err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to generate messages.json: %+v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("All files have been generated and updated.")
|
|
}
|
|
|
|
func codeEncode(in interface{}) string {
|
|
out, err := json.MarshalIndent(in, "", " ")
|
|
if err != nil {
|
|
_, _ = fmt.Fprintf(os.Stderr, "Unable to encode to JSON: %+v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
return string(out)
|
|
}
|
|
|
|
func sortMessages() []*text.Message {
|
|
var toSort []*text.Message
|
|
for _, m := range messages {
|
|
toSort = append(toSort, m)
|
|
}
|
|
|
|
sort.Slice(toSort, func(i, j int) bool {
|
|
if toSort[i].ID == toSort[j].ID {
|
|
return toSort[i].Text < toSort[j].Text
|
|
}
|
|
return toSort[i].ID < toSort[j].ID
|
|
})
|
|
|
|
return toSort
|
|
}
|
|
|
|
func writeMessages(path string, sortedMessages []*text.Message) error {
|
|
//nolint:gosec
|
|
content, err := os.ReadFile(path) // #nosec
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var w bytes.Buffer
|
|
for _, m := range sortedMessages {
|
|
fmt.Fprintf(&w, `###### %s (%d)
|
|
|
|
%s
|
|
|
|
`, m.Text, m.ID, "```json\n"+codeEncode(m)+"\n```")
|
|
}
|
|
|
|
r := regexp.MustCompile(`(?s)<!-- START MESSAGE TABLE -->(.*?)<!-- END MESSAGE TABLE -->`)
|
|
result := r.ReplaceAllString(string(content), "<!-- START MESSAGE TABLE -->\n"+w.String()+"\n<!-- END MESSAGE TABLE -->")
|
|
|
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) // #nosec
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := f.WriteString(result); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeMessagesJson(path string, sortedMessages []*text.Message) error {
|
|
result := codeEncode(sortedMessages)
|
|
|
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644) // #nosec
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := f.WriteString(result); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateAllMessages(path string) error {
|
|
type message struct {
|
|
ID, Name string
|
|
}
|
|
|
|
usedIDs := make([]message, 0, len(messages))
|
|
set := token.NewFileSet()
|
|
packs, err := parser.ParseDir(set, path, nil, 0)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to parse text directory")
|
|
}
|
|
info := &types.Info{
|
|
Defs: make(map[*ast.Ident]types.Object),
|
|
}
|
|
|
|
//nolint:staticcheck
|
|
var pack *ast.Package
|
|
for _, p := range packs {
|
|
if p.Name == "text" {
|
|
pack = p
|
|
break
|
|
}
|
|
}
|
|
allFiles := make([]*ast.File, 0)
|
|
for fn, f := range pack.Files {
|
|
if strings.HasSuffix(fn, "/id.go") {
|
|
allFiles = append(allFiles, f)
|
|
}
|
|
}
|
|
_, err = (&types.Config{Importer: importer.Default()}).Check("text", set, allFiles, info)
|
|
if err != nil {
|
|
return err // type error
|
|
}
|
|
|
|
for _, f := range pack.Files {
|
|
for _, d := range f.Decls {
|
|
switch decl := d.(type) {
|
|
case *ast.FuncDecl:
|
|
if name := decl.Name.String(); decl.Name.IsExported() && strings.HasPrefix(name, "New") {
|
|
if _, ok := messages[name]; !ok {
|
|
return errors.Errorf("expected to find message %s in the list for the documentation generation but could not", name)
|
|
}
|
|
}
|
|
case *ast.GenDecl:
|
|
if decl.Tok == token.CONST {
|
|
for _, spec := range decl.Specs {
|
|
value := spec.(*ast.ValueSpec) // safe because decl.Tok is token.CONST
|
|
if t, ok := value.Type.(*ast.Ident); ok {
|
|
if t.Name == "ID" {
|
|
for _, name := range value.Names {
|
|
c := info.ObjectOf(name)
|
|
if c == nil {
|
|
return errors.Errorf("expected to find const %s in text/id.go", name.Name)
|
|
}
|
|
usedIDs = append(usedIDs, message{
|
|
ID: c.(*types.Const).Val().ExactString(),
|
|
Name: name.Name,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Slice(usedIDs, func(i, j int) bool {
|
|
return usedIDs[i].ID < usedIDs[j].ID
|
|
})
|
|
for i := 1; i < len(usedIDs); i++ {
|
|
if usedIDs[i].ID == usedIDs[i-1].ID {
|
|
return errors.Errorf("message ID %s is used more than once: %s %s", usedIDs[i].ID, usedIDs[i].Name, usedIDs[i-1].Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateElements(messageFilePath string) error {
|
|
// If the file at messageFilePath does not exist, create it
|
|
f, err := os.OpenFile(messageFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) // #nosec
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to open or create message file at %s", messageFilePath)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return errors.Wrapf(err, "unable to close message file at %s", messageFilePath)
|
|
}
|
|
|
|
sortedMessages := sortMessages()
|
|
if err := writeMessagesJson(messageFilePath, sortedMessages); err != nil {
|
|
return errors.Wrapf(err, "unable to write messages json to %s", messageFilePath)
|
|
}
|
|
return nil
|
|
}
|