-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
150 lines (138 loc) · 5.34 KB
/
main.go
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main
import (
"bufio"
"flag"
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/robfig/cron/v3"
)
// Version contains the binary version. This is added at build time.
var Version = "uncommitted"
var showVersion = false
var showDeltaSeconds bool = false
var showDeltaHMS bool = false
var showDeltaCoarse bool = false
var showTimestamp bool = true
var showCronSpec bool = false
var showRedirectDetails bool = false
var timeFormat = "2006-01-02T15:04:05MST"
var nowTimestamp = time.Now().Format(timeFormat)
var now time.Time
func init() {
flag.BoolVar(&showVersion, "version", showVersion, `Displays version information, then exits`)
flag.BoolVar(&showDeltaSeconds, "deltaseconds", showDeltaSeconds, `Show delta seconds until next run, before delta hms and full timestamp`)
flag.BoolVar(&showDeltaHMS, "deltahms", showDeltaHMS, `Show delta h/m/s until next run, before full timestamp`)
flag.BoolVar(&showDeltaCoarse, "deltacoarse", showDeltaCoarse, `Show "coarse" delta h/m/s until next run, before full timestamp`)
flag.BoolVar(&showTimestamp, "timestamp", showTimestamp, `Show full timestamp, before cron spec`)
flag.BoolVar(&showCronSpec, "spec", showCronSpec, `Show full/original cron spec (i.e. the "* * * * *" bits + command etc.).`)
flag.BoolVar(&showRedirectDetails, "redir", showRedirectDetails, `(requires -spec=0) Show redirect details (">/dev/null", "2>&1") in command`)
flag.StringVar(&nowTimestamp, "now", nowTimestamp, `override the timestamp from "now" to a given date/time (`+timeFormat+` format required)`)
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage:\n\tcrontab -l | prettycrontab [OPTIONS]\n\n")
fmt.Fprintf(os.Stderr, "Shows the next run time of interesting cron entries, from most recent to least.\n")
fmt.Fprintf(os.Stderr, "You can tag a block of entries as `## UNINTERESTING` to have the block skipped.\n")
fmt.Fprintf(os.Stderr, "You can tag an entry as `## LABEL foo` to have the label changed.\n")
fmt.Fprintf(os.Stderr, "Otherwise, all commands are shown in the list.\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, "\t* * * * * /usr/local/bin/foo --bar >/dev/null 2>&1 # shown as-is\n")
fmt.Fprintf(os.Stderr, "\t## UNINTERESTING\n\t* * * * * /usr/local/bin/foo --bar >/dev/null 2>&1 # not shown\n")
fmt.Fprintf(os.Stderr, "\t* * * * * /usr/local/bin/foo --bar >/dev/null 2>&1 # not shown due to previous UNINTERESTING\n\n")
fmt.Fprintf(os.Stderr, "\t* * * * * /usr/local/bin/foo --bar >/dev/null 2>&1 # shown as-is as the double new line reset the block\n")
fmt.Fprintf(os.Stderr, "\t## LABEL foo --bar\n\t* * * * * /usr/local/bin/foo --bar >/dev/null 2>&1 # shown with custom label\n")
fmt.Fprintf(os.Stderr, "\t* * * * * /usr/local/bin/foo --bar >/dev/null 2>&1 # shown as-is as the custom label is only valid for one entry\n")
fmt.Fprintf(os.Stderr, "\nOptions:\n")
flag.PrintDefaults()
fmt.Printf("\nThis is prettycrontab %s\n", Version)
}
flag.Parse()
if showVersion {
fmt.Printf("%s\n", Version)
os.Exit(0)
}
if showDeltaHMS && showDeltaCoarse {
panic(fmt.Errorf("cannot -deltahms and -deltacoarse at the same time"))
}
var err error
now, err = time.Parse(timeFormat, nowTimestamp)
if err != nil {
panic(fmt.Errorf("Could not parse -now '%s': %v", nowTimestamp, err))
}
}
// Once finding "## UNINTERESTING", skip the next entries until an *empty line* is found.
var nextIsUninteresting bool = false
// Once finding "## LABEL ...", "label" the next *cron* entry as "label".
var foundLabel bool = false
var wantedLabel string = ""
// If the first chunk starts with a # then it's a comment!
var rxAllComments *regexp.Regexp = regexp.MustCompile(`\A\s*[#]`)
// If the first chunk looks like a variable setting
var rxVariableSet *regexp.Regexp = regexp.MustCompile(`\A\s*\w+\s*=`)
func main() {
p := cron.NewParser(
cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
spec := scanner.Text()
chunks := strings.Fields(spec)
// Skip empty lines
if len(chunks) == 0 {
nextIsUninteresting = false
foundLabel = false
continue
}
if chunks[0] == "##" && len(chunks) >= 2 && chunks[1] == "UNINTERESTING" {
nextIsUninteresting = true
continue
}
if chunks[0] == "##" && len(chunks) >= 2 && chunks[1] == "LABEL" {
nextIsUninteresting = false
foundLabel = true
wantedLabel = strings.Join(chunks[2:], " ")
continue
}
// Skip comment lines
if rxAllComments.MatchString(chunks[0]) {
continue
}
// Skip lines setting variables
if rxVariableSet.MatchString(chunks[0]) {
continue
}
if len(chunks) < 5 {
panic(fmt.Errorf("Invalid cron entry too few fields: %s", spec))
}
cronPart := strings.Join(chunks[0:5], " ")
s, err := p.Parse(cronPart)
if err != nil {
panic(fmt.Errorf("Could not parse cron part of %s: %s: %s", spec, cronPart, err))
}
if nextIsUninteresting {
foundLabel = false
continue
}
nextRun := s.Next(now)
ce := CronEntry{
spec: spec,
chunks: chunks,
nextRun: nextRun,
label: wantedLabel,
}
CronEntries = append(CronEntries, ce)
wantedLabel = ""
foundLabel = false
}
if err := scanner.Err(); err != nil {
panic(err)
}
sort.Slice(CronEntries, func(i, j int) bool {
return CronEntries[i].nextRun.Unix() < CronEntries[j].nextRun.Unix()
})
for _, ce := range CronEntries {
fmt.Println(ce.Show())
}
}