diff --git a/kadai1/tanaka0325/README.md b/kadai1/tanaka0325/README.md new file mode 100644 index 0000000..c04f8c5 --- /dev/null +++ b/kadai1/tanaka0325/README.md @@ -0,0 +1,60 @@ +# Image Converter + +## Spec + +``` +## 次の仕様を満たすコマンドを作って下さい + +- ディレクトリを指定する +- 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) +- ディレクトリ以下は再帰的に処理する +- 変換前と変換後の画像形式を指定できる(オプション) + +## 以下を満たすように開発してください + +- mainパッケージと分離する +- 自作パッケージと標準パッケージと準標準パッケージのみ使う +- 準標準パッケージ:golang.org/x以下のパッケージ +- ユーザ定義型を作ってみる +- GoDocを生成してみる +- Go Modulesを使ってみる +``` + +## Usage + +```zsh +# build +$ cd imgconv +$ go build -o imgconv ./cmd/imgconv + +# display help +$ ./imgconv -h +Usage of ./imgconv: + -f string + file extention before convert (default "jpg") + -n dry run + -t string + file extention after convert (default "png") + +# single directory +$ ./imgconv testdata/images + +# multi directories +$ ./imgconv testdata/images testdata/images2 + +# customize ext +$ ./imgconv -f png -t gif testdata/images + +# dry run +$ ./imgconv -n testdata/images testdata/images2 +testdata/images/sample1.jpg => testdata/images/sample1.png +testdata/images2/img/sample3.jpg => testdata/images2/img/sample3.png +testdata/images2/sample2.jpg => testdata/images2/sample2.png + +``` + +## 感想 + +- long option(?) はどうやってやれば良いのだろうか +- そもそも作りとしてこれで良いのだろうか・・・めっちゃ悩みました + diff --git a/kadai1/tanaka0325/imgconv/args.go b/kadai1/tanaka0325/imgconv/args.go new file mode 100644 index 0000000..4212901 --- /dev/null +++ b/kadai1/tanaka0325/imgconv/args.go @@ -0,0 +1,19 @@ +package imgconv + +// Args is type for command line arguments. +type Args []string + +func (args Args) uniq() []string { + m := map[string]bool{} + u := []string{} + + for _, v := range args { + if !m[v] { + m[v] = true + + u = append(u, v) + } + } + + return u +} diff --git a/kadai1/tanaka0325/imgconv/cmd/imgconv/main.go b/kadai1/tanaka0325/imgconv/cmd/imgconv/main.go new file mode 100644 index 0000000..875aefa --- /dev/null +++ b/kadai1/tanaka0325/imgconv/cmd/imgconv/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/gopherdojo/dojo8/kadai1/tanaka0325/imgconv" +) + +var options imgconv.Options +var args imgconv.Args + +func init() { + options.From = flag.String("f", "jpg", "file extension before convert") + options.To = flag.String("t", "png", "file extension after convert") + options.DryRun = flag.Bool("n", false, "dry run") + flag.Parse() + + args = flag.Args() +} + +func main() { + if err := imgconv.Run(options, args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/kadai1/tanaka0325/imgconv/cnv_image.go b/kadai1/tanaka0325/imgconv/cnv_image.go new file mode 100644 index 0000000..869a7d9 --- /dev/null +++ b/kadai1/tanaka0325/imgconv/cnv_image.go @@ -0,0 +1,81 @@ +package imgconv + +import ( + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + + "golang.org/x/image/bmp" + "golang.org/x/image/tiff" +) + +type Decoder interface { + Decode(io.Reader) (image.Image, error) +} + +type Encoder interface { + Encode(io.Writer, image.Image) error +} + +type DecodeEncoder interface { + Decoder + Encoder +} + +type CnvImage struct{} + +// CnvImagePng is type for png format. +type CnvImagePNG CnvImage + +func (ip CnvImagePNG) Decode(r io.Reader) (image.Image, error) { return png.Decode(r) } + +func (ip CnvImagePNG) Encode(w io.Writer, i image.Image) error { return png.Encode(w, i) } + +// CnvImageJPEG is type for jpeg format. +type CnvImageJPEG CnvImage + +func (ip CnvImageJPEG) Decode(r io.Reader) (image.Image, error) { return jpeg.Decode(r) } + +func (ip CnvImageJPEG) Encode(w io.Writer, i image.Image) error { return jpeg.Encode(w, i, nil) } + +// CnvImageGIF is type for gif format. +type CnvImageGIF CnvImage + +func (ip CnvImageGIF) Decode(r io.Reader) (image.Image, error) { return gif.Decode(r) } + +func (ip CnvImageGIF) Encode(w io.Writer, i image.Image) error { + return gif.Encode(w, i, &gif.Options{NumColors: 256}) +} + +// CnvImageBMP is type for bmp format. +type CnvImageBMP CnvImage + +func (ip CnvImageBMP) Decode(r io.Reader) (image.Image, error) { return bmp.Decode(r) } + +func (ip CnvImageBMP) Encode(w io.Writer, i image.Image) error { return bmp.Encode(w, i) } + +// CnvImageTIFF is type for tiff format. +type CnvImageTIFF CnvImage + +func (ip CnvImageTIFF) Decode(r io.Reader) (image.Image, error) { return tiff.Decode(r) } + +func (ip CnvImageTIFF) Encode(w io.Writer, i image.Image) error { return tiff.Encode(w, i, nil) } + +func newCnvImage(ext string) DecodeEncoder { + switch ext { + case "png": + return &CnvImagePNG{} + case "jpg", "jpeg": + return &CnvImageJPEG{} + case "gif": + return &CnvImageGIF{} + case "bmp": + return &CnvImageBMP{} + case "tiff", "tif": + return &CnvImageTIFF{} + } + + return nil +} diff --git a/kadai1/tanaka0325/imgconv/go.mod b/kadai1/tanaka0325/imgconv/go.mod new file mode 100644 index 0000000..ad64074 --- /dev/null +++ b/kadai1/tanaka0325/imgconv/go.mod @@ -0,0 +1,5 @@ +module github.com/gopherdojo/dojo8/kadai1/tanaka0325/imgconv + +go 1.14 + +require golang.org/x/image v0.0.0-20200618115811-c13761719519 diff --git a/kadai1/tanaka0325/imgconv/go.sum b/kadai1/tanaka0325/imgconv/go.sum new file mode 100644 index 0000000..394251b --- /dev/null +++ b/kadai1/tanaka0325/imgconv/go.sum @@ -0,0 +1,3 @@ +golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/kadai1/tanaka0325/imgconv/imgconv.go b/kadai1/tanaka0325/imgconv/imgconv.go new file mode 100644 index 0000000..e80c35c --- /dev/null +++ b/kadai1/tanaka0325/imgconv/imgconv.go @@ -0,0 +1,116 @@ +// Imgconv package is to convert images file format. +package imgconv + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +var allowedExts = []string{"png", "jpg", "jpeg", "gif", "bmp", "tiff", "tif"} +var fromExt string +var toExt string + +// Run is to convert image file format. +func Run(options Options, args Args) error { + // // validator + if err := options.validate(allowedExts); err != nil { + return err + } + + fromExt = *options.From + toExt = *options.To + + // get target image flepaths from args + paths, err := getTargetFilePaths(args, fromExt) + if err != nil { + return err + } + + // convert + for _, path := range paths { + filename := strings.Replace(path, "."+fromExt, "", -1) + // ")) + f := newCnvImage(fromExt) + t := newCnvImage(toExt) + + if *options.DryRun { + fmt.Printf("%s.%s => %s.%s \n", filename, fromExt, filename, toExt) + } else if err := convert(f, t, filename); err != nil { + return err + } + } + + return nil +} + +func convert(d Decoder, e Encoder, filename string) (err error) { + // open file + r, err := os.Open(filename + "." + fromExt) + if err != nil { + return + } + defer r.Close() + + // decode + img, err := d.Decode(r) + if err != nil { + return + } + + // create file + w, err := os.Create(filename + "." + toExt) + if err != nil { + return err + } + + defer func() { + err = w.Close() + }() + + // encode + if err := e.Encode(w, img); err != nil { + return err + } + + return +} + +func getTargetFilePaths(args Args, from string) ([]string, error) { + uns := args.uniq() + + paths := []string{} + for _, n := range uns { + if ok, err := isDir(n); err != nil { + return nil, err + } else if !ok { + return nil, fmt.Errorf("%s is not a directory", n) + } + + if err := filepath.Walk(n, func(path string, info os.FileInfo, err error) error { + if filepath.Ext(path) == "."+from { + paths = append(paths, path) + } + return nil + }); err != nil { + return nil, err + } + } + + return paths, nil +} + +func isDir(path string) (bool, error) { + f, err := os.Open(path) + if err != nil { + return false, err + } + + fi, err := f.Stat() + if err != nil { + return false, err + } + + return fi.IsDir(), nil +} diff --git a/kadai1/tanaka0325/imgconv/options.go b/kadai1/tanaka0325/imgconv/options.go new file mode 100644 index 0000000..e3e231c --- /dev/null +++ b/kadai1/tanaka0325/imgconv/options.go @@ -0,0 +1,37 @@ +package imgconv + +import ( + "errors" + "fmt" + "strings" +) + +// Options is type for command line options. +type Options struct { + From *string + To *string + DryRun *bool +} + +func (opt Options) validate(allowList []string) error { + to := strings.ToLower(*opt.To) + from := strings.ToLower(*opt.From) + targetExts := []string{to, from} + + for _, e := range targetExts { + if err := include(allowList, e); err != nil { + return fmt.Errorf("%w. ext is only allowd in %s", err, allowList) + } + } + + return nil +} + +func include(list []string, w string) error { + for _, e := range list { + if e == w { + return nil + } + } + return errors.New(w + " is not allowed") +} diff --git a/kadai1/tanaka0325/imgconv/testdata/images/sample1.jpg b/kadai1/tanaka0325/imgconv/testdata/images/sample1.jpg new file mode 100644 index 0000000..7a10881 Binary files /dev/null and b/kadai1/tanaka0325/imgconv/testdata/images/sample1.jpg differ diff --git a/kadai1/tanaka0325/imgconv/testdata/images2/img/sample3.jpg b/kadai1/tanaka0325/imgconv/testdata/images2/img/sample3.jpg new file mode 100644 index 0000000..7a10881 Binary files /dev/null and b/kadai1/tanaka0325/imgconv/testdata/images2/img/sample3.jpg differ diff --git a/kadai1/tanaka0325/imgconv/testdata/images2/sample2.jpg b/kadai1/tanaka0325/imgconv/testdata/images2/sample2.jpg new file mode 100644 index 0000000..7a10881 Binary files /dev/null and b/kadai1/tanaka0325/imgconv/testdata/images2/sample2.jpg differ