github.com/kjk/siser@v0.0.0-20220410204903-1b1e84ea1397/pak/README.md (about)

     1  ## What is `pak`
     2  
     3  `pak` is a go library for creating an archive (like a `.zip` archive).
     4  
     5  Use it when you need to bundle multiple files as a single file.
     6  
     7  Why not just use a `.zip`?
     8  
     9  The difference between a `.zip` and `pak` archive:
    10  
    11  - `pak` supports arbitrary file metadata as key / value pairs
    12  - `pak` doesn't compress by itself but you can use any compression type you like (record the compression type in metadata)
    13  - `pak` archive header is at the beginning of the file and the format is optimized for random access to files
    14  
    15  What are the limitations? It's meant for 'create once, read multiple times'. It doesn't support updating archives after creation.
    16  
    17  ## Create `pak` archive
    18  
    19  ```go
    20  a := pak.NewWriter()
    21  meta := pak.Metadata{}
    22  // optional, add arbitrary meta-data about the file
    23  meta.Set("Type", "text file")
    24  // add a file "foo.txt" to the archive
    25  err := a.AddFile("foo.txt", meta)
    26  if err != nil {
    27      log.Fatal("failed to add a file\n")
    28  }
    29  
    30  // add data with name "bar.txt"
    31  err := a.AddData([]byte("content of the file", "bar.txt", pak.Metadata{}))
    32  if err != nil {
    33      log.Fatal("failed to add a file\n")
    34  }
    35  // ... add more files
    36  
    37  // write creates an archive
    38  err = a.WriteToFile("myarchive.pak")
    39  if err != nil {
    40      log.Fatal("failed to write an archive\n")
    41  }
    42  // alternatively, write to any io.Writer:
    43  // var wr io.Writer = ...
    44  // err = w.Write(wr)
    45  ```
    46  
    47  ## Read `pak` archive
    48  
    49  ```go
    50  archive, err := pak.ReadArchive("myarchive.pak")
    51  if err != nil {
    52      log.Fatalf("failed to open an archive with '%s'\n", err)
    53  }
    54  defer f.Close()
    55  entries := archive.Entries
    56  fmt.Printf("Archive has %d entries\n", len(entries))
    57  entry := entries[0]
    58  d, err := archive.ReadEntry(entry)
    59  if err != nil {
    60      log.Fatalf("archive.ReadEntry() failed with '%s'\n", err)
    61  }
    62  // alternatively, you can use entry.Offset and entry.Size to read the data
    63  
    64  // print metadata entries
    65  for _, m := entry.Metadata.Meta {
    66  	fmt.Printf("metadata entry: key='%s', value='%s'\n", m.Key, m.Value)
    67  }
    68  ```
    69  
    70  ## More on motivation
    71  
    72  I wanted to package assets (.html files etc.) to be served via a web server into a single file.
    73  
    74  Usually I would `.zip` file for that but `ZIP` is an old, quirky file format.
    75  
    76  I wanted a simple and fast way to send a file out of the archive to the browser, already pre-compressed with a browser-compatible compression algorithm like brotli.
    77  
    78  Zip doesn't natively support brotli compression. Not an unfixable problem (zip allows to store uncompressed files) but I took this opportunity to implement my own archive format which supports arbitrary per-file meta-data.
    79  
    80  Building on top of my [siser](https://github.com/kjk/siser) library it's under 400 lines of code.
    81  
    82  The format is very simple: the header at the beginning of the file contains a list of files with their mata-data and position in the archive.
    83  
    84  The header is stored in [siser](https://github.com/kjk/siser) format.
    85  
    86  The data for each file follows.
    87  
    88  The archive is meant for batch creation. It doesn't support adding/removing/updating of files after the archive has been created.