twext

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 24, 2025 License: GPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package twext provides a library for implementing timewarrior extensions in golang.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/aibor/timewarrior-extensions/twext"
)

func main() {
	stdin := strings.NewReader(`color: on
reports.day.axis: internal

[
{"id":3,"start":"20240629T102128Z","end":"20240629T102131Z"},
{"id":2,"start":"20240630T143940Z","end":"20240630T143943Z"},
{"id":1,"start":"20240630T144010Z"}
]
`)

	reader := twext.NewReader(stdin)

	cfg, err := reader.ReadConfig()
	if err != nil {
		panic("cannot read config section: " + err.Error())
	}

	fmt.Println("color:", cfg["color"])

	entries, err := reader.ReadEntries()
	if err != nil {
		panic("cannot read entries: " + err.Error())
	}

	groups := twext.Group(
		entries,
		func(e twext.Entry) int {
			return e.Start.Day()
		},
		func(r int, _ twext.Entry) int {
			return r + 1
		},
	)

	fmt.Println("29:", groups[29])
	fmt.Println("30:", groups[30])

}
Output:

color: on
29: 1
30: 2

Index

Examples

Constants

View Source
const DateFmt = "20060102T150405Z07"

DateFmt is the date format used by timewarrior.

Variables

View Source
var (
	// ErrDateUnmarshalNotString is returned in case the value given to the
	// JSON unmarshaller is not a string.
	ErrDateUnmarshalNotString = errors.New("input is not a JSON string")

	// ErrConfigInvalidLine is returned if the line can not be split into a
	// key and value part.
	ErrConfigInvalidLine = errors.New("config line has invalid format")

	// ErrConfigEmpty is returned if the config section is empty.
	ErrConfigEmpty = errors.New("config is empty")

	// ErrReaderConfigConsumed is returned in case the config section is
	// tried to be read again.
	ErrReaderConfigConsumed = errors.New("config section already consumed")

	// ErrReaderConfigNotConsumed is returned if the entries are tried to be
	// read before the config section has been read.
	ErrReaderConfigNotConsumed = errors.New("config section not read yet")
)

Functions

This section is empty.

Types

type Config

type Config map[ConfigKey]ConfigValue

Config is a collection of configuration directives.

type ConfigKey

type ConfigKey string

ConfigKey is an e complete config key string.

const (
	ConfigKeyVerbose      ConfigKey = "verbose"
	ConfigKeyDebug        ConfigKey = "debug"
	ConfigKeyConfirmation ConfigKey = "confirmation"
)

Well known ConfigKeys.

func NewConfigKey

func NewConfigKey(s ...string) ConfigKey

NewConfigKey composes a new ConfigKey.

func (ConfigKey) String

func (k ConfigKey) String() string

type ConfigValue

type ConfigValue string

ConfigValue is a config value string.

func (ConfigValue) Bool

func (v ConfigValue) Bool() bool

Bool returns true if the ConfigValue matches on of the defined values indication trueness.

func (ConfigValue) Duration added in v0.1.1

func (v ConfigValue) Duration() (time.Duration, error)

Duration tries to parse the ConfigValue as time.Duration. It returns an error if the string can not be parsed as time.Duration. See time.ParseDuration for the supported format.

func (ConfigValue) Int

func (v ConfigValue) Int() (int, error)

Int tries to parse the ConfigValue as integer. It returns an error if the string can not be parsed as integer.

func (ConfigValue) String

func (v ConfigValue) String() string

type Entries

type Entries []Entry

Entries is a list of [Entry]s.

func (Entries) Filter added in v0.1.1

func (e Entries) Filter(filter func(entry Entry) bool) Entries

Filter removes entries that do not match the filter.

func (Entries) SplitAtMidnight added in v0.1.1

func (e Entries) SplitAtMidnight() Entries

SplitAtMidnight creates a list of single-day entries.

The [Entry.ID] will be copied when an Entry is split, causing multiple entries with the same ID to exist. The covered sum of durations does not change.

type Entry

type Entry struct {
	ID    int      `json:"id"`
	Start Time     `json:"start"`
	End   Time     `json:"end,omitempty"`
	Tags  []string `json:"tags,omitempty"`
}

Entry is a timewarrior entry that covers a single recorded time interval.

func (*Entry) CurrentEnd added in v0.1.1

func (e *Entry) CurrentEnd() *Time

CurrentEnd calculates the actual end of the Entry.

If the Entry is still active, the current time is returned. Otherwise, the recorded time is returned.

func (*Entry) Duration

func (e *Entry) Duration() time.Duration

Duration calculates the duration of the Entry.

If the Entry is still active, the current time is used as the end time.

func (*Entry) IsActive added in v0.1.1

func (e *Entry) IsActive() bool

IsActive checks if this Entry is still active.

type GroupKey

type GroupKey interface {
	cmp.Ordered
}

GroupKey defines the interface for types that can be used as Groups keys.

type GroupKeyFunc

type GroupKeyFunc[K GroupKey] func(Entry) K

GroupKeyFunc returns the group key for the given entry.

type GroupValueFunc

type GroupValueFunc[V any] func(result V, entry Entry) (newResult V)

GroupValueFunc aggregates entries of a group.

The returned "newResult" is used as input "result" for the next iteration. The value can be a single scalar value or a slice.

type Groups

type Groups[K GroupKey, V any] map[K]V

Groups is a map of any values grouped by a common GroupKey.

func Group

func Group[K GroupKey, V any](
	entries Entries,
	keyFn GroupKeyFunc[K],
	valueFn GroupValueFunc[V],
) Groups[K, V]

Group aggregates entries into groups.

It returns Groups grouped by GroupKey. The return value of the given GroupKeyFunc is used as mapping key for the processed Entry. The value depends on the return value of the given GroupValueFunc.

Skipping entries is possible by returning the input result unaltered in the GroupValueFunc.

Example (Reduce)
package main

import (
	"fmt"
	"time"

	"github.com/aibor/timewarrior-extensions/twext"
)

func parseTime(s string) twext.Time {
	t, err := twext.ParseTime(s)
	if err != nil {
		panic(err)
	}

	return t
}

func main() {
	entries := twext.Entries{
		twext.Entry{
			ID:    3,
			Start: parseTime("20100629T080000Z"),
			End:   parseTime("20100629T140000Z"),
		},
		twext.Entry{
			ID:    2,
			Start: parseTime("20100629T150000Z"),
			End:   parseTime("20100629T180000Z"),
		},
		twext.Entry{
			ID:    1,
			Start: parseTime("20100630T080000Z"),
			End:   parseTime("20100630T160000Z"),
		},
	}

	groups := twext.Group(
		entries,
		func(entry twext.Entry) string {
			return entry.Start.Format(time.DateOnly)
		},
		func(result time.Duration, entry twext.Entry) time.Duration {
			return result + entry.Duration()
		},
	)

	for day, group := range groups.Sorted() {
		fmt.Println(day, group)
	}
}
Output:

2010-06-29 9h0m0s
2010-06-30 8h0m0s
Example (Slices)
package main

import (
	"fmt"
	"time"

	"github.com/aibor/timewarrior-extensions/twext"
)

func parseTime(s string) twext.Time {
	t, err := twext.ParseTime(s)
	if err != nil {
		panic(err)
	}

	return t
}

func main() {
	entries := twext.Entries{
		twext.Entry{
			ID:    3,
			Start: parseTime("20100629T080000Z"),
			End:   parseTime("20100629T140000Z"),
		},
		twext.Entry{
			ID:    2,
			Start: parseTime("20100629T150000Z"),
			End:   parseTime("20100629T180000Z"),
		},
		twext.Entry{
			ID:    1,
			Start: parseTime("20100630T080000Z"),
			End:   parseTime("20100630T160000Z"),
		},
	}

	groups := twext.Group(
		entries,
		func(entry twext.Entry) string {
			return entry.Start.Format(time.DateOnly)
		},
		func(result []int, entry twext.Entry) []int {
			return append(result, entry.ID)
		},
	)

	for day, group := range groups.Sorted() {
		fmt.Println(day, group)
	}
}
Output:

2010-06-29 [3 2]
2010-06-30 [1]

func (Groups[K, V]) Sorted

func (g Groups[K, V]) Sorted() iter.Seq2[K, V]

Sorted returns an iterator that iterates the Groups sorted by [GroupKey]s.

func (Groups[K, V]) SortedKeys

func (g Groups[K, V]) SortedKeys() []K

SortedKeys returns the sorted list of [GroupKey]s.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader parses timewarrior extension input data.

The format is expected as described here: https://timewarrior.net/docs/api/#input-format

After creating a NewReader, call Reader.ReadConfig first and then Reader.ReadEntries to get the actual time data.

func NewReader

func NewReader(r io.ReadSeeker) *Reader

NewReader creates a new Reader object.

It does not read any data from the given reader yet.

func (*Reader) ReadConfig

func (r *Reader) ReadConfig() (Config, error)

ReadConfig reads the config section of the input.

It must be called before calling Reader.ReadEntries.

func (*Reader) ReadEntries

func (r *Reader) ReadEntries() (Entries, error)

ReadEntries reads the list of timewarrior entries.

It returns ErrReaderConfigNotConsumed if the configuration section of the input data has not been read yet. Call Reader.ReadConfig beforehand.

type Time

type Time struct {
	time.Time
}

Time extends time.Time with a custom functionality.

func MustParseTime

func MustParseTime(tb testing.TB, s string) Time

MustParseTime parses a time from a string in the timewarrior format.

func ParseTime

func ParseTime(s string) (Time, error)

ParseTime parses a time from a string according to the timewarrior format.

func (*Time) SameDate

func (t *Time) SameDate(o *Time) bool

SameDate compares two Time values and returns true if they are the same date.

func (*Time) UnmarshalJSON

func (t *Time) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals timestamp strings from the timewarrior format.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL