pax_global_header00006660000000000000000000000064125710772420014521gustar00rootroot0000000000000052 comment=14e19be16030f47c8024f505e7fbd185fdc3fbdd .gitignore000066400000000000000000000000051257107724200130500ustar00rootroot00000000000000*swp LICENSE000066400000000000000000000050751257107724200121010ustar00rootroot00000000000000Copyright (c) Paul R. Tagliamonte , 2015 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 restriction, including without limitation 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 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. Version module ============== Copyright © 2012 Michael Stapelberg and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Michael Stapelberg nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. README.md000066400000000000000000000006311257107724200123440ustar00rootroot00000000000000go-debian ========= `go-debian` is a Debian Toolbelt for Go hackers! This package contains a bunch of helpful routines to help work with fun bits of Debian data. dependency ---------- This module contains bits to parse and work with Debian package relations. version ------- This module contains bits to work with package versions control ------- This module contains bits to work with control files changelog/000077500000000000000000000000001257107724200130145ustar00rootroot00000000000000changelog/changelog.go000066400000000000000000000105771257107724200153040ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package changelog import ( "bufio" "fmt" "io" "os" "strings" "time" "pault.ag/go/debian/version" ) // A ChangelogEntry is the encapsulation for each entry for a given version // in a series of uploads. type ChangelogEntry struct { Source string Version version.Version Target string Arguments map[string]string Changelog string ChangedBy string When time.Time } const whenLayout = time.RFC1123Z // "Mon, 02 Jan 2006 15:04:05 -0700" type ChangelogEntries []ChangelogEntry func trim(line string) string { return strings.Trim(line, "\n\r\t ") } func partition(line, delim string) (string, string) { entries := strings.SplitN(line, delim, 2) if len(entries) != 2 { return line, "" } return entries[0], entries[1] } func ParseOne(reader *bufio.Reader) (*ChangelogEntry, error) { changeLog := ChangelogEntry{} var header string for { line, err := reader.ReadString('\n') if err != nil { return nil, err } if line == "\n" { continue } if !strings.HasPrefix(line, " ") { /* Great. Let's work with this. */ header = line break } else { return nil, fmt.Errorf("Unexpected line: %s", line) } } /* OK, so, we have a header. Let's run with it * hello (2.10-1) unstable; urgency=low */ arguments, options := partition(header, ";") /* Arguments: hello (2.10-1) unstable * Options: urgency=low, other=bar */ source, remainder := partition(arguments, "(") versionString, suite := partition(remainder, ")") var err error changeLog.Source = trim(source) changeLog.Version, err = version.Parse(trim(versionString)) if err != nil { return nil, err } changeLog.Target = trim(suite) changeLog.Arguments = map[string]string{} for _, entry := range strings.Split(options, ",") { key, value := partition(trim(entry), "=") changeLog.Arguments[trim(key)] = trim(value) } var signoff string /* OK, we've got the header. Let's zip down. */ for { line, err := reader.ReadString('\n') if err != nil { return nil, err } if !strings.HasPrefix(line, " ") && trim(line) != "" { return nil, fmt.Errorf("Error! Didn't get ending line!") } if strings.HasPrefix(line, " -- ") { signoff = line break } changeLog.Changelog = changeLog.Changelog + line } /* Right, so we have a signoff line */ _, signoff = partition(signoff, "--") /* Get rid of the leading " -- " */ whom, when := partition(signoff, " ") /* Split on the " " */ changeLog.ChangedBy = trim(whom) changeLog.When, err = time.Parse(whenLayout, trim(when)) if err != nil { return nil, fmt.Errorf("Failed parsing When %q: %v", when, err) } return &changeLog, nil } func ParseFileOne(path string) (*ChangelogEntry, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return ParseOne(bufio.NewReader(f)) } func Parse(reader io.Reader) (ChangelogEntries, error) { stream := bufio.NewReader(reader) ret := ChangelogEntries{} for { entry, err := ParseOne(stream) if err == io.EOF { break } if err != nil { return ChangelogEntries{}, err } ret = append(ret, *entry) } return ret, nil } func ParseFile(path string) (ChangelogEntries, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() return Parse(bufio.NewReader(f)) } // vim: foldmethod=marker changelog/changelog_test.go000066400000000000000000000060251257107724200163340ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package changelog_test import ( "bufio" "log" "strings" "testing" "pault.ag/go/debian/changelog" ) /* * */ func isok(t *testing.T, err error) { if err != nil { log.Printf("Error! Error is not nil! %s\n", err) t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") t.FailNow() } } /* * */ // {{{ test changelog entry var changeLog = `hello (2.10-1) unstable; urgency=low * New upstream release. * debian/patches: Drop 01-fix-i18n-of-default-message, no longer needed. * debian/patches: Drop 99-config-guess-config-sub, no longer needed. * debian/rules: Drop override_dh_auto_build hack, no longer needed. * Standards-Version: 3.9.6 (no changes for this). -- Santiago Vila Sun, 22 Mar 2015 11:56:00 +0100 hello (2.9-2) unstable; urgency=low * Apply patch from Reuben Thomas to fix i18n of default message. This is upstream commit c4aed00. Closes: #767172. * The previous change in src/hello.c trigger a rebuild of man/hello.1 that we don't need. Add a "touch man/hello.1" to avoid it. * Use Breaks: hello-debhelper (<< 2.9), not Conflicts, as hello-debhelper is deprecated. * Restore simple watch file from old hello package that was lost when the packages were renamed. * Update 99-config-guess-config-sub patch. -- Santiago Vila Thu, 06 Nov 2014 12:03:40 +0100 ` // }}} func TestChangelogEntry(t *testing.T) { changeLog, err := changelog.ParseOne(bufio.NewReader(strings.NewReader(changeLog))) isok(t, err) assert(t, changeLog.ChangedBy == "Santiago Vila ") } func TestChangelogEntries(t *testing.T) { changeLogs, err := changelog.Parse(strings.NewReader(changeLog)) isok(t, err) assert(t, len(changeLogs) == 2) } // vim: foldmethod=marker control/000077500000000000000000000000001257107724200125455ustar00rootroot00000000000000control/changes.go000066400000000000000000000163671257107724200145210ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "bufio" "fmt" "os" "path/filepath" "strconv" "strings" "pault.ag/go/debian/dependency" "pault.ag/go/debian/internal" "pault.ag/go/debian/version" ) // {{{ .changes Files list entries type FileListChangesFileHash struct { DebianFileHash Component string Priority string } func (c *FileListChangesFileHash) UnmarshalControl(data string) error { var err error c.Algorithm = "md5" vals := strings.Split(data, " ") if len(data) < 5 { return fmt.Errorf("Error: Unknown File List Hash line: '%s'", data) } c.Hash = vals[0] c.Size, err = strconv.ParseInt(vals[1], 10, 64) if err != nil { return err } c.Component = vals[2] c.Priority = vals[3] c.Filename = vals[4] return nil } // }}} // The Changes struct is the default encapsulation of the Debian .changes // package filetype.This struct contains an anonymous member of type Paragraph, // allowing you to use the standard .Values and .Order of the Paragraph type. // // The .changes files are used by the Debian archive maintenance software to // process updates to packages. They consist of a single paragraph, possibly // surrounded by a PGP signature. That paragraph contains information from the // debian/control file and other data about the source package gathered via // debian/changelog and debian/rules. type Changes struct { Paragraph Filename string Format string Source string Binaries []string `control:"Binary" delim:" "` Architectures []dependency.Arch `control:"Architecture"` Version version.Version Origin string Distribution string Urgency string Maintainer string ChangedBy string `control:"Changed-By"` Closes []string Changes string ChecksumsSha1 []SHA1DebianFileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256DebianFileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` Files []FileListChangesFileHash `control:"Files" delim:"\n" strip:"\n\r\t "` } // Given a path on the filesystem, Parse the file off the disk and return // a pointer to a brand new Changes struct, unless error is set to a value // other than nil. func ParseChangesFile(path string) (ret *Changes, err error) { path, err = filepath.Abs(path) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() ret, err = ParseChanges(bufio.NewReader(f), path) if err != nil { return nil, err } return ret, nil } // Given a bufio.Reader, consume the Reader, and return a Changes object // for use. The "path" argument is used to set Changes.Filename, which // is used by Changes.GetDSC, Changes.Remove, Changes.Move and Changes.Copy to // figure out where all the files on the filesystem are. This value can be set // to something invalid if you're not using those functions. func ParseChanges(reader *bufio.Reader, path string) (*Changes, error) { ret := &Changes{Filename: path} if err := Unmarshal(ret, reader); err != nil { return nil, err } return ret, nil } // Return a DSC struct for the DSC listed in the .changes file. This requires // Changes.Filename to be correctly set, and for the .dsc file to exist // in the correct place next to the .changes. // // This function may also return an error if the given .changes does not // include the .dsc (binary-only upload) func (changes *Changes) GetDSC() (*DSC, error) { for _, file := range changes.Files { if strings.HasSuffix(file.Filename, ".dsc") { // Right, now lets resolve the absolute path. baseDir := filepath.Dir(changes.Filename) dsc, err := ParseDscFile(baseDir + "/" + file.Filename) if err != nil { return nil, err } return dsc, nil } } return nil, fmt.Errorf("No .dsc file in .changes") } // Copy the .changes file and all refrenced files to the directory // listed by the dest argument. This function will error out if the dest // argument is not a directory, or if there is an IO operation in transfer. // // This function will always move .changes last, making it suitable to // be used to move something into an incoming directory with an inotify // hook. This will also mutate Changes.Filename to match the new location. func (changes *Changes) Copy(dest string) error { if file, err := os.Stat(dest); err == nil && !file.IsDir() { return fmt.Errorf("Attempting to move .changes to a non-directory") } for _, file := range changes.Files { dirname := filepath.Base(file.Filename) err := internal.Copy(file.Filename, dest+"/"+dirname) if err != nil { return err } } dirname := filepath.Base(changes.Filename) err := internal.Copy(changes.Filename, dest+"/"+dirname) changes.Filename = dest + "/" + dirname if err != nil { return err } return nil } // Move the .changes file and all refrenced files to the directory // listed by the dest argument. This function will error out if the dest // argument is not a directory, or if there is an IO operation in transfer. // // This function will always move .changes last, making it suitable to // be used to move something into an incoming directory with an inotify // hook. This will also mutate Changes.Filename to match the new location. func (changes *Changes) Move(dest string) error { if file, err := os.Stat(dest); err == nil && !file.IsDir() { return fmt.Errorf("Attempting to move .changes to a non-directory") } for _, file := range changes.Files { dirname := filepath.Base(file.Filename) err := os.Rename(file.Filename, dest+"/"+dirname) if err != nil { return err } } dirname := filepath.Base(changes.Filename) err := os.Rename(changes.Filename, dest+"/"+dirname) changes.Filename = dest + "/" + dirname if err != nil { return err } return nil } // Remove the .changes file and any associated files. This function will // always remove the .changes last, in the event there are filesystem i/o errors // on removing associated files. func (changes *Changes) Remove() error { for _, file := range changes.Files { err := os.Remove(file.Filename) if err != nil { return err } } err := os.Remove(changes.Filename) if err != nil { return err } return nil } // vim: foldmethod=marker control/changes_test.go000066400000000000000000000115611257107724200155470ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func TestChangesParse(t *testing.T) { // Test Paragraph {{{ reader := bufio.NewReader(strings.NewReader(`Format: 1.8 Date: Wed, 29 Apr 2015 21:29:13 -0400 Source: dput-ng Binary: dput-ng python-dput dput-ng-doc Architecture: source Version: 1.9 Distribution: unstable Urgency: medium Maintainer: dput-ng Maintainers Changed-By: Paul Tagliamonte Description: dput-ng - next generation Debian package upload tool dput-ng-doc - next generation Debian package upload tool (documentation) python-dput - next generation Debian package upload tool (Python library) Closes: 783746 Changes: dput-ng (1.9) unstable; urgency=medium . * The "< jessfraz> ya!!!! jessie FTW" release . [ Sebastian Ramacher ] * Remove obsolete conffile /etc/dput.d/profiles/backports.json. Thanks to Jakub Wilk for spotting the obsolete conffile. . [ Luca Falavigna ] * Add support for Deb-o-Matic binnmu command. . [ Tristan Seligmann ] * Add jessie-backports to list of Debian codenames (closes: #783746). Checksums-Sha1: cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc 77e97879793f57ee93cb3d59d8c5d16361f75b37 82504 dput-ng_1.9.tar.xz Checksums-Sha256: 2489ed1a2e052ccc4c321719a2394ac4b6958209f05b1531305d2a52173aa5c1 1131 dput-ng_1.9.dsc 5ef401d9b67b009443f249aa79b952839c69a2b5437fbe957832599b655e1df0 82504 dput-ng_1.9.tar.xz Files: a74c9e3e9fe05d480d24cd43b225ee0c 1131 devel extra dput-ng_1.9.dsc 67e67e85a267c0c8110001b1a6cfc293 82504 devel extra dput-ng_1.9.tar.xz `)) // }}} changes, err := control.ParseChanges(reader, "") isok(t, err) assert(t, changes.Format == "1.8") assert(t, changes.ChangedBy == "Paul Tagliamonte ") assert(t, len(changes.Binaries) == 3) assert(t, changes.Binaries[2] == "dput-ng-doc") assert(t, len(changes.Closes) == 1) assert(t, changes.Closes[0] == "783746") } func TestChangesParseFiles(t *testing.T) { // Test Paragraph {{{ reader := bufio.NewReader(strings.NewReader(`Format: 1.8 Date: Wed, 29 Apr 2015 21:29:13 -0400 Source: dput-ng Binary: dput-ng python-dput dput-ng-doc Architecture: source Version: 1.9 Distribution: unstable Urgency: medium Maintainer: dput-ng Maintainers Changed-By: Paul Tagliamonte Description: dput-ng - next generation Debian package upload tool dput-ng-doc - next generation Debian package upload tool (documentation) python-dput - next generation Debian package upload tool (Python library) Closes: 783746 Changes: dput-ng (1.9) unstable; urgency=medium . * The "< jessfraz> ya!!!! jessie FTW" release . [ Sebastian Ramacher ] * Remove obsolete conffile /etc/dput.d/profiles/backports.json. Thanks to Jakub Wilk for spotting the obsolete conffile. . [ Luca Falavigna ] * Add support for Deb-o-Matic binnmu command. . [ Tristan Seligmann ] * Add jessie-backports to list of Debian codenames (closes: #783746). Checksums-Sha1: cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc 77e97879793f57ee93cb3d59d8c5d16361f75b37 82504 dput-ng_1.9.tar.xz Checksums-Sha256: 2489ed1a2e052ccc4c321719a2394ac4b6958209f05b1531305d2a52173aa5c1 1131 dput-ng_1.9.dsc 5ef401d9b67b009443f249aa79b952839c69a2b5437fbe957832599b655e1df0 82504 dput-ng_1.9.tar.xz Files: a74c9e3e9fe05d480d24cd43b225ee0c 1131 devel extra dput-ng_1.9.dsc 67e67e85a267c0c8110001b1a6cfc293 82504 devel extra dput-ng_1.9.tar.xz `)) // }}} changes, err := control.ParseChanges(reader, "") isok(t, err) assert(t, len(changes.ChecksumsSha1) == 2) assert(t, len(changes.ChecksumsSha256) == 2) assert(t, len(changes.Files) == 2) } // vim: foldmethod=marker control/control.go000066400000000000000000000114741257107724200145630ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "bufio" "fmt" "os" "path/filepath" "pault.ag/go/debian/dependency" ) // Encapsulation for a debian/control file, which is a series of RFC2822-like // blocks, starting with a Source control paragraph, and then a series of // Binary control paragraphs. // // The debian/control file contains the most vital (and version-independent) // information about the source package and about the binary packages it // creates. // // The first paragraph of the control file contains information about the source // package in general. The subsequent sets each describe a binary package that // the source tree builds. type Control struct { Filename string Source SourceParagraph Binaries []BinaryParagraph } // Encapsulation for a debian/control Source entry. This contains information // that will wind up in the .dsc and friends. Really quite fun! type SourceParagraph struct { Paragraph Maintainer string Uploaders []string `delim:","` Source string Priority string Section string Description string BuildDepends dependency.Dependency `control:"Build-Depends"` BuildDependsIndep dependency.Dependency `control:"Build-Depends-Indep"` BuildConflicts dependency.Dependency `control:"Build-Conflicts"` BuildConflictsIndep dependency.Dependency `control:"Build-Conflicts-Indep"` } // Return a list of all entities that are responsible for the package's // well being. The 0th element is always the package's Maintainer, // with any Uploaders following. func (s *SourceParagraph) Maintainers() []string { return append([]string{s.Maintainer}, s.Uploaders...) } // Encapsulation for a debian/control Binary control entry. This contains // information that will be eventually put lovingly into the .deb file // after it's built on a given Arch. type BinaryParagraph struct { Paragraph Architectures []dependency.Arch `control:"Architecture"` Package string Priority string Section string Essential bool Description string Depends dependency.Dependency Recommends dependency.Dependency Suggests dependency.Dependency Enhances dependency.Dependency PreDepends dependency.Dependency `control:"Pre-Depends"` Breaks dependency.Dependency Conflicts dependency.Dependency Replaces dependency.Dependency BuiltUsing dependency.Dependency `control:"Built-Using"` } func (para *Paragraph) getDependencyField(field string) (*dependency.Dependency, error) { if val, ok := para.Values[field]; ok { return dependency.Parse(val) } return nil, fmt.Errorf("Field `%s' Missing", field) } func (para *Paragraph) getOptionalDependencyField(field string) dependency.Dependency { val := para.Values[field] dep, err := dependency.Parse(val) if err != nil { return dependency.Dependency{} } return *dep } // Given a path on the filesystem, Parse the file off the disk and return // a pointer to a brand new Control struct, unless error is set to a value // other than nil. func ParseControlFile(path string) (ret *Control, err error) { path, err = filepath.Abs(path) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() ret, err = ParseControl(bufio.NewReader(f), path) if err != nil { return nil, err } return ret, nil } // Given a bufio.Reader, consume the Reader, and return a Control object // for use. func ParseControl(reader *bufio.Reader, path string) (*Control, error) { ret := Control{ Filename: path, Binaries: []BinaryParagraph{}, Source: SourceParagraph{}, } if err := Unmarshal(&ret.Source, reader); err != nil { return nil, err } if err := Unmarshal(&ret.Binaries, reader); err != nil { return nil, err } return &ret, nil } // vim: foldmethod=marker control/control_test.go000066400000000000000000000107411257107724200156160ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func TestDependencyControlParse(t *testing.T) { // Test Control {{{ reader := bufio.NewReader(strings.NewReader(`Source: fbautostart Section: misc Priority: optional Maintainer: Paul Tagliamonte Build-Depends: debhelper (>= 9) Standards-Version: 3.9.3 Homepage: https://launchpad.net/fbautostart Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Package: fbautostart Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. `)) // }}} c, err := control.ParseControl(reader, "") isok(t, err) assert(t, c != nil) assert(t, len(c.Binaries) == 1) assert(t, c.Source.Maintainer == "Paul Tagliamonte ") assert(t, c.Source.Source == "fbautostart") depends := c.Source.BuildDepends assert(t, len(c.Source.Maintainers()) == 1) assert(t, len(c.Source.Uploaders) == 0) assert(t, len(c.Source.BuildDepends.Relations) == 1) assert(t, len(c.Source.BuildDependsIndep.Relations) == 0) assert(t, len(c.Source.BuildConflicts.Relations) == 0) assert(t, len(c.Source.BuildConflictsIndep.Relations) == 0) assert(t, depends.Relations[0].Possibilities[0].Name == "debhelper") assert(t, depends.Relations[0].Possibilities[0].Version.Number == "9") assert(t, depends.Relations[0].Possibilities[0].Version.Operator == ">=") assert(t, len(c.Binaries[0].Architectures) == 1) assert(t, c.Binaries[0].Architectures[0].CPU == "any") assert(t, c.Binaries[0].Package == "fbautostart") } func TestMaintainersParse(t *testing.T) { // Test Control {{{ reader := bufio.NewReader(strings.NewReader(`Source: fbautostart Section: misc Priority: optional Maintainer: Paul Tagliamonte Uploaders: John Doe , Foo Bar Build-Depends: debhelper (>= 9) Standards-Version: 3.9.3 Homepage: https://launchpad.net/fbautostart Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Package: fbautostart Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, test Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. Package: fbautostart-foo Architecture: amd64 sparc kfreebsd-any Depends: ${shlibs:Depends}, ${misc:Depends}, test Description: XDG compliant autostarting app for Fluxbox The fbautostart app was designed to have little to no overhead, while still maintaining the needed functionality of launching applications according to the XDG spec. . This package contains support for GNOME and KDE. `)) // }}} c, err := control.ParseControl(reader, "") isok(t, err) assert(t, c != nil) assert(t, len(c.Binaries) == 2) assert(t, len(c.Source.Maintainers()) == 3) arches := c.Binaries[1].Architectures assert(t, len(arches) == 3) } // vim: foldmethod=marker control/decode.go000066400000000000000000000162571257107724200143320ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "bufio" "fmt" "io" "reflect" "strconv" "strings" ) func decodeCustomValues(incoming reflect.Value, incomingField reflect.StructField, data string) error { /* Incoming is a slice */ underlyingType := incoming.Type().Elem() var delim = " " if it := incomingField.Tag.Get("delim"); it != "" { delim = it } var strip = "" if it := incomingField.Tag.Get("strip"); it != "" { strip = it } if strip != "" { data = strings.Trim(data, strip) } for _, el := range strings.Split(data, delim) { if strip != "" { el = strings.Trim(el, strip) } targetValue := reflect.New(underlyingType) err := decodeValue(targetValue.Elem(), incomingField, el) if err != nil { return err } incoming.Set(reflect.Append(incoming, targetValue.Elem())) } return nil } func decodeCustomValue(incoming reflect.Value, incomingField reflect.StructField, data string) error { /* Right, so, we've got a type we don't know what to do with. We should * grab the method, or throw a shitfit. */ elem := incoming.Addr() if unmarshal, ok := elem.Interface().(Unmarshalable); ok { return unmarshal.UnmarshalControl(data) } return fmt.Errorf( "Type '%s' does not implement control.Unmarshalable", incomingField.Type.Name(), ) } func decodeValue(incoming reflect.Value, incomingField reflect.StructField, data string) error { switch incoming.Type().Kind() { case reflect.String: incoming.SetString(data) return nil case reflect.Int: if data == "" { incoming.SetInt(0) return nil } value, err := strconv.Atoi(data) if err != nil { return err } incoming.SetInt(int64(value)) return nil case reflect.Slice: return decodeCustomValues(incoming, incomingField, data) case reflect.Struct: return decodeCustomValue(incoming, incomingField, data) } return fmt.Errorf("Unknown type of field: %s", incoming.Type()) } func decodePointer(incoming reflect.Value, data Paragraph) error { if incoming.Type().Kind() == reflect.Ptr { /* If we have a pointer, let's follow it */ return decodePointer(incoming.Elem(), data) } for i := 0; i < incoming.NumField(); i++ { field := incoming.Field(i) fieldType := incoming.Type().Field(i) if field.Type().Kind() == reflect.Struct { err := decodePointer(field, data) if err != nil { return err } } paragraphKey := fieldType.Name if it := fieldType.Tag.Get("control"); it != "" { paragraphKey = it } if paragraphKey == "-" { continue } required := fieldType.Tag.Get("required") == "true" if val, ok := data.Values[paragraphKey]; ok { err := decodeValue(field, fieldType, val) if err != nil { return fmt.Errorf( "pault.ag/go/debian/control: failed to set %s: %s", fieldType.Name, err, ) } } else if required { return fmt.Errorf( "pault.ag/go/debian/control: required field %s missing", fieldType.Name, ) } } return nil } // Given a struct (or list of structs), read the io.Reader RFC822-alike // Debian control-file stream into the struct, unpacking keys into the // struct as needed. If a list of structs is given, unpack all RFC822 // Paragraphs into the structs. // // This code will attempt to unpack it into the struct based on the // literal name of the key, compared byte-for-byte. If this is not // OK, the struct tag `control:""` can be used to define the key to use // in the RFC822 stream. // // If you're unpacking into a list of strings, you have the option of defining // a string to split tokens on (`delim:", "`), and things to strip off each // element (`strip:"\n\r\t "`). // // If you're unpacking into a struct, the struct will be walked acording to // the rules above. If you wish to override how this writes to the nested // struct, objects that implement the Unmarshalable interface will be // Unmarshaled via that method call only. // // Structs that contain Paragraph as an Anonymous member will have that // member populated with the parsed RFC822 block, to allow access to the // .Values and .Order members. func Unmarshal(incoming interface{}, data io.Reader) error { /* Dispatch if incoming is a slice or not */ val := reflect.ValueOf(incoming) if val.Type().Kind() != reflect.Ptr { return fmt.Errorf("Ouchie! Please give me a pointer!") } switch val.Elem().Type().Kind() { case reflect.Struct: return unmarshalStruct(incoming, data) case reflect.Slice: return unmarshalSlice(incoming, data) default: return fmt.Errorf( "Ouchie! I don't know how to deal with a %s", val.Elem().Type().Name, ) } } // The Unmarshalable interface defines the interface that Unmarshal will use // to do custom unpacks into Structs. // // The argument passed in will be a string that contains the value of the // RFC822 key this object relates to. type Unmarshalable interface { UnmarshalControl(data string) error } func unmarshalSlice(incoming interface{}, data io.Reader) error { /* Good holy hot damn this code is ugly */ for { val := reflect.ValueOf(incoming) flavor := val.Elem().Type().Elem() targetValue := reflect.New(flavor) target := targetValue.Interface() err := unmarshalStruct(target, data) if err == io.EOF { break } else if err != nil { return err } val.Elem().Set(reflect.Append(val.Elem(), targetValue.Elem())) } return nil } func isParagraph(incoming reflect.Value) (int, bool) { paragraphType := reflect.TypeOf(Paragraph{}) val := incoming.Type() for i := 0; i < val.NumField(); i++ { field := val.Field(i) if field.Anonymous && field.Type == paragraphType { return i, true } } return -1, false } func unmarshalStruct(incoming interface{}, data io.Reader) error { reader := bufio.NewReader(data) para, err := ParseParagraph(reader) if err != nil { return err } if para == nil { return io.EOF } val := reflect.ValueOf(incoming).Elem() /* Before we dump it back, we should give the Paragraph back to * the object */ if index, is := isParagraph(val); is { /* If we're a Paragraph, let's go ahead and set the index. */ val.Field(index).Set(reflect.ValueOf(*para)) } return decodePointer(reflect.ValueOf(incoming), *para) } // vim: foldmethod=marker control/decode_test.go000066400000000000000000000052271257107724200153640ustar00rootroot00000000000000package control_test import ( "strings" "testing" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) type TestStruct struct { Value string `required:"true"` ValueTwo string `control:"Value-Two"` ValueThree []string Depends dependency.Dependency Version version.Version Arch dependency.Arch Arches []dependency.Arch Fnord struct { FooBar string `control:"Fnord-Foo-Bar"` } } func TestBasicUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Foo-Bar: baz `))) assert(t, foo.Value == "foo") } func TestBasicArrayUnmarshal(t *testing.T) { foo := []TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Foo-Bar: baz Value: Bar Value Baz `))) assert(t, foo[0].Value == "foo") } func TestTagUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Value-Two: baz `))) assert(t, foo.Value == "foo") assert(t, foo.ValueTwo == "baz") } func TestDependsUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Depends: foo, bar `))) assert(t, foo.Value == "foo") assert(t, foo.Depends.Relations[0].Possibilities[0].Name == "foo") /* Actually invalid below */ notok(t, control.Unmarshal(&foo, strings.NewReader(`Depends: foo (>= 1.0) (<= 1.0) `))) } func TestVersionUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Version: 1.0-1 `))) assert(t, foo.Value == "foo") assert(t, foo.Version.Revision == "1") } func TestArchUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Arch: amd64 `))) assert(t, foo.Value == "foo") assert(t, foo.Arch.CPU == "amd64") foo = TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Arches: amd64 sparc any `))) assert(t, foo.Value == "foo") assert(t, foo.Arches[0].CPU == "amd64") assert(t, foo.Arches[1].CPU == "sparc") assert(t, foo.Arches[2].CPU == "any") } func TestNestedUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo Fnord-Foo-Bar: Thing `))) assert(t, foo.Value == "foo") assert(t, foo.Fnord.FooBar == "Thing") } func TestListUnmarshal(t *testing.T) { foo := TestStruct{} isok(t, control.Unmarshal(&foo, strings.NewReader(`Value: foo ValueThree: foo bar baz `))) assert(t, foo.Value == "foo") assert(t, foo.ValueThree[0] == "foo") } func TestRequiredUnmarshal(t *testing.T) { foo := TestStruct{} notok(t, control.Unmarshal(&foo, strings.NewReader(`Foo-Bar: baz `))) } control/doc.go000066400000000000000000000000541257107724200136400ustar00rootroot00000000000000/* Parse control files */ package control control/dsc.go000066400000000000000000000133301257107724200136450ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "bufio" "os" "path/filepath" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" "pault.ag/go/topsort" ) // {{{ MD5 DSCFileHash type FileListDSCFileHash struct{ SHADebianFileHash } func (c *FileListDSCFileHash) UnmarshalControl(data string) error { return c.unmarshalControl("md5", data) } // }}} // A DSC is the ecapsulation of a Debian .dsc control file. This contains // information about the source package, and is general handy. // // The Debian source control file is generated by dpkg-source when it builds // the source archive, from other files in the source package. // When unpacking, it is checked against the files and directories in the // other parts of the source package. type DSC struct { Paragraph Filename string Format string Source string Binaries []string `control:"Binary" delim:","` Architectures []dependency.Arch `control:"Architecture"` Version version.Version Origin string Maintainer string Uploaders []string Homepage string StandardsVersion string `control:"Standards-Version"` BuildDepends dependency.Dependency `control:"Build-Depends"` ChecksumsSha1 []SHA1DebianFileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256DebianFileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` Files []FileListDSCFileHash `control:"Files" delim:"\n" strip:"\n\r\t "` /* TODO: Package-List */ } // Given a bunch of DSC objects, sort the packages topologically by // build order by looking at the relationship between the Build-Depends // field. func OrderDSCForBuild(dscs []DSC, arch dependency.Arch) ([]DSC, error) { sourceMapping := map[string]string{} network := topsort.NewNetwork() ret := []DSC{} /* * - Create binary -> source mapping. * - Error if two sources provide the same binary * - Create a node for each source * - Create an edge from the source -> source * - return sorted list of dsc files */ for _, dsc := range dscs { for _, binary := range dsc.Binaries { sourceMapping[binary] = dsc.Source } network.AddNode(dsc.Source, dsc) } for _, dsc := range dscs { concreteBuildDepends := dsc.BuildDepends.GetPossibilities(arch) for _, relation := range concreteBuildDepends { if val, ok := sourceMapping[relation.Name]; ok { err := network.AddEdge(val, dsc.Source) if err != nil { return nil, err } } } } nodes, err := network.Sort() if err != nil { return nil, err } for _, node := range nodes { ret = append(ret, node.Value.(DSC)) } return ret, nil } // Given a path on the filesystem, Parse the file off the disk and return // a pointer to a brand new DSC struct, unless error is set to a value // other than nil. func ParseDscFile(path string) (ret *DSC, err error) { path, err = filepath.Abs(path) if err != nil { return nil, err } f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() ret, err = ParseDsc(bufio.NewReader(f), path) if err != nil { return nil, err } return ret, nil } // Given a bufio.Reader, consume the Reader, and return a DSC object // for use. func ParseDsc(reader *bufio.Reader, path string) (*DSC, error) { ret := DSC{Filename: path} err := Unmarshal(&ret, reader) if err != nil { return nil, err } return &ret, nil } // Check to see if this .dsc contains any arch:all binary packages along // with any arch dependent packages. func (d *DSC) HasArchAll() bool { for _, arch := range d.Architectures { if arch.CPU == "all" && arch.OS == "all" && arch.ABI == "all" { return true } } return false } // Return a list of all entities that are responsible for the package's // well being. The 0th element is always the package's Maintainer, // with any Uploaders following. func (d *DSC) Maintainers() []string { return append([]string{d.Maintainer}, d.Uploaders...) } // Validate the attached files by checking the target Filesize and Checksum. func (d DSC) Validate() error { dscDir := filepath.Dir(d.Filename) for _, f := range d.ChecksumsSha1 { f.Filename = filepath.Join(dscDir, f.Filename) if err := f.Validate(); err != nil { return err } } for _, f := range d.ChecksumsSha256 { f.Filename = filepath.Join(dscDir, f.Filename) if err := f.Validate(); err != nil { return err } } for _, f := range d.Files { f.Filename = filepath.Join(dscDir, f.Filename) if err := f.Validate(); err != nil { return err } } // TODO also verify that all three lists contain the _same_ files (and the same filesizes) return nil } // vim: foldmethod=marker control/dsc_test.go000066400000000000000000000173461257107724200147170ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func TestDSCParse(t *testing.T) { // Test DSC {{{ reader := bufio.NewReader(strings.NewReader(`Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional arch=any Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz eaed7f053dce48d4ad4e442bbb0da73ea1181a26 2356 fbautostart_2.718281828-1.debian.tar.xz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz f7186d1bebde403527b5b3fd80406decaaf295366206667d5b402da962f0b772 2356 fbautostart_2.718281828-1.debian.tar.xz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz f58c0e0bf4d56461e776232484c07301 2356 fbautostart_2.718281828-1.debian.tar.xz `)) // }}} c, err := control.ParseDsc(reader, "") isok(t, err) assert(t, c != nil) assert(t, c.Format == "3.0 (quilt)") assert(t, c.Source == "fbautostart") assert(t, len(c.Maintainers()) == 1) assert(t, c.Maintainers()[0] == "Paul Tagliamonte ") assert(t, c.Maintainer == "Paul Tagliamonte ") assert(t, c.Version.Version == "2.718281828") assert(t, c.Version.Revision == "1") assert(t, c.StandardsVersion == "3.9.3") assert(t, c.Homepage == "https://launchpad.net/fbautostart") } func TestOpenPGPDSCParse(t *testing.T) { // Test OpenPGP DSC {{{ reader := bufio.NewReader(strings.NewReader(`-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz af4f1950dd8ed5bb7bd8952c8c00ffdd42eadb46 2396 fbautostart_2.718281828-1.debian.tar.gz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz 49f402ff3a72653e63542037be9f4da56e318e412d26d4154f9336fb88df3519 2396 fbautostart_2.718281828-1.debian.tar.gz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz 3b0e6dd201d5036f6d1b80f0ac4e1e7d 2396 fbautostart_2.718281828-1.debian.tar.gz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIcBAEBCAAGBQJP3sSmAAoJEJcyXdj5/dUG+0MQAMg7Naio+BQpssqth2p+4j7L Z87vdCd1fzRszRRptyBRbmTzTAzWPCNn15u5R+edCy7tyXi1TTty5QO/gU6p11mK wJ4EewJuagYcqZpiL6/6sGyDoLBdz5O0+TwmOp8M+mwKrHzU3kyyuKEtAyvhnsSv M6PaXU5hFaImwg+EZ1kJnzzxgM9UoJZ1Muib5mwS13FuH2uYU47t4h1vzgEsWDMm BaZoPJZhpmNksT+phPo1nlYaGg2M3bSyFrtFmWi9U+qih3l6hQ3N+6gNOTithccY lLf0PoEQiOu1ymbShsKtKkH2B7unkGIuyp3ElHt+XdRIhcoIrSu76RdLA2g8GLya eaoif0U/n3FbHRwE61uGiP6ykFRr6Lsih0Um61XLtGFHmtc/78+TknzoK2p98rDv vopNzdx0vrNOuqqJIQ+cjt9lCw493TxEo0uz+Ll0bhknLCByDgcA0AzciYoSpR+B 4KRQJwJAesFXhNueHCkYq8dsxSsIugKaEvBf7Z94bWam+AyVzakNMUF9ehp6KYhF jKL78j4v9XEo994zqhqTIuzA9xUoAXikzRrb97UbK5oqde4/G95/SotM50NgfQ2k 0DAgtrREBQGzSPSMCbWBJxbjEETsEBlxRkpceNwuE+kZ5HYkpPEzBWuugGsDThwx 5Q/HJMGB+4wV0I0IbEuI =RGXD -----END PGP SIGNATURE----- `)) // }}} c, err := control.ParseDsc(reader, "") isok(t, err) assert(t, c != nil) assert(t, c.Format == "3.0 (quilt)") assert(t, c.Source == "fbautostart") assert(t, len(c.Maintainers()) == 1) assert(t, c.Maintainers()[0] == "Paul Tagliamonte ") assert(t, c.Maintainer == "Paul Tagliamonte ") assert(t, c.Version.Version == "2.718281828") assert(t, c.Version.Revision == "1") assert(t, c.StandardsVersion == "3.9.3") assert(t, c.Homepage == "https://launchpad.net/fbautostart") } func TestDSCArchAllParse(t *testing.T) { // Test DSC (arch: any) {{{ reader := bufio.NewReader(strings.NewReader(`Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional arch=any Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz eaed7f053dce48d4ad4e442bbb0da73ea1181a26 2356 fbautostart_2.718281828-1.debian.tar.xz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz f7186d1bebde403527b5b3fd80406decaaf295366206667d5b402da962f0b772 2356 fbautostart_2.718281828-1.debian.tar.xz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz f58c0e0bf4d56461e776232484c07301 2356 fbautostart_2.718281828-1.debian.tar.xz `)) // }}} c, err := control.ParseDsc(reader, "") isok(t, err) assert(t, !c.HasArchAll()) // Test DSC (arch: any all) {{{ reader = bufio.NewReader(strings.NewReader(`Format: 3.0 (quilt) Source: fbautostart Binary: fbautostart Architecture: any all Version: 2.718281828-1 Maintainer: Paul Tagliamonte Homepage: https://launchpad.net/fbautostart Standards-Version: 3.9.3 Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Build-Depends: debhelper (>= 9) Package-List: fbautostart deb misc optional arch=any Checksums-Sha1: bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz eaed7f053dce48d4ad4e442bbb0da73ea1181a26 2356 fbautostart_2.718281828-1.debian.tar.xz Checksums-Sha256: bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz f7186d1bebde403527b5b3fd80406decaaf295366206667d5b402da962f0b772 2356 fbautostart_2.718281828-1.debian.tar.xz Files: 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz f58c0e0bf4d56461e776232484c07301 2356 fbautostart_2.718281828-1.debian.tar.xz `)) // }}} c, err = control.ParseDsc(reader, "") isok(t, err) assert(t, c.HasArchAll()) } // vim: foldmethod=marker control/filehash.go000066400000000000000000000066441257107724200146710ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "encoding/hex" "fmt" "hash" "io" "os" "strconv" "strings" "crypto/md5" "crypto/sha1" "crypto/sha256" ) func hashFile(path string, algo hash.Hash) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() if _, err := io.Copy(algo, f); err != nil { return "", err } return hex.EncodeToString(algo.Sum(nil)), nil } // A DebianFileHash is an entry as found in the Files, Checksum-Sha1, and // Checksum-Sha256 entry for the .dsc or .changes files. type DebianFileHash struct { // cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc Algorithm string Hash string Size int64 Filename string } // Validate the DebianFileHash by checking the target Filesize and Checksum. func (d DebianFileHash) Validate() error { var algo hash.Hash stat, err := os.Stat(d.Filename) if err != nil { return err } if size := stat.Size(); size != d.Size { return fmt.Errorf("Size mismatch: %d != %d", size, d.Size) } switch d.Algorithm { case "md5": algo = md5.New() case "sha1": algo = sha1.New() case "sha256": algo = sha256.New() default: return fmt.Errorf("Unknown algorithm: %s", d.Algorithm) } fileHash, err := hashFile(d.Filename, algo) if err != nil { return fmt.Errorf("Hash failed: %v", err) } if fileHash != d.Hash { return fmt.Errorf("Incorrect hash: %q != %q", fileHash, d.Hash) } return nil } // {{{ SHA DebianFileHash (both 1 and 256) type SHADebianFileHash struct { DebianFileHash } func (c *SHADebianFileHash) unmarshalControl(algorithm, data string) error { var err error c.Algorithm = algorithm vals := strings.Split(data, " ") if len(data) < 4 { return fmt.Errorf("Error: Unknown SHA Hash line: '%s'", data) } c.Hash = vals[0] c.Size, err = strconv.ParseInt(vals[1], 10, 64) if err != nil { return err } c.Filename = vals[2] return nil } // {{{ SHA1 DebianFileHash type SHA1DebianFileHash struct{ SHADebianFileHash } func (c *SHA1DebianFileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha1", data) } // }}} // {{{ SHA256 DebianFileHash type SHA256DebianFileHash struct{ SHADebianFileHash } func (c *SHA256DebianFileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha256", data) } // }}} // }}} // vim: foldmethod=marker control/index.go000066400000000000000000000112071257107724200142040ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "bufio" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) // The BinaryIndex struct represents the exported APT Binary package index // file, as seen on Debian (and Debian derived) mirrors, as well as the // cached version in /var/lib/apt/lists/. // // This can be used to examine Binary packages contained in the Archive, // to examine things like Built-Using, Depends, Tags or Binary packages // present on an Architecture. type BinaryIndex struct { Paragraph Package string Source string Version version.Version InstalledSize string `control:"Installed-Size"` Maintainer string Architecture dependency.Arch MultiArch string `control:"Multi-Arch"` Description string Homepage string DescriptionMD5 string `control:"Description-md5"` Tags []string `delim:", "` Section string Priority string Filename string Size string MD5sum string SHA1 string SHA256 string DebugBuildIds []string `control:"Build-Ids" delim:" "` } // Parse the Depends Dependency relation on this package. func (index *BinaryIndex) GetDepends() dependency.Dependency { return index.getOptionalDependencyField("Depends") } // Parse the Depends Suggests relation on this package. func (index *BinaryIndex) GetSuggests() dependency.Dependency { return index.getOptionalDependencyField("Suggests") } // Parse the Depends Breaks relation on this package. func (index *BinaryIndex) GetBreaks() dependency.Dependency { return index.getOptionalDependencyField("Breaks") } // Parse the Depends Replaces relation on this package. func (index *BinaryIndex) GetReplaces() dependency.Dependency { return index.getOptionalDependencyField("Replaces") } // Parse the Depends Pre-Depends relation on this package. func (index *BinaryIndex) GetPreDepends() dependency.Dependency { return index.getOptionalDependencyField("Pre-Depends") } // The SourceIndex struct represents the exported APT Source index // file, as seen on Debian (and Debian derived) mirrors, as well as the // cached version in /var/lib/apt/lists/. // // This can be used to examine Source packages, to examine things like // Binary packages built by Source packages, who maintains a package, // or where to find the VCS repo for that package. type SourceIndex struct { Paragraph Package string Binaries []string `control:"Binary" delim:","` Version version.Version Maintainer string Uploaders string `delim:","` Architecture []dependency.Arch StandardsVersion string Format string Files []string `delim:"\n"` VcsBrowser string `control:"Vcs-Browser"` VcsGit string `control:"Vcs-Git"` VcsSvn string `control:"Vcs-Svn"` VcsBzr string `control:"Vcs-Bzr"` Homepage string Directory string Priority string Section string } // Parse the Depends Build-Depends relation on this package. func (index *SourceIndex) GetBuildDepends() dependency.Dependency { return index.getOptionalDependencyField("Build-Depends") } // Given a reader, parse out a list of BinaryIndex structs. func ParseBinaryIndex(reader *bufio.Reader) (ret []BinaryIndex, err error) { ret = []BinaryIndex{} err = Unmarshal(&ret, reader) return ret, err } // Given a reader, parse out a list of SourceIndex structs. func ParseSourceIndex(reader *bufio.Reader) (ret []SourceIndex, err error) { ret = []SourceIndex{} err = Unmarshal(&ret, reader) return ret, err } // vim: foldmethod=marker control/index_test.go000066400000000000000000000203061257107724200152430ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control_test import ( "bufio" "strings" "testing" "pault.ag/go/debian/control" ) func TestSourceIndexParse(t *testing.T) { // Test Source Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: fbasics Binary: r-cran-fbasics Version: 3011.87-2 Maintainer: Dirk Eddelbuettel Build-Depends: debhelper (>= 7.0.0), r-base-dev (>= 3.2.0), cdbs, r-cran-mass, r-cran-timedate, r-cran-timeseries (>= 2100.84), r-cran-stabledist, xvfb, xauth, xfonts-base, r-cran-gss Architecture: any Standards-Version: 3.9.6 Format: 1.0 Files: 8bb6eda1e01be26c5446d21c64420e7f 1818 fbasics_3011.87-2.dsc f9f6e7f84bff1ce90cdc5890b9a3f6b5 932125 fbasics_3011.87.orig.tar.gz afc2e90feddb30baf96babfc767dff60 3818 fbasics_3011.87-2.diff.gz Checksums-Sha1: 45137d257a8bf2ed01b1809add5641bea7353072 1818 fbasics_3011.87-2.dsc cb0d17a055b7eaea72b14938e2948e603011b548 932125 fbasics_3011.87.orig.tar.gz 03c215003ddca5a9651d315902d2d3ee67e8c37a 3818 fbasics_3011.87-2.diff.gz Checksums-Sha256: 0a4f8cc793903e366a84379a651bf1a4542d50823b4bd4e038efcdb85a1af95e 1818 fbasics_3011.87-2.dsc f0a79bb3931cd145677c947d8cd87cf60869f604933e685e74225bb01ad992f4 932125 fbasics_3011.87.orig.tar.gz e087596fc0ac2bca6cf9ad531afc4329e75b2d7b26f0a0334be8dcb29d94f4ee 3818 fbasics_3011.87-2.diff.gz Homepage: http://www.Rmetrics.org Package-List: r-cran-fbasics deb gnu-r optional arch=any Directory: pool/main/f/fbasics Priority: source Section: gnu-r Package: fbautostart Binary: fbautostart Version: 2.718281828-1 Maintainer: Paul Tagliamonte Build-Depends: debhelper (>= 9) Architecture: any Standards-Version: 3.9.3 Format: 3.0 (quilt) Files: 9d610c30f96623cff07bd880e5cca12f 1899 fbautostart_2.718281828-1.dsc 06495f9b23b1c9b1bf35c2346cb48f63 92748 fbautostart_2.718281828.orig.tar.gz 3b0e6dd201d5036f6d1b80f0ac4e1e7d 2396 fbautostart_2.718281828-1.debian.tar.gz Vcs-Browser: http://git.debian.org/?p=collab-maint/fbautostart.git Vcs-Git: git://git.debian.org/collab-maint/fbautostart.git Checksums-Sha1: 3e0dcbe5549f47f35eb7f8960c0ada33bbc3f48b 1899 fbautostart_2.718281828-1.dsc bc36310c15edc9acf48f0a1daf548bcc6f861372 92748 fbautostart_2.718281828.orig.tar.gz af4f1950dd8ed5bb7bd8952c8c00ffdd42eadb46 2396 fbautostart_2.718281828-1.debian.tar.gz Checksums-Sha256: 0adda8e19e217dd2fa69d0842dcef0fa250bd428b1e43e78723d76909e5f51cc 1899 fbautostart_2.718281828-1.dsc bb2fdfd4a38505905222ee02d8236a594bdf6eaefca23462294cacda631745c1 92748 fbautostart_2.718281828.orig.tar.gz 49f402ff3a72653e63542037be9f4da56e318e412d26d4154f9336fb88df3519 2396 fbautostart_2.718281828-1.debian.tar.gz Homepage: https://launchpad.net/fbautostart Package-List: fbautostart deb misc optional Directory: pool/main/f/fbautostart Priority: source Section: misc `)) // }}} sources, err := control.ParseSourceIndex(reader) isok(t, err) assert(t, len(sources) == 2) fbautostart := sources[1] assert(t, fbautostart.Maintainer == "Paul Tagliamonte ") assert(t, fbautostart.VcsGit == "git://git.debian.org/collab-maint/fbautostart.git") } func TestBinaryIndexParse(t *testing.T) { // Test Binary Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: android-tools-fastboot Source: android-tools Version: 4.2.2+git20130529-5.1 Installed-Size: 184 Maintainer: Android tools Maintainer Architecture: amd64 Depends: libc6 (>= 2.14), libselinux1 (>= 2.0.65), zlib1g (>= 1:1.2.3.4) Description: Android Fastboot protocol CLI tool Homepage: http://developer.android.com/guide/developing/tools/adb.html Description-md5: 56b9309fa4fb2f92a313a815c7d7b5d3 Section: devel Priority: extra Filename: pool/main/a/android-tools/android-tools-fastboot_4.2.2+git20130529-5.1_amd64.deb Size: 56272 MD5sum: cd858b3257b250747822ebeea6c69f4a SHA1: 9d45825f07b2bc52edc787ba78966db0d4a48e69 SHA256: c094b7e53eb030957cdfab865f68c817d65bf6a1345b10d2982af38d042c3e84 Package: android-tools-fsutils Source: android-tools Version: 4.2.2+git20130529-5.1 Installed-Size: 504 Maintainer: Android tools Maintainer Architecture: amd64 Depends: python:any, libc6 (>= 2.14), libselinux1 (>= 2.0.65), zlib1g (>= 1:1.2.3.4) Description: Android ext4 utilities with sparse support Homepage: http://developer.android.com/guide/developing/tools/adb.html Description-md5: 23135bc652e7b302961741f9bcff8397 Section: devel Priority: extra Filename: pool/main/a/android-tools/android-tools-fsutils_4.2.2+git20130529-5.1_amd64.deb Size: 71900 MD5sum: 996732fc455acdcf4682de4f80a2dc95 SHA1: 5c2320913cc7cc46305390d8b3a7ef51f0a174ef SHA256: 270ad759d1fef9cedf894c42b5f559d7386aa1ec4de4cc3880eb44fe8c53c833 Package: androidsdk-ddms Source: androidsdk-tools Version: 22.2+git20130830~92d25d6-1 Installed-Size: 211 Maintainer: Debian Java Maintainers Architecture: all Depends: libandroidsdk-swtmenubar-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmlib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmuilib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-sdkstats-java (= 22.2+git20130830~92d25d6-1), eclipse-rcp Description: Graphical debugging tool for Android Homepage: http://developer.android.com/tools/help/index.html Description-md5: a2f559d2abf6ebb1d25bc3929d5aa2b0 Section: java Priority: extra Filename: pool/main/a/androidsdk-tools/androidsdk-ddms_22.2+git20130830~92d25d6-1_all.deb Size: 132048 MD5sum: fde05f3552457e91a415c99ab2a2a514 SHA1: 82b05c97163ccfbbb10a52a5514882412a13ee43 SHA256: fa53e4f50349c5c9b564b8dc1da86c503b0baf56ab95a4ef6e204b6f77bfe70c `)) // }}} sources, err := control.ParseBinaryIndex(reader) isok(t, err) assert(t, len(sources) == 3) assert(t, sources[2].Source == "androidsdk-tools") assert(t, sources[2].Filename == "pool/main/a/androidsdk-tools/androidsdk-ddms_22.2+git20130830~92d25d6-1_all.deb") } func TestBinaryIndexDependsParse(t *testing.T) { // Test Binary Index {{{ reader := bufio.NewReader(strings.NewReader(`Package: androidsdk-ddms Source: androidsdk-tools Version: 22.2+git20130830~92d25d6-1 Installed-Size: 211 Maintainer: Debian Java Maintainers Architecture: all Depends: libandroidsdk-swtmenubar-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmlib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-ddmuilib-java (= 22.2+git20130830~92d25d6-1), libandroidsdk-sdkstats-java (= 22.2+git20130830~92d25d6-1), eclipse-rcp Description: Graphical debugging tool for Android Homepage: http://developer.android.com/tools/help/index.html Description-md5: a2f559d2abf6ebb1d25bc3929d5aa2b0 Section: java Priority: extra Filename: pool/main/a/androidsdk-tools/androidsdk-ddms_22.2+git20130830~92d25d6-1_all.deb Size: 132048 MD5sum: fde05f3552457e91a415c99ab2a2a514 SHA1: 82b05c97163ccfbbb10a52a5514882412a13ee43 SHA256: fa53e4f50349c5c9b564b8dc1da86c503b0baf56ab95a4ef6e204b6f77bfe70c `)) // }}} sources, err := control.ParseBinaryIndex(reader) isok(t, err) assert(t, len(sources) == 1) ddms := sources[0] ddmsDepends := ddms.GetDepends() assert(t, ddmsDepends.GetAllPossibilities()[0].Version.Number == "22.2+git20130830~92d25d6-1") } // vim: foldmethod=marker control/parse.go000066400000000000000000000070111257107724200142050ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control import ( "bufio" "fmt" "io" "strings" "golang.org/x/crypto/openpgp/clearsign" ) // func encodeValue(value string) string { // ret := "" // // lines := strings.Split(value, "\n") // for _, line := range lines { // line = strings.Trim(line, " \t\r\n") // if line == "" { // line = "." // } // line = " " + line // ret = ret + line + "\n" // } // // return ret // } // A Paragraph is a block of RFC2822-like key value pairs. This struct contains // two methods to fetch values, a Map called Values, and a Slice called // Order, which maintains the ordering as defined in the RFC2822-like block type Paragraph struct { Values map[string]string Order []string } // func (para Paragraph) String() string { // ret := "" // // for _, key := range para.Order { // value := encodeValue(para.Values[key]) // ret = ret + fmt.Sprintf("%s:%s", key, value) // } // // return ret // } func ParseOpenPGPParagraph(reader *bufio.Reader) (*Paragraph, error) { els := "" for { line, err := reader.ReadString('\n') if err == io.EOF { break } els = els + line } block, _ := clearsign.Decode([]byte(els)) /** * XXX: With the block, we need to validate everything. * * We need to hit openpgp.CheckDetachedSignature with block and * a keyring. For now, it'll ignore all signature checking entirely. */ return ParseParagraph(bufio.NewReader(strings.NewReader(string(block.Bytes)))) } // Given a bufio.Reader, go through and return a Paragraph. func ParseParagraph(reader *bufio.Reader) (*Paragraph, error) { line, _ := reader.Peek(15) if string(line) == "-----BEGIN PGP " { return ParseOpenPGPParagraph(reader) } ret := &Paragraph{ Values: map[string]string{}, Order: []string{}, } var key = "" var value = "" var noop = " \n\r\t" for { line, err := reader.ReadString('\n') if err == io.EOF { if len(ret.Order) == 0 { return nil, nil } return ret, nil } if line == "\n" { break } if line[0] == ' ' { line = line[1:] ret.Values[key] += "\n" + strings.Trim(line, noop) continue } els := strings.SplitN(line, ":", 2) switch len(els) { case 2: key = strings.Trim(els[0], noop) value = strings.Trim(els[1], noop) ret.Values[key] = value ret.Order = append(ret.Order, key) continue default: return nil, fmt.Errorf("Line %q is not 'key: val'", line) } } return ret, nil } // vim: foldmethod=marker control/parse_test.go000066400000000000000000000101151257107724200152430ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package control_test import ( "bufio" "log" "strings" "testing" "pault.ag/go/debian/control" ) /* * */ func isok(t *testing.T, err error) { if err != nil { log.Printf("Error! Error is not nil! - %s\n", err) t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") t.FailNow() } } /* * */ func TestBasicControlParse(t *testing.T) { reader := bufio.NewReader(strings.NewReader(`Foo: bar `)) deb822, err := control.ParseParagraph(reader) isok(t, err) assert(t, deb822 != nil) assert(t, len(deb822.Order) == 1) assert(t, len(deb822.Order) == len(deb822.Values)) assert(t, deb822.Values["Foo"] == "bar") reader = bufio.NewReader(strings.NewReader(`Foo: bar`)) deb822, err = control.ParseParagraph(reader) assert(t, deb822 == nil) assert(t, err == nil) } func TestMultilineControlParse(t *testing.T) { reader := bufio.NewReader(strings.NewReader(`Foo: bar Bar-Baz: fnord and second line here `)) deb822, err := control.ParseParagraph(reader) isok(t, err) assert(t, deb822 != nil) assert(t, len(deb822.Order) == 2) assert(t, len(deb822.Order) == len(deb822.Values)) assert(t, deb822.Values["Foo"] == "bar") } func TestFunControlParse(t *testing.T) { reader := bufio.NewReader(strings.NewReader(`Foo: bar Bar-Baz: fnord:and:this:here and:not second:here line here `)) deb822, err := control.ParseParagraph(reader) isok(t, err) assert(t, deb822 != nil) assert(t, len(deb822.Order) == 2) assert(t, len(deb822.Order) == len(deb822.Values)) assert(t, deb822.Values["Foo"] == "bar") } func TestSingleParse(t *testing.T) { // Test Paragraph {{{ reader := bufio.NewReader(strings.NewReader(`Foo: bar Bar-Baz: fnord:and:this:here and:not second:here line here Hello: world But-not: me `)) // }}} deb822, err := control.ParseParagraph(reader) isok(t, err) assert(t, deb822 != nil) assert(t, len(deb822.Order) == 3) assert(t, len(deb822.Order) == len(deb822.Values)) assert(t, deb822.Values["Foo"] == "bar") if _, ok := deb822.Values["But-not"]; ok { /* In the case where we *have* this key; let's abort this guy * quickly. */ t.FailNow() } deb822, err = control.ParseParagraph(reader) isok(t, err) assert(t, deb822 != nil) assert(t, len(deb822.Order) == 1) assert(t, len(deb822.Order) == len(deb822.Values)) assert(t, deb822.Values["But-not"] == "me") } // func TestSeralize(t *testing.T) { // // Test Paragraph {{{ // para := `Foo: bar // Bar-Baz: fnord:and:this:here // and:not // second:here // line // here // Hello: world // // But-not: me // ` // // }}} // reader := bufio.NewReader(strings.NewReader(para)) // deb822, err := control.ParseParagraph(reader) // isok(t, err) // // out := deb822.String() // assert(t, out == `Foo: bar // Bar-Baz: fnord:and:this:here // and:not // second:here // line // here // Hello: world // `) // } // vim: foldmethod=marker dependency/000077500000000000000000000000001257107724200132035ustar00rootroot00000000000000dependency/arch.go000066400000000000000000000100661257107724200144520ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency import ( "errors" "strings" ) /* */ type Arch struct { ABI string OS string CPU string } func ParseArchitectures(arch string) ([]Arch, error) { ret := []Arch{} arches := strings.Split(arch, " ") for _, el := range arches { el := strings.Trim(el, " \t\n\r") if el == "" { continue } arch, err := ParseArch(el) if err != nil { return nil, err } ret = append(ret, *arch) } return ret, nil } func (arch *Arch) UnmarshalControl(data string) error { return parseArchInto(arch, data) } func ParseArch(arch string) (*Arch, error) { ret := &Arch{ ABI: "any", OS: "any", CPU: "any", } return ret, parseArchInto(ret, arch) } /* */ func parseArchInto(ret *Arch, arch string) error { /* May be in the following form: * `any` (implicitly any-any-any) * kfreebsd-any (implicitly any-kfreebsd-any) * kfreebsd-amd64 (implicitly any-kfreebsd-any) * bsd-openbsd-i386 */ flavors := strings.SplitN(arch, "-", 3) switch len(flavors) { case 1: flavor := flavors[0] /* OK, we've got a single guy like `any` or `amd64` */ switch flavor { case "all", "any": ret.ABI = flavor ret.OS = flavor ret.CPU = flavor default: /* right, so we've got something like `amd64`, which is implicitly * gnu-linux-amd64. Confusing, I know. */ ret.ABI = "gnu" ret.OS = "linux" ret.CPU = flavor } case 2: /* Right, this is something like kfreebsd-amd64, which is implicitly * gnu-kfreebsd-amd64 */ ret.OS = flavors[0] ret.CPU = flavors[1] case 3: /* This is something like bsd-openbsd-amd64 */ ret.ABI = flavors[0] ret.OS = flavors[1] ret.CPU = flavors[2] default: return errors.New("Hurm, no idea what happened here") } return nil } /* */ func (set *ArchSet) Matches(other *Arch) bool { /* If [!amd64 sparc] matches gnu-linux-any */ if len(set.Architectures) == 0 { /* We're not a thing. Always true. */ return true } not := set.Not for _, el := range set.Architectures { if el.Is(other) { /* For each arch; check if it matches. If it does, then * return true (unless we're negated) */ return !not } } /* Otherwise, let's return false (unless we're negated) */ return not } /* */ func (arch *Arch) IsWildcard() bool { if arch.CPU == "all" { return false } if arch.ABI == "any" || arch.OS == "any" || arch.CPU == "any" { return true } return false } /* */ func (arch *Arch) Is(other *Arch) bool { if arch.IsWildcard() && other.IsWildcard() { /* We can't compare wildcards to other wildcards. That's just * insanity. We always need a concrete arch. Not even going to try. */ return false } else if arch.IsWildcard() { /* OK, so we're a wildcard. Let's defer to the other * struct to deal with this */ return other.Is(arch) } if (arch.CPU == other.CPU || other.CPU == "any") && (arch.OS == other.OS || other.OS == "any") && (arch.ABI == other.ABI || other.ABI == "any") { return true } return false } // vim: foldmethod=marker dependency/arch_test.go000066400000000000000000000050461257107724200155130ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency_test import ( "testing" "pault.ag/go/debian/dependency" ) /* * */ func TestArchBasics(t *testing.T) { arch, err := dependency.ParseArch("amd64") isok(t, err) assert(t, arch.CPU == "amd64") assert(t, arch.ABI == "gnu") assert(t, arch.OS == "linux") } /* */ func TestArchCompareBasics(t *testing.T) { arch, err := dependency.ParseArch("amd64") isok(t, err) equivs := []string{ "gnu-linux-amd64", "linux-amd64", "linux-any", "any", "gnu-linux-any", } for _, el := range equivs { other, err := dependency.ParseArch(el) isok(t, err) assert(t, arch.Is(other)) assert(t, other.Is(arch)) } unequivs := []string{ "gnu-linux-all", "all", "gnuu-linux-amd64", "gnu-linuxx-amd64", "gnu-linux-amd644", } for _, el := range unequivs { other, err := dependency.ParseArch(el) isok(t, err) assert(t, !arch.Is(other)) assert(t, !other.Is(arch)) } } /* */ func TestArchSetCompare(t *testing.T) { dep, err := dependency.Parse("foo [amd64], bar [!sparc]") isok(t, err) iAm, err := dependency.ParseArch("amd64") isok(t, err) fooArch := dep.Relations[0].Possibilities[0].Architectures barArch := dep.Relations[1].Possibilities[0].Architectures assert(t, fooArch.Matches(iAm)) assert(t, barArch.Matches(iAm)) iAmNot, err := dependency.ParseArch("armhf") isok(t, err) assert(t, !fooArch.Matches(iAmNot)) assert(t, barArch.Matches(iAmNot)) } // vim: foldmethod=marker dependency/dependency.go000066400000000000000000000041701257107724200156520ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency // func (dep *Dependency) GetPossibilities(arch Arch) []Possibility { possies := []Possibility{} for _, relation := range dep.Relations { for _, possibility := range relation.Possibilities { if possibility.Substvar { continue } if possibility.Architectures.Matches(&arch) { possies = append(possies, possibility) break } } } return possies } // func (dep *Dependency) GetAllPossibilities() []Possibility { possies := []Possibility{} for _, relation := range dep.Relations { for _, possibility := range relation.Possibilities { if possibility.Substvar { continue } possies = append(possies, possibility) } } return possies } // func (dep *Dependency) GetSubstvars() []Possibility { possies := []Possibility{} for _, relation := range dep.Relations { for _, possibility := range relation.Possibilities { if possibility.Substvar { possies = append(possies, possibility) } } } return possies } // vim: foldmethod=marker dependency/dependency_test.go000066400000000000000000000047761257107724200167250ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency_test import ( "testing" "pault.ag/go/debian/dependency" ) /* * */ func TestSliceParse(t *testing.T) { dep, err := dependency.Parse("foo, bar | baz") isok(t, err) arch, err := dependency.ParseArch("amd64") isok(t, err) els := dep.GetPossibilities(*arch) assert(t, len(els) == 2) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "bar") } func TestArchSliceParse(t *testing.T) { dep, err := dependency.Parse("foo, bar [sparc] | baz") isok(t, err) arch, err := dependency.ParseArch("amd64") isok(t, err) els := dep.GetPossibilities(*arch) assert(t, len(els) == 2) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "baz") } func TestSliceAllParse(t *testing.T) { dep, err := dependency.Parse("foo, bar | baz") isok(t, err) els := dep.GetAllPossibilities() assert(t, len(els) == 3) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "bar") assert(t, els[2].Name == "baz") } func TestSliceSubParse(t *testing.T) { dep, err := dependency.Parse("${foo:Depends}, foo, bar | baz, ${bar:Depends}") isok(t, err) els := dep.GetAllPossibilities() assert(t, len(els) == 3) assert(t, els[0].Name == "foo") assert(t, els[1].Name == "bar") assert(t, els[2].Name == "baz") els = dep.GetSubstvars() assert(t, len(els) == 2) assert(t, els[0].Name == "foo:Depends") assert(t, els[1].Name == "bar:Depends") } // vim: foldmethod=marker dependency/doc.go000066400000000000000000000010531257107724200142760ustar00rootroot00000000000000/* The Dependency module provides an interface to parse and inspect Debian Dependency relationships. Dependency | foo, bar (>= 1.0) [amd64] | baz -> Relations | -> Relation bar (>= 1.0) [amd64] | baz -> Possibilities | -> Possibility bar (>= 1.0) [amd64] | Name | -> Name bar | Version | -> Version (>= 1.0) | Architectures | -> Arch amd64 | Stages | */ package dependency dependency/models.go000066400000000000000000000060651257107724200150240ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency // Possibilities {{{ // Arch models an architecture dependency restriction, commonly used to // restrict the relation to one some architectures. This is also usually // used in a string of many possibilities. type ArchSet struct { Not bool Architectures []Arch } // VersionRelation models a version restriction on a possibility, such as // greater than version 1.0, or less than 2.0. The values that are valid // in the Operator field are defined by section 7.1 of Debian policy. // // The relations allowed are <<, <=, =, >= and >> for strictly earlier, // earlier or equal, exactly equal, later or equal and strictly later, // respectively. // type VersionRelation struct { Number string Operator string } type Stage struct { Not bool Name string } type StageSet struct { Stages []Stage } // Possibility models a concrete Possibility that may be satisfied in order // to satisfy the Dependency Relation. Given the Dependency line: // // Depends: foo, bar | baz // // All of foo, bar and baz are Possibilities. Possibilities may come with // further restrictions, such as restrictions on Version, Architecture, or // Build Stage. // type Possibility struct { Name string Arch *Arch Architectures *ArchSet Stages *StageSet Version *VersionRelation Substvar bool } // }}} // A Relation is a set of Possibilities that must be satisfied. Given the // Dependency line: // // Depends: foo, bar | baz // // There are two Relations, one composed of foo, and another composed of // bar and baz. type Relation struct { Possibilities []Possibility } // A Dependency is the top level type that models a full Dependency relation. type Dependency struct { Relations []Relation } func (dep *Dependency) UnmarshalControl(data string) error { ibuf := Input{Index: 0, Data: data} dep.Relations = []Relation{} err := parseDependency(&ibuf, dep) return err } // vim: foldmethod=marker dependency/parser.go000066400000000000000000000202261257107724200150300ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency import ( "errors" "fmt" ) // Parse a string into a Dependency object. The input should look something // like "foo, bar | baz". func Parse(in string) (*Dependency, error) { ibuf := Input{Index: 0, Data: in} dep := &Dependency{Relations: []Relation{}} err := parseDependency(&ibuf, dep) if err != nil { return nil, err } return dep, nil } // Input Model {{{ /* */ type Input struct { Data string Index int } /* */ func (i *Input) Peek() byte { if (i.Index) >= len(i.Data) { return 0 } return i.Data[i.Index] } /* */ func (i *Input) Next() byte { chr := i.Peek() i.Index++ return chr } // }}} // Parse Helpers {{{ /* */ func eatWhitespace(input *Input) { for { peek := input.Peek() switch peek { case '\r', '\n', ' ', '\t': input.Next() continue } break } } // }}} // Dependency Parser {{{ /* */ func parseDependency(input *Input, ret *Dependency) error { eatWhitespace(input) for { peek := input.Peek() switch peek { case 0: /* EOF, yay */ return nil case ',': /* Next relation set */ input.Next() eatWhitespace(input) continue } err := parseRelation(input, ret) if err != nil { return err } } } // }}} // Relation Parser {{{ /* */ func parseRelation(input *Input, dependency *Dependency) error { eatWhitespace(input) /* Clean out leading whitespace */ ret := &Relation{Possibilities: []Possibility{}} for { peek := input.Peek() switch peek { case 0, ',': /* EOF, or done with this relation! yay */ dependency.Relations = append(dependency.Relations, *ret) return nil case '|': /* Next Possi */ input.Next() eatWhitespace(input) continue } err := parsePossibility(input, ret) if err != nil { return err } } } // }}} // Possibility Parser {{{ /* */ func parsePossibility(input *Input, relation *Relation) error { eatWhitespace(input) /* Clean out leading whitespace */ peek := input.Peek() if peek == '$' { /* OK, nice. So, we've got a substvar. Let's eat it. */ return parseSubstvar(input, relation) } /* Otherwise, let's punt and build it up ourselves. */ ret := &Possibility{ Name: "", Version: nil, Architectures: &ArchSet{Architectures: []Arch{}}, Stages: &StageSet{Stages: []Stage{}}, Substvar: false, } for { peek := input.Peek() switch peek { case ':': err := parseMultiarch(input, ret) if err != nil { return err } continue case ' ': err := parsePossibilityControllers(input, ret) if err != nil { return err } continue case ',', '|', 0: /* I'm out! */ if ret.Name == "" { return errors.New("No package name in Possibility") } relation.Possibilities = append(relation.Possibilities, *ret) return nil } /* Not a control, let's append */ ret.Name += string(input.Next()) } } func parseSubstvar(input *Input, relation *Relation) error { eatWhitespace(input) input.Next() /* Assert ch == '$' */ input.Next() /* Assert ch == '{' */ ret := &Possibility{ Name: "", Version: nil, Substvar: true, } for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before substvar finished") case '}': input.Next() relation.Possibilities = append(relation.Possibilities, *ret) return nil } ret.Name += string(input.Next()) } } /* */ func parseMultiarch(input *Input, possi *Possibility) error { input.Next() /* mandated to be a : */ name := "" for { peek := input.Peek() switch peek { case ',', '|', 0, ' ', '(', '[', '<': arch, err := ParseArch(name) if err != nil { return err } possi.Arch = arch return nil default: name += string(input.Next()) } } return nil } /* */ func parsePossibilityControllers(input *Input, possi *Possibility) error { for { eatWhitespace(input) /* Clean out leading whitespace */ peek := input.Peek() switch peek { case ',', '|', 0: return nil case '(': if possi.Version != nil { return errors.New( "Only one Version relation per Possibility, please!", ) } err := parsePossibilityVersion(input, possi) if err != nil { return err } continue case '[': if len(possi.Architectures.Architectures) != 0 { return errors.New( "Only one Arch relation per Possibility, please!", ) } err := parsePossibilityArchs(input, possi) if err != nil { return err } continue } return fmt.Errorf("Trailing garbage in a Possibility: %c", peek) } return nil } /* */ func parsePossibilityVersion(input *Input, possi *Possibility) error { eatWhitespace(input) input.Next() /* mandated to be ( */ // assert ch == '(' version := VersionRelation{} err := parsePossibilityOperator(input, &version) if err != nil { return err } err = parsePossibilityNumber(input, &version) if err != nil { return err } input.Next() /* OK, let's tidy up */ // assert ch == ')' possi.Version = &version return nil } /* */ func parsePossibilityOperator(input *Input, version *VersionRelation) error { eatWhitespace(input) leader := input.Next() /* may be 0 */ if leader == '=' { /* Great, good enough. */ version.Operator = "=" return nil } /* This is always one of: * >=, <=, <<, >> */ secondary := input.Next() if leader == 0 || secondary == 0 { return errors.New("Oh no. Reached EOF before Operator finished") } operator := string([]rune{rune(leader), rune(secondary)}) switch operator { case ">=", "<=", "<<", ">>": version.Operator = operator return nil } return fmt.Errorf( "Unknown Operator in Possibility Version modifier: %s", operator, ) } /* */ func parsePossibilityNumber(input *Input, version *VersionRelation) error { eatWhitespace(input) for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Number finished") case ')': return nil } version.Number += string(input.Next()) } } /* */ func parsePossibilityArchs(input *Input, possi *Possibility) error { eatWhitespace(input) input.Next() /* Assert ch == '[' */ /* So the first line of each guy can be a not (!), so let's check for * that with a Peek :) */ peek := input.Peek() if peek == '!' { input.Next() /* Omnom */ possi.Architectures.Not = true } for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Arch list finished") case ']': input.Next() return nil } err := parsePossibilityArch(input, possi) if err != nil { return err } } } /* */ func parsePossibilityArch(input *Input, possi *Possibility) error { eatWhitespace(input) arch := "" for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Arch list finished") case '!': return errors.New("You can only negate whole blocks :(") case ']', ' ': /* Let our parent deal with both of these */ archObj, err := ParseArch(arch) if err != nil { return err } possi.Architectures.Architectures = append( possi.Architectures.Architectures, *archObj, ) return nil } arch += string(input.Next()) } } // }}} // vim: foldmethod=marker dependency/parser_test.go000066400000000000000000000135231257107724200160710ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency_test import ( "log" "testing" "pault.ag/go/debian/dependency" ) /* * */ func isok(t *testing.T, err error) { if err != nil { log.Printf("Error! Error is not nil! %s\n", err) t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") t.FailNow() } } /* * */ func TestSingleParse(t *testing.T) { dep, err := dependency.Parse("foo") isok(t, err) if dep.Relations[0].Possibilities[0].Name != "foo" { t.Fail() } } func TestMultiarchParse(t *testing.T) { dep, err := dependency.Parse("foo:amd64") isok(t, err) assert(t, dep.Relations[0].Possibilities[0].Name == "foo") assert(t, dep.Relations[0].Possibilities[0].Arch.CPU == "amd64") dep, err = dependency.Parse("foo:amd64 [amd64 sparc]") isok(t, err) assert(t, dep.Relations[0].Possibilities[0].Name == "foo") assert(t, dep.Relations[0].Possibilities[0].Arch.CPU == "amd64") assert(t, dep.Relations[0].Possibilities[0].Architectures.Architectures[0].CPU == "amd64") assert(t, dep.Relations[0].Possibilities[0].Architectures.Architectures[1].CPU == "sparc") } func TestTwoRelations(t *testing.T) { dep, err := dependency.Parse("foo, bar") isok(t, err) assert(t, len(dep.Relations) == 2) } func TestTwoPossibilities(t *testing.T) { dep, err := dependency.Parse("foo, bar | baz") isok(t, err) assert(t, len(dep.Relations) == 2) possi := dep.Relations[1].Possibilities assert(t, len(possi) == 2) assert(t, possi[0].Name == "bar") assert(t, possi[1].Name == "baz") } func TestVersioning(t *testing.T) { dep, err := dependency.Parse("foo (>= 1.0)") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] version := possi.Version assert(t, version.Operator == ">=") assert(t, version.Number == "1.0") } func TestSingleArch(t *testing.T) { dep, err := dependency.Parse("foo [arch]") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, len(arches) == 1) assert(t, arches[0].CPU == "arch") } func TestSingleNotArch(t *testing.T) { dep, err := dependency.Parse("foo [!arch]") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, len(arches) == 1) assert(t, arches[0].CPU == "arch") assert(t, possi.Architectures.Not) } func TestDoubleInvalidNotArch(t *testing.T) { _, err := dependency.Parse("foo [arch !foo]") notok(t, err) _, err = dependency.Parse("foo [arch!foo]") notok(t, err) } func TestDoubleArch(t *testing.T) { dep, err := dependency.Parse("foo [arch arch2]") isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, len(arches) == 2) assert(t, arches[0].CPU == "arch") assert(t, arches[1].CPU == "arch2") } func TestVersioningOperators(t *testing.T) { opers := map[string]string{ ">=": "foo (>= 1.0)", "<=": "foo (<= 1.0)", ">>": "foo (>> 1.0)", "<<": "foo (<< 1.0)", "=": "foo (= 1.0)", } for operator, vstring := range opers { dep, err := dependency.Parse(vstring) isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] version := possi.Version assert(t, version.Operator == operator) assert(t, version.Number == "1.0") } } func TestNoComma(t *testing.T) { _, err := dependency.Parse("foo bar") notok(t, err) } func TestTwoVersions(t *testing.T) { _, err := dependency.Parse("foo (>= 1.0) (<= 2.0)") notok(t, err) } func TestTwoArchitectures(t *testing.T) { _, err := dependency.Parse("foo [amd64] [sparc]") notok(t, err) } func TestBadVersion(t *testing.T) { vers := []string{ "foo (>= 1.0", "foo (>= 1", "foo (>= ", "foo (>=", "foo (>", "foo (", } for _, ver := range vers { _, err := dependency.Parse(ver) notok(t, err) } } func TestBadArch(t *testing.T) { vers := []string{ "foo [amd64", "foo [amd6", "foo [amd", "foo [am", "foo [a", "foo [", } for _, ver := range vers { _, err := dependency.Parse(ver) notok(t, err) } } func TestSingleSubstvar(t *testing.T) { dep, err := dependency.Parse("${foo:Depends}, bar, baz") isok(t, err) assert(t, len(dep.Relations) == 3) assert(t, dep.Relations[0].Possibilities[0].Name == "foo:Depends") assert(t, dep.Relations[1].Possibilities[0].Name == "bar") assert(t, dep.Relations[2].Possibilities[0].Name == "baz") assert(t, dep.Relations[0].Possibilities[0].Substvar) assert(t, !dep.Relations[1].Possibilities[0].Substvar) assert(t, !dep.Relations[2].Possibilities[0].Substvar) } // vim: foldmethod=marker dependency/string.go000066400000000000000000000061041257107724200150410ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency import ( "strings" ) func (a Arch) String() string { /* ABI-OS-CPU -- gnu-linux-amd64 */ els := []string{} if a.ABI != "any" && a.ABI != "all" && a.ABI != "gnu" { els = append(els, a.ABI) } if a.OS != "any" && a.OS != "all" && a.OS != "linux" { els = append(els, a.OS) } els = append(els, a.CPU) return strings.Join(els, "-") } func (set ArchSet) String() string { if len(set.Architectures) == 0 { return "" } arches := []string{} for _, arch := range set.Architectures { arches = append(arches, arch.String()) } not := "" if set.Not { not = "!" } return "[" + not + strings.Join(arches, " ") + "]" } func (version VersionRelation) String() string { return "(" + version.Operator + " " + version.Number + ")" } func (stage Stage) String() string { if stage.Not { return "!" + stage.Name } return stage.Name } func (stageSet StageSet) String() string { if len(stageSet.Stages) == 0 { return "" } stages := []string{} for _, stage := range stageSet.Stages { stages = append(stages, stage.String()) } return "<" + strings.Join(stages, " ") + ">" } func (possi Possibility) String() string { str := possi.Name if possi.Arch != nil { str += ":" + possi.Arch.String() } if possi.Architectures != nil { if arch := possi.Architectures.String(); arch != "" { str += " " + arch } } if possi.Version != nil { str += " " + possi.Version.String() } if possi.Stages != nil { if stages := possi.Stages.String(); stages != "" { str += " " + stages } } return str } func (relation Relation) String() string { possis := []string{} for _, possi := range relation.Possibilities { possis = append(possis, possi.String()) } return strings.Join(possis, " | ") } func (dependency Dependency) String() string { relations := []string{} for _, relation := range dependency.Relations { relations = append(relations, relation.String()) } return strings.Join(relations, ", ") } // vim: foldmethod=marker dependency/string_test.go000066400000000000000000000031261257107724200161010ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2015 * * 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 restriction, including without limitation 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 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. }}} */ package dependency_test import ( "testing" "pault.ag/go/debian/dependency" ) func TestArchString(t *testing.T) { equivs := map[string]string{ "all": "all", "any": "all", "amd64": "amd64", "gnu-linux-amd64": "amd64", "bsd-windows-i386": "bsd-windows-i386", } for _, el := range equivs { arch, err := dependency.ParseArch(el) isok(t, err) assert(t, arch.String() == equivs[el]) } } // vim: foldmethod=marker internal/000077500000000000000000000000001257107724200127015ustar00rootroot00000000000000internal/copy.go000066400000000000000000000005221257107724200142010ustar00rootroot00000000000000package internal import ( "io" "os" ) func Copy(source, dest string) error { in, err := os.Open(source) if err != nil { return err } defer in.Close() out, err := os.Create(dest) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) cerr := out.Close() if err != nil { return err } return cerr } version/000077500000000000000000000000001257107724200125525ustar00rootroot00000000000000version/version.go000066400000000000000000000134301257107724200145670ustar00rootroot00000000000000/* {{{ Copyright © 2012 Michael Stapelberg and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Michael Stapelberg nor the * names of contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. }}} */ // version is a pure-go implementation of dpkg version string functions // (parsing, comparison) which is compatible with dpkg(1). package version import ( "fmt" "strconv" "strings" "unicode" ) // Slice is a slice versions, satisfying sort.Interface type Slice []Version func (a Slice) Len() int { return len(a) } func (a Slice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a Slice) Less(i, j int) bool { return Compare(a[i], a[j]) < 0 } type Version struct { Epoch uint Version string Revision string } func (v *Version) IsNative() bool { return len(v.Revision) == 0 } func (version *Version) UnmarshalControl(data string) error { return parseInto(version, data) } func (v Version) String() string { var result string if v.Epoch > 0 { result = strconv.Itoa(int(v.Epoch)) + ":" + v.Version } else { result = v.Version } if len(v.Revision) > 0 { result += "-" + v.Revision } return result } func cisdigit(r rune) bool { return r >= '0' && r <= '9' } func cisalpha(r rune) bool { return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') } func order(r rune) int { if cisdigit(r) { return 0 } if cisalpha(r) { return int(r) } if r == '~' { return -1 } if int(r) != 0 { return int(r) + 256 } return 0 } func verrevcmp(a string, b string) int { i := 0 j := 0 for i < len(a) || j < len(b) { var first_diff int for (i < len(a) && !cisdigit(rune(a[i]))) || (j < len(b) && !cisdigit(rune(b[j]))) { ac := 0 if i < len(a) { ac = order(rune(a[i])) } bc := 0 if j < len(b) { bc = order(rune(b[j])) } if ac != bc { return ac - bc } i++ j++ } for i < len(a) && a[i] == '0' { i++ } for j < len(b) && b[j] == '0' { j++ } for i < len(a) && cisdigit(rune(a[i])) && j < len(b) && cisdigit(rune(b[j])) { if first_diff == 0 { first_diff = int(rune(a[i]) - rune(b[j])) } i++ j++ } if i < len(a) && cisdigit(rune(a[i])) { return 1 } if j < len(b) && cisdigit(rune(b[j])) { return -1 } if first_diff != 0 { return first_diff } } return 0 } // Compare compares the two provided Debian versions. It returns 0 if a and b // are equal, a value < 0 if a is smaller than b and a value > 0 if a is // greater than b. func Compare(a Version, b Version) int { if a.Epoch > b.Epoch { return 1 } if a.Epoch < b.Epoch { return -1 } rc := verrevcmp(a.Version, b.Version) if rc != 0 { return rc } return verrevcmp(a.Revision, b.Revision) } // Parse returns a Version struct filled with the epoch, version and revision // specified in input. It verifies the version string as a whole, just like // dpkg(1), and even returns roughly the same error messages. func Parse(input string) (Version, error) { result := Version{} return result, parseInto(&result, input) } func parseInto(result *Version, input string) error { trimmed := strings.TrimSpace(input) if trimmed == "" { return fmt.Errorf("version string is empty") } if strings.IndexFunc(trimmed, unicode.IsSpace) != -1 { return fmt.Errorf("version string has embedded spaces") } colon := strings.Index(trimmed, ":") if colon != -1 { epoch, err := strconv.ParseInt(trimmed[:colon], 10, 64) if err != nil { return fmt.Errorf("epoch: %v", err) } if epoch < 0 { return fmt.Errorf("epoch in version is negative") } result.Epoch = uint(epoch) } result.Version = trimmed[colon+1:] if len(result.Version) == 0 { return fmt.Errorf("nothing after colon in version number") } if hyphen := strings.LastIndex(result.Version, "-"); hyphen != -1 { result.Revision = result.Version[hyphen+1:] result.Version = result.Version[:hyphen] } if len(result.Version) > 0 && !unicode.IsDigit(rune(result.Version[0])) { return fmt.Errorf("version number does not start with digit") } if strings.IndexFunc(result.Version, func(c rune) bool { return !cisdigit(c) && !cisalpha(c) && c != '.' && c != '-' && c != '+' && c != '~' && c != ':' }) != -1 { return fmt.Errorf("invalid character in version number") } if strings.IndexFunc(result.Revision, func(c rune) bool { return !cisdigit(c) && !cisalpha(c) && c != '.' && c != '+' && c != '~' }) != -1 { return fmt.Errorf("invalid character in revision number") } return nil } // vim:ts=4:sw=4:noexpandtab foldmethod=marker version/version_test.go000066400000000000000000000233611257107724200156320ustar00rootroot00000000000000/* {{{ Copyright © 2012 Michael Stapelberg and contributors * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Michael Stapelberg nor the * names of contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Michael Stapelberg ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL Michael Stapelberg BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. }}} */ package version import ( "testing" ) // Abbreviation for creating a new Version object. func v(epoch uint, version string, revision string) Version { return Version{Epoch: epoch, Version: version, Revision: revision} } func TestEpoch(t *testing.T) { if Compare(Version{Epoch: 1}, Version{Epoch: 2}) == 0 { t.Errorf("epoch=1, epoch=2") } if a, b := v(0, "1", "1"), v(0, "2", "1"); Compare(a, b) == 0 { t.Errorf("a, b") } if a, b := v(0, "1", "1"), v(0, "1", "2"); Compare(a, b) == 0 { t.Errorf("a, b") } } func TestEquality(t *testing.T) { if a, b := v(0, "0", "0"), v(0, "0", "0"); Compare(a, b) != 0 { t.Errorf("a, b") } if a, b := v(0, "0", "00"), v(0, "00", "0"); Compare(a, b) != 0 { t.Errorf("a, b") } if a, b := v(1, "2", "3"), v(1, "2", "3"); Compare(a, b) != 0 { t.Errorf("a, b") } } func TestEpochDifference(t *testing.T) { a := v(0, "0", "0") b := v(1, "0", "0") if Compare(a, b) >= 0 { t.Errorf("a, b") } if Compare(b, a) <= 0 { t.Errorf("a, b") } } func TestVersionDifference(t *testing.T) { a := v(0, "a", "0") b := v(0, "b", "0") if Compare(a, b) >= 0 { t.Errorf("a, b") } if Compare(b, a) <= 0 { t.Errorf("a, b") } } func TestRevisionDifference(t *testing.T) { a := v(0, "0", "a") b := v(0, "0", "b") if Compare(a, b) >= 0 { t.Errorf("a, b") } if Compare(b, a) <= 0 { t.Errorf("a, b") } } func TestCompareCodesearch(t *testing.T) { a := v(0, "1.8.6", "2") b := v(0, "1.8.6", "2.1") if Compare(a, b) >= 0 { t.Errorf("a, b") } } func TestParseZeroVersions(t *testing.T) { var a Version var err error b := v(0, "0", "") if a, err = Parse("0"); err != nil { t.Errorf("Parsing %q failed: %v", "0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse("0:0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse("0:0-"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0", "0") if a, err = Parse("0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0.0", "0.0") if a, err = Parse("0:0.0-0.0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0.0-0.0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseEpochedVersions(t *testing.T) { var a Version var err error b := v(1, "0", "") if a, err = Parse("1:0"); err != nil { t.Errorf("Parsing %q failed: %v", "1:0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(5, "1", "") if a, err = Parse("5:1"); err != nil { t.Errorf("Parsing %q failed: %v", "5:1", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseMultipleHyphens(t *testing.T) { var a Version var err error b := v(0, "0-0", "0") if a, err = Parse("0:0-0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0-0-0", "0") if a, err = Parse("0:0-0-0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0-0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseMultipleColons(t *testing.T) { var a Version var err error b := v(0, "0:0", "0") if a, err = Parse("0:0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0:0:0", "0") if a, err = Parse("0:0:0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0:0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseMultipleHyphensAndColons(t *testing.T) { var a Version var err error b := v(0, "0:0-0", "0") if a, err = Parse("0:0:0-0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0:0-0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } b = v(0, "0-0:0", "0") if a, err = Parse("0:0-0:0-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-0:0-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseValidUpstreamVersionCharacters(t *testing.T) { var a Version var err error b := v(0, "09azAZ.-+~:", "0") if a, err = Parse("0:09azAZ.-+~:-0"); err != nil { t.Errorf("Parsing %q failed: %v", "0:09azAZ.-+~:-0", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseValidRevisionCharacters(t *testing.T) { var a Version var err error b := v(0, "0", "azAZ09.+~") if a, err = Parse("0:0-azAZ09.+~"); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-azAZ09.+~", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseLeadingTrailingSpaces(t *testing.T) { var a Version var err error b := v(0, "0", "1") if a, err = Parse(" 0:0-1"); err != nil { t.Errorf("Parsing %q failed: %v", " 0:0-1", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse("0:0-1 "); err != nil { t.Errorf("Parsing %q failed: %v", "0:0-1 ", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } if a, err = Parse(" 0:0-1 "); err != nil { t.Errorf("Parsing %q failed: %v", " 0:0-1 ", err) } if Compare(a, b) != 0 { t.Errorf("Compare(%v, %v), got %d, want 0", a, b, Compare(a, b)) } } func TestParseEmptyVersion(t *testing.T) { if _, err := Parse(""); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "") } if _, err := Parse(" "); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", " ") } } func TestParseEmptyUpstreamVersionAfterEpoch(t *testing.T) { if _, err := Parse("0:"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:") } } func TestParseVersionWithEmbeddedSpaces(t *testing.T) { if _, err := Parse("0:0 0-1"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:0 0-1") } } func TestParseVersionWithNegativeEpoch(t *testing.T) { if _, err := Parse("-1:0-1"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "-1:0-1") } } func TestParseVersionWithHugeEpoch(t *testing.T) { if _, err := Parse("999999999999999999999999:0-1"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "999999999999999999999999:0-1") } } func TestParseInvalidCharactersInEpoch(t *testing.T) { if _, err := Parse("a:0-0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "a:0-0") } if _, err := Parse("A:0-0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "A:0-0") } } func TestParseUpstreamVersionNotStartingWithADigit(t *testing.T) { if _, err := Parse("0:abc3-0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:abc3-0") } } func TestParseInvalidCharactersInUpstreamVersion(t *testing.T) { chars := "!#@$%&/|\\<>()[]{};,_=*^'" for i := 0; i < len(chars); i++ { verstr := "0:0" + chars[i:i+1] + "-0" if _, err := Parse(verstr); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", verstr) } } } func TestParseInvalidCharactersInRevision(t *testing.T) { if _, err := Parse("0:0-0:0"); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", "0:0-0:0") } chars := "!#@$%&/|\\<>()[]{}:;,_=*^'" for i := 0; i < len(chars); i++ { verstr := "0:0-" + chars[i:i+1] if _, err := Parse(verstr); err == nil { t.Errorf("Expected an error, but %q was parsed without an error", verstr) } } } // vim:ts=4:sw=4:noexpandtab foldmethod=marker