golang-pault-go-debian-0.5/0000755000175000017500000000000013005220460015437 5ustar paultagpaultaggolang-pault-go-debian-0.5/AUTHORS0000644000175000017500000000016212735211213016513 0ustar paultagpaultagPaul Tagliamonte Tianon Gravi Michael Stapelberg golang-pault-go-debian-0.5/deb/0000755000175000017500000000000012735211213016176 5ustar paultagpaultaggolang-pault-go-debian-0.5/deb/doc.go0000644000175000017500000000151512735211213017274 0ustar paultagpaultag/* This module provides an API to access and programmatically process Debian `.deb` archives on disk. Debian files, at a high level, are `ar(1)` archives, which contain a few sections, most notably the `control` member, which contains information about the Debian package itself, and the `data` member, which contains the actual contents of the files, as they should be written out on disk. Here's a trivial example, which will print out the Package name for a `.deb` archive given on the command line: package main import ( "log" "os" "pault.ag/go/debian/deb" ) func main() { path := os.Args[1] fd, err := os.Open(path) if err != nil { panic(err) } defer fd.Close() debFile, err := deb.Load(fd, path) if err != nil { panic(err) } log.Printf("Package: %s\n", debFile.Control.Package) } */ package deb golang-pault-go-debian-0.5/deb/deb.go0000644000175000017500000001331012735211213017255 0ustar paultagpaultag/* {{{ 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 deb import ( "archive/tar" "bufio" "fmt" "io" "os" "path" "strings" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) // Control {{{ // Binary Control format, as exists in the Control section of the `.deb` // archive, as defined in Debian Policy, section 5.3, entitled "Binary // package control files -- DEBIAN/control". type Control struct { control.Paragraph Package string `required:"true"` Source string Version version.Version `required:"true"` Architecture dependency.Arch `required:"true"` Maintainer string `required:"true"` InstalledSize int `control:"Installed-Size"` Depends dependency.Dependency Recommends dependency.Dependency Suggests dependency.Dependency Breaks dependency.Dependency Replaces dependency.Dependency BuiltUsing dependency.Dependency `control:"Built-Using"` Section string Priority string Homepage string Description string `required:"true"` } func (c Control) SourceName() string { if c.Source == "" { return c.Package } return c.Source } // }}} // Deb {{{ // Container struct to encapsulate a `.deb` file on disk. This contains // information about what exactly we're looking at. When loaded. information // regarding the Control file is read from the control section of the .deb, // and Unmarshaled into the `Control` member of the Struct. type Deb struct { Control Control Path string Data *tar.Reader } // Load {{{ // Load {{{ // Given a reader, and the file path to the file (for use in the Deb later) // create a deb.Deb object, and populate the Control and Data members. func Load(in io.Reader, pathname string) (*Deb, error) { ar, err := LoadAr(in) if err != nil { return nil, err } deb, err := loadDeb(ar) if err != nil { return nil, err } deb.Path = pathname return deb, nil } // }}} // LoadFile {{{ type Closer func() error func LoadFile(path string) (*Deb, Closer, error) { fd, err := os.Open(path) if err != nil { return nil, nil, err } debFile, err := Load(fd, path) if err != nil { fd.Close() return nil, nil, err } return debFile, fd.Close, nil } // }}} // Debian .deb Loader Internals {{{ // Top-level .deb loader dispatch on Version {{{ // Look for the debian-binary member and figure out which version to read // it as. Return the newly created .deb struct. func loadDeb(archive *Ar) (*Deb, error) { for { member, err := archive.Next() if err == io.EOF { return nil, fmt.Errorf("Archive contains no binary version member!") } if err != nil { return nil, err } if member.Name == "debian-binary" { reader := bufio.NewReader(member.Data) version, err := reader.ReadString('\n') if err != nil { return nil, err } switch version { case "2.0\n": return loadDeb2(archive) default: return nil, fmt.Errorf("Unknown binary version: '%s'", version) } } } } // }}} // Debian .deb format 2.0 {{{ // Top-level .deb loader dispatch for 2.0 {{{ // Load a Debian 2.x series .deb - track down the control and data members. func loadDeb2(archive *Ar) (*Deb, error) { ret := Deb{} if err := loadDeb2Control(archive, &ret); err != nil { return nil, err } if err := loadDeb2Data(archive, &ret); err != nil { return nil, err } return &ret, nil } // }}} // Decode .deb 2.0 control data into the struct {{{ // Load a Debian 2.x series .deb control file and write it out to // the deb.Deb.Control member. func loadDeb2Control(archive *Ar, deb *Deb) error { for { member, err := archive.Next() if err == io.EOF { return fmt.Errorf("Missing or out of order .deb member 'control'") } if err != nil { return err } if strings.HasPrefix(member.Name, "control.") { archive, err := member.Tarfile() if err != nil { return err } for { member, err := archive.Next() if err != nil { return err } if path.Clean(member.Name) == "control" { return control.Unmarshal(&deb.Control, archive) } } } } } // }}} // Decode .deb 2.0 package data into the struct {{{ // Load a Debian 2.x series .deb data file and write it out to // the deb.Deb.Data member. func loadDeb2Data(archive *Ar, deb *Deb) error { for { member, err := archive.Next() if err == io.EOF { return fmt.Errorf("Missing or out of order .deb member 'data'") } if err != nil { return err } if strings.HasPrefix(member.Name, "data.") { archive, err := member.Tarfile() if err != nil { return err } deb.Data = archive return nil } } } // }}} // }}} }}} }}} }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/deb/ar.go0000644000175000017500000001231712735211213017133 0ustar paultagpaultag/* {{{ 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 deb import ( "fmt" "io" "io/ioutil" "strconv" "strings" ) // ArEntry {{{ // Container type to access the different parts of a Debian `ar(1)` Archive. // // The most interesting parts of this are the `Name` attribute, Data // `io.Reader`, and the Tarfile helpers. This will allow the developer to // programmatically inspect the information inside without forcing her to // unpack the .deb to the filesystem. type ArEntry struct { Name string Timestamp int64 OwnerID int64 GroupID int64 FileMode string Size int64 Data io.Reader } // }}} // Ar {{{ // This struct encapsulates a Debian .deb flavored `ar(1)` archive. type Ar struct { in io.Reader lastReader *io.Reader offset bool } // LoadAr {{{ // Load an Ar archive reader from an io.Reader func LoadAr(in io.Reader) (*Ar, error) { if err := checkAr(in); err != nil { return nil, err } debFile := Ar{in: in} return &debFile, nil } // }}} // Next {{{ // Function to jump to the next file in the Debian `ar(1)` archive, and // return the next member. func (d *Ar) Next() (*ArEntry, error) { if d.lastReader != nil { /* Before we do much more, let's empty out the reader, since we * can't be sure of our position in the reader until the LimitReader * is empty */ if _, err := io.Copy(ioutil.Discard, *d.lastReader); err != nil { return nil, err } if d.offset { /* .ar archives align on 2 byte boundaries, so if we're odd, go * ahead and read another byte. If we get an io.EOF, it's fine * to return it. */ _, err := d.in.Read(make([]byte, 1)) if err != nil { return nil, err } } } line := make([]byte, 60) count, err := d.in.Read(line) if err != nil { return nil, err } if count == 1 && line[0] == '\n' { return nil, io.EOF } if count != 60 { return nil, fmt.Errorf("Caught a short read at the end") } entry, err := parseArEntry(line) if err != nil { return nil, err } entry.Data = io.LimitReader(d.in, entry.Size) d.lastReader = &entry.Data d.offset = (entry.Size % 2) == 1 return entry, nil } // }}} // toDecimal {{{ // Take a byte array, and return an int64 func toDecimal(input []byte) (int64, error) { stream := strings.TrimSpace(string(input)) out, err := strconv.Atoi(stream) return int64(out), err } // }}} // }}} // AR Format Hackery {{{ // parseArEntry {{{ // Take the AR format line, and create an ArEntry (without .Data set) // to be returned to the user later. // // +------------------------------------------------------- // | Offset Length Name Format // +------------------------------------------------------- // | 0 16 File name ASCII // | 16 12 File modification timestamp Decimal // | 28 6 Owner ID Decimal // | 34 6 Group ID Decimal // | 40 8 File mode Octal // | 48 10 File size in bytes Decimal // | 58 2 File magic 0x60 0x0A // func parseArEntry(line []byte) (*ArEntry, error) { if len(line) != 60 { return nil, fmt.Errorf("Malformed file entry line length") } if line[58] != 0x60 && line[59] != 0x0A { return nil, fmt.Errorf("Malformed file entry line endings") } entry := ArEntry{ Name: strings.TrimSpace(string(line[0:16])), FileMode: strings.TrimSpace(string(line[48:58])), } for target, value := range map[*int64][]byte{ &entry.Timestamp: line[16:28], &entry.OwnerID: line[28:34], &entry.GroupID: line[34:40], &entry.Size: line[48:58], } { intValue, err := toDecimal(value) if err != nil { return nil, err } *target = intValue } return &entry, nil } // }}} // checkAr {{{ // Given a brand spank'n new os.File entry, go ahead and make sure it looks // like an `ar(1)` archive, and not some random file. func checkAr(reader io.Reader) error { header := make([]byte, 8) if _, err := reader.Read(header); err != nil { return err } if string(header) != "!\n" { return fmt.Errorf("Header doesn't look right!") } return nil } // }}} // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/deb/tarfile.go0000644000175000017500000000564412735211213020164 0ustar paultagpaultag/* {{{ 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 deb import ( "fmt" "io" "strings" "archive/tar" "compress/bzip2" "compress/gzip" "xi2.org/x/xz" ) // known compression types {{{ type compressionReader func(io.Reader) (io.Reader, error) func gzipNewReader(r io.Reader) (io.Reader, error) { return gzip.NewReader(r) } func xzNewReader(r io.Reader) (io.Reader, error) { return xz.NewReader(r, 0) } func bzipNewReader(r io.Reader) (io.Reader, error) { return bzip2.NewReader(r), nil } var knownCompressionAlgorithms = map[string]compressionReader{ ".tar.gz": gzipNewReader, ".tar.bz2": bzipNewReader, ".tar.xz": xzNewReader, } // }}} // IsTarfile {{{ // Check to see if the given ArEntry is, in fact, a Tarfile. This method // will return `true` for `control.tar.*` and `data.tar.*` files. // // This will return `false` for the `debian-binary` file. If this method // returns `true`, the `.Tarfile()` method will be around to give you a // tar.Reader back. func (e *ArEntry) IsTarfile() bool { return e.getCompressionReader() != nil } // }}} // Tarfile {{{ // `.Tarfile()` will return a `tar.Reader` created from the ArEntry member // to allow further inspection of the contents of the `.deb`. func (e *ArEntry) Tarfile() (*tar.Reader, error) { decompressor := e.getCompressionReader() if decompressor == nil { return nil, fmt.Errorf("%s appears to not be a tarfile", e.Name) } reader, err := (*decompressor)(e.Data) if err != nil { return nil, err } return tar.NewReader(reader), nil } // }}} // getCompressionReader {{{ // Get a compressionReader that we can use to unpack the member. func (e *ArEntry) getCompressionReader() *compressionReader { for key, decompressor := range knownCompressionAlgorithms { if strings.HasSuffix(e.Name, key) { return &decompressor } } return nil } // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/dependency/0000755000175000017500000000000012735211213017562 5ustar paultagpaultaggolang-pault-go-debian-0.5/dependency/parser.go0000644000175000017500000002305112735211213021406 0ustar paultagpaultag/* {{{ 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{}}, StageSets: []StageSet{}, 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 case '<': err := parsePossibilityStageSet(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 == '[' */ 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 := "" // Exclamation marks may be prepended to each of the names. (It is not // permitted for some names to be prepended with exclamation marks while // others aren't.) hasNot := input.Peek() == '!' if hasNot { input.Next() // '!' } if len(possi.Architectures.Architectures) == 0 { possi.Architectures.Not = hasNot } else if possi.Architectures.Not != hasNot { return errors.New("Either the entire arch list needs negations, or none of it does -- no mix and match :/") } 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()) } } /* */ func parsePossibilityStageSet(input *input, possi *Possibility) error { eatWhitespace(input) input.Next() /* Assert ch == '<' */ stageSet := StageSet{} for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before StageSet finished") case '>': input.Next() possi.StageSets = append(possi.StageSets, stageSet) return nil } err := parsePossibilityStage(input, &stageSet) if err != nil { return err } } } /* */ func parsePossibilityStage(input *input, stageSet *StageSet) error { eatWhitespace(input) stage := Stage{} for { peek := input.Peek() switch peek { case 0: return errors.New("Oh no. Reached EOF before Stage finished") case '!': input.Next() if stage.Not { return errors.New("Double-negation (!!) of a single Stage is not permitted :(") } stage.Not = !stage.Not case '>', ' ': /* Let our parent deal with both of these */ stageSet.Stages = append(stageSet.Stages, stage) return nil } stage.Name += string(input.Next()) } } // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/dependency/dependency.go0000644000175000017500000000503012735211213022225 0ustar paultagpaultag/* {{{ 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 ( "pault.ag/go/debian/version" ) // 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 } func (v VersionRelation) SatisfiedBy(ver version.Version) bool { vVer, err := version.Parse(v.Number) if err != nil { return false } q := version.Compare(ver, vVer) switch v.Operator { case ">=": return q >= 0 case "<=": return q <= 0 case ">>": return q > 0 case "<<": return q < 0 case "=": return q == 0 } // XXX: WHAT THE SHIT return false } // vim: foldmethod=marker golang-pault-go-debian-0.5/dependency/doc.go0000644000175000017500000000105312735211213020655 0ustar paultagpaultag/* 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 golang-pault-go-debian-0.5/dependency/string.go0000644000175000017500000000623412735211213021424 0ustar paultagpaultag/* {{{ 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) MarshalControl() (string, error) { return a.String(), nil } 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 "" } not := "" if set.Not { not = "!" } arches := []string{} for _, arch := range set.Architectures { arches = append(arches, not+arch.String()) } return "[" + 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() } for _, stageSet := range possi.StageSets { if stages := stageSet.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 golang-pault-go-debian-0.5/dependency/models.go0000644000175000017500000000621412735211213021377 0ustar paultagpaultag/* {{{ 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 StageSets []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 } func (dep Dependency) MarshalControl() (string, error) { return dep.String(), nil } // vim: foldmethod=marker golang-pault-go-debian-0.5/dependency/string_test.go0000644000175000017500000000312612735211213022460 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/dependency/arch.go0000644000175000017500000001006612735211213021031 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/dependency/dependency_test.go0000644000175000017500000000647112735211213023276 0ustar paultagpaultag/* {{{ 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" "pault.ag/go/debian/version" ) /* * */ 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") } func TestVersionRelationSatisfiedBy(t *testing.T) { for _, test := range []struct { Operator string Number string Version string Match bool }{ {"=", "1.0.0", "1.0.0", true}, {"=", "1.0.1", "1.0.0", false}, {"=", "1.0.0", "1.0.1", false}, {"<<", "2.0", "1.0", true}, {">>", "2.0", "1.0", false}, {">>", "2.0", "3.0", true}, {"<<", "2.0", "3.0", false}, {">=", "1.0~", "1.0", true}, {">=", "1.0~", "1.0.2", true}, {">=", "1.0~", "1.0.2.3", true}, {"<=", "1.0~", "1.0", false}, {"<=", "1.0~", "1.0.2", false}, {"<=", "1.0~", "1.0.2.3", false}, } { vr := dependency.VersionRelation{ Operator: test.Operator, Number: test.Number, } v, err := version.Parse(test.Version) assert(t, err == nil) assert(t, vr.SatisfiedBy(v) == test.Match) } } // vim: foldmethod=marker golang-pault-go-debian-0.5/dependency/arch_test.go0000644000175000017500000000504612735211213022072 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/dependency/parser_test.go0000644000175000017500000001767612735211213022465 0ustar paultagpaultag/* {{{ 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" "runtime/debug" "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) debug.PrintStack() t.FailNow() } } func notok(t *testing.T, err error) { if err == nil { log.Printf("Error! Error is nil!\n") debug.PrintStack() t.FailNow() } } func assert(t *testing.T, expr bool) { if !expr { log.Printf("Assertion failed!") debug.PrintStack() 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) _, err = dependency.Parse("foo [arch!foo]") notok(t, err) _, err = dependency.Parse("foo [arch!]") notok(t, err) } func TestDoubleArch(t *testing.T) { for depStr, not := range map[string]bool{ "foo [arch arch2]": false, "foo [!arch !arch2]": true, } { dep, err := dependency.Parse(depStr) isok(t, err) assert(t, len(dep.Relations) == 1) possi := dep.Relations[0].Possibilities[0] arches := possi.Architectures.Architectures assert(t, possi.Architectures.Not == not) 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 TestTwoStages(t *testing.T) { dep, err := dependency.Parse("foo ") isok(t, err) possi := dep.Relations[0].Possibilities[0] assert(t, len(possi.StageSets) == 2) // assert(t, len(possi.StageSets[0].Stages) == 2) assert(t, !possi.StageSets[0].Stages[0].Not) assert(t, possi.StageSets[0].Stages[0].Name == "stage1") assert(t, possi.StageSets[0].Stages[1].Not) assert(t, possi.StageSets[0].Stages[1].Name == "cross") // assert(t, len(possi.StageSets[1].Stages) == 2) assert(t, possi.StageSets[1].Stages[0].Not) assert(t, possi.StageSets[1].Stages[0].Name == "stage1") assert(t, !possi.StageSets[1].Stages[1].Not) assert(t, possi.StageSets[1].Stages[1].Name == "cross") } 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 TestBadStages(t *testing.T) { vers := []string{ "foo <", "foo [amd64 i386] (>= 1.2:3.4~5.6-7.8~9.0) ") isok(t, err) assert(t, dep.String() == "foo:armhf [amd64 i386] (>= 1.2:3.4~5.6-7.8~9.0) ") rtDep, err := dependency.Parse(dep.String()) isok(t, err) assert(t, dep.String() == rtDep.String()) dep.Relations[0].Possibilities[0].Architectures.Not = true assert(t, dep.String() == "foo:armhf [!amd64 !i386] (>= 1.2:3.4~5.6-7.8~9.0) ") rtDep, err = dependency.Parse(dep.String()) isok(t, err) assert(t, dep.String() == rtDep.String()) } // vim: foldmethod=marker golang-pault-go-debian-0.5/README.md0000644000175000017500000000040712735211213016724 0ustar paultagpaultaggo-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. [![GoDoc](https://godoc.org/pault.ag/go/debian?status.svg)](https://godoc.org/pault.ag/go/debian) golang-pault-go-debian-0.5/.gitignore0000644000175000017500000000000512735211213017427 0ustar paultagpaultag*swp golang-pault-go-debian-0.5/.travis.yml0000644000175000017500000000011612735211213017553 0ustar paultagpaultaglanguage: go go_import_path: pault.ag/go/debian go: - 1.6 - 1.5 - 1.4.2 golang-pault-go-debian-0.5/internal/0000755000175000017500000000000012735211213017260 5ustar paultagpaultaggolang-pault-go-debian-0.5/internal/copy.go0000644000175000017500000000052212735211213020560 0ustar paultagpaultagpackage 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 } golang-pault-go-debian-0.5/version/0000755000175000017500000000000012735211213017131 5ustar paultagpaultaggolang-pault-go-debian-0.5/version/version_test.go0000644000175000017500000002336112735211213022211 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/version/version.go0000644000175000017500000001356312735211213021155 0ustar paultagpaultag/* {{{ 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 (version Version) MarshalControl() (string, error) { return version.String(), nil } 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 golang-pault-go-debian-0.5/transput/0000755000175000017500000000000012735211213017324 5ustar paultagpaultaggolang-pault-go-debian-0.5/transput/construct.go0000644000175000017500000000242212735211213021677 0ustar paultagpaultagpackage transput import ( "io" ) func NewHasherWriter(hash string, target io.Writer) (io.Writer, *Hasher, error) { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } endWriter := io.MultiWriter(target, hw) return endWriter, hw, nil } func NewHasherWriters(hashes []string, target io.Writer) (io.Writer, []*Hasher, error) { hashers := []*Hasher{} writers := []io.Writer{} for _, hash := range hashes { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } hashers = append(hashers, hw) writers = append(writers, hw) } endWriter := io.MultiWriter(append(writers, target)...) return endWriter, hashers, nil } func NewHasherReader(hash string, target io.Reader) (io.Reader, *Hasher, error) { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } endReader := io.TeeReader(target, hw) return endReader, hw, nil } func NewHasherReaders(hashes []string, target io.Reader) (io.Reader, []*Hasher, error) { hashers := []*Hasher{} writers := []io.Writer{} for _, hash := range hashes { hw, err := NewHasher(hash) if err != nil { return nil, nil, err } hashers = append(hashers, hw) writers = append(writers, hw) } endReader := io.TeeReader(target, io.MultiWriter(writers...)) return endReader, hashers, nil } golang-pault-go-debian-0.5/transput/hash.go0000644000175000017500000000166512735211213020606 0ustar paultagpaultagpackage transput import ( "fmt" "hash" "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" ) func GetHash(name string) (hash.Hash, error) { switch name { case "md5": return md5.New(), nil case "sha1": return sha1.New(), nil case "sha256": return sha256.New(), nil case "sha512": return sha512.New(), nil default: return nil, fmt.Errorf("Unknown algorithm: %s", name) } } func NewHasher(name string) (*Hasher, error) { hash, err := GetHash(name) if err != nil { return nil, err } hw := Hasher{ name: name, hash: hash, size: 0, } return &hw, nil } type Hasher struct { name string hash hash.Hash size int64 } func (dh *Hasher) Name() string { return dh.name } func (dh *Hasher) Write(p []byte) (int, error) { n, err := dh.hash.Write(p) dh.size += int64(n) return n, err } func (dh *Hasher) Size() int64 { return dh.size } func (dh *Hasher) Sum(b []byte) []byte { return dh.hash.Sum(b) } golang-pault-go-debian-0.5/transput/compressors.go0000644000175000017500000000073612735211213022240 0ustar paultagpaultagpackage transput import ( "fmt" "io" "compress/gzip" ) type Compressor func(io.Writer) (io.WriteCloser, error) func gzipCompressor(in io.Writer) (io.WriteCloser, error) { return gzip.NewWriter(in), nil } var knownCompressors = map[string]Compressor{ "gz": gzipCompressor, } func GetCompressor(name string) (Compressor, error) { if compressor, ok := knownCompressors[name]; ok { return compressor, nil } return nil, fmt.Errorf("No such compressor: '%s'", name) } golang-pault-go-debian-0.5/control/0000755000175000017500000000000012735211213017124 5ustar paultagpaultaggolang-pault-go-debian-0.5/control/index_test.go0000644000175000017500000002030612735211213021622 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/control/doc.go0000644000175000017500000000007612735211213020223 0ustar paultagpaultag/* Parse the Debian control file format. */ package control golang-pault-go-debian-0.5/control/control.go0000644000175000017500000001147412735211213021142 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/control/parse_test.go0000644000175000017500000001657512735211213021642 0ustar paultagpaultag/* {{{ 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 ( "io" "log" "strings" "testing" "golang.org/x/crypto/openpgp" "pault.ag/go/debian/control" ) /* * */ // Signed Paragraph {{{ const signedParagraph = `-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 1.8 Date: Mon, 16 Nov 2015 21:15:55 -0800 Source: hy Binary: hy python-hy python3-hy Architecture: source Version: 0.11.0-4 Distribution: unstable Urgency: medium Maintainer: Tianon Gravi Changed-By: Tianon Gravi Description: hy - Lisp (s-expression) based frontend to Python (metapackage) python-hy - Lisp (s-expression) based frontend to Python python3-hy - Lisp (s-expression) based frontend to Python 3 Closes: 805204 Changes: hy (0.11.0-4) unstable; urgency=medium . * Fix FTBFS due to rply trying to write to HOME during sphinx-build. * Fix build repeatability with proper override_dh_auto_clean. * Fix FTBFS now that the tests actually run (Closes: #805204). * Add "hyc" and "hy2py" as slaves to the "hy" alternative. * Add alternatives to python-hy package also (to make testing easier). Checksums-Sha1: cbb00b96ba8ad4f27f8f6f6ceb626f0857c1d985 2170 hy_0.11.0-4.dsc 802ebce0dc09000a243cf809c922d5f0e1af90f0 7536 hy_0.11.0-4.debian.tar.xz Checksums-Sha256: 2c91c414f8c7a0556372c94301b8786801a05b29aafeceb2e308e037d47d5ddc 2170 hy_0.11.0-4.dsc 27610d4e31645bc888c633881082270917aedd3443e36031a0030d3dae6f7380 7536 hy_0.11.0-4.debian.tar.xz Files: 42d61a06f37db6f2d2fc6c35b2d4e683 2170 python optional hy_0.11.0-4.dsc aa8bfae41ef33a85e0f08f21e0a5e67b 7536 python optional hy_0.11.0-4.debian.tar.xz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBCgAGBQJWSrgkAAoJEANqnCW/NX3UEjQP/ikiMZWvhco6jz9ObT/Q1FbK fjoaZbOBLAoP/kBD4m9s/GiBNb0mHAtn3186uh5ZbXw7NEz9hfQeNAL2qHOqYsT3 7Ha31kwjV9ZfSLbu6giCImoBPBV6kqn904QVjHiSJ/d08PdEgDPcOrDYUBPYH+O0 BmHDlWb9mBRIMIjXl6HtZIMvJ1adU613h3T6C4VMExV1YRGaD4UlUxUkfdKMZ0kx GUNsyARKxXbMMr9uqnmDp3K85U6BynOiZ8aLnzWjVKDpXPQOK/3n4sfN6iNJF+9K nl26axBfgoj/kvNLXGOR+rpGz0IBE7QpecVh7eFUb87i4En+lZi9pl0+hC/2n+Xy vShNXphZZQ444P5CMX5s8OK0IbWl1wVe0OCjcQhW9juOIGdn3bxWJ68HaIbw5Y+V TZcmHxjJJbO+D+ng3OHqCg1tooy1dAeMZlRwsgYyDtx0Rhd7gZKzx5NkuWlBOdSH P+FikrKFHW6rvvO0esWqgm7GuBDrMrsPgU9T4UZ1sOOwsBdWTgp9QWceFIrrptX0 C/hiBXZkJP/cXueZQ38GDny6ahuR5HDmNwHhvV/EuZ28GOdKzCxcOCyhoNI2xgOg A8Ija2WnFdScMVRuMxDxK8yMdy1/BtZQKV6uzSt7ebHfPcUopBM4yARM8C90EbJD /FevdZ9cGw/0bCyun86t =dtCZ -----END PGP SIGNATURE----- ` // }}} func isok(t *testing.T, err error) { if err != nil && err != io.EOF { 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 TestBasicParagraphReader(t *testing.T) { // Reader {{{ reader, err := control.NewParagraphReader(strings.NewReader(`Para: one Para: two Para: three `), nil) // }}} isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, len(blocks) == 3) } func TestOpenPGPParagraphReader(t *testing.T) { reader, err := control.NewParagraphReader(strings.NewReader(signedParagraph), nil) isok(t, err) blocks, err := reader.All() isok(t, err) assert(t, len(blocks) == 1) } func TestEmptyKeyringOpenPGPParagraphReader(t *testing.T) { keyring := openpgp.EntityList{} // Reader {{{ _, err := control.NewParagraphReader(strings.NewReader(`-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 Format: 1.8 Date: Mon, 16 Nov 2015 21:15:55 -0800 Source: hy Binary: hy python-hy python3-hy Architecture: source Version: 0.11.0-4 Distribution: unstable Urgency: medium Maintainer: Tianon Gravi Changed-By: Tianon Gravi Description: hy - Lisp (s-expression) based frontend to Python (metapackage) python-hy - Lisp (s-expression) based frontend to Python python3-hy - Lisp (s-expression) based frontend to Python 3 Closes: 805204 Changes: hy (0.11.0-4) unstable; urgency=medium . * Fix FTBFS due to rply trying to write to HOME during sphinx-build. * Fix build repeatability with proper override_dh_auto_clean. * Fix FTBFS now that the tests actually run (Closes: #805204). * Add "hyc" and "hy2py" as slaves to the "hy" alternative. * Add alternatives to python-hy package also (to make testing easier). Checksums-Sha1: cbb00b96ba8ad4f27f8f6f6ceb626f0857c1d985 2170 hy_0.11.0-4.dsc 802ebce0dc09000a243cf809c922d5f0e1af90f0 7536 hy_0.11.0-4.debian.tar.xz Checksums-Sha256: 2c91c414f8c7a0556372c94301b8786801a05b29aafeceb2e308e037d47d5ddc 2170 hy_0.11.0-4.dsc 27610d4e31645bc888c633881082270917aedd3443e36031a0030d3dae6f7380 7536 hy_0.11.0-4.debian.tar.xz Files: 42d61a06f37db6f2d2fc6c35b2d4e683 2170 python optional hy_0.11.0-4.dsc aa8bfae41ef33a85e0f08f21e0a5e67b 7536 python optional hy_0.11.0-4.debian.tar.xz -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBCgAGBQJWSrgkAAoJEANqnCW/NX3UEjQP/ikiMZWvhco6jz9ObT/Q1FbK fjoaZbOBLAoP/kBD4m9s/GiBNb0mHAtn3186uh5ZbXw7NEz9hfQeNAL2qHOqYsT3 7Ha31kwjV9ZfSLbu6giCImoBPBV6kqn904QVjHiSJ/d08PdEgDPcOrDYUBPYH+O0 BmHDlWb9mBRIMIjXl6HtZIMvJ1adU613h3T6C4VMExV1YRGaD4UlUxUkfdKMZ0kx GUNsyARKxXbMMr9uqnmDp3K85U6BynOiZ8aLnzWjVKDpXPQOK/3n4sfN6iNJF+9K nl26axBfgoj/kvNLXGOR+rpGz0IBE7QpecVh7eFUb87i4En+lZi9pl0+hC/2n+Xy vShNXphZZQ444P5CMX5s8OK0IbWl1wVe0OCjcQhW9juOIGdn3bxWJ68HaIbw5Y+V TZcmHxjJJbO+D+ng3OHqCg1tooy1dAeMZlRwsgYyDtx0Rhd7gZKzx5NkuWlBOdSH P+FikrKFHW6rvvO0esWqgm7GuBDrMrsPgU9T4UZ1sOOwsBdWTgp9QWceFIrrptX0 C/hiBXZkJP/cXueZQ38GDny6ahuR5HDmNwHhvV/EuZ28GOdKzCxcOCyhoNI2xgOg A8Ija2WnFdScMVRuMxDxK8yMdy1/BtZQKV6uzSt7ebHfPcUopBM4yARM8C90EbJD /FevdZ9cGw/0bCyun86t =dtCZ -----END PGP SIGNATURE----- `), &keyring) // }}} notok(t, err) } func TestLineWrapping(t *testing.T) { reader, err := control.NewParagraphReader(strings.NewReader(signedParagraph), nil) isok(t, err) el, err := reader.Next() isok(t, err) assert(t, el.Values["Changes"] == `hy (0.11.0-4) unstable; urgency=medium * Fix FTBFS due to rply trying to write to HOME during sphinx-build. * Fix build repeatability with proper override_dh_auto_clean. * Fix FTBFS now that the tests actually run (Closes: #805204). * Add "hyc" and "hy2py" as slaves to the "hy" alternative. * Add alternatives to python-hy package also (to make testing easier). `) } // vim: foldmethod=marker golang-pault-go-debian-0.5/control/encode.go0000644000175000017500000002163212735211213020714 0ustar paultagpaultag/* {{{ 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 ( "fmt" "io" "reflect" "strconv" "strings" ) // Marshallable {{{ // The Marshallable interface defines the interface that Marshal will use // to do custom dehydration of the Struct back into the Debian 822 format. type Marshallable interface { MarshalControl() (string, error) } // }}} // ConvertToParagraph {{{ // Given a Struct, convert that Struct back into a control.Paragraph. // This is not exactly useful as part of the external API, but may be // useful in some funny circumstances where you need to treat a Struct // you Unmarshaled into as a control.Paragraph again. // // In most cases, the Marshal API should be sufficient. Use of this API // is mildly discouraged. func ConvertToParagraph(incoming interface{}) (*Paragraph, error) { data := reflect.ValueOf(incoming) if data.Type().Kind() != reflect.Ptr { return nil, fmt.Errorf("Can only Decode a pointer to a Struct") } return convertToParagraph(data.Elem()) } // Top-level conversion dispatch {{{ func convertToParagraph(data reflect.Value) (*Paragraph, error) { order := []string{} values := map[string]string{} if data.Type().Kind() != reflect.Struct { return nil, fmt.Errorf("Can only Decode a Struct") } paragraphType := reflect.TypeOf(Paragraph{}) var foundParagraph Paragraph = Paragraph{} for i := 0; i < data.NumField(); i++ { field := data.Field(i) fieldType := data.Type().Field(i) if fieldType.Anonymous { if fieldType.Type == paragraphType { foundParagraph = field.Interface().(Paragraph) } continue } paragraphKey := fieldType.Name if it := fieldType.Tag.Get("control"); it != "" { paragraphKey = it } if paragraphKey == "-" { /* If the key is "-", lets go ahead and skip it */ continue } data, err := marshalStructValue(field, fieldType) if err != nil { return nil, err } required := fieldType.Tag.Get("required") == "true" if data == "" && !required { continue } if fieldType.Tag.Get("multiline") == "true" { data = "\n" + data } order = append(order, paragraphKey) values[paragraphKey] = data } para := foundParagraph.Update(Paragraph{Order: order, Values: values}) return ¶, nil } // }}} // convert a struct value {{{ func marshalStructValue(field reflect.Value, fieldType reflect.StructField) (string, error) { switch field.Type().Kind() { case reflect.String: return field.String(), nil case reflect.Uint: return strconv.Itoa(int(field.Uint())), nil case reflect.Int: return strconv.Itoa(int(field.Int())), nil case reflect.Ptr: return marshalStructValue(field.Elem(), fieldType) case reflect.Slice: return marshalStructValueSlice(field, fieldType) case reflect.Struct: return marshalStructValueStruct(field, fieldType) } return "", fmt.Errorf("Unknown type: %s", field.Type().Kind()) } // }}} // convert a struct value of type struct {{{ func marshalStructValueStruct(field reflect.Value, fieldType reflect.StructField) (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. */ if marshal, ok := field.Interface().(Marshallable); ok { return marshal.MarshalControl() } return "", fmt.Errorf( "Type '%s' does not implement control.Marshallable", field.Type().Name(), ) } // }}} // convert a struct value of type slice {{{ func marshalStructValueSlice(field reflect.Value, fieldType reflect.StructField) (string, error) { var delim = " " if it := fieldType.Tag.Get("delim"); it != "" { delim = it } data := []string{} for i := 0; i < field.Len(); i++ { elem := field.Index(i) if stringification, err := marshalStructValue(elem, fieldType); err != nil { return "", err } else { data = append(data, stringification) } } return strings.Join(data, delim), nil } // }}} // }}} // Marshal {{{ // Marshal is a one-off interface to serialize a single object to a writer. // // Most notably, this will *not* separate Paragraphs with a newline as is // expected upon repeated calls, please use the Encoder streaming interface // for that. // // It's also worth noting that this *will* also write out elements that // were Unmarshaled into a Struct without a member of that name if (and only // if) the target Struct contains a `control.Paragraph` anonymous member. // // This is handy if the Unmarshaler was given any `X-*` keys that were not // present on your Struct. // // Given a struct (or list of structs), write to the io.Writer stream // in the RFC822-alike Debian control-file format // // This code will attempt to unpack it into the struct based on the // literal name of the key, This can be overridden by the struct tag // `control:""`. // // If you're dehydrating a list of strings, you have the option of defining // a string to join the tokens with (`delim:", "`). // // In order to Marshal a custom Struct, you are required to implement the // Marshallable interface. It's highly encouraged to put this interface on // the struct without a pointer receiver, so that pass-by-value works // when you call Marshal. func Marshal(writer io.Writer, data interface{}) error { encoder, err := NewEncoder(writer) if err != nil { return err } return encoder.Encode(data) } // }}} // Encoder {{{ // Encoder is a struct that allows for the streaming Encoding of data // back out to an `io.Writer`. Most notably, this will separate // subsequent `Encode` calls of a Struct with a newline. // // It's also worth noting that this *will* also write out elements that // were Unmarshaled into a Struct without a member of that name if (and only // if) the target Struct contains a `control.Paragraph` anonymous member. // // This is handy if the Unmarshaler was given any `X-*` keys that were not // present on your Struct. // // Given a struct (or list of structs), write to the io.Writer stream // in the RFC822-alike Debian control-file format // // This code will attempt to unpack it into the struct based on the // literal name of the key, This can be overridden by the struct tag // `control:""`. // // If you're dehydrating a list of strings, you have the option of defining // a string to join the tokens with (`delim:", "`). // // In order to Marshal a custom Struct, you are required to implement the // Marshallable interface. It's highly encouraged to put this interface on // the struct without a pointer receiver, so that pass-by-value works // when you call Marshal. type Encoder struct { writer io.Writer alreadyWritten bool } // NewEncoder {{{ // Create a new Encoder, which is configured to write to the given `io.Writer`. func NewEncoder(writer io.Writer) (*Encoder, error) { return &Encoder{ writer: writer, alreadyWritten: false, }, nil } // }}} // Encode {{{ // Take a Struct, Encode it into a Paragraph, and write that out to the // io.Writer set up when the Encoder was configured. func (e *Encoder) Encode(incoming interface{}) error { data := reflect.ValueOf(incoming) return e.encode(data) } // Top-level Encode reflect dispatch {{{ func (e *Encoder) encode(data reflect.Value) error { if data.Type().Kind() == reflect.Ptr { return e.encode(data.Elem()) } switch data.Type().Kind() { case reflect.Slice: return e.encodeSlice(data) case reflect.Struct: return e.encodeStruct(data) } return fmt.Errorf("Unknown type") } // }}} // Encode a Slice {{{ func (e *Encoder) encodeSlice(data reflect.Value) error { for i := 0; i < data.Len(); i++ { if err := e.encodeStruct(data.Index(i)); err != nil { return err } } return nil } // }}} // Encode a Struct {{{ func (e *Encoder) encodeStruct(data reflect.Value) error { if e.alreadyWritten { _, err := e.writer.Write([]byte("\n")) if err != nil { return err } } paragraph, err := convertToParagraph(data) if err != nil { return err } e.alreadyWritten = true return paragraph.WriteTo(e.writer) } // }}} // }}} // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/control/dsc.go0000644000175000017500000001152612735211213020231 0ustar paultagpaultag/* {{{ 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" ) // A DSC is the encapsulation 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 []SHA1FileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256FileHash `control:"Checksums-Sha256" delim:"\n" strip:"\n\r\t "` Files []MD5FileHash `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...) } // vim: foldmethod=marker golang-pault-go-debian-0.5/control/decode_test.go0000644000175000017500000000526212735211213021742 0ustar paultagpaultagpackage 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, len(foo) == 3) 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 `))) } golang-pault-go-debian-0.5/control/parse.go0000644000175000017500000002141712735211213020572 0ustar paultagpaultag/* {{{ 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" "bytes" "fmt" "io" "io/ioutil" "strings" "unicode" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/clearsign" ) // 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 } // Paragraph Helpers {{{ func (p Paragraph) Set(key, value string) { if _, found := p.Values[key]; found { /* We've got the key */ p.Values[key] = value return } /* Otherwise, go ahead and set it in the order and dict, * and call it a day */ p.Order = append(p.Order, key) p.Values[key] = value } func (p Paragraph) WriteTo(out io.Writer) error { for _, key := range p.Order { value := p.Values[key] value = strings.Replace(value, "\n", "\n ", -1) value = strings.Replace(value, "\n \n", "\n .\n", -1) if _, err := out.Write( []byte(fmt.Sprintf("%s: %s\n", key, value)), ); err != nil { return err } } return nil } func (p Paragraph) Update(other Paragraph) Paragraph { ret := Paragraph{ Order: []string{}, Values: map[string]string{}, } seen := map[string]bool{} for _, el := range p.Order { ret.Order = append(ret.Order, el) ret.Values[el] = p.Values[el] seen[el] = true } for _, el := range other.Order { if _, ok := seen[el]; !ok { ret.Order = append(ret.Order, el) seen[el] = true } ret.Values[el] = other.Values[el] } return ret } // }}} // ParagraphReader {{{ // Wrapper to allow iteration on a set of Paragraphs without consuming them // all into memory at one time. This is also the level in which data is // signed, so information such as the entity that signed these documents // can be read by calling the `.Signer` method on this struct. The next // unread Paragraph can be returned by calling the `.Next` method on this // struct. type ParagraphReader struct { reader *bufio.Reader signer *openpgp.Entity } // {{{ NewParagraphReader // Create a new ParagraphReader from the given `io.Reader`, and `keyring`. // if `keyring` is set to `nil`, this will result in all OpenPGP signature // checking being disabled. *including* that the contents match! // // Also keep in mind, `reader` may be consumed 100% in memory due to // the underlying OpenPGP API being hella fiddly. func NewParagraphReader(reader io.Reader, keyring *openpgp.EntityList) (*ParagraphReader, error) { bufioReader := bufio.NewReader(reader) ret := ParagraphReader{ reader: bufioReader, signer: nil, } // OK. We have a document. Now, let's peek ahead and see if we've got an // OpenPGP Clearsigned set of Paragraphs. If we do, we're going to go ahead // and do the decode dance. line, _ := bufioReader.Peek(15) if string(line) != "-----BEGIN PGP " { return &ret, nil } if err := ret.decodeClearsig(keyring); err != nil { return nil, err } return &ret, nil } // }}} // Signer {{{ // Return the Entity (if one exists) that signed this set of Paragraphs. func (p *ParagraphReader) Signer() *openpgp.Entity { return p.signer } // }}} // All {{{ func (p *ParagraphReader) All() ([]Paragraph, error) { ret := []Paragraph{} for { paragraph, err := p.Next() if err == io.EOF { return ret, nil } else if err != nil { return []Paragraph{}, err } ret = append(ret, *paragraph) } } // }}} // Next {{{ // Consume the io.Reader and return the next parsed Paragraph, modulo // garbage lines causing us to return an error. func (p *ParagraphReader) Next() (*Paragraph, error) { paragraph := Paragraph{ Order: []string{}, Values: map[string]string{}, } var lastKey string for { line, err := p.reader.ReadString('\n') if err == io.EOF && line != "" { err = nil line = line + "\n" /* We'll clean up the last of the buffer. */ } if err == io.EOF { /* Let's return the parsed paragraph if we have it */ if len(paragraph.Order) > 0 { return ¶graph, nil } /* Else, let's go ahead and drop the EOF out raw */ return nil, err } else if err != nil { return nil, err } if line == "\n" { /* Lines are ended by a blank line; so we're able to go ahead * and return this guy as-is. All set. Done. Finished. */ return ¶graph, nil } /* Right, so we have a line in one of the following formats: * * "Key: Value" * " Foobar" * * Foobar is seen as a continuation of the last line, and the * Key line is a Key/Value mapping. */ if strings.HasPrefix(line, " ") { /* This is a continuation line; so we're going to go ahead and * clean it up, and throw it into the list. We're going to remove * the space, and if it's a line that only has a dot on it, we'll * remove that too (since " .\n" is actually "\n"). We only * trim off space on the right hand, because indentation under * the single space is up to the data format. Not us. */ /* TrimFunc(line[1:], unicode.IsSpace) is identical to calling * TrimSpace. */ line = strings.TrimRightFunc(line[1:], unicode.IsSpace) if line == "." { line = "" } if paragraph.Values[lastKey] == "" { paragraph.Values[lastKey] = line + "\n" } else { if !strings.HasSuffix(paragraph.Values[lastKey], "\n") { paragraph.Values[lastKey] = paragraph.Values[lastKey] + "\n" } paragraph.Values[lastKey] = paragraph.Values[lastKey] + line + "\n" } continue } /* So, if we're here, we've got a key line. Let's go ahead and split * this on the first key, and set that guy */ els := strings.SplitN(line, ":", 2) if len(els) != 2 { return nil, fmt.Errorf("Bad line: '%s' has no ':'", line) } /* We'll go ahead and take off any leading spaces */ lastKey = strings.TrimSpace(els[0]) value := strings.TrimSpace(els[1]) paragraph.Order = append(paragraph.Order, lastKey) paragraph.Values[lastKey] = value } } // }}} // decodeClearsig {{{ // Internal method to read an OpenPGP Clearsigned document, store related // OpenPGP information onto the shell Struct, and return any errors that // we encounter along the way, such as an invalid signature, unknown // signer, or incomplete document. If `keyring` is `nil`, checking of the // signed data is *not* preformed. func (p *ParagraphReader) decodeClearsig(keyring *openpgp.EntityList) error { // One *massive* downside here is that the OpenPGP module in Go operates // on byte arrays in memory, and *not* on Readers and Writers. This is a // huge PITA because it doesn't need to be that way, and this forces // clearsigned documents into memory. Which fucking sucks. But here // we are. It's likely worth a bug or two on this. signedData, err := ioutil.ReadAll(p.reader) if err != nil { return err } block, _ := clearsign.Decode(signedData) /* We're only interested in the first block. This may change in the * future, in which case, we should likely set reader back to * the remainder, and return that out to put through another * ParagraphReader, since it may have a different signer. */ if keyring == nil { /* As a special case, if the keyring is nil, we can go ahead * and assume this data isn't intended to be checked against the * keyring. So, we'll just pass on through. */ p.reader = bufio.NewReader(bytes.NewBuffer(block.Bytes)) return nil } /* Now, we have to go ahead and check that the signature is valid and * relates to an entity we have in our keyring */ signer, err := openpgp.CheckDetachedSignature( keyring, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, ) if err != nil { return err } p.signer = signer p.reader = bufio.NewReader(bytes.NewBuffer(block.Bytes)) return nil } // }}} // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/control/encode_test.go0000644000175000017500000000370012735211213021747 0ustar paultagpaultagpackage control_test import ( "bytes" "strings" "testing" "pault.ag/go/debian/control" "pault.ag/go/debian/dependency" "pault.ag/go/debian/version" ) type TestMarshalStruct struct { Foo string } type SomeComplexStruct struct { control.Paragraph Version version.Version Dependency dependency.Dependency } type TestParaMarshalStruct struct { control.Paragraph Foo string } func TestExtraMarshal(t *testing.T) { el := TestParaMarshalStruct{} isok(t, control.Unmarshal(&el, strings.NewReader(`Foo: test X-A-Test: Foo `))) assert(t, el.Foo == "test") writer := bytes.Buffer{} isok(t, control.Marshal(&writer, el)) assert(t, writer.String() == `Foo: test X-A-Test: Foo `) } func TestBasicMarshal(t *testing.T) { testStruct := TestMarshalStruct{Foo: "Hello"} writer := bytes.Buffer{} err := control.Marshal(&writer, testStruct) isok(t, err) assert(t, writer.String() == `Foo: Hello `) writer = bytes.Buffer{} err = control.Marshal(&writer, []TestMarshalStruct{ testStruct, }) isok(t, err) assert(t, writer.String() == `Foo: Hello `) writer = bytes.Buffer{} err = control.Marshal(&writer, []TestMarshalStruct{ testStruct, testStruct, }) isok(t, err) assert(t, writer.String() == `Foo: Hello Foo: Hello `) } func TestExternalMarshal(t *testing.T) { testStruct := SomeComplexStruct{} isok(t, control.Unmarshal(&testStruct, strings.NewReader(`Version: 1.0-1 Dependency: foo, bar X-Foo: bar `))) writer := bytes.Buffer{} err := control.Marshal(&writer, testStruct) isok(t, err) assert(t, testStruct.Dependency.Relations[0].Possibilities[0].Name == "foo") assert(t, writer.String() == `Version: 1.0-1 Dependency: foo, bar X-Foo: bar `) } func TestMultilineMarshal(t *testing.T) { testStruct := TestMarshalStruct{Foo: `Hello This Is A Test`} writer := bytes.Buffer{} err := control.Marshal(&writer, testStruct) isok(t, err) assert(t, writer.String() == `Foo: Hello This Is . A Test `) } // vim: foldmethod=marker golang-pault-go-debian-0.5/control/filehash.go0000644000175000017500000000647312735211213021250 0ustar paultagpaultag/* {{{ 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 ( "fmt" "strconv" "strings" "pault.ag/go/debian/transput" ) // A FileHash is an entry as found in the Files, Checksum-Sha1, and // Checksum-Sha256 entry for the .dsc or .changes files. type FileHash struct { // cb136f28a8c971d4299cc68e8fdad93a8ca7daf3 1131 dput-ng_1.9.dsc Algorithm string Hash string Size int64 Filename string } func FileHashFromHasher(path string, hasher transput.Hasher) FileHash { return FileHash{ Algorithm: hasher.Name(), Hash: fmt.Sprintf("%x", hasher.Sum(nil)), Size: hasher.Size(), Filename: path, } } type FileHashes []FileHash // {{{ Hash File implementations func (c FileHash) marshalControl() (string, error) { return fmt.Sprintf("%s %d %s", c.Hash, c.Size, c.Filename), nil } func (c *FileHash) unmarshalControl(algorithm, data string) error { var err error c.Algorithm = algorithm vals := strings.Fields(data) if len(data) < 4 { return fmt.Errorf("Error: Unknown Debian 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 } // {{{ MD5 FileHash type MD5FileHash struct{ FileHash } func (c *MD5FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("md5", data) } func (c MD5FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // {{{ SHA1 FileHash type SHA1FileHash struct{ FileHash } func (c *SHA1FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha1", data) } func (c SHA1FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // {{{ SHA256 FileHash type SHA256FileHash struct{ FileHash } func (c *SHA256FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha256", data) } func (c SHA256FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // {{{ SHA512 FileHash type SHA512FileHash struct{ FileHash } func (c *SHA512FileHash) UnmarshalControl(data string) error { return c.unmarshalControl("sha512", data) } func (c SHA512FileHash) MarshalControl() (string, error) { return c.marshalControl() } // }}} // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/control/changes_test.go0000644000175000017500000001156112735211213022126 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/control/changes.go0000644000175000017500000001602312735211213021065 0ustar paultagpaultag/* {{{ 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 { FileHash 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 []SHA1FileHash `control:"Checksums-Sha1" delim:"\n" strip:"\n\r\t "` ChecksumsSha256 []SHA256FileHash `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() return ParseChanges(bufio.NewReader(f), path) } // 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} return ret, Unmarshal(ret, reader) } // 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 referenced 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 return err } // Move the .changes file and all referenced 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 return err } // 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 } } return os.Remove(changes.Filename) } // vim: foldmethod=marker golang-pault-go-debian-0.5/control/index.go0000644000175000017500000001120712735211213020563 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/control/control_test.go0000644000175000017500000001074112735211213022175 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/control/decode.go0000644000175000017500000002100712735211213020676 0ustar paultagpaultag/* {{{ 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 ( "fmt" "io" "reflect" "strconv" "strings" "golang.org/x/crypto/openpgp" ) // Unmarshallable {{{ // The Unmarshallable 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 Unmarshallable interface { UnmarshalControl(data string) error } // }}} // Unmarshal {{{ // 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 according to // the rules above. If you wish to override how this writes to the nested // struct, objects that implement the Unmarshallable 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(data interface{}, reader io.Reader) error { decoder, err := NewDecoder(reader, nil) if err != nil { return err } return decoder.Decode(data) } // }}} // Decoder {{{ type Decoder struct { paragraphReader ParagraphReader } // NewDecoder {{{ func NewDecoder(reader io.Reader, keyring *openpgp.EntityList) (*Decoder, error) { ret := Decoder{} pr, err := NewParagraphReader(reader, keyring) if err != nil { return nil, err } ret.paragraphReader = *pr return &ret, nil } // }}} // Decode {{{ func (d *Decoder) Decode(into interface{}) error { return decode(&d.paragraphReader, reflect.ValueOf(into)) } // Top-level decode dispatch {{{ func decode(p *ParagraphReader, into reflect.Value) error { if into.Type().Kind() != reflect.Ptr { return fmt.Errorf("Decode can only decode into a pointer!") } switch into.Elem().Type().Kind() { case reflect.Struct: paragraph, err := p.Next() if err != nil { return err } return decodeStruct(*paragraph, into) case reflect.Slice: return decodeSlice(p, into) default: return fmt.Errorf("Can't Decode into a %s", into.Elem().Type().Name()) } return nil } // }}} // Top-level struct dispatch {{{ func decodeStruct(p Paragraph, into reflect.Value) error { /* If we have a pointer, let's follow it */ if into.Type().Kind() == reflect.Ptr { return decodeStruct(p, into.Elem()) } /* Store the Paragraph type for later use when checking Anonymous * values. */ paragraphType := reflect.TypeOf(Paragraph{}) /* Right, now, we're going to decode a Paragraph into the struct */ for i := 0; i < into.NumField(); i++ { field := into.Field(i) fieldType := into.Type().Field(i) if field.Type().Kind() == reflect.Struct { err := decodeStruct(p, field) if err != nil { return err } } /* First, let's get the name of the field as we'd index into the * map[string]string. */ paragraphKey := fieldType.Name if it := fieldType.Tag.Get("control"); it != "" { paragraphKey = it } if paragraphKey == "-" { /* If the key is "-", lets go ahead and skip it */ continue } /* Now, if we have an Anonymous field, we're either going to * set it to the Paragraph if it's the Paragraph Anonymous member, * or, more likely, continue through */ if fieldType.Anonymous { if fieldType.Type == paragraphType { /* Neat! Let's give the struct this data */ field.Set(reflect.ValueOf(p)) } else { /* Otherwise, we're going to avoid doing more maths on it */ continue } } if value, ok := p.Values[paragraphKey]; ok { if err := decodeStructValue(field, fieldType, value); err != nil { return err } continue } else { if fieldType.Tag.Get("required") == "true" { return fmt.Errorf( "Required field '%s' is missing!", fieldType.Name, ) } continue } } return nil } // }}} // set a struct field value {{{ func decodeStructValue(field reflect.Value, fieldType reflect.StructField, value string) error { switch field.Type().Kind() { case reflect.String: field.SetString(value) return nil case reflect.Int: if value == "" { field.SetInt(0) return nil } value, err := strconv.Atoi(value) if err != nil { return err } field.SetInt(int64(value)) return nil case reflect.Slice: return decodeStructValueSlice(field, fieldType, value) case reflect.Struct: return decodeStructValueStruct(field, fieldType, value) } return fmt.Errorf("Unknown type of field: %s", field.Type()) } // }}} // set a struct field value of type struct {{{ func decodeStructValueStruct(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().(Unmarshallable); ok { return unmarshal.UnmarshalControl(data) } return fmt.Errorf( "Type '%s' does not implement control.Unmarshallable", incomingField.Type.Name(), ) } // }}} // set a struct field value of type slice {{{ func decodeStructValueSlice(field reflect.Value, fieldType reflect.StructField, value string) error { underlyingType := field.Type().Elem() var delim = " " if it := fieldType.Tag.Get("delim"); it != "" { delim = it } var strip = "" if it := fieldType.Tag.Get("strip"); it != "" { strip = it } value = strings.Trim(value, strip) for _, el := range strings.Split(value, delim) { el = strings.Trim(el, strip) targetValue := reflect.New(underlyingType) err := decodeStructValue(targetValue.Elem(), fieldType, el) if err != nil { return err } field.Set(reflect.Append(field, targetValue.Elem())) } return nil } // }}} // Top-level slice dispatch {{{ func decodeSlice(p *ParagraphReader, into reflect.Value) error { flavor := into.Elem().Type().Elem() for { targetValue := reflect.New(flavor) /* Get a Paragraph */ para, err := p.Next() if err == io.EOF { break } else if err != nil { return err } if err := decodeStruct(*para, targetValue); err != nil { return err } into.Elem().Set(reflect.Append(into.Elem(), targetValue.Elem())) } return nil } // }}} // }}} // Signer {{{ func (d *Decoder) Signer() *openpgp.Entity { return d.paragraphReader.Signer() } // }}} // }}} // UnpackFromParagraph {{{ // Unpack a Paragraph into a Struct, as if that data had been unpacked into // that struct to begin with. The normal rules from running the Unmarshal // API directly apply when unpacking a Paragraph using UnpackFromParagraph. // // In most cases, the Unmarshal API should be sufficient. Use of this API // is mildly discouraged. func UnpackFromParagraph(para Paragraph, incoming interface{}) error { data := reflect.ValueOf(incoming) if data.Type().Kind() != reflect.Ptr { return fmt.Errorf("Can only Decode a pointer to a Struct") } return decodeStruct(para, data.Elem()) } // }}} // vim: foldmethod=marker golang-pault-go-debian-0.5/control/dsc_test.go0000644000175000017500000001734612735211213021276 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/LICENSE0000644000175000017500000000510212735211213016447 0ustar paultagpaultagCopyright (c) Paul R. Tagliamonte , 2015-2016 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. golang-pault-go-debian-0.5/changelog/0000755000175000017500000000000012735211213017373 5ustar paultagpaultaggolang-pault-go-debian-0.5/changelog/doc.go0000644000175000017500000000007512735211213020471 0ustar paultagpaultag/* Parse the Debian changelog format. */ package changelog golang-pault-go-debian-0.5/changelog/changelog.go0000644000175000017500000001057712735211213021663 0ustar paultagpaultag/* {{{ 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 golang-pault-go-debian-0.5/changelog/changelog_test.go0000644000175000017500000000605412735211213022715 0ustar paultagpaultag/* {{{ 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" "io" "log" "strings" "testing" "pault.ag/go/debian/changelog" ) /* * */ func isok(t *testing.T, err error) { if err != nil && err != io.EOF { 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