(Feat): Add path autocompletion and use sensible defaults when no inputs are provided by the user
This commit is contained in:
@@ -3,6 +3,7 @@ package ui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"zipprine/internal/archiver"
|
"zipprine/internal/archiver"
|
||||||
@@ -20,34 +21,105 @@ func RunCompressFlow() error {
|
|||||||
var verify bool
|
var verify bool
|
||||||
var compressionLevel string
|
var compressionLevel string
|
||||||
|
|
||||||
|
// Get current working directory
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
|
||||||
|
// Function to get directory completions
|
||||||
|
getDirCompletions := func(input string) []string {
|
||||||
|
if input == "" {
|
||||||
|
input = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand home directory
|
||||||
|
if strings.HasPrefix(input, "~") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
input = filepath.Join(home, input[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the directory and file pattern
|
||||||
|
dir := filepath.Dir(input)
|
||||||
|
pattern := filepath.Base(input)
|
||||||
|
|
||||||
|
// If input ends with /, we want to list that directory
|
||||||
|
if strings.HasSuffix(input, string(filepath.Separator)) {
|
||||||
|
dir = input
|
||||||
|
pattern = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read directory
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
// If can't read, try parent directory
|
||||||
|
entries, err = os.ReadDir(".")
|
||||||
|
if err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
completions := []string{}
|
||||||
|
for _, entry := range entries {
|
||||||
|
name := entry.Name()
|
||||||
|
|
||||||
|
// Skip hidden files unless explicitly requested
|
||||||
|
if strings.HasPrefix(name, ".") && !strings.HasPrefix(pattern, ".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by pattern
|
||||||
|
if pattern != "" && !strings.HasPrefix(strings.ToLower(name), strings.ToLower(pattern)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(dir, name)
|
||||||
|
if entry.IsDir() {
|
||||||
|
fullPath += string(filepath.Separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
completions = append(completions, fullPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit to 10 suggestions
|
||||||
|
if len(completions) > 10 {
|
||||||
|
completions = completions[:10]
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
|
||||||
form := huh.NewForm(
|
form := huh.NewForm(
|
||||||
huh.NewGroup(
|
huh.NewGroup(
|
||||||
huh.NewInput().
|
huh.NewInput().
|
||||||
Title("📁 Source Path").
|
Title("📁 Source Path").
|
||||||
Description("Enter the path to compress (file or directory)").
|
Description("Enter the path to compress (file or directory) - Tab for completions").
|
||||||
Placeholder("/path/to/source").
|
Placeholder(cwd).
|
||||||
Value(&sourcePath).
|
Value(&sourcePath).
|
||||||
Validate(func(s string) error {
|
Validate(func(s string) error {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return fmt.Errorf("source path cannot be empty")
|
return fmt.Errorf("source path cannot be empty")
|
||||||
}
|
}
|
||||||
|
// Expand home directory
|
||||||
|
if strings.HasPrefix(s, "~") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
s = filepath.Join(home, s[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, err := os.Stat(s); os.IsNotExist(err) {
|
if _, err := os.Stat(s); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("path does not exist")
|
return fmt.Errorf("path does not exist")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}),
|
}).
|
||||||
|
Suggestions(getDirCompletions("")),
|
||||||
|
|
||||||
huh.NewInput().
|
huh.NewInput().
|
||||||
Title("💾 Output Path").
|
Title("💾 Output Path").
|
||||||
Description("Where to save the archive").
|
Description("Where to save (leave empty for auto-naming in current directory)").
|
||||||
Placeholder("/path/to/output.zip").
|
Placeholder("Auto: <source-name>.<type> in current directory").
|
||||||
Value(&outputPath).
|
Value(&outputPath).
|
||||||
Validate(func(s string) error {
|
Suggestions(getDirCompletions("")),
|
||||||
if s == "" {
|
|
||||||
return fmt.Errorf("output path cannot be empty")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}).Suggestions([]string{".zip", ".tar.gz", ".tar", ".gz"}),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
huh.NewGroup(
|
huh.NewGroup(
|
||||||
@@ -101,6 +173,66 @@ func RunCompressFlow() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expand home directory in source path
|
||||||
|
if strings.HasPrefix(sourcePath, "~") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
sourcePath = filepath.Join(home, sourcePath[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make source path absolute
|
||||||
|
if !filepath.IsAbs(sourcePath) {
|
||||||
|
absPath, err := filepath.Abs(sourcePath)
|
||||||
|
if err == nil {
|
||||||
|
sourcePath = absPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-generate output path if not provided
|
||||||
|
if outputPath == "" {
|
||||||
|
sourceName := filepath.Base(sourcePath)
|
||||||
|
|
||||||
|
// Remove trailing slashes
|
||||||
|
sourceName = strings.TrimSuffix(sourceName, string(filepath.Separator))
|
||||||
|
|
||||||
|
// Determine file extension based on archive type
|
||||||
|
var extension string
|
||||||
|
switch models.ArchiveType(archiveTypeStr) {
|
||||||
|
case models.ZIP:
|
||||||
|
extension = ".zip"
|
||||||
|
case models.TARGZ:
|
||||||
|
extension = ".tar.gz"
|
||||||
|
case models.TAR:
|
||||||
|
extension = ".tar"
|
||||||
|
case models.GZIP:
|
||||||
|
extension = ".gz"
|
||||||
|
default:
|
||||||
|
extension = ".zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create output path in current working directory
|
||||||
|
outputPath = filepath.Join(cwd, sourceName+extension)
|
||||||
|
|
||||||
|
fmt.Println(InfoStyle.Render(fmt.Sprintf("📝 Auto-generated output: %s", outputPath)))
|
||||||
|
} else {
|
||||||
|
// Expand home directory in output path
|
||||||
|
if strings.HasPrefix(outputPath, "~") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
outputPath = filepath.Join(home, outputPath[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make output path absolute if relative
|
||||||
|
if !filepath.IsAbs(outputPath) {
|
||||||
|
absPath, err := filepath.Abs(outputPath)
|
||||||
|
if err == nil {
|
||||||
|
outputPath = absPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config.SourcePath = sourcePath
|
config.SourcePath = sourcePath
|
||||||
config.OutputPath = outputPath
|
config.OutputPath = outputPath
|
||||||
config.ArchiveType = models.ArchiveType(archiveTypeStr)
|
config.ArchiveType = models.ArchiveType(archiveTypeStr)
|
||||||
@@ -123,12 +255,26 @@ func RunCompressFlow() error {
|
|||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println(InfoStyle.Render("🎯 Starting compression..."))
|
fmt.Println(InfoStyle.Render("🎯 Starting compression..."))
|
||||||
|
fmt.Println(InfoStyle.Render(fmt.Sprintf(" Source: %s", config.SourcePath)))
|
||||||
|
fmt.Println(InfoStyle.Render(fmt.Sprintf(" Output: %s", config.OutputPath)))
|
||||||
|
|
||||||
if err := archiver.Compress(config); err != nil {
|
if err := archiver.Compress(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(SuccessStyle.Render("✅ Archive created successfully!"))
|
fmt.Println(SuccessStyle.Render("✅ Archive created successfully!"))
|
||||||
|
|
||||||
|
// Show file info
|
||||||
|
fileInfo, err := os.Stat(config.OutputPath)
|
||||||
|
if err == nil {
|
||||||
|
sizeKB := float64(fileInfo.Size()) / 1024
|
||||||
|
sizeMB := sizeKB / 1024
|
||||||
|
if sizeMB >= 1 {
|
||||||
|
fmt.Println(InfoStyle.Render(fmt.Sprintf("📦 Size: %.2f MB", sizeMB)))
|
||||||
|
} else {
|
||||||
|
fmt.Println(InfoStyle.Render(fmt.Sprintf("📦 Size: %.2f KB", sizeKB)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if config.VerifyIntegrity {
|
if config.VerifyIntegrity {
|
||||||
fmt.Println(InfoStyle.Render("🔍 Verifying archive integrity..."))
|
fmt.Println(InfoStyle.Render("🔍 Verifying archive integrity..."))
|
||||||
|
|||||||
Reference in New Issue
Block a user