package config import ( "errors" "flag" "fmt" "log" "os" "strconv" "time" ) type rooms []string type configOption[T any] struct { flagName string flagDefaultValue T flagHelpBase string envVarName string } type configOptions struct { userID configOption[string] token configOption[string] rooms configOption[rooms] recursive configOption[bool] recursionMaxDepth configOption[int] recursionSuggestedOnly configOption[bool] json configOption[bool] outputHomeserverMemberCount configOption[bool] homeserverVersionInfoTimeout configOption[time.Duration] } type Config struct { UserID string Token string Rooms rooms Recursive bool RecursionMaxDepth *int RecursionSuggestedOnly bool JSON bool OutputHomeserverMemberCount bool HomeserverVersionInfoTimeout time.Duration } var configOpts = configOptions{ userID: configOption[string]{"user-id", "", "The Matrix user ID to use.", "MRVC_USER_ID"}, token: configOption[string]{"token", "", "The Matrix access token to use.", "MRVC_TOKEN"}, rooms: configOption[rooms]{"room", []string{}, "The Matrix room to check. The flag can be set multiple times to check multiple rooms.", "MRVC_ROOM"}, recursive: configOption[bool]{"recursive", false, "Recursively check the child rooms for the given rooms (spaces) as well.", "MRVC_RECURSIVE"}, recursionMaxDepth: configOption[int]{"recursion-max-depth", 0, "The maximum depth when recursively getting child rooms. Defaults to 0, which gets interpreted as the servers default.", "MRVC_RECURSION_MAX_DEPTH"}, recursionSuggestedOnly: configOption[bool]{"recursion-suggested-only", false, "Only include the suggested children, when recursively getting child rooms.", "MRVC_RECURSION_SUGGESTED_ONLY"}, json: configOption[bool]{"json", false, "Output result as JSON.", "MRVC_JSON"}, outputHomeserverMemberCount: configOption[bool]{"output-homeserver-member-count", false, "Output the member count for each homeserver.", "MRVC_OUTPUT_HOMESERVER_MEMBER_COUNT"}, homeserverVersionInfoTimeout: configOption[time.Duration]{"homeserver-version-info-timeout", time.Second * 5, "Timeout for getting the homeservers version information per homeserver.", "MRVC_HOMESERVER_VERSION_INFO_TIMEOUT"}, } func (r *rooms) String() string { return fmt.Sprint(*r) } func (r *rooms) Set(value string) error { *r = append(*r, value) return nil } func (configOpt configOption[T]) getFlagHelp() string { return fmt.Sprintf("%s (EnvVar: %s)", configOpt.flagHelpBase, configOpt.envVarName) } func (configOpt configOption[T]) getFlagArgs() (string, T, string) { return configOpt.flagName, configOpt.flagDefaultValue, configOpt.getFlagHelp() } // Functions for getting config values from flags and environment variables. // Flags take precedence over environment variables. func (configOpt configOption[T]) getConfigValueWithDefault(configFlag *T, visitedFlags map[string]bool, envVarParser func(string) T) T { if visitedFlags[configOpt.flagName] { return *configFlag } else if envVar, ok := os.LookupEnv(configOpt.envVarName); ok { return envVarParser(envVar) } else { return configOpt.flagDefaultValue } } // This function can be used to ensure some configuration options got explicitly set. func (configOpt configOption[T]) getConfigValueWithError(configFlag *T, visitedFlags map[string]bool, envVarParser func(string) T) (T, error) { if visitedFlags[configOpt.flagName] { return *configFlag, nil } else if envVar, ok := os.LookupEnv(configOpt.envVarName); ok { return envVarParser(envVar), nil } else { return configOpt.flagDefaultValue, errors.New("no command-line flag or environment variable set") } } var userIdFlag = flag.String(configOpts.userID.getFlagArgs()) var tokenFlag = flag.String(configOpts.token.getFlagArgs()) var recursiveFlag = flag.Bool(configOpts.recursive.getFlagArgs()) var recursionMaxDepthFlag = flag.Int(configOpts.recursionMaxDepth.getFlagArgs()) var recursionSuggestedOnlyFlag = flag.Bool(configOpts.recursionSuggestedOnly.getFlagArgs()) var jsonFlag = flag.Bool(configOpts.json.getFlagArgs()) var outputHomeserverMemberCountFlag = flag.Bool(configOpts.outputHomeserverMemberCount.getFlagArgs()) var homeserverVersionInfoTimeoutFlag = flag.Duration(configOpts.homeserverVersionInfoTimeout.getFlagArgs()) var roomsFlag rooms func init() { flag.Var(&roomsFlag, configOpts.rooms.flagName, configOpts.rooms.getFlagHelp()) } func Get() Config { flag.Parse() var config Config visitedFlags := make(map[string]bool) flag.Visit(func(flag *flag.Flag) { visitedFlags[flag.Name] = true }) var err error config.UserID, err = configOpts.userID.getConfigValueWithError(userIdFlag, visitedFlags, func(envVar string) string { return envVar }) if err != nil { log.Fatal("A Matrix user ID must be provided.") } config.Token, err = configOpts.token.getConfigValueWithError(tokenFlag, visitedFlags, func(envVar string) string { return envVar }) if err != nil { log.Fatal("A Matrix access token must be provided.") } config.Rooms, err = configOpts.rooms.getConfigValueWithError(&roomsFlag, visitedFlags, func(envVar string) rooms { return []string{envVar} }) if err != nil { log.Fatal("A Matrix room must be provided.") } config.Recursive = configOpts.recursive.getConfigValueWithDefault(recursiveFlag, visitedFlags, func(envVar string) bool { parsedEnvVar, err := strconv.ParseBool(envVar) if err != nil { log.Printf("Error parsing %s:\n", configOpts.recursive.envVarName) log.Fatal(err) } return parsedEnvVar }) givenRecursionMaxDepth := configOpts.recursionMaxDepth.getConfigValueWithDefault(recursionMaxDepthFlag, visitedFlags, func(envVar string) int { parsedEnvVar, err := strconv.Atoi(envVar) if err != nil { log.Printf("Error parsing %s:\n", configOpts.recursionMaxDepth.envVarName) log.Fatal(err) } return parsedEnvVar }) // Since we actually want an *int, interpret 0 as nil as described in the help text. if givenRecursionMaxDepth != 0 { config.RecursionMaxDepth = &givenRecursionMaxDepth } else { config.RecursionMaxDepth = nil } config.RecursionSuggestedOnly = configOpts.recursionSuggestedOnly.getConfigValueWithDefault(recursionSuggestedOnlyFlag, visitedFlags, func(envVar string) bool { parsedEnvVar, err := strconv.ParseBool(envVar) if err != nil { log.Printf("Error parsing %s:\n", configOpts.recursionSuggestedOnly.envVarName) log.Fatal(err) } return parsedEnvVar }) config.JSON = configOpts.json.getConfigValueWithDefault(jsonFlag, visitedFlags, func(envVar string) bool { parsedEnvVar, err := strconv.ParseBool(envVar) if err != nil { log.Printf("Error parsing %s:\n", configOpts.json.envVarName) log.Fatal(err) } return parsedEnvVar }) config.OutputHomeserverMemberCount = configOpts.outputHomeserverMemberCount.getConfigValueWithDefault(outputHomeserverMemberCountFlag, visitedFlags, func(envVar string) bool { parsedEnvVar, err := strconv.ParseBool(envVar) if err != nil { log.Printf("Error parsing %s:\n", configOpts.outputHomeserverMemberCount.envVarName) log.Fatal(err) } return parsedEnvVar }) config.HomeserverVersionInfoTimeout = configOpts.homeserverVersionInfoTimeout.getConfigValueWithDefault(homeserverVersionInfoTimeoutFlag, visitedFlags, func(envVar string) time.Duration { parsedEnvVar, err := time.ParseDuration(envVar) if err != nil { log.Printf("Error parsing %s:\n", configOpts.homeserverVersionInfoTimeout.envVarName) log.Fatal(err) } return parsedEnvVar }) return config }