github.com/mentimeter/morty@v1.2.2-0.20221012065510-5596adecd154/mortems/mortem_data.go (about) 1 package mortems 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "strconv" 8 "strings" 9 "time" 10 ) 11 12 type MortemData struct { 13 File string `json:"file"` 14 Title string `json:"title"` 15 Owner string `json:"owner"` 16 Date time.Time `json:"date"` 17 Severity string `json:"severity"` 18 Detect time.Duration `json:"detect"` 19 Resolve time.Duration `json:"resolve"` 20 Downtime time.Duration `json:"total_down"` 21 } 22 23 var ErrNoTitle = errors.New("no title of format \"# Title Here\"") 24 var ErrNoOwner = errors.New("no owner of format \"Owner: First Last\"") 25 var ErrNoDate = errors.New("no date of format \"July 1, 2020\" (no st, nd, th)") 26 var ErrNoSeverity = errors.New("no severity of format \"| Severity | sev |\"") 27 var ErrNoDetect = errors.New("no time to detect of format \"| Time to Detect | x unit[, y smaller_unit] |\"") 28 var ErrNoResolve = errors.New("no time to resolve of format \"| Time to Resolve | x unit[, y smaller_unit] |\"") 29 var ErrNoDowntime = errors.New("no total downtime format \"| Total Downtime | x unit[, y smaller_unit] |\"") 30 31 func NewMortemData(content, path string) (MortemData, error) { 32 title, err := ParseTitle(content) 33 if err != nil { 34 return MortemData{}, err 35 } 36 37 owner, err := ParseOwner(content) 38 if err != nil { 39 return MortemData{}, err 40 } 41 42 date, err := ParseDate(content) 43 if err != nil { 44 return MortemData{}, err 45 } 46 47 severity, err := ParseSeverity(content) 48 if err != nil { 49 return MortemData{}, err 50 } 51 52 detect, err := ParseDetect(content) 53 if err != nil { 54 return MortemData{}, err 55 } 56 57 resolve, err := ParseResolve(content) 58 if err != nil { 59 return MortemData{}, err 60 } 61 62 downtime, err := ParseDowntime(content) 63 if err != nil { 64 return MortemData{}, err 65 } 66 67 return MortemData{ 68 File: path, 69 Title: title, 70 Owner: owner, 71 Date: date, 72 Severity: severity, 73 Detect: detect, 74 Resolve: resolve, 75 Downtime: downtime, 76 }, nil 77 } 78 79 func ParseTitle(content string) (string, error) { 80 re := regexp.MustCompile(`(?i)# (?P<Title>.+)`) 81 82 title := re.FindStringSubmatch(content) 83 if title == nil { 84 return "", ErrNoTitle 85 } 86 87 return title[1], nil 88 } 89 90 func ParseOwner(content string) (string, error) { 91 re := regexp.MustCompile(`(?i).*Owner: (?P<Owner>.+)`) 92 93 owner := re.FindStringSubmatch(content) 94 if owner == nil { 95 return "", ErrNoOwner 96 } 97 98 return owner[1], nil 99 } 100 101 func ParseDate(content string) (time.Time, error) { 102 re := regexp.MustCompile(`(?i).*Date: (?P<Date>.+)`) 103 104 dateStr := re.FindStringSubmatch(content) 105 if dateStr == nil { 106 return time.Now(), ErrNoDate 107 } 108 109 var date time.Time 110 var err error 111 112 date, err = time.Parse("January 2, 2006", dateStr[1]) 113 if err != nil { 114 date, err = time.Parse("Jan 2, 2006", dateStr[1]) 115 if err != nil { 116 return time.Now(), fmt.Errorf("incorrect date format, %s: %w", err, ErrNoDate) 117 } 118 } 119 120 return date, nil 121 } 122 123 func ParseSeverity(content string) (string, error) { 124 re := regexp.MustCompile(`(?i) *\| *Severity +\| *(.+) +\| *`) 125 126 sev := re.FindStringSubmatch(content) 127 if sev == nil { 128 return "", ErrNoSeverity 129 } 130 131 return sev[1], nil 132 } 133 134 func ParseDetect(content string) (time.Duration, error) { 135 re := regexp.MustCompile(`(?i) *\|.*Detect +\| *(.+) +\| *`) 136 137 detectMatches := re.FindStringSubmatch(content) 138 if detectMatches == nil { 139 return 0, ErrNoDetect 140 } 141 142 detectString := detectMatches[1] 143 144 detectTime, err := stringToTime(detectString) 145 if err != nil { 146 return 0, fmt.Errorf("%w: %s", ErrNoDetect, err) 147 } 148 149 return detectTime, nil 150 } 151 152 func ParseResolve(content string) (time.Duration, error) { 153 re := regexp.MustCompile(`(?i) *\|.*Resolve +\| *(.+) +\| *`) 154 155 resolveMatches := re.FindStringSubmatch(content) 156 if resolveMatches == nil { 157 return 0, ErrNoResolve 158 } 159 160 resolveString := resolveMatches[1] 161 162 resolveTime, err := stringToTime(resolveString) 163 if err != nil { 164 return 0, fmt.Errorf("%w: %s", ErrNoResolve, err) 165 } 166 167 return resolveTime, nil 168 } 169 170 func ParseDowntime(content string) (time.Duration, error) { 171 re := regexp.MustCompile(`(?i) *\|.*Downtime +\| *(.+) +\| *`) 172 173 downtimeMatches := re.FindStringSubmatch(content) 174 if downtimeMatches == nil { 175 return 0, ErrNoDowntime 176 } 177 178 downtimeString := downtimeMatches[1] 179 180 downtimeTime, err := stringToTime(downtimeString) 181 if err != nil { 182 return 0, fmt.Errorf("%w: %s", ErrNoDowntime, err) 183 } 184 185 return downtimeTime, nil 186 } 187 188 func stringToTime(timeStr string) (time.Duration, error) { 189 noSpaceDetect := strings.ReplaceAll(timeStr, " ", "") 190 lowerCaseDetect := strings.ToLower(noSpaceDetect) 191 timeGroups := strings.Split(lowerCaseDetect, ",") 192 193 totalTime := time.Duration(0) 194 for _, t := range timeGroups { 195 re := regexp.MustCompile(`[^0-9]*([0-9]+)[^0-9]*`) 196 goTimeString := "" 197 198 if strings.Contains(t, "day") { 199 timeString := re.FindStringSubmatch(t) 200 if timeString == nil { 201 return 0, errors.New("missing number of days") 202 } 203 204 days, err := strconv.Atoi(timeString[1]) 205 if err != nil { 206 return 0, fmt.Errorf("could not parse days: %w", err) 207 } 208 209 hours := days * 24 210 goTimeString = strconv.Itoa(hours) + "h" 211 } else if strings.Contains(t, "hour") { 212 timeString := re.FindStringSubmatch(t) 213 if timeString == nil { 214 return 0, errors.New("missing number of hours") 215 } 216 217 goTimeString = timeString[1] + "h" 218 } else if strings.Contains(t, "min") { 219 timeString := re.FindStringSubmatch(t) 220 if timeString == nil { 221 return 0, errors.New("missing number of minutes") 222 } 223 224 goTimeString = timeString[1] + "m" 225 } else if strings.Contains(t, "sec") { 226 timeString := re.FindStringSubmatch(t) 227 if timeString == nil { 228 return 0, errors.New("missing number of seconds") 229 } 230 231 goTimeString = timeString[1] + "s" 232 } 233 234 thisTime, err := time.ParseDuration(goTimeString) 235 if err != nil { 236 return 0, ErrNoDetect 237 } 238 239 totalTime += thisTime 240 } 241 242 return totalTime, nil 243 }