mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 02:34:19 -05:00
feat(segments): add taskwarrior segment
This commit is contained in:
committed by
Jan De Dobbeleer
parent
6e19a8f514
commit
ea0dc668e5
@@ -127,6 +127,7 @@ func init() {
|
||||
gob.Register(&segments.Swift{})
|
||||
gob.Register(&segments.SystemInfo{})
|
||||
gob.Register(&segments.TalosCTL{})
|
||||
gob.Register(&segments.Taskwarrior{})
|
||||
gob.Register(&segments.Tauri{})
|
||||
gob.Register(&segments.Terraform{})
|
||||
gob.Register(&segments.Text{})
|
||||
@@ -338,6 +339,8 @@ const (
|
||||
SYSTEMINFO SegmentType = "sysinfo"
|
||||
// TALOSCTL writes the talosctl context
|
||||
TALOSCTL SegmentType = "talosctl"
|
||||
// TASKWARRIOR writes Taskwarrior task counts and context
|
||||
TASKWARRIOR SegmentType = "taskwarrior"
|
||||
// Tauri Segment
|
||||
TAURI SegmentType = "tauri"
|
||||
// TERRAFORM writes the terraform workspace we're currently in
|
||||
@@ -470,6 +473,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
|
||||
SWIFT: func() SegmentWriter { return &segments.Swift{} },
|
||||
SYSTEMINFO: func() SegmentWriter { return &segments.SystemInfo{} },
|
||||
TALOSCTL: func() SegmentWriter { return &segments.TalosCTL{} },
|
||||
TASKWARRIOR: func() SegmentWriter { return &segments.Taskwarrior{} },
|
||||
TAURI: func() SegmentWriter { return &segments.Tauri{} },
|
||||
TERRAFORM: func() SegmentWriter { return &segments.Terraform{} },
|
||||
TEXT: func() SegmentWriter { return &segments.Text{} },
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package segments
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
c "golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/log"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
|
||||
)
|
||||
|
||||
// Taskwarrior option constants
|
||||
const (
|
||||
TaskwarriorCommand options.Option = "command"
|
||||
TaskwarriorCommands options.Option = "commands"
|
||||
)
|
||||
|
||||
// Taskwarrior displays task counts and context from Taskwarrior.
|
||||
// The Commands field is a map from capitalized command name to the raw output
|
||||
// of the corresponding Taskwarrior invocation. Each entry in the config map
|
||||
// has the command name as key and a full Taskwarrior argument string as value.
|
||||
type Taskwarrior struct {
|
||||
Base
|
||||
|
||||
// Commands holds the raw output of each configured command, keyed by name
|
||||
// with the first letter uppercased.
|
||||
Commands map[string]string
|
||||
}
|
||||
|
||||
func (t *Taskwarrior) Template() string {
|
||||
return " \uf4a0 {{ range $k, $v := .Commands }}{{ $k }}:{{ $v }} {{ end }}"
|
||||
}
|
||||
|
||||
func (t *Taskwarrior) Enabled() bool {
|
||||
cmd := t.options.String(TaskwarriorCommand, "task")
|
||||
|
||||
if !t.env.HasCommand(cmd) {
|
||||
return false
|
||||
}
|
||||
|
||||
defaultCommands := map[string]string{
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
"scheduled": "+PENDING scheduled.before:tomorrow count",
|
||||
"waiting": "+WAITING count",
|
||||
"context": "_get rc.context",
|
||||
}
|
||||
|
||||
configuredCommands := t.options.KeyValueMap(TaskwarriorCommands, defaultCommands)
|
||||
|
||||
t.Commands = make(map[string]string, len(configuredCommands))
|
||||
|
||||
for name, args := range configuredCommands {
|
||||
key := c.Title(language.English).String(name)
|
||||
t.Commands[key] = t.runCommand(cmd, args)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Taskwarrior) runCommand(cmd, args string) string {
|
||||
output, err := t.env.RunCommand(cmd, strings.Fields(args)...)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(output)
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package segments
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTaskwarrior(t *testing.T) {
|
||||
cases := []struct {
|
||||
ConfiguredCommands map[string]string
|
||||
CommandOutputs map[string]string
|
||||
CommandErrors map[string]error
|
||||
ExpectedCommands map[string]string
|
||||
Case string
|
||||
Command string
|
||||
HasCommand bool
|
||||
ExpectedEnabled bool
|
||||
}{
|
||||
{
|
||||
Case: "happy path default commands",
|
||||
HasCommand: true,
|
||||
CommandOutputs: map[string]string{
|
||||
"+PENDING due.before:tomorrow count": "3",
|
||||
"+PENDING scheduled.before:tomorrow count": "1",
|
||||
"+WAITING count": "2",
|
||||
},
|
||||
ExpectedEnabled: true,
|
||||
ExpectedCommands: map[string]string{
|
||||
"Due": "3",
|
||||
"Scheduled": "1",
|
||||
"Waiting": "2",
|
||||
"Context": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "no command",
|
||||
HasCommand: false,
|
||||
ExpectedEnabled: false,
|
||||
},
|
||||
{
|
||||
Case: "all zeros",
|
||||
HasCommand: true,
|
||||
CommandOutputs: map[string]string{
|
||||
"+PENDING due.before:tomorrow count": "0",
|
||||
"+PENDING scheduled.before:tomorrow count": "0",
|
||||
"+WAITING count": "0",
|
||||
},
|
||||
ExpectedEnabled: true,
|
||||
ExpectedCommands: map[string]string{
|
||||
"Due": "0",
|
||||
"Scheduled": "0",
|
||||
"Waiting": "0",
|
||||
"Context": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "custom commands only",
|
||||
HasCommand: true,
|
||||
ConfiguredCommands: map[string]string{
|
||||
"urgent": "+PENDING +OVERDUE count",
|
||||
},
|
||||
CommandOutputs: map[string]string{
|
||||
"+PENDING +OVERDUE count": "5",
|
||||
},
|
||||
ExpectedEnabled: true,
|
||||
ExpectedCommands: map[string]string{
|
||||
"Urgent": "5",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "context command via commands map",
|
||||
HasCommand: true,
|
||||
ConfiguredCommands: map[string]string{
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
"context": "_get rc.context",
|
||||
},
|
||||
CommandOutputs: map[string]string{
|
||||
"+PENDING due.before:tomorrow count": "3",
|
||||
"_get rc.context": "work",
|
||||
},
|
||||
ExpectedEnabled: true,
|
||||
ExpectedCommands: map[string]string{
|
||||
"Due": "3",
|
||||
"Context": "work",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "command error returns empty string for that command",
|
||||
HasCommand: true,
|
||||
ConfiguredCommands: map[string]string{
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
},
|
||||
CommandErrors: map[string]error{
|
||||
"+PENDING due.before:tomorrow count": errors.New("command failed"),
|
||||
},
|
||||
ExpectedEnabled: true,
|
||||
ExpectedCommands: map[string]string{
|
||||
"Due": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
Case: "custom executable is used when TaskwarriorCommand is set",
|
||||
Command: "task2",
|
||||
ConfiguredCommands: map[string]string{
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
},
|
||||
CommandOutputs: map[string]string{
|
||||
"+PENDING due.before:tomorrow count": "7",
|
||||
},
|
||||
HasCommand: true,
|
||||
ExpectedEnabled: true,
|
||||
ExpectedCommands: map[string]string{
|
||||
"Due": "7",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Case, func(t *testing.T) {
|
||||
env := new(mock.Environment)
|
||||
|
||||
cmd := tc.Command
|
||||
if cmd == "" {
|
||||
cmd = "task"
|
||||
}
|
||||
|
||||
env.On("HasCommand", cmd).Return(tc.HasCommand)
|
||||
|
||||
configuredCommands := tc.ConfiguredCommands
|
||||
if configuredCommands == nil && tc.HasCommand {
|
||||
// default commands
|
||||
configuredCommands = map[string]string{
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
"scheduled": "+PENDING scheduled.before:tomorrow count",
|
||||
"waiting": "+WAITING count",
|
||||
"context": "_get rc.context",
|
||||
}
|
||||
}
|
||||
|
||||
for _, args := range configuredCommands {
|
||||
splitArgs := splitTaskArgs(args)
|
||||
var output string
|
||||
var err error
|
||||
if tc.CommandOutputs != nil {
|
||||
output = tc.CommandOutputs[args]
|
||||
}
|
||||
if tc.CommandErrors != nil {
|
||||
err = tc.CommandErrors[args]
|
||||
}
|
||||
env.On("RunCommand", cmd, splitArgs).Return(output, err)
|
||||
}
|
||||
|
||||
props := options.Map{}
|
||||
if tc.ConfiguredCommands != nil {
|
||||
props[TaskwarriorCommands] = tc.ConfiguredCommands
|
||||
}
|
||||
if tc.Command != "" {
|
||||
props[TaskwarriorCommand] = tc.Command
|
||||
}
|
||||
|
||||
tw := &Taskwarrior{}
|
||||
tw.Init(props, env)
|
||||
|
||||
assert.Equal(t, tc.ExpectedEnabled, tw.Enabled(), tc.Case)
|
||||
|
||||
if !tc.ExpectedEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.ExpectedCommands, tw.Commands, tc.Case)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// splitTaskArgs splits a space-separated argument string into a slice.
|
||||
func splitTaskArgs(s string) []string {
|
||||
return strings.Fields(s)
|
||||
}
|
||||
@@ -454,6 +454,7 @@
|
||||
"swift",
|
||||
"sysinfo",
|
||||
"talosctl",
|
||||
"taskwarrior",
|
||||
"tauri",
|
||||
"terraform",
|
||||
"text",
|
||||
@@ -4476,6 +4477,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "taskwarrior"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"title": "Taskwarrior Segment",
|
||||
"description": "https://ohmyposh.dev/docs/segments/cli/taskwarrior",
|
||||
"properties": {
|
||||
"options": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"title": "Taskwarrior Command",
|
||||
"description": "The taskwarrior command to use",
|
||||
"default": "task"
|
||||
},
|
||||
"commands": {
|
||||
"type": "object",
|
||||
"title": "Commands",
|
||||
"description": "Map of name to Taskwarrior arguments; the trimmed stdout of each invocation is exposed as .Commands.<Name> in the template",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": {
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
"scheduled": "+PENDING scheduled.before:tomorrow count",
|
||||
"waiting": "+WAITING count",
|
||||
"context": "_get rc.context"
|
||||
}
|
||||
}
|
||||
},
|
||||
"unevaluatedProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
id: taskwarrior
|
||||
title: Taskwarrior
|
||||
sidebar_label: Taskwarrior
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Display [Taskwarrior][taskwarrior] task data for configurable commands. Each named command runs
|
||||
`task` with the specified arguments and exposes the raw output in the template.
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
import Config from "@site/src/components/Config.js";
|
||||
|
||||
<Config
|
||||
data={{
|
||||
type: "taskwarrior",
|
||||
style: "powerline",
|
||||
powerline_symbol: "\uE0B0",
|
||||
foreground: "#193549",
|
||||
background: "#ffeb3b",
|
||||
template: " \uf4a0 {{ range $k, $v := .Commands }}{{ $k }}:{{ $v }} {{ end }}",
|
||||
options: {
|
||||
commands: {
|
||||
due: "+PENDING due.before:tomorrow count",
|
||||
scheduled: "+PENDING scheduled.before:tomorrow count",
|
||||
waiting: "+WAITING count",
|
||||
context: "_get rc.context",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
## Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ---------- | :-----------------: | :-------: | ------------------------------------------------------------------------------ |
|
||||
| `command` | `string` | `task` | the Taskwarrior executable to use |
|
||||
| `commands` | `map[string]string` | see below | map of name to Taskwarrior arguments; the raw output is exposed in `.Commands` |
|
||||
|
||||
### Default `commands` value
|
||||
|
||||
```json
|
||||
{
|
||||
"due": "+PENDING due.before:tomorrow count",
|
||||
"scheduled": "+PENDING scheduled.before:tomorrow count",
|
||||
"waiting": "+WAITING count",
|
||||
"context": "_get rc.context"
|
||||
}
|
||||
```
|
||||
|
||||
Each entry runs `task <arguments>` and stores the trimmed stdout as a string. Remove entries
|
||||
you do not need to keep prompt rendering fast.
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
:::note default template
|
||||
|
||||
```template
|
||||
{{ "\uf4a0" }} {{ range $k, $v := .Commands }}{{ $k }}:{{ $v }} {{ end }}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------------ | ------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `.Commands` | `map[string]string` | raw command output keyed by name with the first letter uppercased (e.g. `"Due"`) |
|
||||
|
||||
### Examples
|
||||
|
||||
Access a specific command result directly:
|
||||
|
||||
```template
|
||||
{{ "\uf4a0" }} Due: {{ .Commands.Due }}
|
||||
```
|
||||
|
||||
Display multiple results:
|
||||
|
||||
```template
|
||||
{{ "\uf4a0" }} Due: {{ .Commands.Due }} | Waiting: {{ .Commands.Waiting }}
|
||||
```
|
||||
|
||||
Include the active context alongside task counts:
|
||||
|
||||
```template
|
||||
{{ "\uf4a0" }} {{ .Commands.Context }} - Due: {{ .Commands.Due }} Scheduled: {{ .Commands.Scheduled }}
|
||||
```
|
||||
|
||||
[templates]: /docs/configuration/templates
|
||||
[taskwarrior]: https://taskwarrior.org
|
||||
@@ -79,6 +79,7 @@ export default {
|
||||
"segments/cli/react",
|
||||
"segments/cli/svelte",
|
||||
"segments/cli/talosctl",
|
||||
"segments/cli/taskwarrior",
|
||||
"segments/cli/tauri",
|
||||
"segments/cli/terraform",
|
||||
"segments/cli/ui5tooling",
|
||||
|
||||
Reference in New Issue
Block a user