diff --git a/main.go b/main.go index 09c816b..00ef086 100644 --- a/main.go +++ b/main.go @@ -5,8 +5,11 @@ import ( "flag" "fmt" "io/fs" + "math" "os" "path/filepath" + "regexp" + "strconv" "strings" "time" @@ -46,18 +49,196 @@ func main() { fmt.Printf("could not load data: %v", err) } - fmt.Println(data) + query := fs.Arg(0) + + // Find a single feature + if feat, ok := data.Data[query]; ok { + fmt.Println(formatFeat(feat, data, false)) + return + } + + // Find multiple features + features := search(query, data) + switch len(features) { + case 0: + fmt.Printf("%q not found", query) + case 1: + fmt.Println(formatFeat(data.Data[features[0]], data, false)) + default: + for _, feat := range features { + fmt.Println(formatFeat(data.Data[feat], data, true)) + } + + } } -func showFeat(d data) { - var p []string - if d.UsagePercY > 0 { - p = append(p, fmt.Sprintf("%s %s%%", resultmap["y"], color.GreenString("%f", d.UsagePercY))) +var clumpRe = regexp.MustCompile(`\W*`) + +func search(query string, data Data) []string { + var results []string + query = strings.ToLower(query) + for key, dat := range data.Data { + matcher := clumpRe.ReplaceAllString(strings.ToLower(key+dat.Title+dat.Description+dat.Keywords+strings.Join(dat.Categories, "")), "") + if strings.Contains(matcher, query) { + results = append(results, key) + } } - if d.UsagePercA > 0 { - p = append(p, fmt.Sprintf("%s %s%%", resultmap["a"], color.YellowString("%f", d.UsagePercA))) + return results +} + +var replaceNoteRe = regexp.MustCompile(`[\r\n]+`) + +func formatFeat(feat data, d Data, short bool) string { + bold := color.New(color.Bold) + + var out strings.Builder + out.WriteString(bold.Sprint(feat.Title)) + + var p []string + if feat.UsagePercY > 0 { + p = append(p, fmt.Sprintf("%s %s%%", resultmap["y"], color.GreenString("%f", feat.UsagePercY))) + } + if feat.UsagePercA > 0 { + p = append(p, fmt.Sprintf("%s %s%%", resultmap["a"], color.YellowString("%f", feat.UsagePercA))) } percentages := strings.Join(p, " ") + out.WriteString(" " + percentages) + + status := fmt.Sprintf(" [%s]\n", d.Statuses[feat.Status]) + out.WriteString(status) + + if !short { + out.WriteString("\t" + strings.TrimSpace(feat.Description) + "\n\n") + } + + if short { + out.WriteString("\t") + } + + needNote := make(map[int]struct{}) + for browser, stats := range feat.Stats { + if d.Agents[browser].Type != "desktop" { + continue + } + if !short { + out.WriteString("\t") + } + out.WriteString(d.Agents[browser].Browser + " ") + + results := makeResults(d.Agents[browser], stats) + if len(results) == 1 { + results[0].version = "" + } + + for _, result := range results { + out.WriteString(makeResult(result, needNote)) + } + if !short { + out.WriteString("\n") + } + } + + if !short { + out.WriteString("\n") + } + + if !short { + for num, note := range feat.NotesByNum { + n, _ := strconv.Atoi(num) + if _, ok := needNote[n]; !ok { + continue + } + out.WriteString(fmt.Sprintf("\t\t%s%s", color.YellowString(string(supernums[n])), note)) + } + if feat.Notes != "" { + out.WriteString(fmt.Sprintf("\t %s %s", resultmap["i"], replaceNoteRe.ReplaceAllString(feat.Notes, " "))) + } + } + + return out.String() +} + +var supportRe = regexp.MustCompile(`#(\d+)`) + +func makeResult(stat browserStat, nums map[int]struct{}) string { + support := string(stat.support[0]) + var out strings.Builder + bace := "\u2800" + if s, ok := resultmap[support]; ok { + out.WriteString(s) + } else { + out.WriteString(support) + } + out.WriteString(bace) + if stat.version != "" { + out.WriteString(stat.version) + } + if strings.Contains(string(stat.support), "x") { + out.WriteString("ᵖ") + } + match := supportRe.FindStringSubmatch(string(stat.support)) + if match != nil { + num, _ := strconv.Atoi(match[1]) + nums[num] = struct{}{} + out.WriteString(string(supernums[num])) + } + if stat.usage > 0 { + str := out.String() + if string(str[len(str)-1]) != bace { + out.WriteString(" ") + } + out.WriteString(fmt.Sprintf("(%f) ", math.Round(stat.usage*1)/1)) + } + out.WriteString(" ") + + str := out.String() + switch support { + case "y": + return color.GreenString(str) + case "n": + return color.RedString(str) + case "a": + return color.YellowString(str) + default: + return str + } +} + +func makeResults(browser browser, stats map[string]support) []browserStat { + var results []browserStat + var current browserStat + for idx, version := range browser.Versions { + if version == "" { + continue + } + support := stats[version] + + var usage float64 + if u, ok := browser.UsageGlobal[version]; ok { + usage = u + } + + ver := version + if len(browser.Versions) > idx { + ver += "+" + } + + if support[0] == 'p' { + support = "n" + support[1:] + } + + if current.version == "" || current.support != support { + current = browserStat{ + version: ver, + support: support, + usage: 0, + } + results = append(results, current) + } + + current.usage += usage + } + return results } func loadData() (Data, error) { diff --git a/struct.go b/struct.go index 3ffe05a..0c5a5ef 100644 --- a/struct.go +++ b/struct.go @@ -1,32 +1,26 @@ package main type Data struct { - Eras map[string]string `json:"eras"` - Agents map[string]struct { - Browser string `json:"browser"` - LongName string `json:"long_name"` - Abbr string `json:"abbr"` - Prefix string `json:"prefix"` - Type string `json:"type"` - UsageGlobal map[string]float64 `json:"usage_global"` - Versions []string `json:"versions"` - } `json:"agents"` - Statuses struct { - Rec string `json:"rec"` - Pr string `json:"pr"` - Cr string `json:"cr"` - Wd string `json:"wd"` - Ls string `json:"ls"` - Other string `json:"other"` - Unoff string `json:"unoff"` - } `json:"statuses"` - Cats map[string][]string `json:"cats"` - Data map[string]data `json:"data"` + Eras map[string]string `json:"eras"` + Agents map[string]browser `json:"agents"` + Statuses map[string]string `json:"statuses"` + Cats map[string][]string `json:"cats"` + Data map[string]data `json:"data"` } -type stat string +type browser struct { + Browser string `json:"browser"` + LongName string `json:"long_name"` + Abbr string `json:"abbr"` + Prefix string `json:"prefix"` + Type string `json:"type"` + UsageGlobal map[string]float64 `json:"usage_global"` + Versions []string `json:"versions"` +} -func (s stat) Bool() bool { +type support string + +func (s support) Bool() bool { return s == "y" } @@ -39,17 +33,23 @@ type data struct { URL string `json:"url"` Title string `json:"title"` } `json:"links"` - Categories []string `json:"categories"` - Stats map[string]map[string]stat `json:"stats"` - Notes string `json:"notes"` - NotesByNum map[string]string `json:"notes_by_num"` - UsagePercY float64 `json:"usage_perc_y"` - UsagePercA float64 `json:"usage_perc_a"` - UCPrefix bool `json:"ucprefix"` - Parent string `json:"parent"` - Keywords string `json:"keywords"` - IEID string `json:"ie_id"` - ChromeID string `json:"chrome_id"` - FirefoxID string `json:"firefox_id"` - WebkitID string `json:"webkit_id"` + Categories []string `json:"categories"` + Stats map[string]map[string]support `json:"stats"` + Notes string `json:"notes"` + NotesByNum map[string]string `json:"notes_by_num"` + UsagePercY float64 `json:"usage_perc_y"` + UsagePercA float64 `json:"usage_perc_a"` + UCPrefix bool `json:"ucprefix"` + Parent string `json:"parent"` + Keywords string `json:"keywords"` + IEID string `json:"ie_id"` + ChromeID string `json:"chrome_id"` + FirefoxID string `json:"firefox_id"` + WebkitID string `json:"webkit_id"` +} + +type browserStat struct { + version string + support support + usage float64 }