(Feat): Added a command line mode as an alternative to interactive mode

This commit is contained in:
2025-11-22 08:32:18 +00:00
parent e6f1382691
commit 77587fb6b3

240
internal/cli/cli.go Normal file
View File

@@ -0,0 +1,240 @@
package cli
import (
"flag"
"fmt"
"os"
"strings"
"zipprine/internal/archiver"
"zipprine/internal/fetcher"
"zipprine/internal/models"
"zipprine/internal/version"
)
// Run executes the CLI mode
func Run() bool {
// Define flags
compress := flag.String("compress", "", "Compress files/folders (source path)")
extract := flag.String("extract", "", "Extract archive (archive path)")
analyze := flag.String("analyze", "", "Analyze archive (archive path)")
output := flag.String("output", "", "Output path for compression or extraction")
archiveType := flag.String("type", "zip", "Archive type (zip, tar, tar.gz, gzip, rar)")
level := flag.Int("level", 6, "Compression level (1=fast, 6=balanced, 9=best)")
overwrite := flag.Bool("overwrite", false, "Overwrite existing files during extraction")
preservePerms := flag.Bool("preserve-perms", true, "Preserve file permissions during extraction")
exclude := flag.String("exclude", "", "Comma-separated list of patterns to exclude")
include := flag.String("include", "", "Comma-separated list of patterns to include")
verify := flag.Bool("verify", false, "Verify archive integrity after compression")
remoteURL := flag.String("url", "", "Remote URL to download and extract archive from")
showVersion := flag.Bool("version", false, "Show version information")
help := flag.Bool("help", false, "Show help information")
flag.Parse()
// Show version
if *showVersion {
fmt.Println(version.FullVersion())
return true
}
// Show help
if *help {
printHelp()
return true
}
// Check if any CLI flags were provided
if flag.NFlag() == 0 {
return false // No flags, use interactive mode
}
// Handle remote URL fetching
if *remoteURL != "" {
if *output == "" {
fmt.Println("❌ Error: --output is required when using --url")
os.Exit(1)
}
if !fetcher.IsValidArchiveURL(*remoteURL) {
fmt.Println("⚠️ Warning: URL does not appear to point to a supported archive format")
}
if err := fetcher.FetchAndExtract(*remoteURL, *output, *overwrite, *preservePerms); err != nil {
fmt.Printf("❌ Error: %v\n", err)
os.Exit(1)
}
fmt.Println("✨ Remote archive fetched and extracted successfully!")
return true
}
// Handle compression
if *compress != "" {
if *output == "" {
fmt.Println("❌ Error: --output is required for compression")
os.Exit(1)
}
archType := parseArchiveType(*archiveType)
if archType == models.RAR {
fmt.Println("❌ Error: RAR compression is not supported (proprietary format)")
os.Exit(1)
}
config := &models.CompressConfig{
SourcePath: *compress,
OutputPath: *output,
ArchiveType: archType,
CompressionLevel: *level,
VerifyIntegrity: *verify,
}
if *exclude != "" {
config.ExcludePaths = strings.Split(*exclude, ",")
}
if *include != "" {
config.IncludePaths = strings.Split(*include, ",")
}
fmt.Printf("📦 Compressing %s to %s (%s)...\n", *compress, *output, archType)
if err := archiver.Compress(config); err != nil {
fmt.Printf("❌ Error: %v\n", err)
os.Exit(1)
}
fmt.Println("✨ Compression completed successfully!")
return true
}
// Handle extraction
if *extract != "" {
if *output == "" {
fmt.Println("❌ Error: --output is required for extraction")
os.Exit(1)
}
// Detect archive type if not specified or set to auto
archType := parseArchiveType(*archiveType)
if archType == models.AUTO || *archiveType == "" {
detectedType, err := archiver.DetectArchiveType(*extract)
if err != nil {
fmt.Printf("❌ Error detecting archive type: %v\n", err)
os.Exit(1)
}
archType = detectedType
fmt.Printf("🔍 Detected archive type: %s\n", archType)
}
config := &models.ExtractConfig{
ArchivePath: *extract,
DestPath: *output,
ArchiveType: archType,
OverwriteAll: *overwrite,
PreservePerms: *preservePerms,
}
fmt.Printf("📂 Extracting %s to %s...\n", *extract, *output)
if err := archiver.Extract(config); err != nil {
fmt.Printf("❌ Error: %v\n", err)
os.Exit(1)
}
fmt.Println("✨ Extraction completed successfully!")
return true
}
// Handle analysis
if *analyze != "" {
info, err := archiver.Analyze(*analyze)
if err != nil {
fmt.Printf("❌ Error: %v\n", err)
os.Exit(1)
}
fmt.Println("\n📊 Archive Analysis")
fmt.Println("==================")
fmt.Printf("Type: %s\n", info.Type)
fmt.Printf("File Count: %d\n", info.FileCount)
fmt.Printf("Total Size: %d bytes\n", info.TotalSize)
fmt.Printf("Compressed Size: %d bytes\n", info.CompressedSize)
if info.CompressionRatio > 0 {
fmt.Printf("Compression Ratio: %.2f%%\n", info.CompressionRatio*100)
}
if info.Checksum != "" {
fmt.Printf("Checksum (SHA256): %s\n", info.Checksum)
}
fmt.Println("\n📁 Files:")
for i, file := range info.Files {
if i >= 20 {
fmt.Printf("... and %d more files\n", len(info.Files)-20)
break
}
fmt.Printf(" - %s (%d bytes)\n", file.Name, file.Size)
}
return true
}
// If we get here, no valid operation was specified
fmt.Println("❌ Error: No valid operation specified. Use --help for usage information.")
os.Exit(1)
return true
}
func parseArchiveType(typeStr string) models.ArchiveType {
switch strings.ToLower(typeStr) {
case "zip":
return models.ZIP
case "tar":
return models.TAR
case "tar.gz", "targz", "tgz":
return models.TARGZ
case "gzip", "gz":
return models.GZIP
case "rar":
return models.RAR
case "auto":
return models.AUTO
default:
return models.ZIP
}
}
func printHelp() {
fmt.Println(version.FullVersion())
fmt.Println("\n🗜 A modern TUI/CLI archiving tool with support for multiple formats")
fmt.Println("\nUSAGE:")
fmt.Println(" Interactive mode (default):")
fmt.Println(" zipprine")
fmt.Println("\n Command-line mode:")
fmt.Println(" zipprine [OPTIONS]")
fmt.Println("\nOPTIONS:")
fmt.Println(" --compress <path> Compress files/folders at the specified path")
fmt.Println(" --extract <path> Extract archive at the specified path")
fmt.Println(" --analyze <path> Analyze archive at the specified path")
fmt.Println(" --output <path> Output path for compression or extraction")
fmt.Println(" --type <type> Archive type: zip, tar, tar.gz, gzip, rar (default: zip)")
fmt.Println(" --level <1-9> Compression level: 1=fast, 6=balanced, 9=best (default: 6)")
fmt.Println(" --overwrite Overwrite existing files during extraction")
fmt.Println(" --preserve-perms Preserve file permissions (default: true)")
fmt.Println(" --exclude <patterns> Comma-separated patterns to exclude")
fmt.Println(" --include <patterns> Comma-separated patterns to include")
fmt.Println(" --verify Verify archive integrity after compression")
fmt.Println(" --url <url> Download and extract archive from remote URL")
fmt.Println(" --version Show version information")
fmt.Println(" --help Show this help message")
fmt.Println("\nEXAMPLES:")
fmt.Println(" # Compress a directory")
fmt.Println(" zipprine --compress /path/to/source --output archive.zip --type zip")
fmt.Println("\n # Extract an archive")
fmt.Println(" zipprine --extract archive.tar.gz --output /path/to/dest")
fmt.Println("\n # Analyze an archive")
fmt.Println(" zipprine --analyze archive.zip")
fmt.Println("\n # Download and extract from URL")
fmt.Println(" zipprine --url https://example.com/archive.zip --output /path/to/dest")
fmt.Println("\n # Compress with exclusions")
fmt.Println(" zipprine --compress /project --output project.tar.gz --type tar.gz --exclude '*.log,*.tmp'")
fmt.Println("\nSUPPORTED FORMATS:")
fmt.Println(" Compression: ZIP, TAR, TAR.GZ, GZIP")
fmt.Println(" Extraction: ZIP, TAR, TAR.GZ, GZIP, RAR")
fmt.Println("\nNOTE:")
fmt.Println(" RAR compression is not supported due to proprietary format.")
fmt.Println(" RAR extraction is supported for reading existing RAR archives.")
}