tme

Toggl like Time Manager
git clone git://gtms.dev/tme
Log | Files | Refs

command.go (9424B)


      1 package main
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"os"
      7 	"strings"
      8 	"time"
      9 )
     10 
     11 type Command struct {
     12 	repository  FSRepository
     13 	args        []string
     14 	timeContext *TimeContext
     15 }
     16 
     17 func (c *Command) nextArg() (string, error) {
     18 	if len(c.args) == 0 {
     19 		return "", errors.New("No more arguments")
     20 	}
     21 
     22 	if len(c.args) == 1 {
     23 		first := (c.args)[0]
     24 		c.args = make([]string, 0)
     25 		return first, nil
     26 	}
     27 
     28 	first := (c.args)[0]
     29 	c.args = (c.args)[1:]
     30 	return first, nil
     31 }
     32 
     33 func (c Command) Add() {
     34 	if len(c.args) != 3 {
     35 		fmt.Fprintln(os.Stderr, "add <group> <start> <stop>")
     36 		os.Exit(1)
     37 	}
     38 
     39 	groupArg, _ := c.nextArg()
     40 	startArg, _ := c.nextArg()
     41 	stopArg, _ := c.nextArg()
     42 
     43 	start, err := c.timeContext.ParseArgDir(startArg, false)
     44 	if err != nil {
     45 		fmt.Fprintf(os.Stderr, "[start] time (%s) could not be parsed\n", startArg)
     46 		os.Exit(1)
     47 	}
     48 
     49 	stop, err := tryParseRelative(*c.timeContext, start, stopArg)
     50 	if err != nil {
     51 		fmt.Fprintf(os.Stderr, "[stop] time (%s) could not be parsed\n", stopArg)
     52 		os.Exit(1)
     53 	}
     54 
     55 	group := NewGroup(groupArg)
     56 
     57 	entry, err := NewCompletedEntry(start, stop)
     58 	if err != nil {
     59 		fmt.Fprint(os.Stderr, err)
     60 		os.Exit(1)
     61 	}
     62 
     63 	if c.repository.ExistsEntry(group, entry) {
     64 		fmt.Fprintln(os.Stderr, "entry already created")
     65 		os.Exit(1)
     66 	}
     67 
     68 	c.repository.Save(group, entry)
     69 }
     70 
     71 func (c Command) Start() {
     72 	if len(c.args) < 1 || len(c.args) > 2 {
     73 		fmt.Fprintln(os.Stderr, "start <group> [<start>]")
     74 		os.Exit(1)
     75 	}
     76 
     77 	groupArg, _ := c.nextArg()
     78 	group := NewGroup(groupArg)
     79 
     80 	startArg, err := c.nextArg()
     81 	start := time.Now()
     82 	if err == nil {
     83 		start, err = c.timeContext.ParseArgDir(startArg, false)
     84 		if err != nil {
     85 			fmt.Fprintf(os.Stderr, "[start] time (%s) could not be parsed\n", startArg)
     86 			os.Exit(1)
     87 		}
     88 	}
     89 
     90 	entry := NewRunningEntry(start)
     91 
     92 	if c.repository.ExistsEntry(group, entry) {
     93 		fmt.Fprintln(os.Stderr, "entry in this group already started")
     94 		os.Exit(1)
     95 	}
     96 
     97 	c.repository.Save(group, entry)
     98 }
     99 
    100 func (c Command) Stop() {
    101 	if len(c.args) < 1 || len(c.args) > 2 {
    102 		fmt.Fprintln(os.Stderr, "stop <group> [<stop>]")
    103 		os.Exit(1)
    104 	}
    105 
    106 	groupArg, _ := c.nextArg()
    107 	group := NewGroup(groupArg)
    108 
    109 	runningEntry, err := c.repository.RunningEntry(group, c.timeContext)
    110 	if err != nil {
    111 		fmt.Fprintf(os.Stderr, "no entry running in %q\n", groupArg)
    112 		os.Exit(1)
    113 	}
    114 
    115 	stop := time.Now()
    116 	stopArg, err := c.nextArg()
    117 	if err == nil {
    118 		stop, err = tryParseRelative(*c.timeContext, runningEntry.TimeRange.Start, stopArg)
    119 		if err != nil {
    120 			fmt.Fprintf(os.Stderr, "[start] time (%s) could not be parsed\n", stopArg)
    121 			os.Exit(1)
    122 		}
    123 	}
    124 
    125 	completedEntry, err := NewCompletedEntry(runningEntry.TimeRange.Start, stop)
    126 	if err != nil {
    127 		fmt.Fprintln(os.Stderr, err)
    128 		os.Exit(1)
    129 	}
    130 
    131 	if c.repository.ExistsEntry(group, completedEntry) {
    132 		fmt.Fprintln(os.Stderr, "entry already created")
    133 		os.Exit(1)
    134 	}
    135 
    136 	err = c.repository.Save(group, completedEntry)
    137 	if err != nil {
    138 		fmt.Fprintln(os.Stderr, err)
    139 		os.Exit(1)
    140 	}
    141 
    142 	c.repository.Remove(group, runningEntry)
    143 }
    144 
    145 func (c Command) Ls() {
    146 	if len(c.args) > 1 {
    147 		fmt.Fprintln(os.Stderr, "ls [<group>]")
    148 		os.Exit(1)
    149 	}
    150 
    151 	rootPath := ""
    152 	if len(c.args) == 1 {
    153 		rootPath, _ = c.nextArg()
    154 	}
    155 
    156 	group := NewGroup(rootPath)
    157 	formatEntries(c.repository, group, c.timeContext)
    158 }
    159 
    160 func (c Command) Lsr() {
    161 	if len(c.args) > 1 {
    162 		fmt.Fprintln(os.Stderr, "lsr [<group>]")
    163 		os.Exit(1)
    164 	}
    165 
    166 	groupPath := ""
    167 	if len(c.args) == 1 {
    168 		groupPath, _ = c.nextArg()
    169 	}
    170 
    171 	groups, err := c.repository.ListGroups(groupPath)
    172 	if err != nil {
    173 		fmt.Fprintln(os.Stderr, err)
    174 		os.Exit(1)
    175 	}
    176 
    177 	for _, group := range groups {
    178 		formatEntries(c.repository, group, c.timeContext)
    179 	}
    180 }
    181 
    182 func (c Command) Report() {
    183 	if len(c.args) < 1 || len(c.args) > 2 {
    184 		fmt.Fprintln(os.Stderr, "report <since> [<until>]")
    185 		os.Exit(1)
    186 	}
    187 
    188 	// TODO(tms) 11.02.23: make as parameter --group
    189 	groupPath := ""
    190 	// if len(c.args) == 2 {
    191 	// 	groupPath, _ = c.nextArg()
    192 	// }
    193 
    194 	sinceArg, _ := c.nextArg()
    195 	sinceTime, err := c.timeContext.ParseArgDir(sinceArg, false)
    196 	if err != nil {
    197 		fmt.Fprintf(os.Stderr, "[since] time (%s) could not be parsed\n", sinceArg)
    198 		os.Exit(1)
    199 	}
    200 
    201 	untilTime := time.Now()
    202 	untilArgs, err := c.nextArg()
    203 	if err == nil {
    204 		untilTime, err = c.timeContext.ParseArgDir(untilArgs, true)
    205 		if err != nil {
    206 			fmt.Fprintf(os.Stderr, "[until] time (%s) could not be parsed\n", sinceArg)
    207 			os.Exit(1)
    208 		}
    209 	}
    210 
    211 	groups, err := c.repository.ListGroups(groupPath)
    212 	if err != nil {
    213 		fmt.Fprintln(os.Stderr, err)
    214 		os.Exit(1)
    215 	}
    216 
    217 	var atLeastOneEntry bool
    218 	var totalDuration time.Duration
    219 	for _, group := range groups {
    220 		entries, err := c.repository.ListEntries(group, c.timeContext)
    221 		if err != nil {
    222 			fmt.Fprintln(os.Stderr, err)
    223 			os.Exit(1)
    224 		}
    225 
    226 		for _, entry := range entries {
    227 			if !entry.Completed {
    228 				continue
    229 			}
    230 
    231 			if (entry.TimeRange.Start.After(sinceTime) || entry.TimeRange.Start.Equal(sinceTime)) &&
    232 				(entry.TimeRange.Stop.Before(untilTime) || entry.TimeRange.Stop.Equal(untilTime)) {
    233 				atLeastOneEntry = true
    234 				totalDuration += entry.Duration()
    235 				formatEntry(group, entry)
    236 			}
    237 		}
    238 	}
    239 
    240 	if atLeastOneEntry {
    241 		fmt.Printf("%s\n", totalDuration.Round(time.Second))
    242 	}
    243 }
    244 
    245 func formatEntries(repository FSRepository, group Group, entryTime *TimeContext) {
    246 	entries, err := repository.ListEntries(group, entryTime)
    247 	if err != nil {
    248 		fmt.Fprintln(os.Stderr, err)
    249 		os.Exit(1)
    250 	}
    251 
    252 	for _, entry := range entries {
    253 		formatEntry(group, entry)
    254 	}
    255 }
    256 
    257 func sFormatEntry(group Group, entry Entry) string {
    258 	groupPath := group.Name
    259 	timeLayout := "15:04:05 02/01/2006"
    260 
    261 	var start, stop string
    262 	var duration time.Duration
    263 	start = entry.TimeRange.Start.Format(timeLayout)
    264 	if entry.Completed {
    265 		stop = entry.TimeRange.Stop.Format(timeLayout)
    266 		duration = entry.Duration().Round(time.Second)
    267 	} else {
    268 		start = entry.TimeRange.Start.Format(timeLayout)
    269 		stop = "running"
    270 		duration = time.Since(entry.TimeRange.Start).Round(time.Second)
    271 	}
    272 
    273 	return fmt.Sprintf("%s\t%s\t%v\t%s", groupPath, start, stop, duration)
    274 }
    275 
    276 func formatEntry(group Group, entry Entry) {
    277 	fmt.Println(sFormatEntry(group, entry))
    278 }
    279 
    280 func tryParseRelative(timeContext TimeContext, start time.Time, rawStop string) (stop time.Time, err error) {
    281 	if strings.IndexAny(rawStop, "+") == 0 {
    282 		stop, err = timeContext.ParseArgRelative(start, rawStop)
    283 	} else {
    284 		stop, err = timeContext.ParseArgDir(rawStop, false)
    285 	}
    286 	return stop, err
    287 }
    288 
    289 func (c Command) Status() {
    290 	groups, err := c.repository.ListGroups("")
    291 	if err != nil {
    292 		fmt.Fprintln(os.Stderr, err)
    293 		os.Exit(1)
    294 	}
    295 
    296 	for _, group := range groups {
    297 		entries, err := c.repository.ListEntries(group, c.timeContext)
    298 		if err != nil {
    299 			fmt.Fprintln(os.Stderr, err)
    300 			os.Exit(1)
    301 		}
    302 
    303 		for _, entry := range entries {
    304 			if entry.Completed {
    305 				continue
    306 			}
    307 
    308 			formatEntry(group, entry)
    309 		}
    310 	}
    311 }
    312 
    313 func (c Command) selectEntry(groupPath string) (Group, Entry, error) {
    314 	groups, err := c.repository.ListGroups(groupPath)
    315 	if err != nil {
    316 		return Group{}, Entry{}, err
    317 	}
    318 
    319 	type groupEntry struct {
    320 		group Group
    321 		entry Entry
    322 	}
    323 
    324 	gEntries := make(map[int]groupEntry)
    325 
    326 	// TODO: most fresh entry be 1
    327 	counter := 1
    328 	for _, group := range groups {
    329 		entries, err := c.repository.ListEntries(group, c.timeContext)
    330 		if err != nil {
    331 			return Group{}, Entry{}, err
    332 		}
    333 
    334 		for _, entry := range entries {
    335 			gEntries[counter] = groupEntry{
    336 				group,
    337 				entry,
    338 			}
    339 			fmt.Printf("%d. %s\n", counter, sFormatEntry(group, entry))
    340 			counter = counter + 1
    341 		}
    342 	}
    343 
    344 	// TODO: format be 1,2,3 ; 1-3 ; 1,3,5-10 ; 1: (1 to end) ; :10 (start to ten) ;
    345 	var number int
    346 	_, err = fmt.Scanln(&number)
    347 	if err != nil {
    348 		return Group{}, Entry{}, err
    349 	}
    350 
    351 	ge := gEntries[number]
    352 	return ge.group, ge.entry, nil
    353 }
    354 
    355 func (c Command) Remove() {
    356 	if len(c.args) != 1 {
    357 		fmt.Fprintln(os.Stderr, "rm <group>")
    358 		os.Exit(1)
    359 	}
    360 
    361 	groupArg, _ := c.nextArg()
    362 	if group := NewGroup(groupArg); !c.repository.Exists(group) {
    363 		fmt.Fprintln(os.Stderr, "group does not exists")
    364 		os.Exit(1)
    365 	}
    366 
    367 	group, entry, err := c.selectEntry(groupArg)
    368 	if err != nil {
    369 		fmt.Fprintln(os.Stderr, err)
    370 		os.Exit(1)
    371 	}
    372 
    373 	fmt.Printf("\n%s\n\nConfirm removal (y/n):", sFormatEntry(group, entry))
    374 
    375 	var answer string
    376 	_, err = fmt.Scanln(&answer)
    377 	if err != nil {
    378 		fmt.Println(err)
    379 		os.Exit(1)
    380 	}
    381 
    382 	if strings.ToLower(answer) == "y" {
    383 		err = c.repository.Remove(group, entry)
    384 		if err != nil {
    385 			fmt.Print(err)
    386 			os.Exit(1)
    387 		}
    388 	}
    389 
    390 }
    391 
    392 func (c Command) Move() {
    393 	if len(c.args) != 2 {
    394 		fmt.Fprintln(os.Stderr, "mv <group-from> <group-to>")
    395 		os.Exit(1)
    396 	}
    397 
    398 	groupFromArg, _ := c.nextArg()
    399 	groupFrom := NewGroup(groupFromArg)
    400 	if !c.repository.Exists(groupFrom) {
    401 		fmt.Fprintf(os.Stderr, "group-from %q does not exists\n", groupFrom.Name)
    402 		os.Exit(1)
    403 	}
    404 
    405 	groupToArg, _ := c.nextArg()
    406 	groupTo := NewGroup(groupToArg)
    407 	if !c.repository.Exists(groupTo) {
    408 		fmt.Fprintf(os.Stderr, "group-to %q does not exists\n", groupTo.Name)
    409 		os.Exit(1)
    410 	}
    411 
    412 	group, entry, err := c.selectEntry(groupFromArg)
    413 	if err != nil {
    414 		fmt.Fprintln(os.Stderr, err)
    415 		os.Exit(1)
    416 	}
    417 
    418 	// TODO(tms) 14.04.23: format: <from> -> <to> [overwrite]
    419 	fmt.Printf("\n%s\n\nConfirm move (y/n):", sFormatEntry(group, entry))
    420 
    421 	var answer string
    422 	_, err = fmt.Scanln(&answer)
    423 	if err != nil {
    424 		fmt.Fprintln(os.Stderr, err)
    425 		os.Exit(1)
    426 	}
    427 
    428 	if strings.ToLower(answer) == "y" {
    429 		_, err = c.repository.Move(groupFrom, []Entry{entry}, groupTo)
    430 		if err != nil {
    431 			fmt.Fprintln(os.Stderr, err)
    432 			os.Exit(1)
    433 		}
    434 	}
    435 }