Files
kenaito-kopsc/internal/executor/archivecreate.go
T

264 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package executor
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// runArchiveCreate implements `kopsc archive create <path>`: create an archive.
func runArchiveCreate(ctx context.Context, req BuiltinRequest) error {
opts, err := parseArchiveCreateArgs(req.Args)
if err != nil {
return err
}
src := opts.source
if _, err := os.Stat(src); err != nil {
return fmt.Errorf("路径 %q 不存在: %w", src, err)
}
// Determine output filename
output := opts.output
if output == "" {
base := filepath.Base(src)
switch opts.format {
case "tar.gz", "tgz":
output = base + ".tar.gz"
case "tar":
output = base + ".tar"
case "zip":
output = base + ".zip"
}
}
fmt.Fprintf(req.Stdout, "创建压缩文件: %s -> %s\n", src, output)
switch opts.format {
case "tar.gz", "tgz":
return createTarGz(src, output, req)
case "tar":
return createTar(src, output, req)
case "zip":
return createZip(src, output, req)
default:
return fmt.Errorf("不支持的格式: %q(支持 tar.gz / tgz / tar / zip)", opts.format)
}
}
type archiveCreateOptions struct {
source string
output string
format string
}
func parseArchiveCreateArgs(args []string) (archiveCreateOptions, error) {
opts := archiveCreateOptions{
format: "tar.gz", // default format
}
var positional []string
for i := 0; i < len(args); i++ {
arg := args[i]
switch {
case arg == "--":
positional = append(positional, args[i+1:]...)
i = len(args)
case arg == "--output" || arg == "-o":
if i+1 >= len(args) {
return opts, fmt.Errorf("%s 缺少输出文件名", arg)
}
i++
opts.output = args[i]
case strings.HasPrefix(arg, "--output="):
opts.output = strings.TrimPrefix(arg, "--output=")
case arg == "--format" || arg == "-f":
if i+1 >= len(args) {
return opts, fmt.Errorf("%s 缺少格式参数", arg)
}
i++
opts.format = strings.ToLower(args[i])
case strings.HasPrefix(arg, "--format="):
opts.format = strings.ToLower(strings.TrimPrefix(arg, "--format="))
case strings.HasPrefix(arg, "-") && arg != "-":
return opts, fmt.Errorf("未知选项 %q", arg)
default:
positional = append(positional, arg)
}
}
if len(positional) == 0 {
return opts, fmt.Errorf("缺少源路径;用法: kopsc archive create <路径>")
}
opts.source = positional[0]
// Normalize format aliases
switch opts.format {
case "tgz":
opts.format = "tar.gz"
}
return opts, nil
}
func createTarGz(src, output string, req BuiltinRequest) error {
outFile, err := os.Create(output)
if err != nil {
return fmt.Errorf("创建输出文件失败: %w", err)
}
defer outFile.Close()
gz := gzip.NewWriter(outFile)
defer gz.Close()
tw := tar.NewWriter(gz)
defer tw.Close()
return writeTarArchive(src, tw, req)
}
func createTar(src, output string, req BuiltinRequest) error {
outFile, err := os.Create(output)
if err != nil {
return fmt.Errorf("创建输出文件失败: %w", err)
}
defer outFile.Close()
tw := tar.NewWriter(outFile)
defer tw.Close()
return writeTarArchive(src, tw, req)
}
func writeTarArchive(src string, tw *tar.Writer, req BuiltinRequest) error {
baseDir := filepath.Dir(src)
baseName := filepath.Base(src)
count := 0
err := filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
// Get relative path
relPath, err := filepath.Rel(baseDir, path)
if err != nil {
return err
}
// For single file, just use the filename
if !info.IsDir() && baseName == filepath.Base(path) {
relPath = filepath.Base(path)
}
header, err := tar.FileInfoHeader(info, "")
if err != nil {
return err
}
header.Name = relPath
if err := tw.WriteHeader(header); err != nil {
return err
}
if !info.IsDir() {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(tw, f); err != nil {
return err
}
count++
}
return nil
})
if err != nil {
return err
}
fmt.Fprintf(req.Stdout, "已压缩 %d 个文件\n", count)
return nil
}
func createZip(src, output string, req BuiltinRequest) error {
outFile, err := os.Create(output)
if err != nil {
return fmt.Errorf("创建输出文件失败: %w", err)
}
defer outFile.Close()
zw := zip.NewWriter(outFile)
defer zw.Close()
baseDir := filepath.Dir(src)
count := 0
err = filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
// Get relative path
relPath, err := filepath.Rel(baseDir, path)
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = relPath
header.Method = zip.Deflate
if info.IsDir() {
header.Name += "/"
_, err = zw.CreateHeader(header)
return err
}
writer, err := zw.CreateHeader(header)
if err != nil {
return err
}
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(writer, f); err != nil {
return err
}
count++
return nil
})
if err != nil {
return err
}
fmt.Fprintf(req.Stdout, "已压缩 %d 个文件\n", count)
return nil
}