diff --git a/klog/app/cli/bookmarks.go b/klog/app/cli/bookmarks.go index 425a243..6bfa79f 100644 --- a/klog/app/cli/bookmarks.go +++ b/klog/app/cli/bookmarks.go @@ -7,27 +7,38 @@ import ( ) type Bookmarks struct { - List BookmarksList `cmd:"" help:"Displays all bookmarks"` - Ls BookmarksList `cmd:"" hidden:"" help:"Alias for 'list'"` + List BookmarksList `cmd:"" help:"Display all bookmarks."` + Ls BookmarksList `cmd:"" hidden:"" help:"Alias for 'list'."` - Set BookmarksSet `cmd:"" help:"Defines a bookmark (or overwrites an existing one)"` - New BookmarksSet `cmd:"" hidden:"" help:"Alias for 'set'"` + Set BookmarksSet `cmd:"" help:"Define a bookmark (or overwrite an existing one)."` + New BookmarksSet `cmd:"" hidden:"" help:"Alias for 'set'."` - Unset BookmarksUnset `cmd:"" help:"Removes a bookmark from the collection"` - Rm BookmarksUnset `cmd:"" hidden:"" help:"Alias for 'unset'"` + Unset BookmarksUnset `cmd:"" help:"Remove a bookmark from the collection. (This only removes the bookmark, not the target file.)"` + Rm BookmarksUnset `cmd:"" hidden:"" help:"Alias for 'unset'."` - Clear BookmarksClear `cmd:"" help:"Clears entire bookmark collection"` + Clear BookmarksClear `cmd:"" help:"Clear entire bookmark collection. (This only removes the bookmarks, not the target files.)"` - Info BookmarksInfo `cmd:"" help:"Prints file information for a bookmark"` + Info BookmarksInfo `cmd:"" help:"Print file information for a bookmark."` } func (opt *Bookmarks) Help() string { - return `Bookmarks allow you to interact with often-used files via an alias, -regardless of your current working directory. A bookmark name is always prefixed with an '@'. + return ` +Bookmarks allow you to interact with often-used files via an alias, independent of your current working directory. +You can think of a bookmark as some sort of klog-specific symlink, that’s always available when you invoke klog, and that resolves to the designated file. +Use the subcommands below to set up and manage your bookmarks. -E.g.: klog total @work +A bookmark name is denoted by the prefix '@'. For example, if you have a bookmark named '@work', that points to a .klg file, you can use klog like this: -You can specify as many bookmarks as you want. There can even be one “unnamed” bookmark.` + klog total @work + klog start --summary 'Started new project' @work + klog edit @work + +You can specify as many bookmarks as you want. There can also be one “unnamed” default bookmark (which internally is identified by the name '@default'). +This is useful in case you only have one main file at a time, and allows you to use klog without any input arguments at all. E.g.: + + klog total + klog start --summary 'Started new project' +` } type BookmarksList struct{} diff --git a/klog/app/cli/config.go b/klog/app/cli/config.go index 6342a58..bf7c4ad 100644 --- a/klog/app/cli/config.go +++ b/klog/app/cli/config.go @@ -7,24 +7,23 @@ import ( ) type Config struct { - ConfigFilePath bool `name:"file-path" help:"Prints the path to your config file"` util.NoStyleArgs } func (opt *Config) Help() string { - return `You are able to configure some of klog’s behaviour by providing a configuration file in your klog config folder. (Run ` + "`" + `klog config --file-path` + "`" + ` to print the path of that config file.) + return ` +You are able to configure some of klog’s behaviour by providing a configuration file. -If you run ` + "`" + `klog config` + "`" + `, you can learn about the supported properties in the file, and which of those you have set. +If you run 'klog config', you can learn about the supported properties in the file, and which of those you have set. +You may use the output of that command as template for setting up your config file, as its format is valid syntax. -You may use the output as template for setting up your config file, as its format is valid syntax.` +The configuration file is named 'config.ini' and resides in your klog config folder. +Run 'klog info config-folder' to learn where your klog config folder is located. +` } func (opt *Config) Run(ctx app.Context) app.Error { opt.NoStyleArgs.Apply(&ctx) - if opt.ConfigFilePath { - ctx.Print(app.Join(ctx.KlogConfigFolder(), app.CONFIG_FILE_NAME).Path() + "\n") - return nil - } styler, _ := ctx.Serialise() for i, e := range app.CONFIG_FILE_ENTRIES { ctx.Print(styler.Props(tf.StyleProps{Color: tf.SUBDUED}).Format(util.Reflower.Reflow(e.Help.Summary, []string{"# "}))) diff --git a/klog/app/cli/create.go b/klog/app/cli/create.go index 983a4f7..c29b455 100644 --- a/klog/app/cli/create.go +++ b/klog/app/cli/create.go @@ -8,18 +8,22 @@ import ( ) type Create struct { - ShouldTotal klog.ShouldTotal `name:"should" placeholder:"DURATION" help:"The should-total of the record"` + ShouldTotal klog.ShouldTotal `name:"should" placeholder:"DURATION" help:"The should-total of the record."` ShouldTotalAlias klog.ShouldTotal `name:"should-total" placeholder:"DURATION" hidden:""` // Alias for “canonical” term - Summary klog.RecordSummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for the new record"` + Summary klog.RecordSummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for the new record."` util.AtDateArgs util.NoStyleArgs - util.OutputFileArgs util.WarnArgs + util.OutputFileArgs } func (opt *Create) Help() string { - return `The new record is inserted into the file at the chronologically correct position. -(Assuming that the records are sorted from oldest to latest.)` + return ` +You can set a should-total value via '--should' and a record summary via '--summary'. + +The new record is inserted into the file at the chronologically correct position. +(Assuming that the records are sorted from oldest to latest.) +` } func (opt *Create) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/edit.go b/klog/app/cli/edit.go index 525f3af..c2edafe 100644 --- a/klog/app/cli/edit.go +++ b/klog/app/cli/edit.go @@ -7,11 +7,11 @@ import ( ) type Edit struct { - util.OutputFileArgs util.QuietArgs + util.OutputFileArgs } -const hint = "You can specify your preferred editor via the $EDITOR environment variable, or the klog config file." +const hint = "You can specify your preferred editor via the $EDITOR environment variable, or via the klog config file." func (opt *Edit) Help() string { return hint diff --git a/klog/app/cli/index.go b/klog/app/cli/index.go index ede06d0..3043978 100644 --- a/klog/app/cli/index.go +++ b/klog/app/cli/index.go @@ -8,44 +8,68 @@ import ( kc "github.com/jotaen/kong-completion" ) +// Guideline for help texts and descriptions: +// - Command and flag descriptions are phrased in imperative style, and they +// end in a period. Examples: +// - Pretty-print records. +// - Sort output by date. +// - Code and literal values are wrapped in single quotes (') +// - Types of flag values are spelled in UPPER-CASE. The type is explained in +// the flag description. For complex types, there should also be an example. + type Cli struct { Default Default `hidden:"" cmd:"" default:"withargs" help:""` // Evaluate Files - Print Print `cmd:"" name:"print" group:"Evaluate Files" help:"Pretty-prints records"` - Total Total `cmd:"" name:"total" group:"Evaluate Files" help:"Evaluates the total time"` - Report Report `cmd:"" name:"report" group:"Evaluate Files" help:"Prints an aggregated calendar report"` - Tags Tags `cmd:"" name:"tags" group:"Evaluate Files" help:"Prints total times aggregated by tags"` - Today Today `cmd:"" name:"today" group:"Evaluate Files" help:"Evaluates the current day"` + Print Print `cmd:"" name:"print" group:"Evaluate Files" help:"Pretty-print records."` + Total Total `cmd:"" name:"total" group:"Evaluate Files" help:"Evaluate the total time."` + Report Report `cmd:"" name:"report" group:"Evaluate Files" help:"Print an aggregated calendar report."` + Tags Tags `cmd:"" name:"tags" group:"Evaluate Files" help:"Print total times aggregated by tags."` + Today Today `cmd:"" name:"today" group:"Evaluate Files" help:"Evaluate the current day."` // Manipulate Files - Track Track `cmd:"" name:"track" group:"Manipulate Files" help:"Adds a new entry to a record"` - Start Start `cmd:"" name:"start" group:"Manipulate Files" aliases:"in" help:"Starts a new open time range"` - Stop Stop `cmd:"" name:"stop" group:"Manipulate Files" aliases:"out" help:"Closes the open time range"` - Pause Pause `cmd:"" name:"pause" group:"Manipulate Files" help:"Pauses the open time range"` - Switch Switch `cmd:"" name:"switch" group:"Manipulate Files" help:"Closes open range and starts a new one"` - Create Create `cmd:"" name:"create" group:"Manipulate Files" help:"Creates a new, empty record"` + Track Track `cmd:"" name:"track" group:"Manipulate Files" help:"Add a new entry to a record."` + Start Start `cmd:"" name:"start" group:"Manipulate Files" aliases:"in" help:"Start a new open time range."` + Stop Stop `cmd:"" name:"stop" group:"Manipulate Files" aliases:"out" help:"Close the open time range."` + Pause Pause `cmd:"" name:"pause" group:"Manipulate Files" help:"Pause the open time range."` + Switch Switch `cmd:"" name:"switch" group:"Manipulate Files" help:"Close open range and starts a new one."` + Create Create `cmd:"" name:"create" group:"Manipulate Files" help:"Create a new, empty record."` // Manage Files - Bookmarks Bookmarks `cmd:"" name:"bookmarks" group:"Manage Files" aliases:"bk" help:"Named aliases for often-used files"` + Bookmarks Bookmarks `cmd:"" name:"bookmarks" group:"Manage Files" aliases:"bk" help:"Named aliases for often-used files."` Bookmark Bookmarks `cmd:"" name:"bookmark" hidden:"" help:"(Alias)"` // Hidden alias for convenience / typo - Edit Edit `cmd:"" name:"edit" group:"Manage Files" help:"Opens a file or bookmark in your editor"` - Goto Goto `cmd:"" name:"goto" group:"Manage Files" help:"Opens the file explorer at a file or bookmark"` + Edit Edit `cmd:"" name:"edit" group:"Manage Files" help:"Open a file or bookmark in your editor."` + Goto Goto `cmd:"" name:"goto" group:"Manage Files" help:"Open the file explorer at a file or bookmark."` // Misc - Version Version `cmd:"" name:"version" group:"Misc" help:"Prints version info and check for updates"` - Config Config `cmd:"" name:"config" group:"Misc" help:"Prints the current configuration"` - Info Info `cmd:"" name:"info" group:"Misc" help:"Prints information about klog"` - Json Json `cmd:"" name:"json" group:"Misc" help:"Converts records to JSON"` - Completion kc.Completion `cmd:"" name:"completion" group:"Misc" help:"Outputs shell code for enabling tab completion"` + Version Version `cmd:"" name:"version" group:"Misc" help:"Print version info and check for updates."` + Config Config `cmd:"" name:"config" group:"Misc" help:"Print the current configuration."` + Info Info `cmd:"" name:"info" group:"Misc" help:"Print information about klog."` + Json Json `cmd:"" name:"json" group:"Misc" help:"Convert records to JSON."` + Completion kc.Completion `cmd:"" name:"completion" group:"Misc" help:"Output shell code for enabling tab completion."` } -const DESCRIPTION = "klog: command line app for time tracking with plain-text files.\n" + - "Run with --help to learn usage.\n" + - "Documentation online at " + KLOG_WEBSITE_URL - type Default struct { - Version bool `short:"v" name:"version" help:"Alias for 'klog version'"` + Version bool `short:"v" name:"version" help:"Alias for 'klog version'."` +} + +func (opt *Default) Help() string { + return ` +klog: command line app for time tracking with plain-text files. See also ` + KLOG_WEBSITE_URL + ` + +Time-tracking data is stored in files ending in the '.klg' extension. +You can use the subcommands below to evaluate, manipulate and manage your files. +Use the '--help' flag on the subcommands to learn more. + +You can specify input data in one of these 3 ways: + - by passing the name of a file or a bookmark, + - by piping data to stdin, + - or by setting up a default bookmark. + +Run 'klog bookmarks --help' to learn about bookmark usage. + +One general note about flags: for all flags that have values, you can either use a space or an equals sign as delimiter, i.e. both '--flag value' and '--flag=value' are fine. +` } func (opt *Default) Run(ctx app.Context) app.Error { @@ -53,6 +77,7 @@ func (opt *Default) Run(ctx app.Context) app.Error { versionCmd := Version{} return versionCmd.Run(ctx) } - ctx.Print(DESCRIPTION + "\n") + ctx.Print("klog: command line app for time tracking with plain-text files.\n") + ctx.Print("Run 'klog --help' to learn usage.\n") return nil } diff --git a/klog/app/cli/info.go b/klog/app/cli/info.go index 847a617..5e36021 100644 --- a/klog/app/cli/info.go +++ b/klog/app/cli/info.go @@ -8,13 +8,24 @@ import ( ) type Info struct { - Spec InfoSpec `cmd:"" name:"spec" help:"Prints file format specification"` - License InfoLicense `cmd:"" name:"license" help:"Prints license / copyright information"` - ConfigFolder InfoConfigFolder `cmd:"" name:"config-folder" help:"Prints path of klog config folder"` + Spec InfoSpec `cmd:"" name:"spec" help:"Print the .klg file format specification."` + License InfoLicense `cmd:"" name:"license" help:"Print license / copyright information."` + ConfigFolder InfoConfigFolder `cmd:"" name:"config-folder" help:"Print the path of the klog config folder."` } func (opt *Info) Help() string { - return "" + return ` +Run 'klog info config-folder' to see the location of your klog config folder. +The location of the config folder depends on your operating system and environment settings. +You can customise the folder’s location via environment variables – run the command to learn which ones klog relies on. + +The config folder is used to store two files: + - 'config.ini' (optional) – you can create this file manually to override some of klog’s default behaviour. Run 'klog config' to learn more. + - 'bookmarks.json' (optional) – if you use bookmarks, then klog uses this file as database. You are not supposed to edit this file by hand! Instead, use the 'klog bookmarks' command to manage your bookmarks. + +Run 'klog info spec' to read the formal specification of the klog file format. +If you want to review klog’s license and copyright information, run 'klog info license'. +` } type InfoConfigFolder struct { diff --git a/klog/app/cli/json.go b/klog/app/cli/json.go index 53eadd5..6f4ec72 100644 --- a/klog/app/cli/json.go +++ b/klog/app/cli/json.go @@ -7,7 +7,7 @@ import ( ) type Json struct { - Pretty bool `name:"pretty" help:"Pretty-print output"` + Pretty bool `name:"pretty" help:"Pretty-print output."` util.NowArgs util.FilterArgs util.SortArgs @@ -15,13 +15,13 @@ type Json struct { } func (opt *Json) Help() string { - return `The output structure contains two properties at the top level: "records" and "errors". + return ` +The output structure is a JSON object which contains two properties at the top level: 'records' and 'errors'. +If the file is valid, 'records' is an array containing a JSON object for each record, and 'errors' is 'null'. +If the file has syntax errors, 'records' is 'null', and 'errors' contains an array of error objects. -If the file is valid, "records" is an array containing a JSON object for each record; "errors" is null. - -If the file has syntax errors, "records" is null and "errors" contains an array of error objects. - -The structure of the "record" and "error" objects is always uniform. You can best explore it by running the command with the --pretty flag. +The structure of the 'record' and 'error' objects is always uniform and should be self-explanatory. +You can best explore it by running the command with the --pretty flag. ` } diff --git a/klog/app/cli/pause.go b/klog/app/cli/pause.go index 5c71b43..8ed7a58 100644 --- a/klog/app/cli/pause.go +++ b/klog/app/cli/pause.go @@ -13,20 +13,25 @@ import ( ) type Pause struct { - Summary klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for the pause entry"` - NoAppendTags bool `name:"no-tags" help:"Do not automatically take over (append) tags from open range"` - Extend bool `name:"extend" short:"e" help:"Extend latest pause, instead of adding a new pause entry"` - util.OutputFileArgs + Summary klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for the pause entry."` + NoAppendTags bool `name:"no-tags" help:"Do not automatically take over (append) tags from open range."` + Extend bool `name:"extend" short:"e" help:"Extend latest pause, instead of adding a new pause entry."` util.NoStyleArgs util.WarnArgs + util.OutputFileArgs } func (opt *Pause) Help() string { - return `Creates a pause entry for a record with an open time range. -The command is blocking – it keeps updating the pause entry until the process is exited. -(The file will be written into once per minute.) + return ` +This command is only available for records that contain an open time range (i.e., an ongoing activity). +The pause is basically a new entry with a negative duration, which is appended to the record. +The command is blocking and keeps updating (incrementing) the duration of the pause entry until the shell process is exited via Ctrl^C. +The file will be written into once per minute. + +If you wish to extend an existing pause, you can use the '--extend' flag. In this case it will increment the last pause entry in the record, instead of appending a new entry. -If the open range in the record contains tags, then these will automatically be taken over and appended to the pause entry. +If the open range in the record contains tags in its summary, then these will automatically be taken over and appended to the pause entry. +You can opt out of this behaviour with the '--no-tags' flag. ` } diff --git a/klog/app/cli/print.go b/klog/app/cli/print.go index fae73cd..e472c1d 100644 --- a/klog/app/cli/print.go +++ b/klog/app/cli/print.go @@ -11,7 +11,7 @@ import ( ) type Print struct { - WithTotals bool `name:"with-totals" help:"Amend output with evaluated total times"` + WithTotals bool `name:"with-totals" help:"Amend output with evaluated total times."` util.FilterArgs util.SortArgs util.WarnArgs @@ -20,7 +20,13 @@ type Print struct { } func (opt *Print) Help() string { - return `The output is syntax-highlighted. Note that the formatting is sanitised/normalised, especially in regards to whitespace.` + return ` +Outputs data on the terminal, by default with syntax-highlighting turned on. +Note that the output doesn’t resemble the file byte by byte, but the command may apply some minor clean-ups of the formatting. + +If run with filter flags, it only outputs those entries that match the filter clauses. +You can optionally also sort the records, or print out the total times for each record and entry. +` } func (opt *Print) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/report.go b/klog/app/cli/report.go index 1696caa..eeed17e 100644 --- a/klog/app/cli/report.go +++ b/klog/app/cli/report.go @@ -12,8 +12,8 @@ import ( ) type Report struct { - AggregateBy string `name:"aggregate" placeholder:"KIND" short:"a" help:"Aggregate data by: day (default), week, month, quarter, year" enum:"DAY,day,d,WEEK,week,w,MONTH,month,m,QUARTER,quarter,q,YEAR,year,y," default:"day"` - Fill bool `name:"fill" short:"f" help:"Fill the gaps and show a consecutive stream"` + AggregateBy string `name:"aggregate" placeholder:"KIND" short:"a" help:"How to aggregate the data. KIND can be 'day' (default), 'week', 'month', 'quarter' or 'year'." enum:"DAY,day,d,WEEK,week,w,MONTH,month,m,QUARTER,quarter,q,YEAR,year,y," default:"day"` + Fill bool `name:"fill" short:"f" help:"Fill the gaps and show a consecutive stream."` util.DiffArgs util.FilterArgs util.NowArgs @@ -24,9 +24,13 @@ type Report struct { } func (opt *Report) Help() string { - return `It aggregates the totals by period, and prints the respective values from oldest to latest. + return ` +It aggregates the totals by period, and prints the respective values chronologically (from oldest to latest). +The default aggregation is by day, but you can choose other periods via the '--aggregate' flag. -The default aggregation is by day, but you choose other periods via the --aggregate flag.` +The report skips all days (weeks, months, etc.) if no data is available for them. +If you want a consecutive, chronological stream, you can use the '--fill' flag. +` } func (opt *Report) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/start.go b/klog/app/cli/start.go index 6117cd9..b32953a 100644 --- a/klog/app/cli/start.go +++ b/klog/app/cli/start.go @@ -10,18 +10,26 @@ import ( ) type Start struct { - SummaryText klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for this entry"` - Resume bool `name:"resume" short:"R" help:"Take over summary of last entry (if applicable)"` + SummaryText klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for this entry."` + Resume bool `name:"resume" short:"R" help:"Take over summary of last entry (if applicable)."` util.AtDateAndTimeArgs util.NoStyleArgs - util.OutputFileArgs util.WarnArgs + util.OutputFileArgs } func (opt *Start) Help() string { - return `A new open-ended entry is appended to the record, e.g. 14:00-?. + return ` +This appends a new open-ended entry to the record. +By default, it uses the record at today’s date for the new entry, or creates a new record if there no record at today’s date. +You can otherwise specify a date with '--date'. + +Unless the '--time' flag is specified, it defaults to the current time as start time. +If you prefer your time to be rounded, you can use the '--round' flag. -If the --time flag is not specified, it defaults to the current time as start time. In the latter case, the time can be rounded via --round.` +You can either assign a summary text for the new entry via the '--summary' flag, or you can use the '--resume' flag to automatically take over the entry summary of the last entry. +Note that '--resume' will fall back to the last record, if the current record doesn’t contain any entries yet. +` } func (opt *Start) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/stop.go b/klog/app/cli/stop.go index d7a78e5..90a7a38 100644 --- a/klog/app/cli/stop.go +++ b/klog/app/cli/stop.go @@ -8,18 +8,22 @@ import ( ) type Stop struct { - Summary klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Text to append to the entry summary"` + Summary klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Text to append to the entry summary."` util.AtDateAndTimeArgs util.NoStyleArgs - util.OutputFileArgs util.WarnArgs + util.OutputFileArgs } func (opt *Stop) Help() string { - return `If the record contains an open-ended time range (e.g. 18:00-?) then this command -will replace the end placeholder with the current time (or the one specified via --time). + return ` +If the record contains an open-ended time range (e.g. '18:00 - ?') then this command will replace the end placeholder with the current time. +By default, it targets the record at today’s date. +You can otherwise specify a date with '--date'. -If the --time flag is not specified, it defaults to the current time as end time. In the latter case, the time can be rounded via --round.` +Unless the '--time' flag is specified, it defaults to the current time as end time. If you prefer your time to be rounded, you can use the '--round' flag. +You may also specify a summary via '--summary', which will be appended to the existing summary of the entry. +` } func (opt *Stop) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/switch.go b/klog/app/cli/switch.go index ed0dafa..0bc5dd2 100644 --- a/klog/app/cli/switch.go +++ b/klog/app/cli/switch.go @@ -8,17 +8,17 @@ import ( ) type Switch struct { - SummaryText klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for the new entry"` + SummaryText klog.EntrySummary `name:"summary" short:"s" placeholder:"TEXT" help:"Summary text for the new entry."` util.AtDateAndTimeArgs util.NoStyleArgs - util.OutputFileArgs util.WarnArgs + util.OutputFileArgs } func (opt *Switch) Help() string { - return `Closes a previously ongoing activity (i.e., open time range), and starts a new one. - -The end time of the previous activity will be the same as the start time for the new entry. + return ` +Closes a previously ongoing activity (i.e., open time range), and starts a new one. +This is basically a convenience for doing 'klog stop' and 'klog start' – however, in contrast to issuing both commands separately, 'klog switch' guarantees that the end time of the previous activity will be the same as the start time for the new entry. ` } diff --git a/klog/app/cli/tags.go b/klog/app/cli/tags.go index 5342bc4..3227e5c 100644 --- a/klog/app/cli/tags.go +++ b/klog/app/cli/tags.go @@ -9,8 +9,8 @@ import ( ) type Tags struct { - Values bool `name:"values" short:"v" help:"Display breakdown of tag values"` - Count bool `name:"count" short:"c" help:"Display the number of matching entries per tag"` + Values bool `name:"values" short:"v" help:"Display breakdown of tag values (if the data contains any; e.g.: '#tag=value')."` + Count bool `name:"count" short:"c" help:"Display the number of matching entries per tag."` util.FilterArgs util.NowArgs util.DecimalArgs @@ -20,11 +20,16 @@ type Tags struct { } func (opt *Tags) Help() string { - return `Aggregates the total times of entries by tags. + return ` +If a tag appears in the overall record summary, then all of the record’s entries match. +If a tag appears in an entry summary, only that particular entry matches. +If tags are specified redundantly in the data, the respective time is still counted uniquely. -If a tag appears in the overall record summary, then all of the record’s entries match. If a tag appears in an entry summary, only that particular entry matches. +If you use tags with values (e.g., '#tag=value'), then these also match against the base tag (e.g., '#tag'). +You can use the '--values' flag to display an additional breakdown by tag value. -Every matching entry is counted individually.` +Note that tag names are case-insensitive (e.g., '#tag' is the same as '#TAG'), whereas tag values are case-sensitive (so '#tag=value' is different from '#tag=VALUE'). +` } func (opt *Tags) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/today.go b/klog/app/cli/today.go index 29a5fc6..2fdc1ce 100644 --- a/klog/app/cli/today.go +++ b/klog/app/cli/today.go @@ -12,7 +12,7 @@ import ( type Today struct { util.DiffArgs util.NowArgs - Follow bool `name:"follow" short:"f" help:"Keep shell open and follow changes"` + Follow bool `name:"follow" short:"f" help:"Keep shell open and follow changes."` util.DecimalArgs util.WarnArgs util.NoStyleArgs @@ -20,13 +20,16 @@ type Today struct { } func (opt *Today) Help() string { - return `Convenience command to “check in” on the current day. + return ` +Convenience command to get a brief overview (“check in”) of the current day. It evaluates the total time separately for today’s records and all other records. +If there are no records today, it falls back to yesterday. -When both --now and --diff are set, it also calculates the forecasted end-time at which the time goal will be reached. -(I.e. when the difference between should and actual time will be 0.) +When both '--now' and '--diff' are set, it also calculates the forecasted end-time at which your time goal will be reached. +(I.e. when the difference between should and actual time would be 0.) -If there are no records today, it falls back to yesterday.` +Use the '--follow' flag to keep the shell open and display changes live. +` } func (opt *Today) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/total.go b/klog/app/cli/total.go index 5b4c702..b3ff5ea 100644 --- a/klog/app/cli/total.go +++ b/klog/app/cli/total.go @@ -18,11 +18,12 @@ type Total struct { } func (opt *Total) Help() string { - return `The total time is the overall sum of all time entries. + return ` +By default, the total time consists of all durations and time ranges, but it doesn’t include open-ended time ranges (e.g., '8:00 - ?'). +If you want to factor them in anyway, you can use the '--now' option, which treats all open-ended time ranges as if they were closed “right now”. -Note that the total time by default doesn’t include open-ended time ranges. -If you want to factor them in anyway, you can use the --now option, -which treats all open-ended time ranges as if they were closed “right now”.` +If the records contain should-total values, you can also compute the difference between should-total and actual total by using the '--diff' flag. +` } func (opt *Total) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/track.go b/klog/app/cli/track.go index 77f53a5..1d8f6ac 100644 --- a/klog/app/cli/track.go +++ b/klog/app/cli/track.go @@ -8,20 +8,27 @@ import ( ) type Track struct { - Entry klog.EntrySummary `arg:"" required:"" placeholder:"ENTRY" help:"The new entry to add"` + Entry klog.EntrySummary `arg:"" required:"" placeholder:"ENTRY" help:"The new entry to add."` util.AtDateArgs util.NoStyleArgs - util.OutputFileArgs util.WarnArgs + util.OutputFileArgs } func (opt *Track) Help() string { - return `The given text is appended to the record as new entry (taken over as is). + return ` +The given text is appended to the record as new entry (taken over as is, i.e. including the entry summary). Example invocations: + + klog track '1h' file.klg + klog track '15:00 - 16:00 Went out running' file.klg + klog track '6h30m #work' file.klg -Example: klog track '1h work' file.klg +It uses the record at today’s date for the new entry, or creates a new record if there no record at today’s date. +You can otherwise specify a date with '--date'. -Remember to use 'quotes' if the entry consists of multiple words, -to avoid the text being split or interpreted by your shell.` +Remember to use 'quotes' if the entry consists of multiple words, to avoid the text being split or otherwise pre-processed by your shell. +There is still one quirk: if you want to track a negative duration, you have to escape the leading minus with a backslash, e.g. '\-45m lunch break', to prevent it from being mistakenly interpreted as a flag. +` } func (opt *Track) Run(ctx app.Context) app.Error { diff --git a/klog/app/cli/util/args.go b/klog/app/cli/util/args.go index 555e36a..adb7a0d 100644 --- a/klog/app/cli/util/args.go +++ b/klog/app/cli/util/args.go @@ -12,18 +12,18 @@ import ( ) type InputFilesArgs struct { - File []app.FileOrBookmarkName `arg:"" optional:"" type:"string" predictor:"file_or_bookmark" name:"file or bookmark" help:".klg source file(s) (if empty the bookmark is used)"` + File []app.FileOrBookmarkName `arg:"" optional:"" type:"string" predictor:"file_or_bookmark" name:"file or bookmark" help:"One or more .klg source files or bookmarks. If absent, klog tries to use the default bookmark."` } type OutputFileArgs struct { - File app.FileOrBookmarkName `arg:"" optional:"" type:"string" predictor:"file_or_bookmark" name:"file or bookmark" help:".klg source file (if empty the bookmark is used)"` + File app.FileOrBookmarkName `arg:"" optional:"" type:"string" predictor:"file_or_bookmark" name:"file or bookmark" help:"One .klg source file or bookmark. If absent, klog tries to use the default bookmark."` } type AtDateArgs struct { - Date klog.Date `name:"date" placeholder:"DATE" short:"d" help:"The date of the record"` - Today bool `name:"today" help:"Use today’s date (default)"` - Yesterday bool `name:"yesterday" help:"Use yesterday’s date"` - Tomorrow bool `name:"tomorrow" help:"Use tomorrow’s date"` + Date klog.Date `name:"date" placeholder:"DATE" short:"d" help:"The date of the record."` + Today bool `name:"today" help:"Use today’s date."` + Yesterday bool `name:"yesterday" help:"Use yesterday’s date."` + Tomorrow bool `name:"tomorrow" help:"Use tomorrow’s date."` } func (args *AtDateArgs) AtDate(now gotime.Time) klog.Date { @@ -51,9 +51,9 @@ func (args *AtDateArgs) DateFormat(config app.Config) reconciling.ReformatDirect } type AtDateAndTimeArgs struct { + Round service.Rounding `name:"round" placeholder:"ROUNDING" short:"r" help:"Round time to nearest multiple number. ROUNDING can be one of '5m', '10m', '12m', '15m', '20m', '30m' or '60m' / '1h'."` AtDateArgs - Time klog.Time `name:"time" placeholder:"TIME" short:"t" help:"Specify the time (defaults to now)"` - Round service.Rounding `name:"round" placeholder:"ROUNDING" short:"r" help:"Round time to nearest multiple of 5m, 10m, 12m, 15m, 20m, 30m, or 60m / 1h"` + Time klog.Time `name:"time" placeholder:"TIME" short:"t" help:"Specify the time (defaults to now). TIME can be given in the 24h or 12h notation, e.g. '13:00' or '1:00pm'."` } func (args *AtDateAndTimeArgs) AtTime(now gotime.Time, config app.Config) (klog.Time, app.Error) { @@ -103,11 +103,11 @@ func (args *AtDateAndTimeArgs) WasAutomatic() bool { } type DiffArgs struct { - Diff bool `name:"diff" short:"d" help:"Show difference between actual and should-total time"` + Diff bool `name:"diff" short:"d" help:"Show difference between actual and should-total time."` } type NowArgs struct { - Now bool `name:"now" short:"n" help:"Assume open ranges to be closed at this moment"` + Now bool `name:"now" short:"n" help:"Assume open ranges to be closed at this moment."` hadOpenRange bool // Field only for internal use } @@ -141,22 +141,22 @@ func (args *NowArgs) GetNowWarnings() []string { type FilterArgs struct { // General filters - Tags []klog.Tag `name:"tag" placeholder:"TAG" group:"Filter" help:"Records (or entries) that match these tags"` - Date klog.Date `name:"date" placeholder:"DATE" group:"Filter" help:"Records at this date"` - Since klog.Date `name:"since" placeholder:"DATE" group:"Filter" help:"Records since this date (inclusive)"` - Until klog.Date `name:"until" placeholder:"DATE" group:"Filter" help:"Records until this date (inclusive)"` - After klog.Date `name:"after" placeholder:"DATE" group:"Filter" help:"Records after this date (exclusive)"` - Before klog.Date `name:"before" placeholder:"DATE" group:"Filter" help:"Records before this date (exclusive)"` - EntryType service.EntryType `name:"entry-type" placeholder:"TYPE" group:"Filter" help:"Entries of this type (duration, range, open-range)"` - Period period.Period `name:"period" placeholder:"PERIOD" group:"Filter" help:"Records in period: YYYY, YYYY-MM, YYYY-Www, or YYYY-Qq"` + Tags []klog.Tag `name:"tag" placeholder:"TAG" group:"Filter" help:"Records (or entries) that match these tags. You can omit the leading '#'."` + Date klog.Date `name:"date" placeholder:"DATE" group:"Filter" help:"Records at this date. DATE has to be in format YYYY-MM-DD or YYYY/MM/DD. E.g., '2024-01-31' or '2024/01/31'."` + Since klog.Date `name:"since" placeholder:"DATE" group:"Filter" help:"Records since this date (inclusive)."` + Until klog.Date `name:"until" placeholder:"DATE" group:"Filter" help:"Records until this date (inclusive)."` + After klog.Date `name:"after" placeholder:"DATE" group:"Filter" help:"Records after this date (exclusive)."` + Before klog.Date `name:"before" placeholder:"DATE" group:"Filter" help:"Records before this date (exclusive)."` + EntryType service.EntryType `name:"entry-type" placeholder:"TYPE" group:"Filter" help:"Entries of this type. TYPE can be 'range', 'open-range', 'duration', 'duration-positive' or 'duration-negative'."` + Period period.Period `name:"period" placeholder:"PERIOD" group:"Filter" help:"Records within a calendar period. PERIOD has to be in format YYYY, YYYY-MM, YYYY-Www or YYYY-Qq. E.g., '2024', '2024-04', '2022-W21' or '2024-Q1'."` // Shortcut filters // The `XXX` ones are dummy entries just for the help output - Today bool `name:"today" group:"Filter" help:"Records at today’s date"` - Yesterday bool `name:"yesterday" group:"Filter" help:"Records at yesterday’s date"` - Tomorrow bool `name:"tomorrow" group:"Filter" help:"Records at tomorrow’s date"` - ThisXXX bool `name:"this-***" group:"Filter" help:"Records of this week/month/quarter/year, e.g. --this-week"` - LastXXX bool `name:"last-***" group:"Filter" help:"Records of last week/month/quarter/year, e.g. --last-month"` + Today bool `name:"today" group:"Filter" help:"Records at today’s date."` + Yesterday bool `name:"yesterday" group:"Filter" help:"Records at yesterday’s date."` + Tomorrow bool `name:"tomorrow" group:"Filter" help:"Records at tomorrow’s date."` + ThisXXX bool `name:"this-***" group:"Filter" help:"Records of this week/month/quarter/year, e.g. '--this-week' or '--this-quarter'."` + LastXXX bool `name:"last-***" group:"Filter" help:"Records of last week/month/quarter/year, e.g. '--last-month' or '--last-year'."` ThisWeek bool `hidden:"" name:"this-week" group:"Filter"` ThisWeekAlias bool `hidden:"" name:"thisweek" group:"Filter"` LastWeek bool `hidden:"" name:"last-week" group:"Filter"` @@ -255,7 +255,7 @@ func (args *FilterArgs) ApplyFilter(now gotime.Time, rs []klog.Record) []klog.Re } type WarnArgs struct { - NoWarn bool `name:"no-warn" help:"Suppress warnings about potential mistakes"` + NoWarn bool `name:"no-warn" help:"Suppress warnings about potential mistakes or logical errors."` } func (args *WarnArgs) PrintWarnings(ctx app.Context, records []klog.Record, additionalWarnings []string) { @@ -272,7 +272,7 @@ func (args *WarnArgs) PrintWarnings(ctx app.Context, records []klog.Record, addi } type NoStyleArgs struct { - NoStyle bool `name:"no-style" help:"Do not style or color the values"` + NoStyle bool `name:"no-style" help:"Do not style or colour the values."` } func (args *NoStyleArgs) Apply(ctx *app.Context) { @@ -284,11 +284,11 @@ func (args *NoStyleArgs) Apply(ctx *app.Context) { } type QuietArgs struct { - Quiet bool `name:"quiet" help:"Output parseable data without descriptive text"` + Quiet bool `name:"quiet" help:"Output parseable data without descriptive text."` } type SortArgs struct { - Sort string `name:"sort" placeholder:"ORDER" help:"Sort output by date (asc or desc)" enum:"asc,desc,ASC,DESC," default:""` + Sort string `name:"sort" placeholder:"ORDER" help:"Sort output by date. ORDER can be 'asc' or 'desc'." enum:"asc,desc,ASC,DESC," default:""` } func (args *SortArgs) ApplySort(rs []klog.Record) []klog.Record { @@ -303,7 +303,7 @@ func (args *SortArgs) ApplySort(rs []klog.Record) []klog.Record { } type DecimalArgs struct { - Decimal bool `name:"decimal" help:"Display totals as decimal values (in minutes)"` + Decimal bool `name:"decimal" help:"Display totals as decimal values (in minutes)."` } func (args *DecimalArgs) Apply(ctx *app.Context) { diff --git a/klog/app/cli/util/prettifier.go b/klog/app/cli/util/prettifier.go index dd6e47f..c22d17a 100644 --- a/klog/app/cli/util/prettifier.go +++ b/klog/app/cli/util/prettifier.go @@ -9,7 +9,7 @@ import ( "strings" ) -var Reflower = tf.NewReflower(60, "\n") +var Reflower = tf.NewReflower(80, "\n") // PrettifyAppError prints app errors including details. func PrettifyAppError(err app.Error, isDebug bool) error { diff --git a/klog/app/cli/util/prettifier_test.go b/klog/app/cli/util/prettifier_test.go index d66d42a..7b1550e 100644 --- a/klog/app/cli/util/prettifier_test.go +++ b/klog/app/cli/util/prettifier_test.go @@ -35,17 +35,19 @@ func TestFormatParserError(t *testing.T) { func TestReflowsLongMessages(t *testing.T) { block, _ := txt.ParseBlock("Foo bar", 1) err := app.NewParserErrors([]txt.Error{ - txt.NewError(block, 0, 4, 3, "CODE", "Some Title", "A verbose description with details, potentially spanning multiple lines with a comprehensive text and tremendously helpful information.\nBut it respects newlines."), + txt.NewError(block, 0, 4, 3, "CODE", "Some Title", "A verbose description with details, potentially spanning multiple lines with a comprehensive text and tremendously helpful information.\nBut\nit\nrespects\nnewlines."), }) text := PrettifyParsingError(err, false, styler).Error() assert.Equal(t, ` [SYNTAX ERROR] in line 2 Foo bar ^^^ - Some Title: A verbose description with details, potentially - spanning multiple lines with a comprehensive text - and tremendously helpful information. - But it respects newlines. + Some Title: A verbose description with details, potentially spanning multiple + lines with a comprehensive text and tremendously helpful information. + But + it + respects + newlines. `, tf.StripAllAnsiSequences(text)) } diff --git a/klog/app/cli/version.go b/klog/app/cli/version.go index 1d056da..a48d96f 100644 --- a/klog/app/cli/version.go +++ b/klog/app/cli/version.go @@ -13,10 +13,16 @@ import ( ) type Version struct { - NoCheck bool `name:"no-check" help:"Don’t check online for updates"` // used for the smoke test + NoCheck bool `name:"no-check" help:"Don’t check online for updates."` // used for the smoke test util.QuietArgs } +func (opt *Version) Help() string { + return ` +If you don’t use a package manager for managing your klog installation, you can subscribe to the release notifications on the Github repository (https://github.com/jotaen/klog). +` +} + const KLOG_WEBSITE_URL = "https://klog.jotaen.net" var versionCheckers = []versionChecker{ diff --git a/klog/app/main/cli.go b/klog/app/main/cli.go index 941fb3a..ba7bf1e 100644 --- a/klog/app/main/cli.go +++ b/klog/app/main/cli.go @@ -21,7 +21,7 @@ func Run(homeDir app.File, meta app.Meta, config app.Config, args []string) (int kongApp, nErr := kong.New( &cli.Cli{}, kong.Name("klog"), - kong.Description(cli.DESCRIPTION), + kong.Description((&cli.Default{}).Help()), func() kong.Option { datePrototype, _ := klog.NewDate(1, 1, 1) return kong.TypeMapper(reflect.TypeOf(&datePrototype).Elem(), dateDecoder()) @@ -63,6 +63,7 @@ func Run(homeDir app.File, meta app.Meta, config app.Config, args []string) (int kong.ConfigureHelp(kong.HelpOptions{ Compact: true, NoExpandSubcommands: true, + WrapUpperBound: 80, }), ) if nErr != nil {