mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 17:55:08 +00:00
All files in cmd/analyze/ are macOS-only but several were missing the //go:build darwin constraint. On Linux (e.g. CodeQL CI), Go compiled these files without the types defined in main.go (which had the tag), causing undefined symbol errors for dirEntry, fileEntry, scanResult, cacheEntry, historyEntry and model. - Add //go:build darwin to heap.go, cache.go, scanner.go, cleanable.go, delete.go, format.go, constants.go and all *_test.go files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
250 lines
5.2 KiB
Go
250 lines
5.2 KiB
Go
//go:build darwin
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func displayPath(path string) string {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil || home == "" {
|
|
return path
|
|
}
|
|
if strings.HasPrefix(path, home) {
|
|
return strings.Replace(path, home, "~", 1)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// truncateMiddle trims the middle, keeping head and tail.
|
|
func truncateMiddle(s string, maxWidth int) string {
|
|
runes := []rune(s)
|
|
currentWidth := displayWidth(s)
|
|
|
|
if currentWidth <= maxWidth {
|
|
return s
|
|
}
|
|
|
|
if maxWidth < 10 {
|
|
width := 0
|
|
for i, r := range runes {
|
|
width += runeWidth(r)
|
|
if width > maxWidth {
|
|
return string(runes[:i])
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
targetHeadWidth := (maxWidth - 3) / 3
|
|
targetTailWidth := maxWidth - 3 - targetHeadWidth
|
|
|
|
headWidth := 0
|
|
headIdx := 0
|
|
for i, r := range runes {
|
|
w := runeWidth(r)
|
|
if headWidth+w > targetHeadWidth {
|
|
break
|
|
}
|
|
headWidth += w
|
|
headIdx = i + 1
|
|
}
|
|
|
|
tailWidth := 0
|
|
tailIdx := len(runes)
|
|
for i := len(runes) - 1; i >= 0; i-- {
|
|
w := runeWidth(runes[i])
|
|
if tailWidth+w > targetTailWidth {
|
|
break
|
|
}
|
|
tailWidth += w
|
|
tailIdx = i
|
|
}
|
|
|
|
return string(runes[:headIdx]) + "..." + string(runes[tailIdx:])
|
|
}
|
|
|
|
func formatNumber(n int64) string {
|
|
if n < 1000 {
|
|
return fmt.Sprintf("%d", n)
|
|
}
|
|
if n < 1000000 {
|
|
return fmt.Sprintf("%.1fk", float64(n)/1000)
|
|
}
|
|
return fmt.Sprintf("%.1fM", float64(n)/1000000)
|
|
}
|
|
|
|
func humanizeBytes(size int64) string {
|
|
if size < 0 {
|
|
return "0 B"
|
|
}
|
|
const unit = 1000
|
|
if size < unit {
|
|
return fmt.Sprintf("%d B", size)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := size / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
value := float64(size) / float64(div)
|
|
return fmt.Sprintf("%.1f %cB", value, "kMGTPE"[exp])
|
|
}
|
|
|
|
func coloredProgressBar(value, maxValue int64, percent float64) string {
|
|
if maxValue <= 0 {
|
|
return colorGray + strings.Repeat("░", barWidth) + colorReset
|
|
}
|
|
|
|
filled := min(int((value*int64(barWidth))/maxValue), barWidth)
|
|
|
|
var barColor string
|
|
if percent >= 50 {
|
|
barColor = colorRed
|
|
} else if percent >= 20 {
|
|
barColor = colorYellow
|
|
} else if percent >= 5 {
|
|
barColor = colorBlue
|
|
} else {
|
|
barColor = colorGreen
|
|
}
|
|
|
|
var bar strings.Builder
|
|
bar.WriteString(barColor)
|
|
for i := range barWidth {
|
|
if i < filled {
|
|
if i < filled-1 {
|
|
bar.WriteString("█")
|
|
} else {
|
|
remainder := (value * int64(barWidth)) % maxValue
|
|
if remainder > maxValue/2 {
|
|
bar.WriteString("█")
|
|
} else if remainder > maxValue/4 {
|
|
bar.WriteString("▓")
|
|
} else {
|
|
bar.WriteString("▒")
|
|
}
|
|
}
|
|
} else {
|
|
bar.WriteString(colorGray + "░" + barColor)
|
|
}
|
|
}
|
|
return bar.String() + colorReset
|
|
}
|
|
|
|
// runeWidth returns display width for wide characters and emoji.
|
|
func runeWidth(r rune) int {
|
|
if r >= 0x4E00 && r <= 0x9FFF || // CJK Unified Ideographs
|
|
r >= 0x3400 && r <= 0x4DBF || // CJK Extension A
|
|
r >= 0x20000 && r <= 0x2A6DF || // CJK Extension B
|
|
r >= 0x2A700 && r <= 0x2B73F || // CJK Extension C
|
|
r >= 0x2B740 && r <= 0x2B81F || // CJK Extension D
|
|
r >= 0x2B820 && r <= 0x2CEAF || // CJK Extension E
|
|
r >= 0x3040 && r <= 0x30FF || // Hiragana and Katakana
|
|
r >= 0x31F0 && r <= 0x31FF || // Katakana Phonetic Extensions
|
|
r >= 0xAC00 && r <= 0xD7AF || // Hangul Syllables
|
|
r >= 0xFF00 && r <= 0xFFEF || // Fullwidth Forms
|
|
r >= 0x1F300 && r <= 0x1F6FF || // Miscellaneous Symbols and Pictographs (includes Transport)
|
|
r >= 0x1F900 && r <= 0x1F9FF || // Supplemental Symbols and Pictographs
|
|
r >= 0x2600 && r <= 0x26FF || // Miscellaneous Symbols
|
|
r >= 0x2700 && r <= 0x27BF || // Dingbats
|
|
r >= 0xFE10 && r <= 0xFE1F || // Vertical Forms
|
|
r >= 0x1F000 && r <= 0x1F02F { // Mahjong Tiles
|
|
return 2
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func displayWidth(s string) int {
|
|
width := 0
|
|
for _, r := range s {
|
|
width += runeWidth(r)
|
|
}
|
|
return width
|
|
}
|
|
|
|
// calculateNameWidth computes name column width from terminal width.
|
|
func calculateNameWidth(termWidth int) int {
|
|
const fixedWidth = 61
|
|
available := termWidth - fixedWidth
|
|
|
|
if available < 24 {
|
|
return 24
|
|
}
|
|
if available > 60 {
|
|
return 60
|
|
}
|
|
return available
|
|
}
|
|
|
|
func trimNameWithWidth(name string, maxWidth int) string {
|
|
const (
|
|
ellipsis = "..."
|
|
ellipsisWidth = 3
|
|
)
|
|
|
|
runes := []rune(name)
|
|
widths := make([]int, len(runes))
|
|
for i, r := range runes {
|
|
widths[i] = runeWidth(r)
|
|
}
|
|
|
|
currentWidth := 0
|
|
for i, w := range widths {
|
|
if currentWidth+w > maxWidth {
|
|
subWidth := currentWidth
|
|
j := i
|
|
for j > 0 && subWidth+ellipsisWidth > maxWidth {
|
|
j--
|
|
subWidth -= widths[j]
|
|
}
|
|
if j == 0 {
|
|
return ellipsis
|
|
}
|
|
return string(runes[:j]) + ellipsis
|
|
}
|
|
currentWidth += w
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func padName(name string, targetWidth int) string {
|
|
currentWidth := displayWidth(name)
|
|
if currentWidth >= targetWidth {
|
|
return name
|
|
}
|
|
return name + strings.Repeat(" ", targetWidth-currentWidth)
|
|
}
|
|
|
|
// formatUnusedTime formats time since last access.
|
|
func formatUnusedTime(lastAccess time.Time) string {
|
|
if lastAccess.IsZero() {
|
|
return ""
|
|
}
|
|
|
|
duration := time.Since(lastAccess)
|
|
days := int(duration.Hours() / 24)
|
|
|
|
if days < 90 {
|
|
return ""
|
|
}
|
|
|
|
months := days / 30
|
|
years := days / 365
|
|
|
|
if years >= 2 {
|
|
return fmt.Sprintf(">%dyr", years)
|
|
} else if years >= 1 {
|
|
return ">1yr"
|
|
} else if months >= 3 {
|
|
return fmt.Sprintf(">%dmo", months)
|
|
}
|
|
|
|
return ""
|
|
}
|