Chapter 10 Packages and The Go Tool

Exercise 10.1: Extend thejpegprogram so that it converts any supported input format to any output format, usingimage.Decodeto detect the input format and a flag to select the output format.

// Copyright © 2017 [email protected]
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// See page 287.

//!+main

// The jpeg command reads a PNG image from the standard input
// and writes it as a JPEG image to the standard output.
package main

import (
    "fmt"
    "image"
    "image/jpeg"
    "io"
    "os"
    "image/png"
    "flag"
    "log"
)

var format = flag.String("format", "jpg", "output format(jpg/png)")

func main() {
    flag.Parse()
    for _, p := range flag.Args() {
        file, err := os.Open(p)
        if err != nil {
            log.Printf("%s open failed: %v\n", file, err)
            continue
        }

        img, kind, err := image.Decode(file)
        if err != nil {
            log.Printf("%s file decode err: %v", p, err)
            continue
        }
        fmt.Fprintln(os.Stderr, "Input format =", kind)
        if kind == *format {
            log.Printf("%s is already %s format", p, kind)
            continue
        }

        outfilename := p + "." + *format
        outfile, err := os.Create(outfilename)
        if err != nil {
            log.Printf("create %s failed: %v", outfilename, err)
            continue
        }
        if *format == "jpg" {
            if err := toJPEG(img, outfile); err != nil {
                fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
                os.Exit(1)
            }
        } else if *format == "png" {
            if err := toPNG(img, outfile); err != nil {
                fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
                os.Exit(1)
            }
        }
        file.Close()
        outfile.Close()
    }
}

func toJPEG(img image.Image, out io.Writer) error {
    return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
}

func toPNG(img image.Image, out io.Writer) error {
    return png.Encode(out, img)
}

//!-main

Exercise 10.2: Define a generic archive file-reading function capable of reading ZIP files (archive/zip) and POSIX tar files (archive/tar). Use a registration mechanism similar to the one described above so that support for each file format can be plugged in using blank imports.

── main.go
├── reader
│   ├── pluggins
│   │   ├── tar
│   │   │   └── tar.go
│   │   └── zip
│   │       └── zip.go
│   └── reader.go
├── readme.tar
└── readme.zip

// main.go
// Copyright © 2017 [email protected]
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package main

import (
    "log"
    _ "gopl.io/gopl-solutions/ch10/10.2/reader/pluggins/zip"
    _ "gopl.io/gopl-solutions/ch10/10.2/reader/pluggins/tar"

    "gopl.io/gopl-solutions/ch10/10.2/reader"
)

func main() {
    // read readme.zip/tar

    err := reader.ArchiveReader("readme.zip")
    if err != nil {
        log.Fatal(err)
    }

    err = reader.ArchiveReader("readme.tar")
    if err != nil {
        log.Fatal(err)
    }
}


// reader.go
package reader

import (
    "strings"
    "fmt"
)

// register for zip/tar file
func Register(n string, f func(s string) error) {
    format := format{name: n, readFunc: f}
    formats = append(formats, format)
}

type format struct {
    name     string
    readFunc func(s string) error
}

var formats []format

func ArchiveReader(s string) error {
    period := strings.LastIndex(s, ".")
    if period == -1 {
        return fmt.Errorf("unknowns archive format")
    }
    suffix := s[period+1:]
    for _, format := range formats {
        if suffix == format.name {
            return format.readFunc(s)
        }
    }
    return fmt.Errorf("unsupported archive format")
}

// tar.go
package tar

import (
    "archive/tar"
    "fmt"
    "io"
    "os"

    "gopl.io/gopl-solutions/ch10/10.2/reader"
)

func Reader(s string) error {
    // Open the tar archive for reading.
    file, err := os.Open(s)
    if err != nil {
        return err
    }

    tr := tar.NewReader(file)
    // Iterate through the files in the archive.
    for {
        hdr, err := tr.Next()
        if err == io.EOF {
            // end of tar archive
            break
        }
        if err != nil {
            return err
        }
        fmt.Printf("%s\n", hdr.Name)
    }
    file.Close()
    return nil
}

func init() {
    reader.Register("tar", Reader)
}

// zip.go
package zip

import (
    "archive/zip"
    "fmt"
    "gopl.io/gopl-solutions/ch10/10.2/reader"
)

func Reader(s string) error {
    // Open a zip archive for reading.
    r, err := zip.OpenReader(s)
    if err != nil {
        return err
    }
    defer r.Close()

    // Iterate through the files in the archive, printing all file names
    for _, f := range r.File {
        fmt.Printf("%s\n", f.Name)
    }
    return nil
}

func init() {
    reader.Register("zip", Reader)
}

Exercise10.3: Usingfetch http://gopl.io/ch1/helloworld?go-get=1, find out which service hosts the code samples for this book. (HTTP requests from go get include the go-get parameter so that servers can distinguish them from ordinary browser requests.)

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="gopl.io git https://github.com/adonovan/gopl.io">
</head>
<body>
</body>
</html>

Exercise 10.4: Construct a tool that reports the set of all packages in the workspace that transitively depend on the packages specified by the arguments. Hint: you will need to run go listtwice, once for the initial packages and once for all packages. You may want to parse its JSON output using theencoding/jsonpackage (§4.5).

// if you use zsh, use `go list \...` instead, since zsh will interpret `...` as `....`

// Copyright © 2017 [email protected]
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package main

import (
    "os/exec"
    "log"
    "os"
    "bytes"
    "encoding/json"
    "fmt"
)

type Package struct {
    ImportPath string   // import path of package in dir
    Name       string   // package name
    Deps       []string // all (recursively) imported dependencies
}

func binarySearch(array []string, key string) bool {
    low, high := 0, len(array)-1
    for low <= high {
        var mid = (low + high) / 2
        if key == array[mid] {
            return true
        }
        if key < array[mid] {
            high = mid - 1
        } else {
            low = mid + 1
        }
    }
    return false
}

func main() {
    var err error
    // first validate parameter package
    if len(os.Args) < 2 {
        log.Fatalf("useage: main package")
    }
    key := os.Args[1]
    cmd := exec.Command("go", "list", key)
    if _, err = cmd.Output(); err != nil {
        log.Fatalf("package %s invalid: %v", key, err)
    }

    // list all packages in workspaces, first in text format
    cmd = exec.Command("go", "list", "-json", "...")
    if cmd == nil {
        log.Fatalf("can't run go list")
    }

    var output []byte
    if output, err = cmd.Output(); err != nil {
        log.Fatal(err)
    }

    var stack []byte
    var buf bytes.Buffer
    for _, b := range output {
        switch b {
        case '{':
            stack = append(stack, b)
        case '}':
            stack = stack[0:len(stack)-1]
        }
        // delete all newline and space
        buf.WriteByte(b)
        if b == '}' && len(stack) == 0 {
            // unmarshal json
            var info Package
            if err = json.Unmarshal(buf.Bytes(), &info); err != nil {
                log.Fatal(err)
            }
            if binarySearch(info.Deps, key) {
                fmt.Println(info.ImportPath)
            }
            buf.Truncate(0)
        }
    }
}

results matching ""

    No results matching ""