Skip to content

Commit

Permalink
init: Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
timo-reymann committed Jun 2, 2021
0 parents commit 1be75f2
Show file tree
Hide file tree
Showing 20 changed files with 323 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Copyright (c) 2021 Timo Reymann

"Climate Strike" License Version 1.0 (Draft)

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without limitation in the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software may not be used in applications and services that are used for or
aid in the exploration, extraction, refinement, processing, or transportation
of fossil fuels.

The Software may not be used by companies that rely on fossil fuel extraction
as their primary means of revenue. This includes but is not limited to the
companies listed at https://climatestrike.software/blocklist

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.PHONY: help

SHELL := /bin/bash

NOW=$(shell date +'%y-%m-%d_%H:%M:%S')

help: ## Display this help page
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}'

coverage: ## Run tests and measure coverage
@go test -covermode=count -coverprofile=/tmp/count.out -v ./...

test-coverage-report: coverage ## Run test and display coverage report in browser
@go tool cover -html=/tmp/count.out

save-coverage-report: coverage ## Save coverage report to coverage.html
@go tool cover -html=/tmp/count.out -o coverage.html

create-dist: ## Create dist folder if not already existent
@mkdir -p dist/
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
deterministic-zip
===

> Work In Progress
Simple (almost drop-in) replacement for zip.

## Installation

> TBD
## Usage

> TBD
## FAQ
### Why?!

Why another zip-tool? What is this deterministic stuff?!

When we are talking about deterministic it means that the hash of the zip file won't change unless the contents of the
zip file changes.

This means only the content, no metadata. You can achieve this with zip, yes.

The problem that still remains is that the order is almost unpredictable and zip is very platform specific, so you will
end up with a bunch of crazy shell pipelines. And I am not event talking about windows at this point.

So this is where this tool comes in, it is intended to be a drop-in replacement for zip in your build process.

The use cases for this are primary:

- Zipping serverless code
- Backups or other files that get rsynced

### How reliable is it?

Of course, it is not as reliable as the battle-proven and billions of times executed zip.

Even though I am heavily relying on the go stdlib this software can of course have bugs. And you are welcome to report
them and help make this even more stable. Of course there will be tests to cover most use cases but at the end this is
still starting from scratch, so if you need advanced features or just dont feel comfortable about using this tool don't
do it!
22 changes: 22 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmd

import (
"fmt"
"github.com/timo-reymann/deterministic-zip/pkg/cli"
"log"
"os"
)

func Execute() {
config := cli.NewConfiguration()
err := config.Parse(os.Args)
if err != nil {
log.Fatalln(err)
}

// TODO Glue together files
// TODO Zip together everything
// TODO Add verbose output possibility

fmt.Printf("%v", config)
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/timo-reymann/deterministic-zip

go 1.15

require github.com/jessevdk/go-flags v1.5.0
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/timo-reymann/deterministic-zip/cmd"

func main() {
cmd.Execute()
}
46 changes: 46 additions & 0 deletions pkg/cli/configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cli

import (
"errors"
"flag"
"os"
)

// Configuration represents the config for the cli
type Configuration struct {
Verbose bool
Recursive bool
ZipFile string
SourceFiles []string
}

func (conf *Configuration) addBoolFlag(field *bool, long string, short string, val bool, usage string) {
flag.BoolVar(field, long, val, usage)
flag.BoolVar(field, short, val, usage+" (short)")
}

// Parse the configuration from cli args
func (conf *Configuration) Parse(args []string) error {
conf.addBoolFlag(&conf.Verbose, "verbose", "v", false, "verbose mode for debug outputs and trouble shooting")
conf.addBoolFlag(&conf.Recursive, "recursive", "R", false, "include all files verbose")

cli := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
if err := cli.Parse(args); err != nil {
return err
}

remaining := flag.Args()
if len(remaining) < 2 {
return errors.New("specify at least the destination package and source files")
}

conf.ZipFile = remaining[0]
conf.SourceFiles = remaining[1:]

return nil
}

// NewConfiguration creates a new configuration
func NewConfiguration() *Configuration {
return &Configuration{}
}
1 change: 1 addition & 0 deletions pkg/cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package cli
13 changes: 13 additions & 0 deletions pkg/file/glob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package file

import (
"path/filepath"
"sort"
)

// FindByGlob searches files by pattern and returns a sorted list
func FindByGlob(pattern string) []string {
results, _ := filepath.Glob(pattern)
sort.Strings(results)
return results
}
49 changes: 49 additions & 0 deletions pkg/file/glob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package file

import (
"os"
"reflect"
"testing"
)

func TestFindByGlob(t *testing.T) {
wd, _ := os.Getwd()
t.Log(wd)
testCases := []struct {
pattern string
result []string
}{
{
pattern: "*.json",
result: []string{
"testdata/glob/a.json",
"testdata/glob/b.json",
"testdata/glob/c.json",
},
},
{
pattern: "*.csv",
result: []string{},
},
{
pattern: "a*",
result: []string{
"testdata/glob/a.json",
"testdata/glob/a.txt",
},
},
}

for _, tc := range testCases {
results := FindByGlob("testdata/glob/" + tc.pattern)

// DeepEquals doesnt like empty arrays
if len(tc.result) == 0 && len(results) == 0 {
continue
}

if !reflect.DeepEqual(results, tc.result) {
t.Fatalf("Expected %d results, but got %d", len(tc.result), len(results))
}
}
}
Empty file added pkg/file/testdata/glob/a.json
Empty file.
Empty file added pkg/file/testdata/glob/a.txt
Empty file.
Empty file added pkg/file/testdata/glob/b.json
Empty file.
Empty file added pkg/file/testdata/glob/b.txt
Empty file.
Empty file added pkg/file/testdata/glob/c.json
Empty file.
Empty file added pkg/file/testdata/glob/c.txt
Empty file.
Empty file.
34 changes: 34 additions & 0 deletions pkg/file/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package file

import (
"io/fs"
"os"
"path/filepath"
)

// IsDir checks if the given path is a valid directory
func IsDir(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}

return fileInfo.IsDir(), err
}

// ReadDirRecursive reads all files from the given path and returns them always in the same lexical order
func ReadDirRecursive(path string) ([]string, error) {
paths := make([]string, 0)
err := filepath.WalkDir(path, func(innerPath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
paths = append(paths, innerPath)
return nil
})
if err != nil {
return nil, err
}

return paths, nil
}
53 changes: 53 additions & 0 deletions pkg/file/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package file

import (
"reflect"
"testing"
)

func TestIsDir(t *testing.T) {
testCases := []struct {
path string
result bool
}{
{
"nonexistent",
false,
},
{
"testdata/util/one",
true,
},
}

for _, tc := range testCases {
if isDir, _ := IsDir(tc.path); isDir != tc.result {
t.Fatalf("Expected result to be %v but got %v", tc.result, isDir)
}
}
}

func TestReadDirRecursive(t *testing.T) {
testCases := []struct {
path string
results []string
}{
{
path: "testdata/util",
results: []string{
"testdata/util",
"testdata/util/one",
"testdata/util/one/two",
"testdata/util/one/two/three",
"testdata/util/one/two/three/file",
},
},
}

for _, tc := range testCases {
results, _ := ReadDirRecursive(tc.path)
if !reflect.DeepEqual(results, tc.results) {
t.Fatalf("Expected %d results, but got %d", len(tc.results), len(results))
}
}
}

0 comments on commit 1be75f2

Please sign in to comment.