Chapter 10 Packages and The Go Tool
Exercise 10.1: Extend thejpeg
program so that it converts any supported input format to any output format, usingimage.Decode
to 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 list
twice, once for the initial packages and once for all packages. You may want to parse its JSON output using theencoding/json
package (§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)
}
}
}