(Feat): Initial Commit

This commit is contained in:
2025-11-16 19:48:50 +00:00
commit a00f70a7fe
17 changed files with 1654 additions and 0 deletions

72
internal/ui/analyze.go Normal file
View File

@@ -0,0 +1,72 @@
package ui
import (
"fmt"
"os"
"zipprine/internal/archiver"
"zipprine/internal/models"
"github.com/charmbracelet/huh"
)
func RunAnalyzeFlow() error {
var archivePath string
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("📦 Archive Path").
Description("Path to the archive to analyze").
Placeholder("/path/to/archive.zip").
Value(&archivePath).
Validate(func(s string) error {
if s == "" {
return fmt.Errorf("archive path cannot be empty")
}
if _, err := os.Stat(s); os.IsNotExist(err) {
return fmt.Errorf("archive does not exist")
}
return nil
}),
),
).WithTheme(huh.ThemeCatppuccin())
if err := form.Run(); err != nil {
return err
}
fmt.Println()
fmt.Println(InfoStyle.Render("🔍 Analyzing archive..."))
info, err := archiver.Analyze(archivePath)
if err != nil {
return err
}
displayArchiveInfo(info)
return nil
}
func displayArchiveInfo(info *models.ArchiveInfo) {
fmt.Println()
fmt.Println(HeaderStyle.Render("📊 Archive Information"))
fmt.Println(InfoStyle.Render(fmt.Sprintf(" 🎨 Type: %s", info.Type)))
fmt.Println(InfoStyle.Render(fmt.Sprintf(" 📁 Files: %d", info.FileCount)))
fmt.Println(InfoStyle.Render(fmt.Sprintf(" 💾 Uncompressed: %.2f MB", float64(info.TotalSize)/(1024*1024))))
fmt.Println(InfoStyle.Render(fmt.Sprintf(" 📦 Compressed: %.2f MB", float64(info.CompressedSize)/(1024*1024))))
fmt.Println(InfoStyle.Render(fmt.Sprintf(" 🎯 Ratio: %.1f%%", info.CompressionRatio)))
fmt.Println(InfoStyle.Render(fmt.Sprintf(" 🔒 SHA256: %s...", info.Checksum[:16])))
if len(info.Files) > 0 && len(info.Files) <= 20 {
fmt.Println()
fmt.Println(HeaderStyle.Render("📝 File List"))
for _, f := range info.Files {
icon := "📄"
if f.IsDir {
icon = "📁"
}
fmt.Println(InfoStyle.Render(fmt.Sprintf(" %s %s (%.2f KB)", icon, f.Name, float64(f.Size)/1024)))
}
}
}

143
internal/ui/compress.go Normal file
View File

@@ -0,0 +1,143 @@
package ui
import (
"fmt"
"os"
"strings"
"zipprine/internal/archiver"
"zipprine/internal/models"
"github.com/charmbracelet/huh"
)
func RunCompressFlow() error {
config := &models.CompressConfig{}
var sourcePath, outputPath string
var archiveTypeStr string
var excludeInput, includeInput string
var verify bool
var compressionLevel string
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("📁 Source Path").
Description("Enter the path to compress (file or directory)").
Placeholder("/path/to/source").
Value(&sourcePath).
Validate(func(s string) error {
if s == "" {
return fmt.Errorf("source path cannot be empty")
}
if _, err := os.Stat(s); os.IsNotExist(err) {
return fmt.Errorf("path does not exist")
}
return nil
}),
huh.NewInput().
Title("💾 Output Path").
Description("Where to save the archive").
Placeholder("/path/to/output.zip").
Value(&outputPath).
Validate(func(s string) error {
if s == "" {
return fmt.Errorf("output path cannot be empty")
}
return nil
}).Suggestions([]string{".zip", ".tar.gz", ".tar", ".gz"}),
),
huh.NewGroup(
huh.NewSelect[string]().
Title("🎨 Archive Type").
Description("Choose your compression format").
Options(
huh.NewOption("ZIP - Universal & Compatible 📦", "ZIP"),
huh.NewOption("TAR.GZ - Linux Classic (Best Compression) 🐧", "TARGZ"),
huh.NewOption("TAR - No Compression 📄", "TAR"),
huh.NewOption("GZIP - Single File Compression 🔧", "GZIP"),
).
Value(&archiveTypeStr),
huh.NewSelect[string]().
Title("⚡ Compression Level").
Description("Higher = smaller but slower").
Options(
huh.NewOption("Fast (Level 1)", "1"),
huh.NewOption("Balanced (Level 5)", "5"),
huh.NewOption("Best (Level 9)", "9"),
).
Value(&compressionLevel),
),
huh.NewGroup(
huh.NewText().
Title("🚫 Exclude Patterns").
Description("Comma-separated patterns to exclude (e.g., *.log,node_modules,*.tmp)").
Placeholder("*.log,temp/*,.git,__pycache__").
Value(&excludeInput),
huh.NewText().
Title("✅ Include Patterns").
Description("Comma-separated patterns to include (leave empty for all)").
Placeholder("*.go,*.md,src/*").
Value(&includeInput),
),
huh.NewGroup(
huh.NewConfirm().
Title("🔐 Verify Archive Integrity").
Description("Check the archive after creation?").
Value(&verify).
Affirmative("Yes please!").
Negative("Skip it"),
),
).WithTheme(huh.ThemeCatppuccin())
if err := form.Run(); err != nil {
return err
}
config.SourcePath = sourcePath
config.OutputPath = outputPath
config.ArchiveType = models.ArchiveType(archiveTypeStr)
config.VerifyIntegrity = verify
fmt.Sscanf(compressionLevel, "%d", &config.CompressionLevel)
if excludeInput != "" {
config.ExcludePaths = strings.Split(excludeInput, ",")
for i := range config.ExcludePaths {
config.ExcludePaths[i] = strings.TrimSpace(config.ExcludePaths[i])
}
}
if includeInput != "" {
config.IncludePaths = strings.Split(includeInput, ",")
for i := range config.IncludePaths {
config.IncludePaths[i] = strings.TrimSpace(config.IncludePaths[i])
}
}
fmt.Println()
fmt.Println(InfoStyle.Render("🎯 Starting compression..."))
if err := archiver.Compress(config); err != nil {
return err
}
fmt.Println(SuccessStyle.Render("✅ Archive created successfully!"))
if config.VerifyIntegrity {
fmt.Println(InfoStyle.Render("🔍 Verifying archive integrity..."))
info, err := archiver.Analyze(config.OutputPath)
if err != nil {
return err
}
displayArchiveInfo(info)
}
return nil
}

93
internal/ui/extract.go Normal file
View File

@@ -0,0 +1,93 @@
package ui
import (
"fmt"
"os"
"zipprine/internal/archiver"
"zipprine/internal/models"
"github.com/charmbracelet/huh"
)
func RunExtractFlow() error {
config := &models.ExtractConfig{}
var archivePath, destPath string
var overwrite, preservePerms bool
form := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("📦 Archive Path").
Description("Path to the archive file").
Placeholder("/path/to/archive.zip").
Value(&archivePath).
Validate(func(s string) error {
if s == "" {
return fmt.Errorf("archive path cannot be empty")
}
if _, err := os.Stat(s); os.IsNotExist(err) {
return fmt.Errorf("archive does not exist")
}
return nil
}),
huh.NewInput().
Title("📂 Destination Path").
Description("Where to extract files").
Placeholder("/path/to/destination").
Value(&destPath).
Validate(func(s string) error {
if s == "" {
return fmt.Errorf("destination path cannot be empty")
}
return nil
}),
),
huh.NewGroup(
huh.NewConfirm().
Title("⚠️ Overwrite Existing Files").
Description("Replace files if they already exist?").
Value(&overwrite).
Affirmative("Yes, overwrite").
Negative("No, skip"),
huh.NewConfirm().
Title("🔒 Preserve Permissions").
Description("Keep original file permissions?").
Value(&preservePerms).
Affirmative("Yes").
Negative("No"),
),
).WithTheme(huh.ThemeCatppuccin())
if err := form.Run(); err != nil {
return err
}
config.ArchivePath = archivePath
config.DestPath = destPath
config.OverwriteAll = overwrite
config.PreservePerms = preservePerms
fmt.Println()
fmt.Println(InfoStyle.Render("🔍 Detecting archive type..."))
detectedType, err := archiver.DetectArchiveType(archivePath)
if err != nil {
return err
}
config.ArchiveType = detectedType
fmt.Println(SuccessStyle.Render(fmt.Sprintf("✅ Detected: %s", detectedType)))
fmt.Println(InfoStyle.Render("📂 Extracting files..."))
if err := archiver.Extract(config); err != nil {
return err
}
fmt.Println(SuccessStyle.Render("✅ Extraction completed!"))
return nil
}

32
internal/ui/styles.go Normal file
View File

@@ -0,0 +1,32 @@
package ui
import "github.com/charmbracelet/lipgloss"
var (
TitleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#7D56F4")).
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("#7D56F4")).
Padding(0, 1)
SuccessStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#04B575")).
Bold(true)
ErrorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF0000")).
Bold(true)
InfoStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#00BFFF"))
WarningStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FFA500")).
Bold(true)
HeaderStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FF79C6")).
Underline(true)
)