1package annotate 2 3import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "time" 8 9 glob "github.com/ryanuber/go-glob" 10) 11 12type RFC3339 struct { 13 time.Time 14} 15 16func (t RFC3339) MarshalJSON() ([]byte, error) { 17 return []byte(`"` + t.Format(time.RFC3339) + `"`), nil 18} 19 20func (t *RFC3339) UnmarshalJSON(b []byte) (err error) { 21 if b[0] == '"' && b[len(b)-1] == '"' { 22 b = b[1 : len(b)-1] 23 } 24 if len(b) == 0 { 25 t.Time = time.Time{} 26 return 27 } 28 t.Time, err = time.Parse(time.RFC3339, string(b)) 29 return 30} 31 32type Epoch struct { 33 time.Time 34} 35 36func (t Epoch) MarshalJSON() ([]byte, error) { 37 return []byte(fmt.Sprintf("%v", t.UTC().Unix())), nil 38} 39 40func (t *Epoch) UnmarshalJSON(b []byte) (err error) { 41 if len(b) == 0 { 42 t.Time = time.Time{} 43 return 44 } 45 epoch, err := strconv.ParseInt(string(b), 10, 64) 46 if err != nil { 47 return err 48 } 49 t.Time = time.Unix(epoch, 0) 50 return 51} 52 53func NewAnnotation(id string, start, end time.Time, user, owner, source, host, category, url, message string) (a Annotation) { 54 a.Id = id 55 a.StartDate.Time = start 56 a.EndDate.Time = end 57 a.CreationUser = user 58 a.Owner = owner 59 a.Source = source 60 a.Category = category 61 a.Url = url 62 a.Message = message 63 a.Host = host 64 return 65} 66 67type Annotation struct { 68 AnnotationFields 69 StartDate RFC3339 70 EndDate RFC3339 71} 72 73type EpochAnnotation struct { 74 AnnotationFields 75 StartDate Epoch 76 EndDate Epoch 77} 78 79// AnnotationsByStartID is a Type to sort by start time then by id 80type AnnotationsByStartID Annotations 81 82func (b AnnotationsByStartID) Len() int { return len(b) } 83func (b AnnotationsByStartID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 84func (b AnnotationsByStartID) Less(i, j int) bool { 85 if b[i].StartDate.Time.Before(b[j].StartDate.Time) { 86 return true 87 } 88 if b[i].StartDate.Time.After(b[j].StartDate.Time) { 89 return false 90 } 91 return b[i].Id < b[j].Id 92} 93 94func emptyOrGlob(userVal, fieldVal string) bool { 95 if userVal == "empty" && fieldVal == "" { 96 return true 97 } 98 99 return glob.Glob(strings.ToLower(userVal), strings.ToLower(fieldVal)) 100} 101 102// Ask makes it so annotations can be filtered in memory using 103// github.com/kylebrandt/boolq 104func (a Annotation) Ask(filter string) (bool, error) { 105 sp := strings.SplitN(filter, ":", 2) 106 if len(sp) != 2 { 107 return false, fmt.Errorf("bad filter, filter must be in k:v format, got %v", filter) 108 } 109 key := sp[0] 110 value := sp[1] 111 switch key { 112 case "owner": 113 return emptyOrGlob(value, a.Owner), nil 114 case "user": 115 return emptyOrGlob(value, a.CreationUser), nil 116 case "host": 117 return emptyOrGlob(value, a.Host), nil 118 case "category": 119 return emptyOrGlob(value, a.Category), nil 120 case "url": 121 return emptyOrGlob(value, a.Url), nil 122 case "message": 123 return emptyOrGlob(value, a.Message), nil 124 default: 125 return false, fmt.Errorf("invalid keyword: %s", key) 126 } 127} 128 129func (ea *EpochAnnotation) AsAnnotation() (a Annotation) { 130 a.AnnotationFields = ea.AnnotationFields 131 a.StartDate.Time = ea.StartDate.Time 132 a.EndDate.Time = ea.EndDate.Time 133 return 134} 135 136func (a *Annotation) AsEpochAnnotation() (ea EpochAnnotation) { 137 ea.AnnotationFields = a.AnnotationFields 138 ea.StartDate.Time = a.StartDate.Time 139 ea.EndDate.Time = a.EndDate.Time 140 return 141} 142 143type AnnotationFields struct { 144 Id string 145 Message string 146 CreationUser string 147 Url string 148 Source string 149 Host string 150 Owner string 151 Category string 152} 153 154const ( 155 Message = "Message" 156 StartDate = "StartDate" 157 EndDate = "EndDate" 158 Source = "Source" 159 Host = "Host" 160 CreationUser = "CreationUser" 161 Owner = "Owner" 162 Category = "Category" 163 Url = "Url" 164) 165 166type Annotations []Annotation 167type EpochAnnotations []EpochAnnotation 168 169func (as Annotations) AsEpochAnnotations() EpochAnnotations { 170 eas := make(EpochAnnotations, len(as)) 171 for i, a := range as { 172 eas[i] = a.AsEpochAnnotation() 173 } 174 return eas 175} 176 177func (a *Annotation) SetNow() { 178 a.StartDate.Time = time.Now() 179 a.EndDate = a.StartDate 180} 181 182func (a *Annotation) IsTimeNotSet() bool { 183 t := time.Time{} 184 return a.StartDate.Equal(t) || a.EndDate.Equal(t) 185} 186 187func (a *Annotation) IsOneTimeSet() bool { 188 t := time.Time{} 189 return (a.StartDate.Equal(t) && !a.EndDate.Equal(t)) || (!a.StartDate.Equal(t) && a.EndDate.Equal(t)) 190} 191 192// Match Times Sets Both times to the greater of the two times 193func (a *Annotation) MatchTimes() { 194 if a.StartDate.After(a.EndDate.Time) { 195 a.EndDate = a.StartDate 196 return 197 } 198 a.StartDate = a.EndDate 199} 200 201func (a *Annotation) ValidateTime() error { 202 t := time.Time{} 203 if a.StartDate.Equal(t) { 204 return fmt.Errorf("StartDate is not set") 205 } 206 if a.EndDate.Equal(t) { 207 return fmt.Errorf("StartDate is not set") 208 } 209 if a.EndDate.Before(a.StartDate.Time) { 210 return fmt.Errorf("EndDate is before StartDate") 211 } 212 return nil 213} 214