sarulabs/statix

View on GitHub
asset.go

Summary

Maintainability
A
2 hrs
Test Coverage
package statix

import (
    "bytes"
    "errors"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    "strings"

    "github.com/sarulabs/statix/helpers"
    "github.com/sarulabs/statix/resource"
)

// Asset is the interface for assets managed by statix Manager.
type Asset interface {
    RewritePaths(string, string) Asset
    Dump([]Filter) error
}

// AssetPack implements the Asset interface. It includes all the assets
// located in the AssetPack.Input directory. Only the files with an output (without md5 suffix)
// matching the AssetPack.Pattern are part of the AssetPack.
// When the asset is dumped with the AssetPack.Dumper, AssetPack.Filters are applied before
// writing the assets in the AssetPack.Output directory.
type AssetPack struct {
    Input       string
    Output      string
    Pattern     Pattern
    Alterations []resource.Alteration
    Dumper      Dumper
}

// RewritePaths returns a new AssetPack with updated input and output.
// More precisely, if the AssetPack.Input or AssetPack.Output is relative, it is prefixed
// by the `input` and `output` parameters.
func (ap AssetPack) RewritePaths(input, output string) Asset {
    return AssetPack{
        Input:       helpers.RewritePath(input, ap.Input),
        Output:      helpers.RewritePath(output, ap.Output),
        Pattern:     ap.Pattern,
        Alterations: ap.Alterations,
        Dumper:      FileDumper{},
    }
}

// Dump reads files contained in AssetPack.Input and dumps
// them in AssetPack.Output if they match AssetPack.Pattern.
// If some filters are defined in AssetPack.Filters, they will be
// applied before dumping the assets with the AssetPack.Dumper.
// If some filters are passed in the `filters` parameter, they will be applied just after
// filters in AssetPack.Filters.
func (ap AssetPack) Dump(filters []Filter) error {
    var r resource.Resource

    files, err := ap.InputFiles()
    if err != nil {
        return err
    }

    for _, filename := range files {
        c, err := ioutil.ReadFile(filename)
        if err != nil {
            return err
        }

        r = resource.NewBytes(c)

        for _, a := range ap.Alterations {
            r, err = a.Alter(r)
            if err != nil {
                return err
            }
        }

        output, err := ap.OutputFile(filename, "")
        for _, f := range filters {
            if f.Pattern.Match(output) {
                r, err = f.Alteration.Alter(r)
                if err != nil {
                    return err
                }
            }
        }

        c, err = r.Dump()
        if err != nil {
            return err
        }

        md5Output, err := ap.OutputFile(filename, "."+helpers.MD5(c))
        if err != nil {
            return err
        }

        err = ap.Dumper.Dump(md5Output, output, c)
        if err != nil {
            return err
        }
    }

    return nil
}

// InputFiles returns all the files contained in AssetPack.Input
// that are not directories and that match AssetPack.Pattern.
func (ap AssetPack) InputFiles() ([]string, error) {
    var walkError error
    files := []string{}

    info, err := os.Stat(ap.Input)
    if err != nil || !info.IsDir() {
        return files, fmt.Errorf("asset input `%s` is not a directory", ap.Input)
    }

    filepath.Walk(ap.Input, func(filename string, info os.FileInfo, err error) error {
        if err != nil {
            walkError = err
        }
        if info.IsDir() {
            return nil
        }
        if ap.Pattern.Match(filename) {
            files = append(files, filename)
        }
        return nil
    })

    return files, walkError
}

// OutputFile returns the absolute filename of an output based on the filename of the input.
// It also add a suffix in the basename (for example an md5 hash or a version number).
// The suffix is inserted just before the file extension and at the end of the filename
// if no extension was found.
func (ap AssetPack) OutputFile(filename string, suffix string) (string, error) {
    filename, err := filepath.Abs(filename)
    if err != nil {
        return "", err
    }

    input, err := filepath.Abs(ap.Input)
    if err != nil {
        return "", err
    }

    if !strings.HasPrefix(filename, input) {
        return "", errors.New(ap.Input + " is not a prefix of " + filename)
    }

    out := bytes.NewBuffer(nil)
    out.WriteString(ap.Output)
    out.WriteByte(os.PathSeparator)
    out.WriteString(filename[len(input):])

    return helpers.AddFileSuffix(out.String(), suffix), nil
}

// SingleAsset implements the Asset interface.
// It includes only one asset (SingleAsset.Input) implementing the Asset
// interface located in the asset package. The asset will be dump in the SingleAsset.Output file.
type SingleAsset struct {
    Input  resource.Resource
    Output string
    Dumper Dumper
}

// RewritePaths returns a new SingleAsset with updated input and output.
// More precisely, if the SingleAsset.Input or SingleAsset.Output path is relative, it is prefixed
// by the `input` and `output` parameters.
func (sa SingleAsset) RewritePaths(input, output string) Asset {
    return SingleAsset{
        Input:  sa.Input.In(input),
        Output: helpers.RewritePath(output, sa.Output),
        Dumper: FileDumper{},
    }
}

// Dump dumps the asset defined in SingleAsset.Input.
// If some filters are passed in the `filters` parameter, they will be applied before
// dumping the asset.
func (sa SingleAsset) Dump(filters []Filter) error {
    r := sa.Input

    output, err := sa.OutputFile("")
    for _, f := range filters {
        if f.Pattern.Match(output) {
            r, err = f.Alteration.Alter(r)
            if err != nil {
                return err
            }
        }
    }

    c, err := r.Dump()
    if err != nil {
        return err
    }

    md5Output, err := sa.OutputFile("." + helpers.MD5(c))
    if err != nil {
        return err
    }

    err = sa.Dumper.Dump(md5Output, output, c)
    if err != nil {
        return err
    }

    return nil
}

// OutputFile returns the absolute filename of the SingleAsset.Output.
// It also add a suffix in the basename (for example an md5 hash or a version number).
// The suffix is inserted just before the file extension and at the end of the filename
// if no extension was found.
func (sa SingleAsset) OutputFile(suffix string) (string, error) {
    out, err := filepath.Abs(sa.Output)
    if err != nil {
        return "", err
    }
    return helpers.AddFileSuffix(out, suffix), nil
}