From 015e778e5afbcb8e7f48cc160c7c8e921cc41860 Mon Sep 17 00:00:00 2001 From: Jahvon Dockery Date: Wed, 9 Oct 2024 21:22:58 -0400 Subject: [PATCH] feat: more customization commands (#175) --- cmd/internal/config.go | 102 +++++++++++++++++++++- cmd/internal/exec.go | 4 +- cmd/internal/flags/types.go | 6 ++ cmd/internal/helpers.go | 3 +- cmd/internal/library.go | 2 +- cmd/internal/secret.go | 4 +- cmd/internal/workspace.go | 2 +- docs/cli/flow_config_set.md | 3 + docs/cli/flow_config_set_notifications.md | 27 ++++++ docs/cli/flow_config_set_theme.md | 26 ++++++ docs/cli/flow_config_set_timeout.md | 26 ++++++ docs/guide/interactive.md | 37 ++++++++ docs/guide/templating.md | 5 +- docs/schemas/config_schema.json | 17 ++++ docs/types/config.md | 2 + go.mod | 2 +- go.sum | 4 +- internal/context/context.go | 10 ++- internal/io/styles.go | 9 +- internal/templates/form.go | 2 +- tests/utils/context.go | 4 +- types/config/config.gen.go | 17 ++++ types/config/config.go | 60 +++++++++---- types/config/schema.yaml | 14 +++ types/executable/executable.go | 7 ++ 25 files changed, 360 insertions(+), 35 deletions(-) create mode 100644 docs/cli/flow_config_set_notifications.md create mode 100644 docs/cli/flow_config_set_theme.md create mode 100644 docs/cli/flow_config_set_timeout.md diff --git a/cmd/internal/config.go b/cmd/internal/config.go index 8e00d33..7a24770 100644 --- a/cmd/internal/config.go +++ b/cmd/internal/config.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" "strings" + "time" tuikitIO "github.com/jahvon/tuikit/io" "github.com/jahvon/tuikit/types" @@ -44,7 +45,7 @@ func registerConfigResetCmd(ctx *context.Context, configCmd *cobra.Command) { func resetConfigFunc(ctx *context.Context, _ *cobra.Command, _ []string) { logger := ctx.Logger form, err := views.NewForm( - io.Theme(), + io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ @@ -80,6 +81,9 @@ func registerSetConfigCmd(ctx *context.Context, configCmd *cobra.Command) { registerSetWorkspaceModeCmd(ctx, setCmd) registerSetLogModeCmd(ctx, setCmd) registerSetTUICmd(ctx, setCmd) + registerSetNotificationsCmd(ctx, setCmd) + registerSetThemeCmd(ctx, setCmd) + registerSetTimeoutCmd(ctx, setCmd) configCmd.AddCommand(setCmd) } @@ -190,6 +194,102 @@ func setInteractiveFunc(ctx *context.Context, _ *cobra.Command, args []string) { logger.PlainTextSuccess("Interactive UI " + strVal) } +func registerSetNotificationsCmd(ctx *context.Context, setCmd *cobra.Command) { + notificationsCmd := &cobra.Command{ + Use: "notifications [true|false]", + Short: "Enable or disable notifications.", + Args: cobra.ExactArgs(1), + ValidArgs: []string{"true", "false"}, + Run: func(cmd *cobra.Command, args []string) { setNotificationsFunc(ctx, cmd, args) }, + } + RegisterFlag(ctx, notificationsCmd, *flags.SetSoundNotificationFlag) + setCmd.AddCommand(notificationsCmd) +} + +func setNotificationsFunc(ctx *context.Context, cmd *cobra.Command, args []string) { + logger := ctx.Logger + enabled, err := strconv.ParseBool(args[0]) + if err != nil { + logger.FatalErr(errors.Wrap(err, "invalid boolean value")) + } + sound := flags.ValueFor[bool](ctx, cmd, *flags.SetSoundNotificationFlag, false) + + userConfig := ctx.Config + if userConfig.Interactive == nil { + userConfig.Interactive = &config.Interactive{} + } + userConfig.Interactive.NotifyOnCompletion = &enabled + if sound { + userConfig.Interactive.SoundOnCompletion = &enabled + } + if err := filesystem.WriteConfig(userConfig); err != nil { + logger.FatalErr(err) + } + strVal := "disabled" + if enabled { + strVal = "enabled" + } + logger.PlainTextSuccess("Notifications " + strVal) +} + +func registerSetThemeCmd(ctx *context.Context, setCmd *cobra.Command) { + themeCmd := &cobra.Command{ + Use: "theme [default|dark|light|dracula|tokyo-night]", + Short: "Set the theme for the TUI views", + Args: cobra.ExactArgs(1), + ValidArgs: []string{ + string(config.ConfigThemeDefault), + string(config.ConfigThemeDark), + string(config.ConfigThemeLight), + string(config.ConfigThemeDracula), + string(config.ConfigThemeTokyoNight), + }, + Run: func(cmd *cobra.Command, args []string) { setThemeFunc(ctx, cmd, args) }, + } + setCmd.AddCommand(themeCmd) +} + +func setThemeFunc(ctx *context.Context, _ *cobra.Command, args []string) { + logger := ctx.Logger + themeName := args[0] + + userConfig := ctx.Config + if userConfig.Interactive == nil { + userConfig.Interactive = &config.Interactive{} + } + userConfig.Theme = config.ConfigTheme(themeName) + if err := filesystem.WriteConfig(userConfig); err != nil { + logger.FatalErr(err) + } + logger.PlainTextSuccess("Theme set to " + themeName) +} + +func registerSetTimeoutCmd(ctx *context.Context, setCmd *cobra.Command) { + timeoutCmd := &cobra.Command{ + Use: "timeout DURATION", + Short: "Set the default timeout for executables.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { setTimeoutFunc(ctx, cmd, args) }, + } + setCmd.AddCommand(timeoutCmd) +} + +func setTimeoutFunc(ctx *context.Context, _ *cobra.Command, args []string) { + logger := ctx.Logger + timeoutStr := args[0] + timeout, err := time.ParseDuration(timeoutStr) + if err != nil { + logger.FatalErr(errors.Wrap(err, "invalid duration")) + } + + userConfig := ctx.Config + userConfig.DefaultTimeout = timeout + if err := filesystem.WriteConfig(userConfig); err != nil { + logger.FatalErr(err) + } + logger.PlainTextSuccess("Default timeout set to " + timeoutStr) +} + func registerViewConfigCmd(ctx *context.Context, configCmd *cobra.Command) { viewCmd := &cobra.Command{ Use: "view", diff --git a/cmd/internal/exec.go b/cmd/internal/exec.go index ebb9861..6effc3b 100644 --- a/cmd/internal/exec.go +++ b/cmd/internal/exec.go @@ -116,7 +116,7 @@ func execFunc(ctx *context.Context, cmd *cobra.Command, verb executable.Verb, ar setAuthEnv(ctx, cmd, e) textInputs := pendingFormFields(ctx, e) if len(textInputs) > 0 { - form, err := views.NewForm(io.Theme(), ctx.StdIn(), ctx.StdOut(), textInputs...) + form, err := views.NewForm(io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), textInputs...) if err != nil { logger.FatalErr(err) } @@ -177,7 +177,7 @@ func runByRef(ctx *context.Context, cmd *cobra.Command, argsStr string) error { func setAuthEnv(ctx *context.Context, _ *cobra.Command, executable *executable.Executable) { if authRequired(ctx, executable) { form, err := views.NewForm( - io.Theme(), + io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/internal/flags/types.go b/cmd/internal/flags/types.go index 4a763fb..fbce456 100644 --- a/cmd/internal/flags/types.go +++ b/cmd/internal/flags/types.go @@ -163,3 +163,9 @@ var TemplateFilePathFlag = &Metadata{ Default: "", Required: false, } + +var SetSoundNotificationFlag = &Metadata{ + Name: "sound", + Usage: "Update completion sound notification setting", + Default: false, +} diff --git a/cmd/internal/helpers.go b/cmd/internal/helpers.go index c442cb0..0c507b8 100644 --- a/cmd/internal/helpers.go +++ b/cmd/internal/helpers.go @@ -88,7 +88,8 @@ func WaitForTUI(ctx *context.Context, cmd *cobra.Command) { func printContext(ctx *context.Context, cmd *cobra.Command) { if TUIEnabled(ctx, cmd) { - ctx.Logger.Println(io.Theme().RenderHeader(context.AppName, context.HeaderCtxKey, ctx.String(), 0)) + ctx.Logger.Println(io.Theme(ctx.Config.Theme.String()). + RenderHeader(context.AppName, context.HeaderCtxKey, ctx.String(), 0)) } } diff --git a/cmd/internal/library.go b/cmd/internal/library.go index aa11ff2..85505ff 100644 --- a/cmd/internal/library.go +++ b/cmd/internal/library.go @@ -83,7 +83,7 @@ func libraryFunc(ctx *context.Context, cmd *cobra.Command, _ []string) { Tags: tagsFilter, Substring: subStr, }, - io.Theme(), + io.Theme(ctx.Config.Theme.String()), runFunc, ) SetView(ctx, cmd, libraryModel) diff --git a/cmd/internal/secret.go b/cmd/internal/secret.go index 34212e8..43d6210 100644 --- a/cmd/internal/secret.go +++ b/cmd/internal/secret.go @@ -47,7 +47,7 @@ func deleteSecretFunc(ctx *context.Context, _ *cobra.Command, args []string) { reference := args[0] form, err := views.NewForm( - io.Theme(), + io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ @@ -138,7 +138,7 @@ func setSecretFunc(ctx *context.Context, _ *cobra.Command, args []string) { switch { case len(args) == 1: form, err := views.NewForm( - io.Theme(), + io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/cmd/internal/workspace.go b/cmd/internal/workspace.go index 8c28006..74faae5 100644 --- a/cmd/internal/workspace.go +++ b/cmd/internal/workspace.go @@ -169,7 +169,7 @@ func deleteWsFunc(ctx *context.Context, _ *cobra.Command, args []string) { name := args[0] form, err := views.NewForm( - io.Theme(), + io.Theme(ctx.Config.Theme.String()), ctx.StdIn(), ctx.StdOut(), &views.FormField{ diff --git a/docs/cli/flow_config_set.md b/docs/cli/flow_config_set.md index 395b02b..5168eb7 100644 --- a/docs/cli/flow_config_set.md +++ b/docs/cli/flow_config_set.md @@ -21,6 +21,9 @@ Update flow configuration values. * [flow config](flow_config.md) - Update flow configuration values. * [flow config set log-mode](flow_config_set_log-mode.md) - Set the default log mode. * [flow config set namespace](flow_config_set_namespace.md) - Change the current namespace. +* [flow config set notifications](flow_config_set_notifications.md) - Enable or disable notifications. +* [flow config set theme](flow_config_set_theme.md) - Set the theme for the TUI views +* [flow config set timeout](flow_config_set_timeout.md) - Set the default timeout for executables. * [flow config set tui](flow_config_set_tui.md) - Enable or disable the interactive terminal UI experience. * [flow config set workspace-mode](flow_config_set_workspace-mode.md) - Switch between fixed and dynamic workspace modes. diff --git a/docs/cli/flow_config_set_notifications.md b/docs/cli/flow_config_set_notifications.md new file mode 100644 index 0000000..f075643 --- /dev/null +++ b/docs/cli/flow_config_set_notifications.md @@ -0,0 +1,27 @@ +## flow config set notifications + +Enable or disable notifications. + +``` +flow config set notifications [true|false] [flags] +``` + +### Options + +``` + -h, --help help for notifications + --sound Update completion sound notification setting +``` + +### Options inherited from parent commands + +``` + -x, --non-interactive Disable displaying flow output via terminal UI rendering. This is only needed if the interactive output is enabled by default in flow's configuration. + --sync Sync flow cache and workspaces + --verbosity int Log verbosity level (-1 to 1) +``` + +### SEE ALSO + +* [flow config set](flow_config_set.md) - Update flow configuration values. + diff --git a/docs/cli/flow_config_set_theme.md b/docs/cli/flow_config_set_theme.md new file mode 100644 index 0000000..ce8aadf --- /dev/null +++ b/docs/cli/flow_config_set_theme.md @@ -0,0 +1,26 @@ +## flow config set theme + +Set the theme for the TUI views + +``` +flow config set theme [default|dark|light|dracula|tokyo-night] [flags] +``` + +### Options + +``` + -h, --help help for theme +``` + +### Options inherited from parent commands + +``` + -x, --non-interactive Disable displaying flow output via terminal UI rendering. This is only needed if the interactive output is enabled by default in flow's configuration. + --sync Sync flow cache and workspaces + --verbosity int Log verbosity level (-1 to 1) +``` + +### SEE ALSO + +* [flow config set](flow_config_set.md) - Update flow configuration values. + diff --git a/docs/cli/flow_config_set_timeout.md b/docs/cli/flow_config_set_timeout.md new file mode 100644 index 0000000..b17c4a0 --- /dev/null +++ b/docs/cli/flow_config_set_timeout.md @@ -0,0 +1,26 @@ +## flow config set timeout + +Set the default timeout for executables. + +``` +flow config set timeout DURATION [flags] +``` + +### Options + +``` + -h, --help help for timeout +``` + +### Options inherited from parent commands + +``` + -x, --non-interactive Disable displaying flow output via terminal UI rendering. This is only needed if the interactive output is enabled by default in flow's configuration. + --sync Sync flow cache and workspaces + --verbosity int Log verbosity level (-1 to 1) +``` + +### SEE ALSO + +* [flow config set](flow_config_set.md) - Update flow configuration values. + diff --git a/docs/guide/interactive.md b/docs/guide/interactive.md index 0665c4c..ff830d9 100644 --- a/docs/guide/interactive.md +++ b/docs/guide/interactive.md @@ -3,6 +3,35 @@ The interactive TUI can be customized in the [flow config file](../types/config.md). Additionally, there are several [flow config commands](../cli/flow_config.md) that can be used to change the TUI settings. +> [!TIP] +> You can view your current settings with the config view command: +> ```shell +> flow config view +> ``` + +### Changing the TUI theme + +There are several themes available in the TUI: +- `default` (everforest) +- `light` +- `dark` +- `dracula` +- `tokyo-night` + +Use the following command to change the theme: + +```shell +flow config set theme (default|light|dark|dracula|tokyo-night) +``` + +### Changing desktop notification settings + +Desktop notifications can be sent when executables are completed. Use the following command to enable or disable desktop notifications: + +```shell +flow config set notifications (true|false) # --sound +``` + ### Changing the log mode There are 4 log modes available in the TUI: @@ -37,6 +66,14 @@ There are 2 workspace modes available in the TUI: See the [workspace guide](workspace.md) for more information on workspaces. +### Changing the default executable timeout + +The global default executable timeout is 30 minutes. Use the following command to change the default executable timeout: + +```shell +flow config set timeout +``` + ### Disable the TUI In some cases, you may want to disable the interactive TUI (in CI/CD pipelines and containers, for example). diff --git a/docs/guide/templating.md b/docs/guide/templating.md index 0a9a788..72bb495 100644 --- a/docs/guide/templating.md +++ b/docs/guide/templating.md @@ -1,3 +1,4 @@ -Guide coming soon... +flow can be used to render templates using the [flow template](../cli/flow_template.md) command. +Templates are defined using the templating language. The `flow template` command can be used to render templates using the following syntax: -In the meantime, see [Template](../types/template.md) and [flow template](../cli/flow_template.md). +```shell \ No newline at end of file diff --git a/docs/schemas/config_schema.json b/docs/schemas/config_schema.json index ffe6f78..6b6703d 100644 --- a/docs/schemas/config_schema.json +++ b/docs/schemas/config_schema.json @@ -45,6 +45,11 @@ "type": "string", "default": "logfmt" }, + "defaultTimeout": { + "description": "The default timeout to use when running executables.\nThis should be a valid duration string.\n", + "type": "string", + "default": "30m" + }, "interactive": { "$ref": "#/definitions/Interactive" }, @@ -56,6 +61,18 @@ "type": "string" } }, + "theme": { + "description": "The theme of the interactive UI.", + "type": "string", + "default": "default", + "enum": [ + "default", + "dark", + "light", + "dracula", + "tokyo-night" + ] + }, "workspaceMode": { "description": "The mode of the workspace. This can be either `fixed` or `dynamic`.\nIn `fixed` mode, the current workspace used at runtime is always the one set in the currentWorkspace config field.\nIn `dynamic` mode, the current workspace used at runtime is determined by the current directory.\nIf the current directory is within a workspace, that workspace is used.\n", "type": "string", diff --git a/docs/types/config.md b/docs/types/config.md index 8152587..a269b9d 100644 --- a/docs/types/config.md +++ b/docs/types/config.md @@ -26,8 +26,10 @@ Alternatively, a custom path can be set using the `FLOW_CONFIG_PATH` environment | `currentNamespace` | The name of the current namespace. Namespaces are used to reference executables in the CLI using the format `workspace:namespace/name`. If the namespace is not set, only executables defined without a namespace will be discovered. | `string` | | | | `currentWorkspace` | The name of the current workspace. This should match a key in the `workspaces` or `remoteWorkspaces` map. | `string` | | | | `defaultLogMode` | The default log mode to use when running executables. This can either be `hidden`, `json`, `logfmt` or `text` `hidden` will not display any logs. `json` will display logs in JSON format. `logfmt` will display logs with a log level, timestamp, and message. `text` will just display the log message. | `string` | logfmt | | +| `defaultTimeout` | The default timeout to use when running executables. This should be a valid duration string. | `string` | 30m | | | `interactive` | | [Interactive](#Interactive) | | | | `templates` | A map of flowfile template names to their paths. | `map` (`string` -> `string`) | map[] | | +| `theme` | The theme of the interactive UI. | `string` | default | | | `workspaceMode` | The mode of the workspace. This can be either `fixed` or `dynamic`. In `fixed` mode, the current workspace used at runtime is always the one set in the currentWorkspace config field. In `dynamic` mode, the current workspace used at runtime is determined by the current directory. If the current directory is within a workspace, that workspace is used. | `string` | dynamic | | | `workspaces` | Map of workspace names to their paths. The path should be a valid absolute path to the workspace directory. | `map` (`string` -> `string`) | | | diff --git a/go.mod b/go.mod index 6b2649b..dc18e6a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/itchyny/gojq v0.12.16 github.com/jahvon/glamour v0.8.1-patch3 github.com/jahvon/open-golang v0.0.0-20240522004812-68511c3bc9ef - github.com/jahvon/tuikit v0.0.25 + github.com/jahvon/tuikit v0.0.26 github.com/mattn/go-runewidth v0.0.16 github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/gomega v1.34.2 diff --git a/go.sum b/go.sum index 90030e1..13a5e7b 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/jahvon/glamour v0.8.1-patch3 h1:LfyMACZavV8yxK4UsPENNQQOqafWuq4ZdLuEA github.com/jahvon/glamour v0.8.1-patch3/go.mod h1:30MVJwG3rcEHrN277NrA4DKzndSL9lBtEmpcfOygwCQ= github.com/jahvon/open-golang v0.0.0-20240522004812-68511c3bc9ef h1:4PS/MNVT6Rsv15x5Rtwaw971e6kFvNUAf9nvUsZ5hcc= github.com/jahvon/open-golang v0.0.0-20240522004812-68511c3bc9ef/go.mod h1:dUmuT5CN6osIeLSRtTPJOf0Yz+qAbcyU6omnCzI+ZfQ= -github.com/jahvon/tuikit v0.0.25 h1:uVvuLCqO8fY9UelwS3ZZEzcT57jG8ENvz1wJXjCRCD0= -github.com/jahvon/tuikit v0.0.25/go.mod h1:zUysbTPUE0tAEB5DfdWvL6AT3g8AZQ+fcwrfUhVXuwA= +github.com/jahvon/tuikit v0.0.26 h1:G3qTYCqwZ94cQfyDB3z0y2aEHj9SyeKl/5dIgeEbxCc= +github.com/jahvon/tuikit v0.0.26/go.mod h1:zUysbTPUE0tAEB5DfdWvL6AT3g8AZQ+fcwrfUhVXuwA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/internal/context/context.go b/internal/context/context.go index a990b35..ce0e753 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -48,6 +48,11 @@ func NewContext(ctx context.Context, stdIn, stdOut *os.File) *Context { } cfg.SetDefaults() + if cfg.DefaultTimeout != 0 && os.Getenv(executable.TimeoutOverrideEnv) == "" { + // HACK: Set the default timeout as an environment variable to be used by the exec runner + // This is a temporary solution until the config handling is refactored a bit + _ = os.Setenv(executable.TimeoutOverrideEnv, cfg.DefaultTimeout.String()) + } wsConfig, err := currentWorkspace(cfg) if err != nil { panic(errors.Wrap(err, "workspace config load error")) @@ -73,7 +78,7 @@ func NewContext(ctx context.Context, stdIn, stdOut *os.File) *Context { CurrentWorkspace: wsConfig, WorkspacesCache: workspaceCache, ExecutableCache: executableCache, - Logger: io.NewLogger(stdOut, flowIO.Theme(), logMode, filesystem.LogsDir()), + Logger: io.NewLogger(stdOut, flowIO.Theme(cfg.Theme.String()), logMode, filesystem.LogsDir()), stdOut: stdOut, stdIn: stdIn, } @@ -83,11 +88,12 @@ func NewContext(ctx context.Context, stdIn, stdOut *os.File) *Context { tuikit.WithState(HeaderCtxKey, c.String()), tuikit.WithLoadingMsg("thinking..."), ) + c.TUIContainer, err = tuikit.NewContainer( ctx, app, tuikit.WithInput(stdIn), tuikit.WithOutput(stdOut), - tuikit.WithTheme(flowIO.Theme()), + tuikit.WithTheme(flowIO.Theme(cfg.Theme.String())), ) if err != nil { panic(errors.Wrap(err, "TUI container initialization error")) diff --git a/internal/io/styles.go b/internal/io/styles.go index e0ceb6b..0f653c3 100644 --- a/internal/io/styles.go +++ b/internal/io/styles.go @@ -2,6 +2,11 @@ package io import "github.com/jahvon/tuikit/styles" -func Theme() styles.Theme { - return styles.EverforestTheme() +func Theme(name string) styles.Theme { + theme := styles.EverforestTheme() + themeFunc, ok := styles.AllThemes()[name] + if ok { + theme = themeFunc() + } + return theme } diff --git a/internal/templates/form.go b/internal/templates/form.go index 3ff0255..5911b1a 100644 --- a/internal/templates/form.go +++ b/internal/templates/form.go @@ -33,7 +33,7 @@ func showForm(ctx *context.Context, fields executable.FormFields) error { ValidationExpr: f.Validate, }) } - form, err := views.NewForm(io.Theme(), in, out, ff...) + form, err := views.NewForm(io.Theme(ctx.Config.Theme.String()), in, out, ff...) if err != nil { return fmt.Errorf("encountered form init error: %w", err) } diff --git a/tests/utils/context.go b/tests/utils/context.go index db1c52a..3f1cbf6 100644 --- a/tests/utils/context.go +++ b/tests/utils/context.go @@ -36,7 +36,7 @@ const ( // Test environment variables are set the config and cache directories override paths. func NewContext(ctx stdCtx.Context, t ginkgo.FullGinkgoTInterface) *context.Context { stdOut, stdIn := createTempIOFiles(t) - logger := tuikitIO.NewLogger(stdOut, io.Theme(), tuikitIO.Text, "") + logger := tuikitIO.NewLogger(stdOut, io.Theme(""), tuikitIO.Text, "") ctxx := newTestContext(ctx, t, logger, stdIn, stdOut) return ctxx } @@ -93,7 +93,7 @@ func ResetTestContext(ctx *context.Context, t ginkgo.FullGinkgoTInterface) { ctx.Ctx = stdCtx.Background() stdIn, stdOut := createTempIOFiles(t) ctx.SetIO(stdIn, stdOut) - logger := tuikitIO.NewLogger(stdOut, io.Theme(), tuikitIO.Text, "") + logger := tuikitIO.NewLogger(stdOut, io.Theme(""), tuikitIO.Text, "") ctx.Logger = logger } diff --git a/types/config/config.gen.go b/types/config/config.gen.go index 47e93b7..a2f930b 100644 --- a/types/config/config.gen.go +++ b/types/config/config.gen.go @@ -3,6 +3,7 @@ package config import "github.com/jahvon/tuikit/io" +import "time" // User Configuration for the Flow CLI. // Includes configurations for workspaces, templates, I/O, and other settings for @@ -39,12 +40,20 @@ type Config struct { // DefaultLogMode io.LogMode `json:"defaultLogMode,omitempty" yaml:"defaultLogMode,omitempty" mapstructure:"defaultLogMode,omitempty"` + // The default timeout to use when running executables. + // This should be a valid duration string. + // + DefaultTimeout time.Duration `json:"defaultTimeout,omitempty" yaml:"defaultTimeout,omitempty" mapstructure:"defaultTimeout,omitempty"` + // Interactive corresponds to the JSON schema field "interactive". Interactive *Interactive `json:"interactive,omitempty" yaml:"interactive,omitempty" mapstructure:"interactive,omitempty"` // A map of flowfile template names to their paths. Templates ConfigTemplates `json:"templates,omitempty" yaml:"templates,omitempty" mapstructure:"templates,omitempty"` + // The theme of the interactive UI. + Theme ConfigTheme `json:"theme,omitempty" yaml:"theme,omitempty" mapstructure:"theme,omitempty"` + // The mode of the workspace. This can be either `fixed` or `dynamic`. // In `fixed` mode, the current workspace used at runtime is always the one set in // the currentWorkspace config field. @@ -63,6 +72,14 @@ type Config struct { // A map of flowfile template names to their paths. type ConfigTemplates map[string]string +type ConfigTheme string + +const ConfigThemeDark ConfigTheme = "dark" +const ConfigThemeDefault ConfigTheme = "default" +const ConfigThemeDracula ConfigTheme = "dracula" +const ConfigThemeLight ConfigTheme = "light" +const ConfigThemeTokyoNight ConfigTheme = "tokyo-night" + type ConfigWorkspaceMode string const ConfigWorkspaceModeDynamic ConfigWorkspaceMode = "dynamic" diff --git a/types/config/config.go b/types/config/config.go index 632e12b..3625008 100644 --- a/types/config/config.go +++ b/types/config/config.go @@ -3,6 +3,7 @@ package config import ( "encoding/json" "fmt" + "slices" tuikitIO "github.com/jahvon/tuikit/io" "golang.org/x/exp/maps" @@ -79,39 +80,68 @@ func (c *Config) JSON() (string, error) { return string(jsonBytes), nil } +//nolint:gocognit func (c *Config) Markdown() string { mkdwn := "# Global Configurations\n" - mkdwn += fmt.Sprintf("## Current workspace\n%s\n", c.CurrentWorkspace) + mkdwn += fmt.Sprintf("**Current workspace:** `%s`\n", c.CurrentWorkspace) if c.WorkspaceMode == ConfigWorkspaceModeFixed { - mkdwn += "**Workspace mode is set to fixed. This means that your working directory will have no impact on the " + - "current workspace.**\n" + mkdwn += "*Workspace mode is set to fixed. This means that your working directory will have no impact on the " + + "current workspace.*\n\n" } else if c.WorkspaceMode == ConfigWorkspaceModeDynamic { - mkdwn += "**Workspace mode is set to dynamic. This means that your current workspace is also determined by " + - "your working directory.**\n" + mkdwn += "*Workspace mode is set to dynamic. This means that your current workspace is also determined by " + + "your working directory.*\n\n" } if c.CurrentNamespace != "" { - mkdwn += fmt.Sprintf("## Current namespace\n%s\n", c.CurrentNamespace) + mkdwn += fmt.Sprintf("**Current namespace**: %s\n\n", c.CurrentNamespace) + } else { + mkdwn += "*No namespace is set*\n\n" } - if c.Interactive != nil { - interactiveConfig, err := yaml.Marshal(c.Interactive) - if err != nil { - mkdwn += "## Interactive UI config\nerror\n" + if c.DefaultTimeout != 0 { + mkdwn += fmt.Sprintf("**Default timeout**: %s\n", c.DefaultTimeout) + } + if c.Theme != "" { + mkdwn += fmt.Sprintf("**Theme**: %s\n", c.Theme) + } + if c.Interactive != nil { //nolint:nestif + mkdwn += "## Interactivity Settings\n" + if c.Interactive.Enabled { + mkdwn += "**Interactive mode is enabled**\n" + if c.Interactive.NotifyOnCompletion != nil { + mkdwn += "*Notify on completion is enabled*\n" + } + if c.Interactive.SoundOnCompletion != nil { + mkdwn += "*Sound on completion is enabled*\n" + } } else { - mkdwn += fmt.Sprintf("## Interactive UI config\n```yaml\n%s```\n", string(interactiveConfig)) + mkdwn += "**Interactive mode is disabled**\n" } } mkdwn += "## Registered Workspaces\n" - for name, path := range c.Workspaces { - mkdwn += fmt.Sprintf("- %s: %s\n", name, path) + allWs := make([]string, 0, len(c.Workspaces)) + for name := range c.Workspaces { + allWs = append(allWs, name) + } + slices.Sort(allWs) + for _, name := range allWs { + mkdwn += fmt.Sprintf("- %s: %s\n", name, c.Workspaces[name]) } if len(c.Templates) > 0 { mkdwn += "## Registered Templates\n" - for name, path := range c.Templates { - mkdwn += fmt.Sprintf("- %s: %s\n", name, path) + allTmpl := make([]string, 0, len(c.Templates)) + for name := range c.Templates { + allTmpl = append(allTmpl, name) + } + slices.Sort(allTmpl) + for _, name := range allTmpl { + mkdwn += fmt.Sprintf("- %s: %s\n", name, c.Templates[name]) } } return mkdwn } + +func (ct ConfigTheme) String() string { + return string(ct) +} diff --git a/types/config/schema.yaml b/types/config/schema.yaml index 398322d..51ee0ea 100644 --- a/types/config/schema.yaml +++ b/types/config/schema.yaml @@ -68,6 +68,20 @@ properties: default: "" interactive: $ref: '#/definitions/Interactive' + theme: + type: string + enum: [default, dark, light, dracula, tokyo-night] + description: The theme of the interactive UI. + default: default + defaultTimeout: + type: string + description: | + The default timeout to use when running executables. + This should be a valid duration string. + default: "30m" + goJSONSchema: + type: time.Duration + imports: ["time"] templates: type: object additionalProperties: diff --git a/types/executable/executable.go b/types/executable/executable.go index fd0405e..d42dbe8 100644 --- a/types/executable/executable.go +++ b/types/executable/executable.go @@ -205,6 +205,8 @@ func (e *Executable) FlowFilePath() string { return e.flowFilePath } +const TimeoutOverrideEnv = "FLOW_DEFAULT_TIMEOUT" + func (e *Executable) SetDefaults() { if e.Verb == "" { e.Verb = "exec" @@ -215,6 +217,11 @@ func (e *Executable) SetDefaults() { } if e.Timeout == 0 { e.Timeout = DefaultTimeout + if v, ok := os.LookupEnv(TimeoutOverrideEnv); ok { + if d, err := time.ParseDuration(v); err == nil { + e.Timeout = d + } + } } }