diff --git a/.gitignore b/.gitignore index de8e5c9..5390778 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ justfile debug.log tasks.txt .cmds +dist diff --git a/README.md b/README.md index 39a2769..c201a52 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,9 @@ omm guide ### TUI -`omm`'s TUI is comprised of several panes: 2 lists (for active and archived -tasks), a context pane, and a task entry/update pane. +`omm`'s TUI is comprised of several panes: 3 lists (for active and archived +tasks, and one for context bookmarks), a context pane, and a task entry/update +pane. #### Active Tasks List @@ -92,18 +93,16 @@ For tasks that need more details that you can fit in a one line summary, there is the context pane. You add/update context for a task via a text editor which is chosen based on the following look ups: -- the `--editor` flag -- the environment variable `OMM_EDITOR` -- the environment variable `EDITOR` -- the environment variable `VISUAL` +- the "--editor" flag +- $OMM_EDITOR +- "editor" property in omm's toml config +- $EDITOR/$VISUAL - `vi` (fallback) -The context pane can be hidden on startup by either setting the environment -variable `OMM_SHOW_CONTEXT=0`, or by providing the flag `--show-context=false` -(the latter takes priority). - ![active-tasks](https://tools.dhruvs.space/images/omm/omm-context-1.png) +**[`^ back to top ^`](#omm)** + #### Task Entry Pane This is where you enter/update a task summary. If you enter a summary in the @@ -166,6 +165,30 @@ up the TUI. omm "Install spring-loaded boxing glove" ``` +### Configuration + +`omm` allows you to change the some of its behavior via configuration, which it +will consider in the order listed below: + +- CLI flags (run `omm -h` to see details) +- Environment variables (eg. `OMM_EDITOR`) +- A TOML configuration file (run `omm -h` to see where this lives; you can + change this via the flag `--config-path`) + + Here's a sample config file: + + ```toml + db_path = "~/.local/share/omm/omm-w.db" + tl_color = "#b8bb26" + atl_color = "#fabd2f" + title = "work" + list_density = "spacious" + show_context = false + editor = "vi -u NONE" + ``` + +**[`^ back to top ^`](#omm)** + Outputting tasks --- @@ -225,8 +248,10 @@ Context Bookmarks List Acknowledgements --- -`omm` is built using [bubbletea][1], and is released using [goreleaser][2], both -of which are amazing tools. +`omm` stands on the shoulders of giants. + +- [bubbletea](https://github.com/charmbracelet/bubbletea) as the TUI framework +- [sqlite](https://www.sqlite.org) as the local database +- [goreleaser](https://github.com/goreleaser/goreleaser) for releasing binaries -[1]: https://github.com/charmbracelet/bubbletea -[2]: https://github.com/goreleaser/goreleaser +**[`^ back to top ^`](#omm)** diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000..5feb76e --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,12 @@ +package cmd + +var ( + sampleCfg = `db_path = "~/.local/share/omm/omm-w.db" +tl_color = "#b8bb26" +atl_color = "#fabd2f" +title = "work" +list_density = "spacious" +show_context = false +editor = "vi -u NONE" +` +) diff --git a/cmd/db_migrations.go b/cmd/db_migrations.go index 301f9bc..c53870f 100644 --- a/cmd/db_migrations.go +++ b/cmd/db_migrations.go @@ -2,6 +2,7 @@ package cmd import ( "database/sql" + "fmt" "time" ) @@ -46,10 +47,10 @@ LIMIT 1; return dbVersion, err } -func upgradeDBIfNeeded(db *sql.DB) { +func upgradeDBIfNeeded(db *sql.DB) error { latestVersionInDB, versionErr := fetchLatestDBVersion(db) if versionErr != nil { - die(`Couldn't get omm's latest database version. This is a fatal error; let %s + return fmt.Errorf(`Couldn't get omm's latest database version. This is a fatal error; let %s know about this via %s. Error: %s`, @@ -59,38 +60,43 @@ Error: %s`, } if latestVersionInDB.version > latestDBVersion { - die(`Looks like you downgraded omm. You should either delete omm's + return fmt.Errorf(`Looks like you downgraded omm. You should either delete omm's database file (you will lose data by doing that), or upgrade omm to the latest version.`) } if latestVersionInDB.version < latestDBVersion { - upgradeDB(db, latestVersionInDB.version) + err := upgradeDB(db, latestVersionInDB.version) + if err != nil { + return err + } } + + return nil } -func upgradeDB(db *sql.DB, currentVersion int) { +func upgradeDB(db *sql.DB, currentVersion int) error { migrations := getMigrations() for i := currentVersion + 1; i <= latestDBVersion; i++ { migrateQuery := migrations[i] migrateErr := runMigration(db, migrateQuery, i) if migrateErr != nil { - die(`Something went wrong migrating omm's database to version %d. This is not + return fmt.Errorf(`Something went wrong migrating omm's database to version %d. This is not supposed to happen. You can try running omm by passing it a custom database file path (using --dbpath; this will create a new database) to see if that fixes things. If that works, you can either delete the previous database, or keep using this new database (both are not ideal). -If you can, let %s know about this error via -%s. +If you can, let %s know about this error via %s. Sorry for breaking the upgrade step! --- -Error: %s +DB Error: %s `, i, author, repoIssuesUrl, migrateErr) } } + return nil } func runMigration(db *sql.DB, migrateQuery string, version int) error { diff --git a/cmd/guide.go b/cmd/guide.go index 66eb63a..63a58d1 100644 --- a/cmd/guide.go +++ b/cmd/guide.go @@ -2,6 +2,7 @@ package cmd import ( "database/sql" + "fmt" "time" pers "github.com/dhth/omm/internal/persistence" @@ -31,7 +32,7 @@ instructions. true, }, { - "guide: tasks", + "domain: tasks", `omm ("on-my-mind") is a task manager. You can also think of it as a keyboard driven to-do list. @@ -46,7 +47,7 @@ you want to save details that don't fit in a single line. true, }, { - "guide: task state", + "domain: task state", `A task can be in one of two states: active or archived. This list shows active tasks. @@ -60,7 +61,7 @@ Press to see the archived list. true, }, { - "guide: task details", + "domain: task details", `The "Task Details" pane is intended for when you simply want to read all the details associated with a task in a full screen view. @@ -78,7 +79,7 @@ smaller display at the moment. true, }, { - "guide: an archived task", + "domain: an archived task", `This is the archived list, meaning it holds tasks that are no longer being worked on. @@ -92,7 +93,7 @@ Press tab/q/esc/ctrl+c to go back to the active list. false, }, { - "guide: list density", + "visuals: list density", `omm's task lists can be viewed in two density modes: compact and spacious. This is the compact mode. As opposed to this, the spacious mode shows tasks in a @@ -100,33 +101,26 @@ more roomier list, alongside highlighting prefixes (we'll see what that means), and showing creation timestamps. Since the list in this mode takes more space, the context pane is shorter than the one in the compact mode. -omm starts up with compact mode by default, but you can change that by either -setting the environment variable $OMM_LIST_DENSITY=spacious, or by passing the -flag "--list-density=spacious" to omm (the latter takes priority). - -You can toggle between the two modes by pressing "v". Choose whichever mode fits -your workflow better. +omm starts up with compact mode by default (you can change this, as we'll see +soon). You can toggle between the two modes by pressing "v". Choose whichever +mode fits your workflow better. Try it out. Come back to this mode once you're done. `, true, }, { - "guide: toggling context pane", + "visuals: toggling context pane", `The context pane can be toggled on/off by pressing "C". You can choose to display it or not based on your preference. For convenience, the lists will always highlight tasks that have a context associated with them by having a "(c)" marker on them. - -You can start omm with the context pane hidden by either setting the environment -variable OMM_SHOW_CONTEXT to "0/1" or "true/false", or by passing the flag -"--show-context=false" (the latter takes priority). `, true, }, { - "guide: adding tasks", + "actions: adding tasks", `Let's get to the crux of omm: adding and prioritizing tasks. We'll begin with adding tasks. You can add a task below the cursor by pressing @@ -145,7 +139,7 @@ Go ahead, create a task, then move to the next guided item. true, }, { - "guide: adding tasks via the CLI", + "actions: adding tasks via the CLI", `You can also add a task to omm via its command line interface. For example: omm 'prefix: a task summary' @@ -166,24 +160,19 @@ omm will expect each line in stdin to hold one task's summary. true, }, { - "guide: adding context", + "actions: adding context", `As mentioned before, once a task is created, you might want to add context to it. -You do that by pressing "c". This will open up the text editor you've configured -via the environment variables $OMM_EDITOR/$EDITOR/$VISUAL (looked up in that -order). You can override this behavior by passing the "editor" flag to omm, like -"--editor='vi -u NONE'". If none of these are set, omm falls back to "vi". - -Go ahead, press "c". Try changing the text, and then save the file. This context -text should get updated accordingly. +You do that by pressing "c". Go ahead, try it out. Try changing the text, and +then save the file. This context text should get updated accordingly. Once saved, you can also copy a tasks's context to your system clipboard by pressing "y". `, true, }, { - "guide: context bookmarks", + "domain: context bookmarks", `Sometimes you'll save some URLs to a task's context. Such URLs (eg. https://github.com/dhth/omm, https://tools.dhruvs.space, @@ -201,7 +190,7 @@ Try both approaches now. Press "b", interact with the list, and then press "B". true, }, { - "guide: task priorities", + "domain: task priorities", `At its core, omm is a dynamic list that maintains a sequence of tasks based on the priorities you assign them. @@ -220,7 +209,7 @@ the top. true, }, { - "guide: updating task details", + "actions: updating task details", `Once a task is created, its summary and context can be changed at any point. You can update a task's summary by pressing "u". @@ -235,6 +224,40 @@ Similarly, you can also update a task's context any time (by pressing "c"). `, true, }, + { + "config: changing the defaults", + `omm allows you to change the some of its behavior via configuration, which it +will consider in the order listed below: + +- CLI flags (run "omm -h" to see details) +- Environment variables (eg. "OMM_EDITOR") +- A TOML configuration file (run "omm -h" to see where this lives; you can + change this via the flag "--config-path") + +omm will consider configuration in the order laid out above, ie, CLI flags will +take the highest priority. +`, + true, + }, + { + "config: flags, env vars, and config file", + `Every flag listed by "omm -h" (except "--config-path") has an environment +variable counterpart, as well as a TOML config counterpart. + +For example: + +--show-context -> OMM_SHOW_CONTEXT -> show_context +--editor -> OMM_EDITOR -> editor +`, + true, + }, + { + "config: a sample TOML config", + fmt.Sprintf(`Here's a sample TOML configuration file: + +%s`, sampleCfg), + true, + }, { "guide: and that's it!", `That's it for the walkthrough! diff --git a/cmd/root.go b/cmd/root.go index 4d76cd9..ebb5f72 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,51 +16,51 @@ import ( "github.com/dhth/omm/internal/ui" "github.com/dhth/omm/internal/utils" "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" ) const ( - author = "@dhth" - repoIssuesUrl = "https://github.com/dhth/omm/issues" - defaultDataDir = ".local/share" - defaultDataDirWindows = "AppData/Local" - dbFileName = "omm/omm.db" - printTasksDefault = 20 - taskListTitleMaxLen = 8 + defaultConfigFilename = "omm" + envPrefix = "OMM" + author = "@dhth" + repoIssuesUrl = "https://github.com/dhth/omm/issues" + defaultConfigDir = ".config" + defaultDataDir = ".local/share" + defaultConfigDirWindows = "AppData/Roaming" + defaultDataDirWindows = "AppData/Local" + configFileName = "omm/omm.toml" + dbFileName = "omm/omm.db" + printTasksDefault = 20 + taskListTitleMaxLen = 8 ) var ( - dbPath string - db *sql.DB - taskListColor string - archivedTaskListColor string - printTasksNum uint8 - taskListTitle string - listDensityFlagInp string - editorFlagInp string - editorCmd string - showContextFlagInp bool + configFileExtIncorrectErr = errors.New("config file must be a TOML file") + configFileDoesntExistErr = errors.New("config file does not exist") + dbFileExtIncorrectErr = errors.New("db file needs to end with .db") + + maxImportLimitExceededErr = fmt.Errorf("Max number of tasks that can be imported at a time: %d", pers.TaskNumLimit) + nothingToImportErr = errors.New("Nothing to import") + + listDensityIncorrectErr = errors.New("List density is incorrect; valid values: compact/spacious") ) -func die(msg string, args ...any) { - fmt.Fprintf(os.Stderr, msg+"\n", args...) - os.Exit(1) -} +func Execute(version string) { + rootCmd, err := NewRootCommand() -func Execute() { - err := rootCmd.Execute() + rootCmd.Version = version if err != nil { - die("Something went wrong: %s", err) + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) } -} - -func setupDB() { - if dbPath == "" { - die("DB path cannot be empty") - } + _ = rootCmd.Execute() +} - dbPathFull := expandTilde(dbPath) +func setupDB(dbPathFull string) (*sql.DB, error) { + var db *sql.DB var err error _, err = os.Stat(dbPathFull) @@ -69,7 +69,7 @@ func setupDB() { dir := filepath.Dir(dbPathFull) err = os.MkdirAll(dir, 0755) if err != nil { - die(`Couldn't create directory for data files: %s + return nil, fmt.Errorf(`Couldn't create directory for data files: %s Error: %s`, dir, err) @@ -78,7 +78,7 @@ Error: %s`, db, err = getDB(dbPathFull) if err != nil { - die(`Couldn't create omm's local database. This is a fatal error; + return nil, fmt.Errorf(`Couldn't create omm's local database. This is a fatal error; Let %s know about this via %s. Error: %s`, @@ -89,7 +89,7 @@ Error: %s`, err = initDB(db) if err != nil { - die(`Couldn't create omm's local database. This is a fatal error; + return nil, fmt.Errorf(`Couldn't create omm's local database. This is a fatal error; Let %s know about this via %s. Error: %s`, @@ -97,11 +97,14 @@ Error: %s`, repoIssuesUrl, err) } - upgradeDB(db, 1) + err = upgradeDB(db, 1) + if err != nil { + return nil, err + } } else { db, err = getDB(dbPathFull) if err != nil { - die(`Couldn't open omm's local database. This is a fatal error; + return nil, fmt.Errorf(`Couldn't open omm's local database. This is a fatal error; Let %s know about this via %s. Error: %s`, @@ -109,14 +112,37 @@ Error: %s`, repoIssuesUrl, err) } - upgradeDBIfNeeded(db) + err = upgradeDBIfNeeded(db) + if err != nil { + return nil, err + } } + + return db, nil } -var rootCmd = &cobra.Command{ - Use: "omm", - Short: "omm (\"on my mind\") is a keyboard-driven task manager for the command line", - Long: `omm ("on my mind") is a keyboard-driven task manager for the command line. +func NewRootCommand() (*cobra.Command, error) { + + var ( + configPath string + configPathFull string + dbPath string + dbPathFull string + db *sql.DB + taskListColor string + archivedTaskListColor string + printTasksNum uint8 + taskListTitle string + listDensityFlagInp string + editorFlagInp string + editorCmd string + showContextFlagInp bool + ) + + rootCmd := &cobra.Command{ + Use: "omm", + Short: "omm (\"on my mind\") is a keyboard-driven task manager for the command line", + Long: `omm ("on my mind") is a keyboard-driven task manager for the command line. It is intended to help you visualize and arrange the tasks you need to finish, based on the priority you assign them. The higher a task is in omm's list, the @@ -124,176 +150,192 @@ higher priority it takes. Tip: Quickly add a task using 'omm "task summary goes here"'. `, - Args: cobra.MaximumNArgs(1), - PersistentPreRun: func(cmd *cobra.Command, args []string) { - - if cmd.CalledAs() == "guide" { - tempDir := os.TempDir() - timestamp := time.Now().UnixNano() - tempFileName := fmt.Sprintf("omm-%d.db", timestamp) - tempFilePath := filepath.Join(tempDir, tempFileName) - dbPath = tempFilePath - } + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + configPathFull = expandTilde(configPath) - setupDB() - }, - Run: func(cmd *cobra.Command, args []string) { + if filepath.Ext(configPathFull) != ".toml" { + return configFileExtIncorrectErr + } + _, err := os.Stat(configPathFull) + + fl := cmd.Flags() + if fl != nil { + cf := fl.Lookup("config-path") + if cf != nil && cf.Changed && errors.Is(err, fs.ErrNotExist) { + return configFileDoesntExistErr + } + } - if len(args) != 0 { - summary := utils.Trim(args[0], ui.TaskSummaryMaxLen) - err := importTask(db, summary) + err = initializeConfig(cmd, configPathFull) if err != nil { - die("There was an error adding the task: %s", err) + return err } - return - } - // config management - if cmd.Flags().Lookup("editor").Changed { - editorCmd = editorFlagInp - } else { - editorCmd = getUserConfiguredEditor(editorFlagInp) - } + if cmd.CalledAs() == "guide" { + tempDir := os.TempDir() + timestamp := time.Now().UnixNano() + tempFileName := fmt.Sprintf("omm-%d.db", timestamp) + tempFilePath := filepath.Join(tempDir, tempFileName) + dbPath = tempFilePath + } - var sc bool - var scErr error - if cmd.Flags().Lookup("show-context").Changed { - sc = showContextFlagInp - } else { - sc, scErr = getUserConfiguredShowContext(showContextFlagInp) - if scErr != nil { - die("%s", scErr) + dbPathFull = expandTilde(dbPath) + if filepath.Ext(dbPathFull) != ".db" { + return dbFileExtIncorrectErr } - } - var ld ui.ListDensityType - var ldErr error - if cmd.Flags().Lookup("list-density").Changed { + db, err = setupDB(dbPathFull) + if err != nil { + return err + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + + if len(args) != 0 { + summary := utils.Trim(args[0], ui.TaskSummaryMaxLen) + err := importTask(db, summary) + if err != nil { + return err + } + return nil + } + + // config management + if cmd.Flags().Lookup("editor").Changed { + editorCmd = editorFlagInp + } else { + editorCmd = getUserConfiguredEditor(editorFlagInp) + } + + var ld ui.ListDensityType switch listDensityFlagInp { case ui.CompactDensityVal: ld = ui.Compact case ui.SpaciousDensityVal: ld = ui.Spacious default: - die("--list-density is incorrect") - } - } else { - ld, ldErr = getUserConfiguredListDensity(listDensityFlagInp) - if ldErr != nil { - die("%s", ldErr) + return listDensityIncorrectErr } - } - if len(taskListTitle) > taskListTitleMaxLen { - taskListTitle = taskListTitle[:taskListTitleMaxLen] - } - - config := ui.Config{ - DBPath: dbPath, - ListDensity: ld, - TaskListColor: taskListColor, - ArchivedTaskListColor: archivedTaskListColor, - TaskListTitle: taskListTitle, - TextEditorCmd: strings.Fields(editorCmd), - ShowContext: sc, - } - - ui.RenderUI(db, config) - }, -} + if len(taskListTitle) > taskListTitleMaxLen { + taskListTitle = taskListTitle[:taskListTitleMaxLen] + } -var importCmd = &cobra.Command{ - Use: "import", - Short: "Import tasks into omm from stdin", - Run: func(cmd *cobra.Command, args []string) { + config := ui.Config{ + DBPath: dbPathFull, + ListDensity: ld, + TaskListColor: taskListColor, + ArchivedTaskListColor: archivedTaskListColor, + TaskListTitle: taskListTitle, + TextEditorCmd: strings.Fields(editorCmd), + ShowContext: showContextFlagInp, + } - var tasks []string - taskCounter := 0 + ui.RenderUI(db, config) - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - if taskCounter > pers.TaskNumLimit { - die("Max number of tasks that can be imported at a time: %d", pers.TaskNumLimit) + return nil + }, + } + rootCmd.SetVersionTemplate(`{{with .Name}}{{printf "%s " .}}{{end}}{{printf "%s" .Version}} +`) + + importCmd := &cobra.Command{ + Use: "import", + Short: "Import tasks into omm from stdin", + RunE: func(cmd *cobra.Command, args []string) error { + + var tasks []string + taskCounter := 0 + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + if taskCounter > pers.TaskNumLimit { + return maxImportLimitExceededErr + } + + line := scanner.Text() + line = strings.TrimSpace(line) + if len(line) > ui.TaskSummaryMaxLen { + line = utils.Trim(line, ui.TaskSummaryMaxLen) + } + + if line != "" { + tasks = append(tasks, line) + } + taskCounter++ } - line := scanner.Text() - line = strings.TrimSpace(line) - if len(line) > ui.TaskSummaryMaxLen { - line = utils.Trim(line, ui.TaskSummaryMaxLen) + if len(tasks) == 0 { + return nothingToImportErr } - if line != "" { - tasks = append(tasks, line) + err := importTasks(db, tasks) + if err != nil { + return err } - taskCounter++ - } - if len(tasks) == 0 { - die("Nothing to import") - } - - err := importTasks(db, tasks) - if err != nil { - die("There was an error importing tasks: %s", err) - } - }, -} - -var tasksCmd = &cobra.Command{ - Use: "tasks", - Short: "Output tasks tracked by omm to stdout", - Run: func(cmd *cobra.Command, args []string) { + return nil + }, + } - err := printTasks(db, printTasksNum, os.Stdout) - if err != nil { - die("There was an error importing tasks: %s", err) - } - }, -} + tasksCmd := &cobra.Command{ + Use: "tasks", + Short: "Output tasks tracked by omm to stdout", + RunE: func(cmd *cobra.Command, args []string) error { + return printTasks(db, printTasksNum, os.Stdout) + }, + } -var guideCmd = &cobra.Command{ - Use: "guide", - Short: "Starts a guided walkthrough of omm's features", - PreRun: func(cmd *cobra.Command, args []string) { + guideCmd := &cobra.Command{ + Use: "guide", + Short: "Starts a guided walkthrough of omm's features", + PreRunE: func(cmd *cobra.Command, args []string) error { - guideErr := insertGuideTasks(db) - if guideErr != nil { - die(`Failed to set up a guided walkthrough. + guideErr := insertGuideTasks(db) + if guideErr != nil { + return fmt.Errorf(`Failed to set up a guided walkthrough. Let %s know about this via %s. Error: %s`, author, repoIssuesUrl, guideErr) - } - }, - Run: func(cmd *cobra.Command, args []string) { + } - if cmd.Flags().Lookup("editor").Changed { - editorCmd = editorFlagInp - } else { - editorCmd = getUserConfiguredEditor(editorFlagInp) - } - config := ui.Config{ - DBPath: dbPath, - ListDensity: ui.Compact, - TaskListColor: taskListColor, - ArchivedTaskListColor: archivedTaskListColor, - TaskListTitle: taskListTitle, - TextEditorCmd: strings.Fields(editorCmd), - ShowContext: true, - Guide: true, - } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { - ui.RenderUI(db, config) - }, -} + if cmd.Flags().Lookup("editor").Changed { + editorCmd = editorFlagInp + } else { + editorCmd = getUserConfiguredEditor(editorFlagInp) + } + config := ui.Config{ + DBPath: dbPathFull, + ListDensity: ui.Compact, + TaskListColor: taskListColor, + ArchivedTaskListColor: archivedTaskListColor, + TaskListTitle: taskListTitle, + TextEditorCmd: strings.Fields(editorCmd), + ShowContext: true, + Guide: true, + } + + ui.RenderUI(db, config) + + return nil + }, + } -func init() { ros := runtime.GOOS - var defaultDBPath string - var dbPathAdditionalCxt string + var defaultConfigPath, defaultDBPath string + var configPathAdditionalCxt, dbPathAdditionalCxt string hd, err := os.UserHomeDir() if err != nil { - die(`Couldn't get your home directory. This is a fatal error; + return nil, fmt.Errorf(`Couldn't get your home directory. This is a fatal error; use --dbpath to specify database path manually Let %s know about this via %s. @@ -303,36 +345,93 @@ Error: %s`, author, repoIssuesUrl, err) switch ros { case "linux": xdgDataHome := os.Getenv("XDG_DATA_HOME") + xdgConfigHome := os.Getenv("XDG_CONFIG_HOME") + if xdgConfigHome != "" { + defaultConfigPath = filepath.Join(xdgConfigHome, configFileName) + } else { + defaultConfigPath = filepath.Join(hd, defaultConfigDir, configFileName) + } if xdgDataHome != "" { defaultDBPath = filepath.Join(xdgDataHome, dbFileName) } else { defaultDBPath = filepath.Join(hd, defaultDataDir, dbFileName) } + configPathAdditionalCxt = "; will use $XDG_CONFIG_HOME by default, if set" dbPathAdditionalCxt = "; will use $XDG_DATA_HOME by default, if set" case "windows": + defaultConfigPath = filepath.Join(hd, defaultConfigDirWindows, configFileName) defaultDBPath = filepath.Join(hd, defaultDataDirWindows, dbFileName) default: + defaultConfigPath = filepath.Join(hd, defaultConfigDir, configFileName) defaultDBPath = filepath.Join(hd, defaultDataDir, dbFileName) } + rootCmd.Flags().StringVarP(&configPath, "config-path", "c", defaultConfigPath, fmt.Sprintf("location of omm's TOML config file%s", configPathAdditionalCxt)) rootCmd.Flags().StringVarP(&dbPath, "db-path", "d", defaultDBPath, fmt.Sprintf("location of omm's database file%s", dbPathAdditionalCxt)) rootCmd.Flags().StringVar(&taskListColor, "tl-color", ui.TaskListColor, "hex color used for the task list") rootCmd.Flags().StringVar(&archivedTaskListColor, "atl-color", ui.ArchivedTLColor, "hex color used for the archived tasks list") rootCmd.Flags().StringVar(&taskListTitle, "title", ui.TaskListDefaultTitle, fmt.Sprintf("title of the task list, will trim till %d chars", taskListTitleMaxLen)) - rootCmd.Flags().StringVar(&listDensityFlagInp, "list-density", ui.CompactDensityVal, fmt.Sprintf("type of density for the list; possible values: [%s, %s]; this config property can also be set via $OMM_LIST_DENSITY", ui.CompactDensityVal, ui.SpaciousDensityVal)) - rootCmd.Flags().StringVar(&editorFlagInp, "editor", "vi", "editor command to run when adding/editing context to a task; this config property can also be set via $OMM_EDITOR/$EDITOR/$VISUAL") - rootCmd.Flags().BoolVar(&showContextFlagInp, "show-context", true, "whether to start omm with a visible task context pane or not; this can later be toggled on/off in the TUI; this config property can also be set via $OMM_SHOW_CONTEXT") + rootCmd.Flags().StringVar(&listDensityFlagInp, "list-density", ui.CompactDensityVal, fmt.Sprintf("type of density for the list; possible values: [%s, %s]", ui.CompactDensityVal, ui.SpaciousDensityVal)) + rootCmd.Flags().StringVar(&editorFlagInp, "editor", "vi", "editor command to run when adding/editing context to a task") + rootCmd.Flags().BoolVar(&showContextFlagInp, "show-context", true, "whether to start omm with a visible task context pane or not; this can later be toggled on/off in the TUI") tasksCmd.Flags().Uint8VarP(&printTasksNum, "num", "n", printTasksDefault, "number of tasks to print") + tasksCmd.Flags().StringVarP(&configPath, "config-path", "c", defaultConfigPath, fmt.Sprintf("location of omm's TOML config file%s", configPathAdditionalCxt)) tasksCmd.Flags().StringVarP(&dbPath, "db-path", "d", defaultDBPath, fmt.Sprintf("location of omm's database file%s", dbPathAdditionalCxt)) + importCmd.Flags().StringVarP(&configPath, "config-path", "c", defaultConfigPath, fmt.Sprintf("location of omm's TOML config file%s", configPathAdditionalCxt)) importCmd.Flags().StringVarP(&dbPath, "db-path", "d", defaultDBPath, fmt.Sprintf("location of omm's database file%s", dbPathAdditionalCxt)) - guideCmd.Flags().StringVar(&editorFlagInp, "editor", "vi", "editor command to run when adding/editing context to a task; this config property can also be set via $OMM_EDITOR/$EDITOR/$VISUAL") + guideCmd.Flags().StringVar(&editorFlagInp, "editor", "vi", "editor command to run when adding/editing context to a task") rootCmd.AddCommand(importCmd) rootCmd.AddCommand(tasksCmd) rootCmd.AddCommand(guideCmd) rootCmd.CompletionOptions.DisableDefaultCmd = true + + return rootCmd, nil +} + +func initializeConfig(cmd *cobra.Command, configFile string) error { + v := viper.New() + + v.SetConfigName(filepath.Base(configFile)) + v.SetConfigType("toml") + v.AddConfigPath(filepath.Dir(configFile)) + + var err error + if err = v.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return err + } + } + + v.SetEnvPrefix(envPrefix) + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + v.AutomaticEnv() + + err = bindFlags(cmd, v) + if err != nil { + return err + } + + return nil +} + +func bindFlags(cmd *cobra.Command, v *viper.Viper) error { + var err error + cmd.Flags().VisitAll(func(f *pflag.Flag) { + configName := strings.ReplaceAll(f.Name, "-", "_") + + if !f.Changed && v.IsSet(configName) { + val := v.Get(configName) + fErr := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) + if fErr != nil { + err = fErr + return + } + } + }) + return err } diff --git a/cmd/utils.go b/cmd/utils.go index 59cbbcb..183f4b3 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1,17 +1,9 @@ package cmd import ( - "errors" "os" "os/user" "strings" - - "github.com/dhth/omm/internal/ui" -) - -var ( - showContextEnvVarMisconfiguredErr = errors.New("OMM_SHOW_CONTEXT can only be one of these values: 0/1/true/false") - listDensityEnvVarMisconfiguredErr = errors.New("OMM_LIST_DENSITY can only be one of these values: compact/spacious") ) func expandTilde(path string) string { @@ -43,35 +35,3 @@ func getUserConfiguredEditor(defaultVal string) string { return defaultVal } - -func getUserConfiguredShowContext(defaultVal bool) (bool, error) { - sc := os.Getenv("OMM_SHOW_CONTEXT") - if sc == "" { - return defaultVal, nil - } - - if sc == "0" || sc == "false" { - return false, nil - } - if sc == "1" || sc == "true" { - return true, nil - } - - return false, showContextEnvVarMisconfiguredErr -} - -func getUserConfiguredListDensity(defaultVal string) (ui.ListDensityType, error) { - ld := os.Getenv("OMM_LIST_DENSITY") - if ld == "" { - ld = defaultVal - } - - switch ld { - case ui.CompactDensityVal: - return ui.Compact, nil - case ui.SpaciousDensityVal: - return ui.Spacious, nil - } - - return ui.Compact, listDensityEnvVarMisconfiguredErr -} diff --git a/go.mod b/go.mod index ad3e153..18f18ed 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/charmbracelet/lipgloss v0.11.0 github.com/dustin/go-humanize v1.0.1 github.com/spf13/cobra v1.8.1 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 modernc.org/sqlite v1.30.1 mvdan.cc/xurls/v2 v2.5.0 @@ -21,29 +23,43 @@ require ( github.com/charmbracelet/x/input v0.1.2 // indirect github.com/charmbracelet/x/term v0.1.1 // indirect github.com/charmbracelet/x/windows v0.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect modernc.org/libc v1.54.3 // indirect diff --git a/go.sum b/go.sum index e686c27..14ae627 100644 --- a/go.sum +++ b/go.sum @@ -17,24 +17,40 @@ github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKU github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -42,6 +58,8 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -52,25 +70,56 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= @@ -85,8 +134,12 @@ golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= diff --git a/main.go b/main.go index c374442..a4f01d9 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,20 @@ package main import ( "github.com/dhth/omm/cmd" + "runtime/debug" +) + +var ( + version = "dev" ) func main() { - cmd.Execute() + v := version + if version == "dev" { + info, ok := debug.ReadBuildInfo() + if ok { + v = info.Main.Version + } + } + cmd.Execute(v) }