zutils/cmd/sys.go
selamanapps 5b334071e0 feat: add user-friendly help system and new commands
New Commands:
- z read - Smart file reader with auto-detection (markdown, syntax highlighting)
- z sys - System overview (CPU, RAM, Disk, processes)
- z find - File search with pattern/size/time filters
- z search - User-friendly grep alternative
- z usage - Process resource usage monitor

Enhancements:
- Unified help system with consistent error messages
- Help on error shows usage and examples for each command
- "Command not found" suggests similar commands
- Directory info now detects .gitignore and shows ignored files
- File handler registry for extensible file type detection
- Added Italic, Dim, Underline color constants
2026-05-02 01:12:55 +03:00

370 lines
9 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cmd
import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/zemenawi/zutils/pkg/colors"
"github.com/zemenawi/zutils/pkg/formatter"
)
type SystemStats struct {
Hostname string
OS string
Platform string
CPUModel string
CPUCount int
Uptime string
LoadAvg string
MemTotal int64
MemUsed int64
MemFree int64
DiskTotal int64
DiskUsed int64
DiskFree int64
TopProcs []ProcInfo
}
type ProcInfo struct {
PID int
Name string
CPU float64
Mem float64
Command string
}
func SystemCommand(args []string) error {
stats, err := getSystemStats()
if err != nil {
return fmt.Errorf("error getting system stats: %v", err)
}
PrintBoxHeader("SYSTEM OVERVIEW", colors.Cyan)
fmt.Printf("%s%s🏠 Hostname:%s %s\n", colors.Blue, colors.Bold, colors.Reset, stats.Hostname)
fmt.Printf("%s%s💻 OS:%s %s\n", colors.Blue, colors.Bold, colors.Reset, stats.OS)
fmt.Printf("%s%s🔧 Platform:%s %s\n", colors.Blue, colors.Bold, colors.Reset, stats.Platform)
fmt.Println()
PrintSectionHeader("CPU")
fmt.Printf("%s%s🖥 Model:%s %s\n", colors.Green, colors.Bold, colors.Reset, stats.CPUModel)
fmt.Printf("%s%s🔢 Cores:%s %d\n", colors.Green, colors.Bold, colors.Reset, stats.CPUCount)
fmt.Printf("%s%s📊 Load Avg:%s %s\n", colors.Green, colors.Bold, colors.Reset, stats.LoadAvg)
fmt.Printf("%s%s⏱ Uptime:%s %s\n", colors.Green, colors.Bold, colors.Reset, stats.Uptime)
fmt.Println()
PrintSectionHeader("MEMORY")
memTotal := formatter.FormatSize(stats.MemTotal)
memUsed := formatter.FormatSize(stats.MemUsed)
memFree := formatter.FormatSize(stats.MemFree)
memPct := float64(stats.MemUsed) / float64(stats.MemTotal) * 100
fmt.Printf("%s%s💾 Total:%s %s\n", colors.Purple, colors.Bold, colors.Reset, memTotal)
fmt.Printf("%s%s📈 Used:%s %s (%.1f%%)\n", colors.Purple, colors.Bold, colors.Reset, memUsed, memPct)
fmt.Printf("%s%s📉 Free:%s %s\n", colors.Purple, colors.Bold, colors.Reset, memFree)
printMemoryBar(stats.MemUsed, stats.MemTotal)
fmt.Println()
PrintSectionHeader("DISK")
diskTotal := formatter.FormatSize(stats.DiskTotal)
diskUsed := formatter.FormatSize(stats.DiskUsed)
diskFree := formatter.FormatSize(stats.DiskFree)
diskPct := float64(stats.DiskUsed) / float64(stats.DiskTotal) * 100
fmt.Printf("%s%s💽 Total:%s %s\n", colors.Yellow, colors.Bold, colors.Reset, diskTotal)
fmt.Printf("%s%s📈 Used:%s %s (%.1f%%)\n", colors.Yellow, colors.Bold, colors.Reset, diskUsed, diskPct)
fmt.Printf("%s%s📉 Free:%s %s\n", colors.Yellow, colors.Bold, colors.Reset, diskFree)
printDiskBar(stats.DiskUsed, stats.DiskTotal)
fmt.Println()
if len(stats.TopProcs) > 0 {
PrintSectionHeader("TOP PROCESSES")
headers := []string{"PID", "Name", "CPU%", "MEM%"}
data := make([][]string, 0, len(stats.TopProcs))
for _, proc := range stats.TopProcs {
row := []string{
fmt.Sprintf("%d", proc.PID),
truncateString(proc.Name, 20),
fmt.Sprintf("%.1f", proc.CPU),
fmt.Sprintf("%.1f", proc.Mem),
}
data = append(data, row)
}
formatter.PrintTable(headers, data, colors.Cyan)
fmt.Println()
}
return nil
}
func printMemoryBar(used, total int64) {
width := 30
if total == 0 {
return
}
fillLen := int(float64(used) / float64(total) * float64(width))
bar := "["
for i := 0; i < width; i++ {
if i < fillLen {
if i < width/3 {
bar += colors.Green + "█" + colors.Reset
} else if i < 2*width/3 {
bar += colors.Yellow + "█" + colors.Reset
} else {
bar += colors.Red + "█" + colors.Reset
}
} else {
bar += colors.Gray + "░" + colors.Reset
}
}
bar += "]"
fmt.Printf("%s%sMemory Bar:%s %s\n", colors.Gray, colors.Bold, colors.Reset, bar)
}
func printDiskBar(used, total int64) {
width := 30
if total == 0 {
return
}
fillLen := int(float64(used) / float64(total) * float64(width))
bar := "["
for i := 0; i < width; i++ {
if i < fillLen {
if i < width/3 {
bar += colors.Green + "█" + colors.Reset
} else if i < 2*width/3 {
bar += colors.Yellow + "█" + colors.Reset
} else {
bar += colors.Red + "█" + colors.Reset
}
} else {
bar += colors.Gray + "░" + colors.Reset
}
}
bar += "]"
fmt.Printf("%s%sDisk Bar:%s %s\n", colors.Gray, colors.Bold, colors.Reset, bar)
}
func getSystemStats() (*SystemStats, error) {
stats := &SystemStats{}
stats.Hostname, _ = os.Hostname()
stats.OS = runtime.GOOS
stats.Platform = runtime.GOARCH
stats.CPUModel = getCPUModel()
stats.CPUCount = runtime.NumCPU()
stats.Uptime = getUptime()
stats.LoadAvg = getLoadAvg()
getMemoryStats(stats)
getDiskStats(stats)
stats.TopProcs = getTopProcs(5)
return stats, nil
}
func getCPUModel() string {
if runtime.GOOS == "linux" {
if data, err := os.ReadFile("/proc/cpuinfo"); err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "model name") || strings.HasPrefix(line, "Processor") || strings.HasPrefix(line, "cpu model") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
return strings.TrimSpace(parts[1])
}
}
}
}
}
return "Unknown"
}
func getUptime() string {
if runtime.GOOS == "linux" {
if data, err := os.ReadFile("/proc/uptime"); err == nil {
parts := strings.Split(string(data), " ")
if len(parts) >= 1 {
if secs, err := strconv.ParseFloat(strings.TrimSpace(parts[0]), 64); err == nil {
duration := time.Duration(int64(secs)) * time.Second
return formatDuration(duration)
}
}
}
}
return "Unknown"
}
func formatDuration(d time.Duration) string {
days := int(d.Hours() / 24)
hours := int(d.Hours()) % 24
minutes := int(d.Minutes()) % 60
if days > 0 {
return fmt.Sprintf("%dd %dh %dm", days, hours, minutes)
}
if hours > 0 {
return fmt.Sprintf("%dh %dm", hours, minutes)
}
return fmt.Sprintf("%dm", minutes)
}
func getLoadAvg() string {
if runtime.GOOS == "linux" {
if data, err := os.ReadFile("/proc/loadavg"); err == nil {
parts := strings.Split(strings.TrimSpace(string(data)), " ")
if len(parts) >= 3 {
return fmt.Sprintf("%.2f %.2f %.2f",
parseFloatSafe(parts[0]),
parseFloatSafe(parts[1]),
parseFloatSafe(parts[2]))
}
}
}
return "N/A"
}
func parseFloatSafe(s string) float64 {
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
}
return 0
}
func getMemoryStats(stats *SystemStats) {
if runtime.GOOS == "linux" {
if data, err := os.ReadFile("/proc/meminfo"); err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "MemTotal") {
stats.MemTotal = parseMeminfoLine(line)
} else if strings.HasPrefix(line, "MemAvailable") || strings.HasPrefix(line, "MemFree") {
if stats.MemFree == 0 {
stats.MemFree = parseMeminfoLine(line)
}
}
}
if stats.MemFree > 0 && stats.MemTotal > 0 {
stats.MemUsed = stats.MemTotal - stats.MemFree
}
}
}
}
func parseMeminfoLine(line string) int64 {
parts := strings.Fields(line)
if len(parts) >= 2 {
if val, err := strconv.ParseInt(parts[1], 10, 64); err == nil {
return val * 1024
}
}
return 0
}
func getDiskStats(stats *SystemStats) {
if runtime.GOOS == "linux" {
output, err := exec.Command("df", "-k", "/").Output()
if err == nil {
lines := strings.Split(string(output), "\n")
if len(lines) >= 2 {
parts := strings.Fields(lines[1])
if len(parts) >= 4 {
stats.DiskTotal, _ = strconv.ParseInt(parts[1], 10, 64)
stats.DiskUsed, _ = strconv.ParseInt(parts[2], 10, 64)
stats.DiskFree, _ = strconv.ParseInt(parts[3], 10, 64)
stats.DiskTotal *= 1024
stats.DiskUsed *= 1024
stats.DiskFree *= 1024
}
}
}
}
}
func getTopProcs(count int) []ProcInfo {
var procs []ProcInfo
if runtime.GOOS == "linux" {
dir, err := os.Open("/proc")
if err != nil {
return procs
}
defer dir.Close()
entries, err := dir.Readdirnames(count * 3)
if err != nil {
return procs
}
for _, entry := range entries {
pid, err := strconv.Atoi(entry)
if err != nil {
continue
}
stat, err := readProcStat(pid)
if err != nil {
continue
}
procs = append(procs, stat)
if len(procs) >= count {
break
}
}
}
return procs
}
func readProcStat(pid int) (ProcInfo, error) {
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil {
return ProcInfo{}, err
}
parts := strings.SplitN(string(data), " ", 4)
if len(parts) < 4 {
return ProcInfo{}, fmt.Errorf("invalid format")
}
name := parts[1]
name = strings.Trim(name, "(")
name = strings.TrimRight(name, ")")
comm, _ := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid))
commName := strings.TrimSpace(string(comm))
var rusage syscall.Rusage
syscall.Getrusage(syscall.RUSAGE_SELF, &rusage)
return ProcInfo{
PID: pid,
Name: commName,
CPU: 0,
Mem: 0,
Command: name,
}, nil
}
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen-3] + "..."
}