Skip to content

Commit

Permalink
added support for zipfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
NodyHub committed Sep 20, 2024
1 parent 3ec9fe7 commit 81338eb
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 37 deletions.
46 changes: 35 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,58 @@ $ go install github.com/NodyHub/golinkwrite@latest
### Help

```shell
$ golinkwrite
$ golinkwrite -h
Usage: golinkwrite <input> <target> <output> [flags]

Create a tar archive containing a symbolic link to a provided target and a provided file.
Create a tar archive containing a provided file and a symlink that points to the write destination.

Arguments:
<input> Input file.
<target> Target destination in the filesystem.
<output> Output file.

Flags:
-h, --help Show context-sensitive help.
-v, --verbose Enable verbose output.
-h, --help Show context-sensitive help.
-t, --type="tar" Type of the archive. (tar, zip)
-v, --verbose Enable verbose output.
```

### In Action

### Tar

```shell
$ echo 'Hello Alice :wave:!' | tee rabbit_hole.txt
(main[2]) ~/git/go-link-write% echo 'Hello Alice :wave:!' | tee rabbit_hole.txt
Hello Alice :wave:!

$ golinkwrite -v rabbit_hole.txt /tmp/hi.txt alice.tar
time=2024-08-09T19:11:35.266+02:00 level=DEBUG msg="command line parameters" cli="{Input:rabbit_hole.txt Target:/tmp/hi.txt Output:alice.tar Verbose:true}"
time=2024-08-09T19:11:35.266+02:00 level=DEBUG msg="input permissions" perm=-rw-r--r--
time=2024-08-09T19:11:35.266+02:00 level=DEBUG msg="input size" size=20
time=2024-08-09T19:11:35.266+02:00 level=INFO msg="tar file created" output=alice.tar
(main[2]) ~/git/go-link-write% golinkwrite -v rabbit_hole.txt /tmp/hi.txt alice.tar
time=2024-09-20T11:52:39.211+02:00 level=DEBUG msg="command line parameters" cli="{Input:rabbit_hole.txt Target:/tmp/hi.txt Output:alice.tar Type:tar Verbose:true}"
time=2024-09-20T11:52:39.212+02:00 level=DEBUG msg="input permissions" perm=-rw-r--r--
time=2024-09-20T11:52:39.213+02:00 level=DEBUG msg="input size" size=20
time=2024-09-20T11:52:39.213+02:00 level=INFO msg="archive created" output=alice.tar

$ tar ztvf alice.tar
(main[2]) ~/git/go-link-write% tar ztvf alice.tar
lrw-r--r-- 0 0 0 0 1 Jan 1970 rabbit_hole.txt -> /tmp/hi.txt
-rw-r--r-- 0 0 0 20 1 Jan 1970 rabbit_hole.txt
```

### Zip

```shell
(main[2]) ~/git/go-link-write% echo 'Hello Alice :wave:!' | tee rabbit_hole.txt
Hello Alice :wave:!

(main[2]) ~/git/go-link-write% golinkwrite -t zip -v rabbit_hole.txt /tmp/hi.txt alice.zip
time=2024-09-20T11:54:12.300+02:00 level=DEBUG msg="command line parameters" cli="{Input:rabbit_hole.txt Target:/tmp/hi.txt Output:alice.zip Type:zip Verbose:true}"
time=2024-09-20T11:54:12.300+02:00 level=DEBUG msg="input permissions" perm=-rw-r--r--
time=2024-09-20T11:54:12.301+02:00 level=DEBUG msg="input size" size=20
time=2024-09-20T11:54:12.301+02:00 level=INFO msg="archive created" output=alice.zip
(main[2]) ~/git/go-link-write% unzip -l alice.zip
Archive: alice.zip
Length Date Time Name
--------- ---------- ----- ----
11 09-20-2024 11:54 rabbit_hole.txt
20 08-09-2024 19:10 rabbit_hole.txt
--------- -------
31 2 files
```
140 changes: 114 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ package main

import (
"archive/tar"
"archive/zip"
"bytes"
"fmt"
"io"
"io/fs"
"log/slog"
"os"
"time"

"github.com/alecthomas/kong"
)

type CLI struct {
var CLI struct {
Input string `arg:"" name:"input" help:"Input file."`
Target string `arg:"" name:"target" help:"Target destination in the filesystem."`
Output string `arg:"" name:"output" help:"Output file."`
Type string `short:"t" name:"type" default:"tar" help:"Type of the archive. (tar, zip)"`
Verbose bool `short:"v" name:"verbose" help:"Enable verbose output."`
}

func main() {
cli := CLI{}
// ctx := kong.Parse(&cli,
kong.Parse(&cli,
kong.Parse(&CLI,
kong.Name("golinkwrite"),
kong.Description("Create a tar archive containing a provided file and a symlink that points to the write destination."),
kong.UsageOnError(),
Expand All @@ -27,7 +33,7 @@ func main() {

// Check for verbose output
logLevel := slog.LevelError
if cli.Verbose {
if CLI.Verbose {
logLevel = slog.LevelDebug
}

Expand All @@ -37,64 +43,146 @@ func main() {
}))

// log parameters
logger.Debug("command line parameters", "cli", cli)
logger.Debug("command line parameters", "cli", CLI)

// get file mode from input file
fileInfo, err := os.Stat(cli.Input)
fileInfo, err := os.Stat(CLI.Input)
if err != nil {
logger.Error("failed to get file info", "error", err)
os.Exit(1)
}
logger.Debug("input permissions", "perm", fileInfo.Mode().Perm())

// read the input file
content, err := os.ReadFile(cli.Input)
content, err := os.ReadFile(CLI.Input)
if err != nil {
logger.Error("failed to read input file", "error", err)
os.Exit(1)
}
logger.Debug("input size", "size", len(content))

// prepare the tar file
out, err := os.Create(cli.Output)
out, err := os.Create(CLI.Output)
if err != nil {
logger.Error("failed to create output file", "error", err)
os.Exit(1)
}

switch CLI.Type {

case "tar":
if err := createTar(out, fileInfo, content); err != nil {
panic(fmt.Errorf("failed to create tar file: %w", err))
}

case "zip":
if err := createZip(out, fileInfo, content); err != nil {
panic(fmt.Errorf("failed to create zip file: %w", err))
}

default:
panic(fmt.Errorf("unsupported archive type: %s", CLI.Type))

}

logger.Info("archive created", "output", CLI.Output)
}

func createTar(out io.Writer, fi fs.FileInfo, content []byte) error {

// create writer add a file and a directory
writer := tar.NewWriter(out)
defer writer.Close()
defer func() {
if err := writer.Close(); err != nil {
panic(fmt.Errorf("failed to close tar writer: %w", err))
}
}()

// add a symlink to the tar file
header := &tar.Header{
Name: cli.Input,
Linkname: cli.Target,
Mode: int64(fileInfo.Mode().Perm()),
Name: CLI.Input,
Linkname: CLI.Target,
Mode: int64(fi.Mode().Perm()),
Typeflag: tar.TypeSymlink,
Size: 0,
}
err = writer.WriteHeader(header)
if err != nil {
logger.Error("failed to write header to tar file", "error", err)
os.Exit(1)
if err := writer.WriteHeader(header); err != nil {
return fmt.Errorf("failed to write header to tar file: %w", err)
}

// add a file
// add a file header
header = &tar.Header{
Name: cli.Input,
Mode: int64(fileInfo.Mode().Perm()),
Name: CLI.Input,
Mode: int64(fi.Mode().Perm()),
Size: int64(len(content)),
}
err = writer.WriteHeader(header)
if err := writer.WriteHeader(header); err != nil {
return fmt.Errorf("failed to write header to tar file: %w", err)
}

// add the file content
if _, err := writer.Write([]byte(content)); err != nil {
return fmt.Errorf("failed to write file to tar file: %w", err)
}

return nil
}

func createZip(out io.Writer, fi fs.FileInfo, content []byte) error {

// create zip writer
writer := zip.NewWriter(out)
defer func() {
if err := writer.Close(); err != nil {
panic(fmt.Errorf("failed to close zip writer: %w", err))
}
}()

// create a new file header
zipHeader := &zip.FileHeader{
Name: CLI.Input,
Method: zip.Store,
Modified: time.Now(),
}
zipHeader.SetMode(os.ModeSymlink | 0755)

// create a new file writer
fw, err := writer.CreateHeader(zipHeader)
if err != nil {
logger.Error("failed to write header to tar file", "error", err)
os.Exit(1)
return fmt.Errorf("failed to create zip header for symlink %s: %s", CLI.Input, err)
}
_, err = writer.Write([]byte(content))

// write the symlink to the zip archive
if _, err := fw.Write([]byte(CLI.Target)); err != nil {
return fmt.Errorf("failed to write symlink target %s to zip archive: %s", CLI.Target, err)
}

// create a new file header
zipHeader, err = zip.FileInfoHeader(fi)
if err != nil {
logger.Error("failed to write to tar file", "error", err)
os.Exit(1)
return fmt.Errorf("failed to create file header: %s", err)
}

// set the name of the file
zipHeader.Name = CLI.Input

// set the method of compression
zipHeader.Method = zip.Deflate

// create a new file writer
zw, err := writer.CreateHeader(zipHeader)
if err != nil {
return fmt.Errorf("failed to create zip file header: %s", err)
}
logger.Info("tar file created", "output", cli.Output)

// write the file to the zip archive

// create reader for byte slice
reader := bytes.NewReader(content)

if _, err := io.Copy(zw, reader); err != nil {
return fmt.Errorf("failed to write file to zip archive: %s", err)
}

return nil
}

0 comments on commit 81338eb

Please sign in to comment.