github.com/AlekSi/nut@v0.3.1-0.20130607203728-cce108d4135e/nut.go (about)

     1  package nut
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"fmt"
     7  	"go/build"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"regexp"
    13  	"sort"
    14  	"strings"
    15  )
    16  
    17  // Check package for errors and return them.
    18  func CheckPackage(pack *build.Package) (errors []string) {
    19  	// check name
    20  	if strings.ToLower(pack.Name) != pack.Name {
    21  		errors = append(errors, `Package name should be lower case.`)
    22  	}
    23  	if strings.HasPrefix(pack.Name, "_") {
    24  		errors = append(errors, `Package name should not starts with "_".`)
    25  	}
    26  	if strings.HasSuffix(pack.Name, "_") {
    27  		errors = append(errors, `Package name should not ends with "_".`)
    28  	}
    29  	if strings.HasSuffix(pack.Name, "_test") {
    30  		errors = append(errors, `Package name should not ends with "_test".`)
    31  	}
    32  
    33  	// check doc summary
    34  	r := regexp.MustCompile(fmt.Sprintf(`Package %s .+\.`, pack.Name))
    35  	if !r.MatchString(pack.Doc) {
    36  		errors = append(errors, fmt.Sprintf(`Package summary in code should be in form "Package %s ... ."`, pack.Name))
    37  	}
    38  
    39  	return
    40  }
    41  
    42  // Describes nut – a Go package with associated meta-information.
    43  // It embeds Spec and build.Package to provide easy access to properties:
    44  // Nut.Name instead of Nut.Package.Name, Nut.Version instead of Nut.Spec.Version.
    45  type Nut struct {
    46  	Spec
    47  	build.Package
    48  }
    49  
    50  // Check nut for errors and return them. Calls Spec.Check() and CheckPackage().
    51  func (nut *Nut) Check() (errors []string) {
    52  	errors = nut.Spec.Check()
    53  	errors = append(errors, CheckPackage(&nut.Package)...)
    54  	return
    55  }
    56  
    57  // Returns canonical filename in format <name>-<version>.nut
    58  func (nut *Nut) FileName() string {
    59  	return fmt.Sprintf("%s-%s.nut", nut.Name, nut.Version)
    60  }
    61  
    62  // Returns canonical filepath in format <prefix>/<vendor>/<name>-<version>.nut
    63  // (with "\" instead of "/" on Windows).
    64  func (nut *Nut) FilePath(prefix string) string {
    65  	return filepath.Join(prefix, nut.Vendor, nut.FileName())
    66  }
    67  
    68  // Returns canonical import path in format <prefix>/<vendor>/<name>
    69  func (nut *Nut) ImportPath(prefix string) string {
    70  	return fmt.Sprintf("%s/%s/%s", prefix, nut.Vendor, nut.Name)
    71  }
    72  
    73  // Read nut from directory: package from <dir> and spec from <dir>/<SpecFileName>.
    74  func (nut *Nut) ReadFrom(dir string) (err error) {
    75  	// This method is called ReadFrom to prevent code n.ReadFrom(r) from calling n.Spec.ReadFrom(r).
    76  
    77  	// read package
    78  	pack, err := build.ImportDir(dir, 0)
    79  	if err != nil {
    80  		return
    81  	}
    82  	nut.Package = *pack
    83  
    84  	// read spec
    85  	f, err := os.Open(filepath.Join(dir, SpecFileName))
    86  	if err != nil {
    87  		return
    88  	}
    89  	defer f.Close()
    90  	_, err = nut.Spec.ReadFrom(f)
    91  	return
    92  }
    93  
    94  // Describes .nut file (a ZIP archive).
    95  type NutFile struct {
    96  	Nut
    97  	Reader *zip.Reader
    98  }
    99  
   100  // check interface
   101  var (
   102  	_ io.ReaderFrom = &NutFile{}
   103  )
   104  
   105  // Reads nut from specified file.
   106  func (nf *NutFile) ReadFile(fileName string) (err error) {
   107  	f, err := os.Open(fileName)
   108  	if err != nil {
   109  		return
   110  	}
   111  	defer f.Close()
   112  
   113  	_, err = nf.ReadFrom(f)
   114  	return
   115  }
   116  
   117  // ReadFrom reads nut from r until EOF.
   118  // The return value n is the number of bytes read.
   119  // Any error except io.EOF encountered during the read is also returned.
   120  // Implements io.ReaderFrom.
   121  func (nf *NutFile) ReadFrom(r io.Reader) (n int64, err error) {
   122  	var b []byte
   123  	b, err = ioutil.ReadAll(r)
   124  	n = int64(len(b))
   125  	if err != nil {
   126  		return
   127  	}
   128  
   129  	nf.Reader, err = zip.NewReader(bytes.NewReader(b), n)
   130  	if err != nil {
   131  		return
   132  	}
   133  
   134  	// read spec (typically the last file)
   135  	var specReader io.ReadCloser
   136  	for i := len(nf.Reader.File) - 1; i >= 0; i-- {
   137  		file := nf.Reader.File[i]
   138  		if file.Name == SpecFileName {
   139  			specReader, err = file.Open()
   140  			if err != nil {
   141  				return
   142  			}
   143  			defer func() {
   144  				e := specReader.Close()
   145  				if err == nil { // don't hide original error
   146  					err = e
   147  				}
   148  			}()
   149  			break
   150  		}
   151  	}
   152  	if specReader == nil {
   153  		err = fmt.Errorf("NutFile.ReadFrom: %q not found", SpecFileName)
   154  		return
   155  	}
   156  	spec := &nf.Spec
   157  	_, err = spec.ReadFrom(specReader)
   158  	if err != nil {
   159  		return
   160  	}
   161  
   162  	// read package
   163  	pack, err := nf.context().ImportDir(".", 0)
   164  	if err != nil {
   165  		return
   166  	}
   167  	nf.Package = *pack
   168  	return
   169  }
   170  
   171  // byName implements sort.Interface.
   172  type byName []os.FileInfo
   173  
   174  func (f byName) Len() int           { return len(f) }
   175  func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
   176  func (f byName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
   177  
   178  // Returns build.Context for given nut.
   179  // Returned value may be used to call normal context methods Import and ImportDir
   180  // to extract information about package in nut without unpacking it: name, doc, dependencies.
   181  func (nf *NutFile) context() (ctxt *build.Context) {
   182  	ctxt = new(build.Context)
   183  	*ctxt = build.Default
   184  
   185  	// FIXME path is ignored (multi-package nuts are not supported yet)
   186  	ctxt.ReadDir = func(path string) (fi []os.FileInfo, err error) {
   187  		// log.Printf("nf.ReadDir %q", path)
   188  
   189  		fi = make([]os.FileInfo, len(nf.Reader.File))
   190  		for i, f := range nf.Reader.File {
   191  			fi[i] = f.FileInfo()
   192  		}
   193  		sort.Sort(byName(fi))
   194  		return fi, nil
   195  	}
   196  
   197  	ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
   198  		// log.Printf("nf.OpenFile %q", path)
   199  
   200  		for _, f := range nf.Reader.File {
   201  			if f.Name == path {
   202  				return f.Open()
   203  			}
   204  		}
   205  
   206  		return nil, fmt.Errorf("NutFile.Context.OpenFile: %q not found", path)
   207  	}
   208  
   209  	return
   210  }