Blank identifier discarding results; verify intentional ignoring of return values
_, _ = fmt.Fprintf(w, "%s:%d\n", k, v)
1package main23import (4 "context"5 "errors"6 "fmt"7 "math"8 "net/http"9 "net/url"10 "os"11 "os/exec"12 "path/filepath"13 "regexp"14 "slices"15 "strconv"16 "strings"17 "sync"18 "time"1920 "github.com/boyter/scc/v3/processor"21 "github.com/boyter/simplecache"22 jsoniter "github.com/json-iterator/go"23 "github.com/rs/zerolog/log"24)2526var (27 uniqueCode = "unique_code"28 cache = simplecache.New[[]processor.LanguageSummary](simplecache.Option{29 MaxItems: intPtr(1000),30 MaxAge: timePtr(time.Hour * 72),31 })32 countingSemaphore = make(chan bool, 1)33 tmpDir = os.TempDir()34 json = jsoniter.ConfigCompatibleWithStandardLibrary35 locationLog = []string{}36 locationTracker = map[string]int{}37 locationLogMutex = sync.Mutex{}38)3940func intPtr(i int) *int {41 return &i42}4344func timePtr(t time.Duration) *time.Duration {45 return &t46}4748func main() {49 http.HandleFunc("/health-check/", func(w http.ResponseWriter, r *http.Request) {50 locationLogMutex.Lock()51 for k, v := range locationTracker {52 _, _ = fmt.Fprintf(w, "%s:%d\n", k, v)53 }54 locationLogMutex.Unlock()55 })5657 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {58 loc, err := processUrlPath(r.URL.Path)59 if err != nil {60 http.Redirect(w, r, "https://github.com/boyter/scc/?tab=readme-ov-file#badges-beta", http.StatusTemporaryRedirect)61 return62 }6364 if filterBad(loc) {65 log.Error().Str(uniqueCode, "bfee4bd8").Str("loc", loc.String()).Msg("filter bad")66 return67 }6869 appendLocationLog(loc.String())7071 res, err := process(1, loc)72 if err != nil {73 log.Error().Str(uniqueCode, "03ec75c3").Err(err).Str("loc", loc.String()).Send()74 w.WriteHeader(http.StatusBadRequest)75 _, _ = w.Write([]byte("something bad happened sorry"))76 return77 }7879 category := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("category")))80 wage := tryParseInt(strings.TrimSpace(strings.ToLower(r.URL.Query().Get("avg-wage"))), 56286)81 title, value := calculate(category, wage, res)8283 if r.URL.Query().Get("lower") != "" {84 title = strings.ToLower(title)85 }8687 s := formatCount(float64(value))88 if category == "effort" {89 s = formatMonthsInt(value)90 }9192 textLength := "250"93 if len(s) <= 3 {94 textLength = "200"95 }9697 bs := parseBadgeSettings(r.URL.Query())9899 log.Info().Str(uniqueCode, "42c5269c").Str("loc", loc.String()).Str("category", category).Send()100 w.Header().Set("Content-Type", "image/svg+xml;charset=utf-8")101 if category == "effort" {102 _, _ = w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="` + bs.TopShadowAccentColor + `" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="240" height="20" rx="3" fill="#` + bs.FontColor + `"/></clipPath><g clip-path="url(#a)"><path fill="#` + bs.TitleBackgroundColor + `" d="M0 0h125v20H0z"/><path fill="#` + bs.BadgeBackgroundColor + `" d="M125 0h115v20H125z"/><path fill="url(#b)" d="M0 0h240v20H0z"/></g><g fill="#` + bs.FontColor + `" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="625" y="150" fill="#` + bs.TextShadowColor + `" fill-opacity=".3" transform="scale(.1)">` + title + `</text><text x="625" y="140" transform="scale(.1)">` + title + `</text><text x="1825" y="150" fill="#` + bs.TextShadowColor + `" fill-opacity=".3" transform="scale(.1)">` + s + `</text><text x="1825" y="140" transform="scale(.1)">` + s + `</text></g> </svg>`))103 } else {104 _, _ = w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="` + bs.TopShadowAccentColor + `" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="100" height="20" rx="3" fill="#` + bs.FontColor + `"/></clipPath><g clip-path="url(#a)"><path fill="#` + bs.TitleBackgroundColor + `" d="M0 0h69v20H0z"/><path fill="#` + bs.BadgeBackgroundColor + `" d="M69 0h31v20H69z"/><path fill="url(#b)" d="M0 0h100v20H0z"/></g><g fill="#` + bs.FontColor + `" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="355" y="150" fill="#` + bs.TextShadowColor + `" fill-opacity=".3" transform="scale(.1)" textLength="590">` + title + `</text><text x="355" y="140" transform="scale(.1)" textLength="590">` + title + `</text><text x="835" y="150" fill="#` + bs.TextShadowColor + `" fill-opacity=".3" transform="scale(.1)" textLength="` + textLength + `">` + s + `</text><text x="835" y="140" transform="scale(.1)" textLength="` + textLength + `">` + s + `</text></g> </svg>`))105 }106 })107108 addr := ":8080"109 log.Info().Str(uniqueCode, "1876ce1e").Str("addr", addr).Msg("serving")110 if err := http.ListenAndServe(addr, nil); err != nil && !errors.Is(err, http.ErrServerClosed) {111 log.Error().Str(uniqueCode, "c28556e8").Err(err).Send()112 os.Exit(1)113 }114}115116func filterBad(loc location) bool {117 l := loc.String()118 bad := []string{"wp-content.com", "wp-admin.com", ".well-known", "wp-includes.com", ".php"}119 count := 0120 for _, b := range bad {121 if strings.Contains(l, b) {122 count++123 }124 }125126 return count >= 2127}128129func appendLocationLog(log string) {130 locationLogMutex.Lock()131 defer locationLogMutex.Unlock()132133 if slices.Contains(locationLog, log) {134 return135 }136 locationLog = append(locationLog, log)137 locationTracker[log] = locationTracker[log] + 1138139 if len(locationLog) > 100 {140 locationLog = locationLog[1:]141 }142}143144func calculate(category string, wage int, res []processor.LanguageSummary) (string, int64) {145 title := ""146 var value int64147148 switch category {149 case "code", "codes":150 title = "Code lines"151 for _, x := range res {152 value += x.Code153 }154 case "blank", "blanks":155 title = "Blank lines"156 for _, x := range res {157 value += x.Blank158 }159 case "comment", "comments":160 title = "Comments"161 for _, x := range res {162 value += x.Comment163 }164 case "cocomo":165 title = "COCOMO $"166 for _, x := range res {167 value += x.Code168 }169170 value = int64(estimateCost(value, wage))171 case "effort":172 title = "COCOMO Man Years"173 for _, x := range res {174 value += x.Code175 }176177 value = int64(estimateScheduleMonths(value))178 case "line", "lines": // lines is the default179 fallthrough180 default:181 //182 title = "Total lines"183 for _, x := range res {184 value += x.Lines185 }186 }187 return title, value188}189190type location struct {191 Provider string192 User string193 Repo string194}195196func (l *location) String() string {197 loc := ".com/"198 ext := ".git"199 switch strings.ToLower(l.Provider) {200 case "bitbucket":201 loc = ".org/"202 case "git.sr.ht":203 loc = "/"204 ext = ""205 }206207 parse, _ := url.Parse("https://" + l.Provider + loc + l.User + "/" + l.Repo + ext)208 return parse.String()209}210211// processUrlPath takes in a string path and returns a struct212// that contains the location user and repo which is what most213// repositories need214// returns an error if we get anything other than 3 parts since thats215// the format we expect216func processUrlPath(path string) (location, error) {217 path = strings.ToLower(path)218 path = strings.TrimPrefix(path, "/")219 path = strings.TrimSuffix(path, "/")220 s := strings.Split(path, "/")221 if len(s) != 3 {222 return location{}, errors.New("invalid path part")223 }224225 if s[0] == "sr.ht" {226 s[0] = "git.sr.ht"227 }228229 return location{230 Provider: s[0],231 User: s[1],232 Repo: s[2],233 }, nil234}235236type badgeSettings struct {237 FontColor string238 TextShadowColor string239 TopShadowAccentColor string240 TitleBackgroundColor string241 BadgeBackgroundColor string242}243244// namedColors maps color names to their hex values (without #).245// Includes shields.io colors and common CSS color names for compatibility.246var namedColors = map[string]string{247 // shields.io specific colors248 "brightgreen": "44cc11",249 "green": "97ca00",250 "yellowgreen": "a4a61d",251 "yellow": "dfb317",252 "orange": "fe7d37",253 "red": "e05d44",254 "blue": "007ec6",255 "lightgrey": "9f9f9f",256 "lightgray": "9f9f9f",257 "grey": "555555",258 "gray": "555555",259 "blueviolet": "8a2be2",260 // shields.io semantic aliases261 "success": "44cc11",262 "important": "fe7d37",263 "critical": "e05d44",264 "informational": "007ec6",265 "inactive": "9f9f9f",266 // Common CSS color names267 "black": "000000",268 "white": "ffffff",269 "silver": "c0c0c0",270 "maroon": "800000",271 "purple": "800080",272 "fuchsia": "ff00ff",273 "lime": "00ff00",274 "olive": "808000",275 "navy": "000080",276 "teal": "008080",277 "aqua": "00ffff",278 "cyan": "00ffff",279 "magenta": "ff00ff",280 "pink": "ffc0cb",281 "coral": "ff7f50",282 "salmon": "fa8072",283 "gold": "ffd700",284 "khaki": "f0e68c",285 "violet": "ee82ee",286 "indigo": "4b0082",287 "crimson": "dc143c",288 "turquoise": "40e0d0",289 "tan": "d2b48c",290 "brown": "a52a2a",291 "chocolate": "d2691e",292 "tomato": "ff6347",293 "orchid": "da70d6",294 "plum": "dda0dd",295 "peru": "cd853f",296 "sienna": "a0522d",297 "beige": "f5f5dc",298 "ivory": "fffff0",299 "linen": "faf0e6",300 "azure": "f0ffff",301 "lavender": "e6e6fa",302 "wheat": "f5deb3",303 "snow": "fffafa",304 "seashell": "fff5ee",305 "honeydew": "f0fff0",306 "mintcream": "f5fffa",307 "aliceblue": "f0f8ff",308 "ghostwhite": "f8f8ff",309 "oldlace": "fdf5e6",310 "papayawhip": "ffefd5",311 "moccasin": "ffe4b5",312 "bisque": "ffe4c4",313 "mistyrose": "ffe4e1",314 "lemonchiffon": "fffacd",315 "cornsilk": "fff8dc",316 "antiquewhite": "faebd7",317 "floralwhite": "fffaf0",318 "steelblue": "4682b4",319 "royalblue": "4169e1",320 "skyblue": "87ceeb",321 "dodgerblue": "1e90ff",322 "deepskyblue": "00bfff",323 "cadetblue": "5f9ea0",324 "cornflowerblue": "6495ed",325 "mediumblue": "0000cd",326 "darkblue": "00008b",327 "midnightblue": "191970",328 "slateblue": "6a5acd",329 "darkslateblue": "483d8b",330 "mediumslateblue": "7b68ee",331 "seagreen": "2e8b57",332 "mediumseagreen": "3cb371",333 "lightgreen": "90ee90",334 "darkgreen": "006400",335 "forestgreen": "228b22",336 "limegreen": "32cd32",337 "springgreen": "00ff7f",338 "palegreen": "98fb98",339 "darkseagreen": "8fbc8f",340 "olivedrab": "6b8e23",341 "darkolivegreen": "556b2f",342 "darkred": "8b0000",343 "firebrick": "b22222",344 "indianred": "cd5c5c",345 "lightsalmon": "ffa07a",346 "darksalmon": "e9967a",347 "lightcoral": "f08080",348 "rosybrown": "bc8f8f",349 "sandybrown": "f4a460",350 "goldenrod": "daa520",351 "darkgoldenrod": "b8860b",352 "darkorange": "ff8c00",353 "orangered": "ff4500",354 "hotpink": "ff69b4",355 "deeppink": "ff1493",356 "palevioletred": "db7093",357 "mediumvioletred": "c71585",358 "mediumpurple": "9370db",359 "darkorchid": "9932cc",360 "darkviolet": "9400d3",361 "darkmagenta": "8b008b",362 "slategray": "708090",363 "slategrey": "708090",364 "lightslategray": "778899",365 "lightslategrey": "778899",366 "darkslategray": "2f4f4f",367 "darkslategrey": "2f4f4f",368 "dimgray": "696969",369 "dimgrey": "696969",370 "darkgray": "a9a9a9",371 "darkgrey": "a9a9a9",372 "gainsboro": "dcdcdc",373 "whitesmoke": "f5f5f5",374}375376// resolveColor converts a color input (name or hex) to a hex value without #.377// Returns the hex value if valid, or empty string if invalid.378func resolveColor(color string) string {379 color = strings.ToLower(color)380381 // Check if it's a named color382 if hex, ok := namedColors[color]; ok {383 return hex384 }385386 // Check if it's a valid hex color (3, 4, 6, or 8 digits)387 hexRegex := regexp.MustCompile(`^(?:(?:[\da-f]{3}){1,2}|(?:[\da-f]{4}){1,2})$`)388 if hexRegex.MatchString(color) {389 return color390 }391392 return ""393}394395// Parses badge settings from url query params396// if error, ignore and return default badge settings397func parseBadgeSettings(values url.Values) *badgeSettings {398 bs := badgeSettings{399 FontColor: "fff",400 TextShadowColor: "010101",401 TopShadowAccentColor: "bbb",402 TitleBackgroundColor: "555",403 BadgeBackgroundColor: "4c1",404 }405406 fontColor := values.Get("font-color")407 textShadowColor := values.Get("font-shadow-color")408 topShadowAccentColor := values.Get("top-shadow-accent-color")409 titleBackgroundColor := values.Get("title-bg-color")410 badgeBackgroundColor := values.Get("badge-bg-color")411412 // Resolve colors (supports both named colors and hex codes)413 if resolved := resolveColor(fontColor); resolved != "" {414 bs.FontColor = resolved415 }416 if resolved := resolveColor(textShadowColor); resolved != "" {417 bs.TextShadowColor = resolved418 }419 if resolved := resolveColor(topShadowAccentColor); resolved != "" {420 bs.TopShadowAccentColor = resolved421 }422 if resolved := resolveColor(titleBackgroundColor); resolved != "" {423 bs.TitleBackgroundColor = resolved424 }425 if resolved := resolveColor(badgeBackgroundColor); resolved != "" {426 bs.BadgeBackgroundColor = resolved427 }428429 return &bs430}431432// formatCount turns a float into a string usable for display433// to the user so, 2532 would be 2.5k and such up the various434// units435func formatCount(count float64) string {436 type r struct {437 val float64438 sym string439 }440 ranges := []r{441 {1e18, "E"},442 {1e15, "P"},443 {1e12, "T"},444 {1e9, "G"},445 {1e6, "M"},446 {1e3, "k"},447 }448449 for _, v := range ranges {450 if count >= v.val {451 t := fmt.Sprintf("%.1f", math.Ceil(count/v.val*10)/10)452453 if len(t) > 3 {454 t = t[:strings.Index(t, ".")]455 }456457 return fmt.Sprintf("%v%v", t, v.sym)458 }459 }460461 return fmt.Sprintf("%v", math.Round(count))462}463464func process(id int, s location) ([]processor.LanguageSummary, error) {465 countingSemaphore <- true466 defer func() {467 <-countingSemaphore // remove one to free up concurrency468 }()469470 val, ok := cache.Get(s.String())471 if ok {472 return val, nil473 }474475 // Clean target just to be sure476 targetPath := filepath.Join(tmpDir, "scc-tmp-path-"+strconv.Itoa(id))477 if err := os.RemoveAll(targetPath); err != nil {478 return nil, err479 }480481 // Run git clone against the target482 // 180 seconds seems enough as the kernel itself takes about 60 seconds483 ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)484 defer cancel()485486 cmd := exec.CommandContext(ctx, "git", "clone", "--depth=1", s.String(), targetPath)487488 cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")489 _, err := cmd.Output()490491 if errors.Is(ctx.Err(), context.DeadlineExceeded) {492 return nil, err493 }494495 if err != nil {496 return nil, err497 }498499 // Run scc against what we just cloned500 fileName := processPath(s.String())501 filePath := filepath.Join(tmpDir, fileName)502503 if fileName == "" {504 return nil, errors.New("processPath returned empty")505 }506507 cmdArgs := []string{508 "-f",509 "json",510 "-o",511 filePath,512 targetPath,513 }514515 cmd = exec.Command("scc", cmdArgs...)516 err = cmd.Run()517 if err != nil {518 return nil, err519 }520521 data, err := os.ReadFile(filePath)522 if err != nil {523 return nil, err524 }525526 var res []processor.LanguageSummary527 err = json.Unmarshal(data, &res)528 if err != nil {529 return nil, err530 }531 _ = cache.Set(s.String(), res)532533 // Cleanup534 if err := os.RemoveAll(filePath); err != nil {535 return nil, err536 }537538 if err := os.RemoveAll(targetPath); err != nil {539 return nil, err540 }541542 return res, nil543}544545func processPath(s string) string {546 s = strings.ToLower(s)547 split := strings.Split(s, "/")548549 if len(split) != 5 {550 return ""551 }552553 sp := make([]string, 0, len(split))554555 for _, s := range split {556 sp = append(sp, cleanString(s))557 }558559 filename := strings.ReplaceAll(sp[2], ".com", "")560 filename = strings.ReplaceAll(filename, ".org", "")561 filename += "." + sp[3]562 filename += "." + strings.ReplaceAll(sp[4], ".git", "") + ".json"563564 return filename565}566567func cleanString(s string) string {568 reg, err := regexp.Compile("[^a-z0-9-._]+")569 if err != nil {570 log.Fatal().Err(err).Send()571 }572573 processedString := reg.ReplaceAllString(s, "")574575 return processedString576}577578func estimateEffort(codeCount int64) float64 {579 return 3.2 * math.Pow(float64(codeCount)/1000, 1.05) * 1580}581582func estimateCost(codeCount int64, averageWage int) float64 {583 return estimateEffort(codeCount) * (float64(averageWage) / 12) * 1.8584}585586func estimateScheduleMonths(codeCount int64) float64 {587 return 2.5 * math.Pow(estimateEffort(codeCount), 0.38)588}589590// FormatMonths converts a float64 representing months into a "X years Y months" string.591func formatMonths(totalMonths float64) string {592 years, fracMonths := math.Modf(totalMonths / 12)593 months := int(math.Round(fracMonths * 12))594595 return fmt.Sprintf("%d years %d months", int(years), months)596}597598// FormatMonthsInt converts an int64 representing months into a "X years Y months" string.599func formatMonthsInt(totalMonths int64) string {600 // Use integer division to get the number of years601 years := totalMonths / 12602603 // Use the modulo operator to get the remaining months604 months := totalMonths % 12605606 return fmt.Sprintf("%d years %d months", years, months)607}608609func tryParseInt(s string, def int) int {610 i, err := strconv.Atoi(s)611 if err != nil {612 return def613 }614 return i615}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.