summaryrefslogtreecommitdiff
path: root/internal/task/exec.go
blob: 9ef84f2e4d41142f64346817ed9d20ac9aa8548f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package task

import (
	"bytes"
	"context"
	"fmt"
	"os/exec"
	"strings"

	"github.com/google/shlex"
)

func run(args ...string) error {
	return runContext(context.Background(), args...)
}

func runContext(ctx context.Context, args ...string) error {
	_, err := RunArgs(ctx, args)
	return err
}

// RunLine splits line using shell-word rules and runs the resulting task
// arguments. A leading "task" token is ignored so callers may accept either
// "add foo" or "task add foo" from user input.
func RunLine(ctx context.Context, line string) (RunResult, error) {
	fields, err := shlex.Split(line)
	if err != nil {
		return RunResult{}, err
	}
	if len(fields) > 0 && fields[0] == "task" {
		fields = fields[1:]
	}
	return RunArgs(ctx, fields)
}

// RunShellLine runs a user-entered task command in non-interactive mode. It
// avoids Taskwarrior's recurring-task prompt by applying the same behavior as
// answering "no": modify only the addressed recurrence.
func RunShellLine(ctx context.Context, line string) (RunResult, error) {
	fields, err := shlex.Split(line)
	if err != nil {
		return RunResult{}, err
	}
	if len(fields) > 0 && fields[0] == "task" {
		fields = fields[1:]
	}
	fields = append([]string{"rc.recurrence.confirmation=no"}, fields...)
	return RunArgs(ctx, fields)
}

// RunArgs runs "task" with args and captures stdout and stderr.
func RunArgs(ctx context.Context, args []string) (RunResult, error) {
	copied := append([]string(nil), args...)
	result := RunResult{Args: copied}
	if len(copied) == 0 {
		return result, fmt.Errorf("empty task command")
	}

	if dbg.writer != nil {
		if _, err := fmt.Fprintln(dbg.writer, "task "+strings.Join(copied, " ")); err != nil {
			return result, fmt.Errorf("write debug log: %w", err)
		}
	}

	cmd := exec.CommandContext(ctx, "task", copied...)
	configureCommandContext(cmd)
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	result.Stdout = stdout.String()
	result.Stderr = stderr.String()
	if err != nil {
		if ctxErr := ctx.Err(); ctxErr != nil {
			return result, fmt.Errorf("task command: %w", ctxErr)
		}
		if strings.TrimSpace(result.Stderr) != "" {
			return result, fmt.Errorf("%w: %s", err, strings.TrimSpace(result.Stderr))
		}
		return result, err
	}
	return result, nil
}