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 }