Break into multi files for more robust and less panicky
parent
7a99f2af50
commit
7fe3934f07
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type BasicStopwatch struct {
|
||||
*Timer
|
||||
}
|
||||
|
||||
func NewBasicStopwatch(t *Timer) string {
|
||||
b := &BasicStopwatch{Timer: t}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t *BasicStopwatch) left() string {
|
||||
cur := t.config.Duration - t.Remaining()
|
||||
return fmt.Sprintf(
|
||||
"%02d:%02d:%02d",
|
||||
int(cur.Hours()),
|
||||
int(cur.Minutes()),
|
||||
int(cur.Seconds())%60,
|
||||
)
|
||||
}
|
||||
|
||||
func (t *BasicStopwatch) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%s",
|
||||
t.left(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type BasicTimer struct {
|
||||
*Timer
|
||||
}
|
||||
|
||||
func NewBasicTimer(t *Timer) string {
|
||||
b := &BasicTimer{Timer: t}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (t *BasicTimer) left() string {
|
||||
cur := t.Remaining()
|
||||
return fmt.Sprintf(
|
||||
"%02d:%02d:%02d",
|
||||
int(cur.Hours()),
|
||||
int(cur.Minutes()),
|
||||
int(cur.Seconds())%60,
|
||||
)
|
||||
}
|
||||
|
||||
func (t *BasicTimer) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%s",
|
||||
t.left(),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"local/args"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Repeat bool
|
||||
Stopwatch bool
|
||||
ETA bool
|
||||
TS bool
|
||||
Done bool
|
||||
Duration time.Duration
|
||||
Offset time.Duration
|
||||
Msg string
|
||||
Interval time.Duration
|
||||
Until time.Time
|
||||
}
|
||||
|
||||
func NewConfig() (Config, error) {
|
||||
as := args.NewArgSet()
|
||||
|
||||
as.Append(args.TIME, "until", "deadline", time.Time{})
|
||||
as.Append(args.BOOL, "repeat", "whether to loop", true)
|
||||
as.Append(args.BOOL, "stopwatch", "count up to time instead of down", false)
|
||||
as.Append(args.BOOL, "eta", "whether to display deadline", false)
|
||||
as.Append(args.BOOL, "ts", "whether to display timestamp", false)
|
||||
as.Append(args.BOOL, "done", "whether to display done or not", false)
|
||||
as.Append(args.DURATION, "duration", "how long the base time is", time.Minute*30)
|
||||
as.Append(args.DURATION, "offset", "offset from the base time", time.Second*0)
|
||||
as.Append(args.STRING, "msg", "message to display", "Timer up")
|
||||
as.Append(args.DURATION, "interval", "refresh interval", time.Millisecond*500)
|
||||
|
||||
if err := as.Parse(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
config := Config{
|
||||
Repeat: as.Get("repeat").GetBool(),
|
||||
Stopwatch: as.Get("stopwatch").GetBool(),
|
||||
ETA: as.Get("eta").GetBool(),
|
||||
TS: as.Get("ts").GetBool(),
|
||||
Done: as.Get("done").GetBool(),
|
||||
Duration: as.Get("duration").GetDuration(),
|
||||
Offset: as.Get("offset").GetDuration(),
|
||||
Msg: as.Get("msg").GetString(),
|
||||
Interval: as.Get("interval").GetDuration(),
|
||||
Until: as.Get("until").GetTime(),
|
||||
}
|
||||
|
||||
if !config.Until.IsZero() {
|
||||
config.Repeat = false
|
||||
config.Duration = time.Until(config.Until) + config.Offset
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package main
|
||||
|
||||
import "github.com/eiannone/keyboard"
|
||||
|
||||
type Keyboard struct {
|
||||
Events chan byte
|
||||
}
|
||||
|
||||
func NewKeyboard() (*Keyboard, error) {
|
||||
err := keyboard.Open()
|
||||
return &Keyboard{
|
||||
Events: make(chan byte),
|
||||
}, err
|
||||
}
|
||||
|
||||
func (k *Keyboard) Listen() {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
for {
|
||||
r, _, err := keyboard.GetKey()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
b := byte(r)
|
||||
k.Events <- b
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keyboard) Next() byte {
|
||||
return <-k.Events
|
||||
}
|
||||
|
||||
func (k *Keyboard) Close() {
|
||||
keyboard.Close()
|
||||
close(k.Events)
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyboardNext(t *testing.T) {
|
||||
defer fakeInput(t)()
|
||||
k, err := NewKeyboard()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go k.Listen()
|
||||
for i := 0; i < 5; i++ {
|
||||
t.Log(k.Next())
|
||||
}
|
||||
k.Close()
|
||||
}
|
||||
|
||||
func TestKeyboardEvents(t *testing.T) {
|
||||
defer fakeInput(t)()
|
||||
k, err := NewKeyboard()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go k.Listen()
|
||||
i := 0
|
||||
for b := range k.Events {
|
||||
i += 1
|
||||
t.Log(b)
|
||||
if i == 5 {
|
||||
k.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fakeInput(t *testing.T) func() {
|
||||
if !strings.Contains(fmt.Sprint(os.Args), "-test.v=true") {
|
||||
t.Fatal("-v not passed, requries manual testing for input")
|
||||
}
|
||||
return func() {}
|
||||
}
|
||||
|
|
@ -1,253 +1,59 @@
|
|||
// Package main has a comment
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eiannone/keyboard"
|
||||
"github.com/everdev/mack"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var notified = false
|
||||
var alertMessage string
|
||||
|
||||
var originalStart = time.Now()
|
||||
|
||||
func main() {
|
||||
|
||||
var duration string
|
||||
var offset string
|
||||
var interval string
|
||||
var repeat bool
|
||||
var invert bool
|
||||
var eta bool
|
||||
var until string
|
||||
|
||||
flag.BoolVar(&repeat, "repeat", true, "Whether to repeat the timer on complete")
|
||||
flag.BoolVar(&invert, "stopwatch", false, "Use as a stopwatch")
|
||||
flag.StringVar(&duration, "duration", "30m", "How long the timer should be")
|
||||
flag.StringVar(&offset, "offset", "0m", "How much time the initial time should skip")
|
||||
flag.StringVar(&alertMessage, "msg", "Timer up", "Message to display on timer expiration")
|
||||
flag.StringVar(&interval, "interval", "500ms", "Interval duration")
|
||||
flag.BoolVar(&eta, "eta", false, "Whether to display the ending time")
|
||||
flag.StringVar(&until, "until", "", "Overload to make a non-repeating timer until given time as 23:59")
|
||||
flag.Parse()
|
||||
|
||||
if until != "" {
|
||||
repeat = false
|
||||
offset = "0m"
|
||||
now := time.Now()
|
||||
targetHourStr := strings.Split(until, ":")[0]
|
||||
targetMinStr := strings.Split(until, ":")[1]
|
||||
hour, err := strconv.Atoi(targetHourStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
min, err := strconv.Atoi(targetMinStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if hour < now.Hour() || (hour == now.Hour() && min < now.Minute()) {
|
||||
now = now.Add(time.Hour * 24)
|
||||
}
|
||||
thenTime := time.Date(now.Year(), now.Month(), now.Day(), hour, min, 0, 0, now.Location())
|
||||
duration = thenTime.Sub(time.Now()).String()
|
||||
}
|
||||
|
||||
tickerInterval, err := time.ParseDuration(interval)
|
||||
config, err := NewConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
monitor := time.NewTicker(tickerInterval)
|
||||
defer monitor.Stop()
|
||||
base, err := time.ParseDuration(duration)
|
||||
k, err := NewKeyboard()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var cur time.Duration
|
||||
if invert {
|
||||
cur = time.Duration(0)
|
||||
} else {
|
||||
cur = base
|
||||
}
|
||||
skip, err := time.ParseDuration(offset)
|
||||
go k.Listen()
|
||||
defer k.Close()
|
||||
|
||||
tp := NewTickPrinter(config.Interval)
|
||||
go tp.Start()
|
||||
defer tp.Stop()
|
||||
|
||||
timer, err := NewTimer(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if invert {
|
||||
cur += skip
|
||||
} else {
|
||||
cur -= skip
|
||||
}
|
||||
tp.Message <- timer.String
|
||||
|
||||
stop := make(chan bool)
|
||||
pause := make(chan bool)
|
||||
confirm := make(chan bool)
|
||||
paused := false
|
||||
delim := ':'
|
||||
sigc := make(chan os.Signal)
|
||||
signal.Notify(sigc, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||
|
||||
log.Print("Quit with 'q', Pause with 'p', Reset with 'r'")
|
||||
go listen(k, tp, timer, func() { close(sigc) })
|
||||
|
||||
keych := keyChannel()
|
||||
|
||||
go func() {
|
||||
last := time.Now()
|
||||
printTime(&cur, base, &delim, invert, repeat, eta)
|
||||
for {
|
||||
select {
|
||||
case <-monitor.C:
|
||||
difference := time.Duration(time.Now().UnixNano() - last.UnixNano())
|
||||
last = time.Now()
|
||||
if difference > time.Hour*2 {
|
||||
keych <- 'p'
|
||||
continue
|
||||
} else if invert {
|
||||
cur += difference
|
||||
} else {
|
||||
cur -= difference
|
||||
case <-sigc:
|
||||
}
|
||||
case state := <-pause:
|
||||
if !state {
|
||||
monitor = time.NewTicker(tickerInterval)
|
||||
last = time.Now()
|
||||
} else {
|
||||
monitor.Stop()
|
||||
}
|
||||
case <-stop:
|
||||
log.Print()
|
||||
confirm <- true
|
||||
return
|
||||
}
|
||||
printTime(&cur, base, &delim, invert, repeat, eta)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func listen(k *Keyboard, tp *TickPrinter, timer *Timer, stop func()) {
|
||||
for {
|
||||
b := <-keych
|
||||
switch b {
|
||||
case 'q':
|
||||
stop <- true
|
||||
<-confirm
|
||||
return
|
||||
key := <-k.Events
|
||||
switch key {
|
||||
case 'p':
|
||||
paused = !paused
|
||||
pause <- paused
|
||||
if paused {
|
||||
keych <- 'z'
|
||||
}
|
||||
case 'r':
|
||||
skip = time.Duration(0)
|
||||
originalStart = time.Now()
|
||||
if invert {
|
||||
cur = time.Duration(0)
|
||||
} else {
|
||||
cur = base
|
||||
}
|
||||
notified = false
|
||||
printTime(&cur, base, &delim, invert, repeat, eta)
|
||||
timer.TogglePause()
|
||||
case 'z':
|
||||
log.Print()
|
||||
printTime(&cur, base, &delim, invert, repeat, eta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTime(pRemains *time.Duration, target time.Duration, delim *rune, reverse bool, repeat bool, eta bool) {
|
||||
var final string
|
||||
remains := *pRemains
|
||||
sec := remains.Seconds()
|
||||
min := int(sec / 60.0)
|
||||
seconds := int(sec) % 60
|
||||
hrs := min / 60
|
||||
min = min % 60
|
||||
if *delim == ':' {
|
||||
*delim = ' '
|
||||
} else {
|
||||
*delim = ':'
|
||||
}
|
||||
if reverse {
|
||||
remains := target - remains
|
||||
rMin := int(remains.Seconds() / 60.0)
|
||||
rSec := int(remains.Seconds()) % 60
|
||||
rHrs := rMin / 60
|
||||
rMin = rMin % 60
|
||||
tdelim := byte(*delim)
|
||||
if remains < 0 {
|
||||
rMin = 0
|
||||
rSec = 0
|
||||
rHrs = 0
|
||||
tdelim = ':'
|
||||
go alertTime(pRemains, target, reverse, repeat)
|
||||
}
|
||||
at := fmt.Sprintf("%2d%c%02d%c%02d ", hrs, byte(*delim), min, byte(*delim), seconds)
|
||||
rem := fmt.Sprintf("%2d%c%02d%c%02d ", rHrs, tdelim, rMin, tdelim, rSec)
|
||||
rem = fmt.Sprintf("%s \tAt: %s ", rem, at)
|
||||
if eta {
|
||||
rem = fmt.Sprintf("%s \tETA: %s", rem, time.Unix(0, (time.Now().UnixNano()+target.Nanoseconds()-pRemains.Nanoseconds())).Format("3:04"))
|
||||
}
|
||||
final = rem
|
||||
} else {
|
||||
rem := fmt.Sprintf("%2d%c%02d%c%02d ", hrs, byte(*delim), min, byte(*delim), seconds)
|
||||
final = rem + " "
|
||||
if eta {
|
||||
final = fmt.Sprintf("%s \tETA: %s", final, time.Unix(0, time.Now().UnixNano()+pRemains.Nanoseconds()).Format("3:04"))
|
||||
}
|
||||
if remains < 0 {
|
||||
go alertTime(pRemains, target, reverse, repeat)
|
||||
}
|
||||
}
|
||||
fmt.Printf("\r%s", final)
|
||||
}
|
||||
|
||||
func alertTime(pRemains *time.Duration, target time.Duration, reverse, repeat bool) {
|
||||
if !notified {
|
||||
notified = true
|
||||
_, err := mack.Alert(fmt.Sprintf("%s\nTimer for %s done", alertMessage, target.String()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if repeat {
|
||||
originalStart = time.Now()
|
||||
if reverse {
|
||||
for *pRemains > 0 {
|
||||
*pRemains -= target
|
||||
}
|
||||
*pRemains += target
|
||||
} else {
|
||||
for *pRemains < 0 {
|
||||
*pRemains += target
|
||||
}
|
||||
}
|
||||
notified = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func keyChannel() chan rune {
|
||||
ch := make(chan rune, 20)
|
||||
go func() {
|
||||
if err := keyboard.Open(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for {
|
||||
b, _, err := keyboard.GetKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
by := rune(b)
|
||||
if by == 'q' {
|
||||
keyboard.Close()
|
||||
ch <- by
|
||||
fmt.Printf("\n")
|
||||
tp.Flush()
|
||||
case 'r':
|
||||
timer.Reset()
|
||||
case 'q':
|
||||
stop()
|
||||
return
|
||||
}
|
||||
ch <- by
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TickPrinter struct {
|
||||
Message chan func() string
|
||||
msg func() string
|
||||
lastLen int
|
||||
Interval time.Duration
|
||||
stop chan struct{}
|
||||
flush chan struct{}
|
||||
}
|
||||
|
||||
func NewTickPrinter(interval time.Duration) *TickPrinter {
|
||||
return &TickPrinter{
|
||||
Message: make(chan func() string),
|
||||
msg: func() string {
|
||||
return time.Now().Format("15:04:05")
|
||||
},
|
||||
stop: make(chan struct{}),
|
||||
flush: make(chan struct{}),
|
||||
Interval: interval,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TickPrinter) Start() {
|
||||
ticker := time.NewTicker(t.Interval)
|
||||
for {
|
||||
select {
|
||||
case <-t.flush:
|
||||
t.Print()
|
||||
case <-ticker.C:
|
||||
t.Print()
|
||||
case msg := <-t.Message:
|
||||
t.msg = msg
|
||||
case <-t.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TickPrinter) Stop() {
|
||||
close(t.stop)
|
||||
time.Sleep(t.Interval)
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
func (t *TickPrinter) Flush() {
|
||||
t.flush <- struct{}{}
|
||||
}
|
||||
|
||||
func (t *TickPrinter) Print() {
|
||||
fmt.Printf("\r%s", strings.Repeat(" ", t.lastLen))
|
||||
msg := t.msg()
|
||||
l := len(msg)
|
||||
if len(msg) < t.lastLen {
|
||||
msg += strings.Repeat(" ", t.lastLen-len(msg))
|
||||
}
|
||||
t.lastLen = l
|
||||
fmt.Printf("\r%s", msg)
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTickPrinter(t *testing.T) {
|
||||
defer testOut(t)()
|
||||
name := os.Stdout.Name()
|
||||
|
||||
tp := NewTickPrinter(time.Millisecond * 250)
|
||||
go tp.Start()
|
||||
time.Sleep(time.Millisecond * 300)
|
||||
tp.Stop()
|
||||
b, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ts, err := time.Parse("15:04:05", strings.TrimSpace(string(b)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if (time.Now().Minute()*60+time.Now().Hour())-(ts.Minute()*60+ts.Hour()) > 1 {
|
||||
t.Fatal(ts)
|
||||
}
|
||||
}
|
||||
|
||||
func testOut(t *testing.T) func() {
|
||||
wasOut := os.Stdout
|
||||
|
||||
var err error
|
||||
if os.Stdout, err = ioutil.TempFile(os.TempDir(), "testOut*"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return func() {
|
||||
os.Stdout = wasOut
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Typed func(*Timer) string
|
||||
|
||||
type Timer struct {
|
||||
config Config
|
||||
From time.Time
|
||||
Typed Typed
|
||||
paused bool
|
||||
offset time.Duration
|
||||
}
|
||||
|
||||
func NewTimer(config Config) (*Timer, error) {
|
||||
t := &Timer{From: time.Now().Add(time.Duration(-1) * config.Offset), config: config}
|
||||
return t, t.setTyped(config)
|
||||
}
|
||||
|
||||
func (t *Timer) TogglePause() {
|
||||
if t.paused {
|
||||
t.Resume()
|
||||
} else {
|
||||
t.Pause()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timer) Resume() {
|
||||
t.paused = false
|
||||
t.From = time.Now().Add(time.Duration(-1) * t.offset)
|
||||
t.offset = 0
|
||||
}
|
||||
|
||||
func (t *Timer) Pause() {
|
||||
t.paused = true
|
||||
t.offset = time.Since(t.From)
|
||||
}
|
||||
|
||||
func (t *Timer) setTyped(config Config) error {
|
||||
if config.Stopwatch {
|
||||
t.Typed = NewBasicStopwatch
|
||||
} else {
|
||||
t.Typed = NewBasicTimer
|
||||
}
|
||||
|
||||
if config.ETA {
|
||||
t.Typed = WithETA(t.Typed)
|
||||
}
|
||||
|
||||
if config.Done {
|
||||
t.Typed = WithDone(t.Typed)
|
||||
}
|
||||
|
||||
if config.TS {
|
||||
t.Typed = WithTS(t.Typed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Timer) Remaining() time.Duration {
|
||||
return time.Until(t.Deadline()) + time.Second
|
||||
}
|
||||
|
||||
func (t *Timer) Deadline() time.Time {
|
||||
from := t.From
|
||||
if t.paused {
|
||||
from = time.Now().Add(time.Duration(-1) * t.offset)
|
||||
}
|
||||
return from.Add(t.config.Duration)
|
||||
}
|
||||
|
||||
func (t *Timer) Ack() {
|
||||
for t.Done() {
|
||||
t.From = t.From.Add(t.config.Duration)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timer) Reset() {
|
||||
t.From = time.Now()
|
||||
}
|
||||
|
||||
func (t *Timer) Done() bool {
|
||||
return time.Now().After(t.From.Add(t.config.Duration))
|
||||
}
|
||||
|
||||
func (t *Timer) String() string {
|
||||
return t.Typed(t)
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func WithETA(typed Typed) Typed {
|
||||
return func(t *Timer) string {
|
||||
s := typed(t)
|
||||
return fmt.Sprintf(
|
||||
"%s\tETA: %s",
|
||||
s,
|
||||
t.Deadline().Format("15:04:05"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func WithDone(typed Typed) Typed {
|
||||
return func(t *Timer) string {
|
||||
s := typed(t)
|
||||
return fmt.Sprintf(
|
||||
"%s\tDone: %v",
|
||||
s,
|
||||
t.Done(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func WithTS(typed Typed) Typed {
|
||||
return func(t *Timer) string {
|
||||
s := typed(t)
|
||||
return fmt.Sprintf(
|
||||
"%s: %s",
|
||||
time.Now().Format("15:04:05"),
|
||||
s,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue