From 466d5d2104a90c3f35725553716d9626aa59d905 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Sun, 5 Jul 2020 21:48:58 +0900 Subject: [PATCH 01/14] Added folder segakazzz --- kadai1/segakazzz/.gitignore | 7 ++ kadai1/segakazzz/README.md | 51 +++++++++ kadai1/segakazzz/go.mod | 3 + kadai1/segakazzz/go.sum | 1 + kadai1/segakazzz/image/download.zsh | 12 ++ kadai1/segakazzz/imgconv/go.mod | 3 + kadai1/segakazzz/imgconv/imgconv.go | 169 ++++++++++++++++++++++++++++ kadai1/segakazzz/main.go | 7 ++ 8 files changed, 253 insertions(+) create mode 100644 kadai1/segakazzz/.gitignore create mode 100644 kadai1/segakazzz/README.md create mode 100644 kadai1/segakazzz/go.mod create mode 100644 kadai1/segakazzz/go.sum create mode 100755 kadai1/segakazzz/image/download.zsh create mode 100644 kadai1/segakazzz/imgconv/go.mod create mode 100644 kadai1/segakazzz/imgconv/imgconv.go create mode 100644 kadai1/segakazzz/main.go diff --git a/kadai1/segakazzz/.gitignore b/kadai1/segakazzz/.gitignore new file mode 100644 index 0000000..ba599ea --- /dev/null +++ b/kadai1/segakazzz/.gitignore @@ -0,0 +1,7 @@ +.idea +image/in/* +image/out/* +image/*.jpg +image/*.png +kadai1-segakazzz + diff --git a/kadai1/segakazzz/README.md b/kadai1/segakazzz/README.md new file mode 100644 index 0000000..3be7c71 --- /dev/null +++ b/kadai1/segakazzz/README.md @@ -0,0 +1,51 @@ +# 課題 1 画像変換コマンドを作ろう + +## 課題内容 + +### 次の仕様を満たすコマンドを作って下さい + +- ディレクトリを指定する +- 指定したディレクトリ以下の JPG ファイルを PNG に変換(デフォルト) +- ディレクトリ以下は再帰的に処理する +- 変換前と変換後の画像形式を指定できる(オプション) + +### 以下を満たすように開発してください + +- main パッケージと分離する +- 自作パッケージと標準パッケージと準標準パッケージのみ使う +- 準標準パッケージ:golang.org/x 以下のパッケージ +- ユーザ定義型を作ってみる +- GoDoc を生成してみる +- Go Modules を使ってみる + +## 回答・動作例 + +- 自作パッケージとして  github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv を作成 +- imgconv.RunConverter()でメインの処理を実行 +- -d オプションで指定したディレクトリの画像をソースとして使用する +- -d オプションで指定したディレクトリ内に out フォルダが作成され、出力される +- -i オプションで入力画像の  拡張子を指定可能(jpg or png) デフォルトは jpg +- -o オプションで出力画像の拡張子を指定可能(png or jpg) デフォルトは png +- (補足) image フォルダ内の download.zsh 実行して、[The Cat API](https://thecatapi.com/) から jpg ファイルを 10 個ダウンロード可能 + +### 動作例 main.go + +``` +$ go build -o kadai1-segakazzz +$ ./kadai1-segakazzz -d [imagedir] -i [jpg|png] -o [png|jpg] +``` + +``` +package main + +import "github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv" + +func main() { + imgconv.RunConverter() +} + +``` + +### 感想等 + +- Go Modules については、使用するパッケージのバージョン管理に使用されるものと理解しましたが、今回使用したパッケージは標準パッケージのみで、バージョンを指定する必要はなさそうでしたので requires の部分は書いていません。 diff --git a/kadai1/segakazzz/go.mod b/kadai1/segakazzz/go.mod new file mode 100644 index 0000000..e847375 --- /dev/null +++ b/kadai1/segakazzz/go.mod @@ -0,0 +1,3 @@ +module github.com/gopherdojo/dojo8/kadai1/segakazzz + +go 1.14 diff --git a/kadai1/segakazzz/go.sum b/kadai1/segakazzz/go.sum new file mode 100644 index 0000000..a632da4 --- /dev/null +++ b/kadai1/segakazzz/go.sum @@ -0,0 +1 @@ +github.com/gopherdojo/dojo8 v0.0.0-20200703052727-6a79d18126bf h1:lpYevjFQMxI5VNBc3WXV6Z5pDDrdppdDKwmeBoyt5BE= diff --git a/kadai1/segakazzz/image/download.zsh b/kadai1/segakazzz/image/download.zsh new file mode 100755 index 0000000..e9fd9f0 --- /dev/null +++ b/kadai1/segakazzz/image/download.zsh @@ -0,0 +1,12 @@ +#!/bin/zsh + +curl https://cdn2.thecatapi.com/images/8er.jpg > 001.jpg +curl https://cdn2.thecatapi.com/images/ceh.jpg > 002.jpg +curl https://cdn2.thecatapi.com/images/MTU2MjQ4NA.jpg > 003.jpg +curl https://cdn2.thecatapi.com/images/5qc.jpg > 004.jpg +curl https://cdn2.thecatapi.com/images/962.jpg > 005.jpg +curl https://cdn2.thecatapi.com/images/MTc2Mzc0Mw.jpg > 006.jpg +curl https://cdn2.thecatapi.com/images/bos.jpg > 007.jpg +curl https://cdn2.thecatapi.com/images/SX2DvLw7u.jpg > 008.jpg +curl https://cdn2.thecatapi.com/images/9eh.jpg > 009.jpg +curl https://cdn2.thecatapi.com/images/7bo.jpg > 010.jpg \ No newline at end of file diff --git a/kadai1/segakazzz/imgconv/go.mod b/kadai1/segakazzz/imgconv/go.mod new file mode 100644 index 0000000..c04a52c --- /dev/null +++ b/kadai1/segakazzz/imgconv/go.mod @@ -0,0 +1,3 @@ +module github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv + +go 1.14 diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go new file mode 100644 index 0000000..867998b --- /dev/null +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -0,0 +1,169 @@ +// Package imgconv is for Gopher Dojo Kadai1 +package imgconv + +import ( + "flag" + "fmt" + "image" + "image/jpeg" + "image/png" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" +) + +type converter struct { + dirname string + input string + output string +} + +// RunConverter converts all image files in the directory which you indicate with -d option. +// If the process is completed succeessfully, you will see the list of output files and "Done!" +// message in the standard output. +func RunConverter() { + var ( + dir = flag.String("d", ".", "Indicate directory to convert") + in = flag.String("i", "jpg", "Indicate input image file's extension") + out = flag.String("o", "png", "Indicate output image file's extension") + err error + ) + + flag.Parse() + c, err := newConverter(*dir, *in, *out) + if err != nil { + log.Fatal(err) + } + err = c.Convert() + if err != nil { + log.Fatal(err) + } + fmt.Println("Done!") + +} + +func newConverter(dirname string, input string, output string) (*converter, error) { + switch input { + case "jpg", "png": + input = strings.ToLower(input) + default: + return &converter{}, fmt.Errorf("Input extension is not valid. Select one from jpg/png") + } + switch output { + case "jpg", "png": + output = strings.ToLower(output) + default: + return &converter{}, fmt.Errorf("Output extension is not valid. Select one from jpg/png") + } + + if input == output { + return &converter{}, fmt.Errorf("Input and Output extensiton is the same. No convertion is needed") + } + return &converter{dirname: dirname, input: input, output: output}, nil +} + +// Convert method converts all jpg files in dirname to png. "out" folder is generated if it doesn't exist. +func (c *converter) Convert() (e error) { + files, e := c.getSourceFiles() + if e != nil { + return + } + e = c.convertFiles(files) + if e != nil { + return + } + return nil +} + +func (c *converter) getSourceFiles() ([]os.FileInfo, error) { + files, err := ioutil.ReadDir(c.dirname) + if err != nil { + return []os.FileInfo{}, err + } + return files, nil +} + +func (c *converter) convertFiles(files []os.FileInfo) (e error) { + re, e := regexp.Compile("." + c.input + "$") + if e != nil { + return + } + for _, file := range files { + if re.MatchString(file.Name()) { + e = c.convertSingle(file.Name()) + if e != nil { + return + } + } + } + return nil +} + +func (c *converter) convertSingle(filename string) (e error) { + input := filepath.Join(c.dirname, filename) + outDir := filepath.Join(c.dirname, "out") + output := filepath.Join(outDir, strings.Replace(strings.ToLower(filename), "."+c.input, "."+c.output, -1)) + fmt.Println(output) + if !c.dirExists(outDir) { + os.Mkdir(outDir, 0755) + } + + in, _ := os.Open(input) + var out *os.File + if c.fileExists(output) { + out, e = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if e != nil { + return + } + } else { + out, e = os.Create(output) + if e != nil { + return + } + } + defer in.Close() + defer out.Close() + + var ( + img image.Image + ) + switch c.input { + case "jpg": + img, e = jpeg.Decode(in) + case "png": + img, e = png.Decode(in) + } + + if e != nil { + return + } + switch c.output { + case "png": + e = png.Encode(out, img) + case "jpg": + e = jpeg.Encode(out, img, nil) + } + if e != nil { + return + } + return nil +} + +func (c *converter) fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func (c *converter) dirExists(dirname string) bool { + info, err := os.Stat(dirname) + if os.IsNotExist(err) { + return false + } + return info.IsDir() +} diff --git a/kadai1/segakazzz/main.go b/kadai1/segakazzz/main.go new file mode 100644 index 0000000..a1a519f --- /dev/null +++ b/kadai1/segakazzz/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv" + +func main() { + imgconv.RunConverter() +} From e019888c0a1d399278c82d63ccc8f8fd0b0ba74b Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Sun, 5 Jul 2020 21:53:03 +0900 Subject: [PATCH 02/14] Updated README.md --- kadai1/segakazzz/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kadai1/segakazzz/README.md b/kadai1/segakazzz/README.md index 3be7c71..74dfb11 100644 --- a/kadai1/segakazzz/README.md +++ b/kadai1/segakazzz/README.md @@ -48,4 +48,5 @@ func main() { ### 感想等 -- Go Modules については、使用するパッケージのバージョン管理に使用されるものと理解しましたが、今回使用したパッケージは標準パッケージのみで、バージョンを指定する必要はなさそうでしたので requires の部分は書いていません。 +- Go Modules については、使用するパッケージのバージョン管理に使用されるものと理解しましたが、今回使用したパッケージは標準パッケージのみで、バージョンを指定する必要はなさそうでしたので require の部分は書いていません。 +- パッケージの書き方の標準を理解しておらず、勘に頼っているところがあるので、良い書き方を知りたいです。 From 98dc6498e424695fe6b1f3129bef2bad0b2492ca Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 17:19:42 +0900 Subject: [PATCH 03/14] Renamed folder name from image to testdata --- kadai1/segakazzz/.gitignore | 8 ++++---- kadai1/segakazzz/{image => testdata}/download.zsh | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename kadai1/segakazzz/{image => testdata}/download.zsh (100%) diff --git a/kadai1/segakazzz/.gitignore b/kadai1/segakazzz/.gitignore index ba599ea..5de97bb 100644 --- a/kadai1/segakazzz/.gitignore +++ b/kadai1/segakazzz/.gitignore @@ -1,7 +1,7 @@ .idea -image/in/* -image/out/* -image/*.jpg -image/*.png +testdata/in/* +testdata/out/* +testdata/*.jpg +testdata/*.png kadai1-segakazzz diff --git a/kadai1/segakazzz/image/download.zsh b/kadai1/segakazzz/testdata/download.zsh similarity index 100% rename from kadai1/segakazzz/image/download.zsh rename to kadai1/segakazzz/testdata/download.zsh From 400ec05973d5129217ff5760ef11601354a20789 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 17:26:04 +0900 Subject: [PATCH 04/14] Removed err declaration as it's duplicated --- kadai1/segakazzz/imgconv/imgconv.go | 1 - 1 file changed, 1 deletion(-) diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go index 867998b..1f84fef 100644 --- a/kadai1/segakazzz/imgconv/imgconv.go +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -29,7 +29,6 @@ func RunConverter() { dir = flag.String("d", ".", "Indicate directory to convert") in = flag.String("i", "jpg", "Indicate input image file's extension") out = flag.String("o", "png", "Indicate output image file's extension") - err error ) flag.Parse() From b08eea35f47e47ff118b3968235b4825f3962180 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 22:35:14 +0900 Subject: [PATCH 05/14] Replaced log.fatal to error & used os.Exit() --- kadai1/segakazzz/imgconv/go.mod | 3 --- kadai1/segakazzz/imgconv/imgconv.go | 11 ++++++----- kadai1/segakazzz/main.go | 12 ++++++++++-- 3 files changed, 16 insertions(+), 10 deletions(-) delete mode 100644 kadai1/segakazzz/imgconv/go.mod diff --git a/kadai1/segakazzz/imgconv/go.mod b/kadai1/segakazzz/imgconv/go.mod deleted file mode 100644 index c04a52c..0000000 --- a/kadai1/segakazzz/imgconv/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv - -go 1.14 diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go index 1f84fef..62f5b92 100644 --- a/kadai1/segakazzz/imgconv/imgconv.go +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -8,7 +8,6 @@ import ( "image/jpeg" "image/png" "io/ioutil" - "log" "os" "path/filepath" "regexp" @@ -24,7 +23,7 @@ type converter struct { // RunConverter converts all image files in the directory which you indicate with -d option. // If the process is completed succeessfully, you will see the list of output files and "Done!" // message in the standard output. -func RunConverter() { +func RunConverter() error { var ( dir = flag.String("d", ".", "Indicate directory to convert") in = flag.String("i", "jpg", "Indicate input image file's extension") @@ -34,14 +33,16 @@ func RunConverter() { flag.Parse() c, err := newConverter(*dir, *in, *out) if err != nil { - log.Fatal(err) + // log.Fatal(err) + return err } err = c.Convert() if err != nil { - log.Fatal(err) + // log.Fatal(err) + return err } fmt.Println("Done!") - + return nil } func newConverter(dirname string, input string, output string) (*converter, error) { diff --git a/kadai1/segakazzz/main.go b/kadai1/segakazzz/main.go index a1a519f..081416d 100644 --- a/kadai1/segakazzz/main.go +++ b/kadai1/segakazzz/main.go @@ -1,7 +1,15 @@ package main -import "github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv" +import ( + "os" + + "github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv" +) func main() { - imgconv.RunConverter() + err := imgconv.RunConverter() + if err != nil { + os.Exit(1) + } + os.Exit(0) } From 6292c2dbeef1ca6068331761732a0fc7b0c739de Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 22:37:55 +0900 Subject: [PATCH 06/14] Replaced empty pointer to nil --- kadai1/segakazzz/imgconv/imgconv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go index 62f5b92..9558467 100644 --- a/kadai1/segakazzz/imgconv/imgconv.go +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -50,17 +50,17 @@ func newConverter(dirname string, input string, output string) (*converter, erro case "jpg", "png": input = strings.ToLower(input) default: - return &converter{}, fmt.Errorf("Input extension is not valid. Select one from jpg/png") + return nil, fmt.Errorf("Input extension is not valid. Select one from jpg/png") } switch output { case "jpg", "png": output = strings.ToLower(output) default: - return &converter{}, fmt.Errorf("Output extension is not valid. Select one from jpg/png") + return nil, fmt.Errorf("Output extension is not valid. Select one from jpg/png") } if input == output { - return &converter{}, fmt.Errorf("Input and Output extensiton is the same. No convertion is needed") + return nil, fmt.Errorf("Input and Output extensiton is the same. No convertion is needed") } return &converter{dirname: dirname, input: input, output: output}, nil } From d2e69995c6d6b7c448ddf64ec6baa8b2cecec784 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 22:41:27 +0900 Subject: [PATCH 07/14] Updated named return to non-named one --- kadai1/segakazzz/imgconv/imgconv.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go index 9558467..4c36bad 100644 --- a/kadai1/segakazzz/imgconv/imgconv.go +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -66,14 +66,14 @@ func newConverter(dirname string, input string, output string) (*converter, erro } // Convert method converts all jpg files in dirname to png. "out" folder is generated if it doesn't exist. -func (c *converter) Convert() (e error) { +func (c *converter) Convert() error { files, e := c.getSourceFiles() if e != nil { - return + return e } e = c.convertFiles(files) if e != nil { - return + return e } return nil } @@ -81,21 +81,21 @@ func (c *converter) Convert() (e error) { func (c *converter) getSourceFiles() ([]os.FileInfo, error) { files, err := ioutil.ReadDir(c.dirname) if err != nil { - return []os.FileInfo{}, err + return nil, err } return files, nil } -func (c *converter) convertFiles(files []os.FileInfo) (e error) { +func (c *converter) convertFiles(files []os.FileInfo) error { re, e := regexp.Compile("." + c.input + "$") if e != nil { - return + return e } for _, file := range files { if re.MatchString(file.Name()) { e = c.convertSingle(file.Name()) if e != nil { - return + return e } } } From d832ddfc13bf08eba42f2d9daf828907cb46f448 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 22:43:44 +0900 Subject: [PATCH 08/14] Updated named return to non-named one, added error check --- kadai1/segakazzz/imgconv/imgconv.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go index 4c36bad..90a2453 100644 --- a/kadai1/segakazzz/imgconv/imgconv.go +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -102,7 +102,7 @@ func (c *converter) convertFiles(files []os.FileInfo) error { return nil } -func (c *converter) convertSingle(filename string) (e error) { +func (c *converter) convertSingle(filename string) error { input := filepath.Join(c.dirname, filename) outDir := filepath.Join(c.dirname, "out") output := filepath.Join(outDir, strings.Replace(strings.ToLower(filename), "."+c.input, "."+c.output, -1)) @@ -111,17 +111,20 @@ func (c *converter) convertSingle(filename string) (e error) { os.Mkdir(outDir, 0755) } - in, _ := os.Open(input) + in, e := os.Open(input) + if e != nil { + return e + } var out *os.File if c.fileExists(output) { out, e = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if e != nil { - return + return e } } else { out, e = os.Create(output) if e != nil { - return + return e } } defer in.Close() @@ -138,7 +141,7 @@ func (c *converter) convertSingle(filename string) (e error) { } if e != nil { - return + return e } switch c.output { case "png": @@ -147,7 +150,7 @@ func (c *converter) convertSingle(filename string) (e error) { e = jpeg.Encode(out, img, nil) } if e != nil { - return + return e } return nil } From 7fa0043d682f403ed1b96cb160971ec003af4236 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Fri, 10 Jul 2020 23:09:18 +0900 Subject: [PATCH 09/14] Set error check on closing file --- kadai1/segakazzz/imgconv/imgconv.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/kadai1/segakazzz/imgconv/imgconv.go b/kadai1/segakazzz/imgconv/imgconv.go index 90a2453..cd19e19 100644 --- a/kadai1/segakazzz/imgconv/imgconv.go +++ b/kadai1/segakazzz/imgconv/imgconv.go @@ -102,7 +102,7 @@ func (c *converter) convertFiles(files []os.FileInfo) error { return nil } -func (c *converter) convertSingle(filename string) error { +func (c *converter) convertSingle(filename string) (e error) { input := filepath.Join(c.dirname, filename) outDir := filepath.Join(c.dirname, "out") output := filepath.Join(outDir, strings.Replace(strings.ToLower(filename), "."+c.input, "."+c.output, -1)) @@ -115,20 +115,24 @@ func (c *converter) convertSingle(filename string) error { if e != nil { return e } + + defer func() { + e = in.Close() + }() + var out *os.File if c.fileExists(output) { out, e = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - if e != nil { - return e - } } else { out, e = os.Create(output) - if e != nil { - return e - } } - defer in.Close() - defer out.Close() + if e != nil { + return e + } + + defer func() { + e = out.Close() + }() var ( img image.Image From 2de02f8d353aacc1d3adba9ef7ca85afe411d55c Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Thu, 16 Jul 2020 11:07:31 +0900 Subject: [PATCH 10/14] Added Test of newconverter --- kadai2/segakazzz/.gitignore | 4 + kadai2/segakazzz/README.md | 182 ++++++++++++++++++++++ kadai2/segakazzz/imgconv/imgconv.go | 183 +++++++++++++++++++++++ kadai2/segakazzz/imgconv/imgconv_test.go | 57 +++++++ kadai2/segakazzz/main.go | 15 ++ kadai2/segakazzz/testdata/download.zsh | 12 ++ 6 files changed, 453 insertions(+) create mode 100644 kadai2/segakazzz/.gitignore create mode 100644 kadai2/segakazzz/README.md create mode 100644 kadai2/segakazzz/imgconv/imgconv.go create mode 100644 kadai2/segakazzz/imgconv/imgconv_test.go create mode 100644 kadai2/segakazzz/main.go create mode 100755 kadai2/segakazzz/testdata/download.zsh diff --git a/kadai2/segakazzz/.gitignore b/kadai2/segakazzz/.gitignore new file mode 100644 index 0000000..2b6ec85 --- /dev/null +++ b/kadai2/segakazzz/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +testdata/*.jpg +testdata/*.png +testdata/out \ No newline at end of file diff --git a/kadai2/segakazzz/README.md b/kadai2/segakazzz/README.md new file mode 100644 index 0000000..dc83a51 --- /dev/null +++ b/kadai2/segakazzz/README.md @@ -0,0 +1,182 @@ +# Try 1: io.Reader と io.Writer について調べてみよう + +## 標準パッケージでどのように使われているか + +### io.Reader/io.Writer とは + +- golang.org の定義によると、それぞれメソッド Read/Write を持つインターフェイス +- io.Reader -> バイト列を読み、Read 関数のバイトスライスへ格納 +- io.Writer -> Write 関数の引数バイトスライスを書き出し +- インターフェースで宣言されているメソッドがすべて実装されている構造体ならばどんな型でも対象のインターフェイスとして扱うことができる + +参照)https://golang.org/pkg/io/ + +``` +type Reader interface { + Read(p []byte) (n int, err error) +} +``` + +``` +type Writer interface { + Write(p []byte) (n int, err error) +} +``` + +### 標準パッケージでの使用 + +リンクの通り多くのパッケージで使用されている + +- io.Reader https://golang.org/search?q=Read#Global +- io.Writer https://golang.org/search?q=Write#Global + +#### 代表的なもの + +- [package bufio](https://golang.org/search?q=Read#Global_pkg/bufio) +- [package csv](https://golang.org/search?q=Read#Global_pkg/csv) +- os.Stdin +- os.Stdout +- os.File + +## io.Reader と io.Writer があることでどういう利点があるのか具体例を挙げて考えてみる + +### 1. どこからデータを読み込み、どこへ書き出すかについて自由に実装ができる + +os.Stdin、os.File からの読み込み、os.Stdout, os.File への書き出しなど、Read/Write 関数の引数・戻値が同じのため、呼び出す元の型を変えるだけで、異なる読み出し・書き出し先を実装できる + +### 2. シンプルな構造で、カスタマイズが簡単に実装できる + +引数1つ、戻値が2つしかないシンプルな関数を書くだけでよい。下のコードはアルファベット以外の文字を読み込まないようにカスタマイズした、Reader の例。 + +```golang +type alphaReader struct { + src string + cur int +} + +func newAlphaReader(src string) *alphaReader { + return &alphaReader{src: src} +} + +func alpha(r byte) byte { + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { + return r + } + return 0 +} + +func (a *alphaReader) Read(p []byte) (int, error) { + if a.cur >= len(a.src) { + return 0, io.EOF + } + + x := len(a.src) - a.cur + n, bound := 0, 0 + if x >= len(p) { + bound = len(p) + } else if x <= len(p) { + bound = x + } + + buf := make([]byte, bound) + for n < bound { + if char := alpha(a.src[a.cur]); char != 0 { + buf[n] = char + } + n++ + a.cur++ + } + copy(p, buf) + return n, nil +} + +func main() { + reader := newAlphaReader("Hello! It's 9am, where is the sun?") + p := make([]byte, 4) + for { + n, err := reader.Read(p) + if err == io.EOF { + break + } + fmt.Print(string(p[:n])) + } + fmt.Println() +} +``` + +### 3. 既存の Reader/Writer からの拡張が簡単に実装できる + +下は io.Reader を持つ構造体の例。下の main 関数では strings.Reader に 2.での例と同様、アルファベットのみ読み込む機能を拡張しているが、この部分は alphaReader を変更することなく、main 関数内で os.File、bufio.Reader などに変更するだけで実現が可能になる。 + +```golang +type alphaReader struct { + reader io.Reader +} + +func newAlphaReader(reader io.Reader) *alphaReader { + return &alphaReader{reader: reader} +} + +func alpha(r byte) byte { + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { + return r + } + return 0 +} + +func (a *alphaReader) Read(p []byte) (int, error) { + n, err := a.reader.Read(p) + if err != nil { + return n, err + } + buf := make([]byte, n) + for i := 0; i < n; i++ { + if char := alpha(p[i]); char != 0 { + buf[i] = char + } + } + + copy(p, buf) + return n, nil +} + +func main() { + // use an io.Reader as source for alphaReader + reader := newAlphaReader(strings.NewReader("Hello! It's 9am, where is the sun?")) + p := make([]byte, 4) + for { + n, err := reader.Read(p) + if err == io.EOF { + break + } + fmt.Print(string(p[:n])) + } + fmt.Println() +} + +``` + +#### 参考文献 + +- https://medium.com/learning-the-go-programming-language/streaming-io-in-go-d93507931185 + +- https://qiita.com/ktnyt/items/8ede94469ba8b1399b12 + +# Try 2: テストを書いてみよう + +## 1 回目の課題のテストを作ってみてください。 + +- テストのしやすさを考えてリファクタリングしてみる +- テストのカバレッジを取ってみる +- テーブル駆動テストを行う +- テストヘルパーを作ってみる + +### 参考文献 + +- カバレッジとは https://www.techmatrix.co.jp/t/quality/coverage.html +- テーブル駆動テストとは https://github.com/golang/go/wiki/TableDrivenTests +- テストヘルパーとは https://qiita.com/atotto/items/f6b8c773264a3183a53c + +``` +testComputeにt.Helper()の1行を追加します。すると、testCompute内で発生したエラーは呼び元のTestComputeのどの行で失敗したのかを表示するようになります。 +``` diff --git a/kadai2/segakazzz/imgconv/imgconv.go b/kadai2/segakazzz/imgconv/imgconv.go new file mode 100644 index 0000000..3b29106 --- /dev/null +++ b/kadai2/segakazzz/imgconv/imgconv.go @@ -0,0 +1,183 @@ +// Package imgconv is for Gopher Dojo Kadai1 +package imgconv + +import ( + "errors" + "flag" + "fmt" + "image" + "image/jpeg" + "image/png" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" +) + +type converter struct { + dirname string + input string + output string +} + +// RunConverter converts all image files in the directory which you indicate with -d option. +// If the process is completed succeessfully, you will see the list of output files and "Done!" +// message in the standard output. +func RunConverter() error { + var ( + dir = flag.String("d", ".", "Indicate directory to convert") + in = flag.String("i", "jpg", "Indicate input image file's extension") + out = flag.String("o", "png", "Indicate output image file's extension") + ) + + flag.Parse() + c, err := newConverter(*dir, *in, *out) + if err != nil { + // log.Fatal(err) + return err + } + err = c.Convert() + if err != nil { + // log.Fatal(err) + return err + } + fmt.Println("Done!") + return nil +} + +func newConverter(dirname string, input string, output string) (*converter, error) { + var ( + ErrInputExt = errors.New("Input extension is not valid. Select one from jpg/png") + ErrOutputExt = errors.New("Output extension is not valid. Select one from jpg/png") + ErrExtSame = errors.New("Input and Output extensiton is the same. No convertion is needed") + ) + + switch input { + case "jpg", "png": + input = strings.ToLower(input) + default: + return nil, ErrInputExt + } + switch output { + case "jpg", "png": + output = strings.ToLower(output) + default: + return nil, ErrOutputExt + } + + if input == output { + return nil, ErrExtSame + } + return &converter{dirname: dirname, input: input, output: output}, nil +} + +// Convert method converts all jpg files in dirname to png. "out" folder is generated if it doesn't exist. +func (c *converter) Convert() error { + files, e := c.getSourceFiles() + if e != nil { + return e + } + e = c.convertFiles(files) + if e != nil { + return e + } + return nil +} + +func (c *converter) getSourceFiles() ([]os.FileInfo, error) { + files, err := ioutil.ReadDir(c.dirname) + if err != nil { + return nil, err + } + return files, nil +} + +func (c *converter) convertFiles(files []os.FileInfo) error { + re, e := regexp.Compile("." + c.input + "$") + if e != nil { + return e + } + for _, file := range files { + if re.MatchString(file.Name()) { + e = c.convertSingle(file.Name()) + if e != nil { + return e + } + } + } + return nil +} + +func (c *converter) convertSingle(filename string) (e error) { + input := filepath.Join(c.dirname, filename) + outDir := filepath.Join(c.dirname, "out") + output := filepath.Join(outDir, strings.Replace(strings.ToLower(filename), "."+c.input, "."+c.output, -1)) + fmt.Println(output) + if !c.dirExists(outDir) { + os.Mkdir(outDir, 0755) + } + + in, e := os.Open(input) + if e != nil { + return e + } + + defer func() { + e = in.Close() + }() + + var out *os.File + if c.fileExists(output) { + out, e = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + } else { + out, e = os.Create(output) + } + if e != nil { + return e + } + + defer func() { + e = out.Close() + }() + + var ( + img image.Image + ) + switch c.input { + case "jpg": + img, e = jpeg.Decode(in) + case "png": + img, e = png.Decode(in) + } + + if e != nil { + return e + } + switch c.output { + case "png": + e = png.Encode(out, img) + case "jpg": + e = jpeg.Encode(out, img, nil) + } + if e != nil { + return e + } + return nil +} + +func (c *converter) fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func (c *converter) dirExists(dirname string) bool { + info, err := os.Stat(dirname) + if os.IsNotExist(err) { + return false + } + return info.IsDir() +} diff --git a/kadai2/segakazzz/imgconv/imgconv_test.go b/kadai2/segakazzz/imgconv/imgconv_test.go new file mode 100644 index 0000000..e5c4936 --- /dev/null +++ b/kadai2/segakazzz/imgconv/imgconv_test.go @@ -0,0 +1,57 @@ +package imgconv + +import ( + "testing" +) + +// func isSameConverter(actual *converter, expected *converter) bool { +// if actual == nil && expected == nil { +// return true +// } +// if actual != nil && expected == nil { +// return false +// } +// if actual == nil && expected != nil { +// return false +// } +// if actual.dirname != expected.dirname { +// return false +// } +// if actual.input != expected.input { +// return false +// } +// if actual.output != expected.output { +// return false +// } +// return true +// } + +func TestNewConverter(t *testing.T) { + patterns := []struct { + dirname string + input string + output string + expected *converter + isError bool + }{ + {"testdata", "png", "jpg", &converter{dirname: "testdata", input: "png", output: "jpg"}, false}, + {"testdata", "jpg", "png", &converter{dirname: "testdata", input: "jpg", output: "png"}, false}, + {"testdata", "jpg", "gif", nil, true}, + {"testdata", "gif", "jpg", nil, true}, + {"testdata", "jpg", "jpg", nil, true}, + } + + for i, p := range patterns { + actual, err := newConverter(p.dirname, p.input, p.output) + if err != nil && p.isError == false { + t.Errorf("pattern: %d want: NO ERROR, actual: %v", i, err) + } + if err == nil && p.isError == true { + t.Errorf("pattern: %d want: ERROR, actual: NO ERROR", i) + } + + if actual != nil && p.expected != nil && *actual != *p.expected { + t.Errorf("pattern: %d want: %v [%T], actual: %v [%T]", i, *actual, *actual, *p.expected, *p.expected) + } + } +} diff --git a/kadai2/segakazzz/main.go b/kadai2/segakazzz/main.go new file mode 100644 index 0000000..081416d --- /dev/null +++ b/kadai2/segakazzz/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "os" + + "github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv" +) + +func main() { + err := imgconv.RunConverter() + if err != nil { + os.Exit(1) + } + os.Exit(0) +} diff --git a/kadai2/segakazzz/testdata/download.zsh b/kadai2/segakazzz/testdata/download.zsh new file mode 100755 index 0000000..e9fd9f0 --- /dev/null +++ b/kadai2/segakazzz/testdata/download.zsh @@ -0,0 +1,12 @@ +#!/bin/zsh + +curl https://cdn2.thecatapi.com/images/8er.jpg > 001.jpg +curl https://cdn2.thecatapi.com/images/ceh.jpg > 002.jpg +curl https://cdn2.thecatapi.com/images/MTU2MjQ4NA.jpg > 003.jpg +curl https://cdn2.thecatapi.com/images/5qc.jpg > 004.jpg +curl https://cdn2.thecatapi.com/images/962.jpg > 005.jpg +curl https://cdn2.thecatapi.com/images/MTc2Mzc0Mw.jpg > 006.jpg +curl https://cdn2.thecatapi.com/images/bos.jpg > 007.jpg +curl https://cdn2.thecatapi.com/images/SX2DvLw7u.jpg > 008.jpg +curl https://cdn2.thecatapi.com/images/9eh.jpg > 009.jpg +curl https://cdn2.thecatapi.com/images/7bo.jpg > 010.jpg \ No newline at end of file From b70574acc5491b1dd22420044c36673b33e4ca6b Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Thu, 16 Jul 2020 11:13:43 +0900 Subject: [PATCH 11/14] Completed test code of new converter --- kadai2/segakazzz/imgconv/imgconv_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kadai2/segakazzz/imgconv/imgconv_test.go b/kadai2/segakazzz/imgconv/imgconv_test.go index e5c4936..b2c3c40 100644 --- a/kadai2/segakazzz/imgconv/imgconv_test.go +++ b/kadai2/segakazzz/imgconv/imgconv_test.go @@ -49,9 +49,12 @@ func TestNewConverter(t *testing.T) { if err == nil && p.isError == true { t.Errorf("pattern: %d want: ERROR, actual: NO ERROR", i) } + if (actual == nil && p.expected != nil) || (actual != nil && p.expected == nil) { + t.Errorf("pattern: %d want: isNil(%t), actual: isNil(%t)", i, p.expected == nil, actual == nil) + } if actual != nil && p.expected != nil && *actual != *p.expected { - t.Errorf("pattern: %d want: %v [%T], actual: %v [%T]", i, *actual, *actual, *p.expected, *p.expected) + t.Errorf("pattern: %d want: %v [%T], actual: %v [%T]", i, *p.expected, *p.expected, *actual, *actual) } } } From 71fcaed8c5bd54748dcf74b0c1e2cca7b355733a Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Thu, 16 Jul 2020 11:53:04 +0900 Subject: [PATCH 12/14] Updated test for new converter --- kadai2/segakazzz/imgconv/imgconv_test.go | 78 +++++++++++------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/kadai2/segakazzz/imgconv/imgconv_test.go b/kadai2/segakazzz/imgconv/imgconv_test.go index b2c3c40..c6342b9 100644 --- a/kadai2/segakazzz/imgconv/imgconv_test.go +++ b/kadai2/segakazzz/imgconv/imgconv_test.go @@ -4,57 +4,53 @@ import ( "testing" ) -// func isSameConverter(actual *converter, expected *converter) bool { -// if actual == nil && expected == nil { -// return true -// } -// if actual != nil && expected == nil { -// return false -// } -// if actual == nil && expected != nil { -// return false -// } -// if actual.dirname != expected.dirname { -// return false -// } -// if actual.input != expected.input { -// return false -// } -// if actual.output != expected.output { -// return false -// } -// return true -// } +type pattern struct { + dirname string + input string + output string + expected *converter + isError bool +} func TestNewConverter(t *testing.T) { - patterns := []struct { - dirname string - input string - output string - expected *converter - isError bool - }{ + successPatterns := []pattern{ {"testdata", "png", "jpg", &converter{dirname: "testdata", input: "png", output: "jpg"}, false}, {"testdata", "jpg", "png", &converter{dirname: "testdata", input: "jpg", output: "png"}, false}, - {"testdata", "jpg", "gif", nil, true}, + } + errorPatterns := []pattern{ + {"testdata", "jpg", "gif", &converter{}, true}, {"testdata", "gif", "jpg", nil, true}, {"testdata", "jpg", "jpg", nil, true}, } - for i, p := range patterns { - actual, err := newConverter(p.dirname, p.input, p.output) - if err != nil && p.isError == false { - t.Errorf("pattern: %d want: NO ERROR, actual: %v", i, err) - } - if err == nil && p.isError == true { - t.Errorf("pattern: %d want: ERROR, actual: NO ERROR", i) - } - if (actual == nil && p.expected != nil) || (actual != nil && p.expected == nil) { - t.Errorf("pattern: %d want: isNil(%t), actual: isNil(%t)", i, p.expected == nil, actual == nil) + t.Run("successPatterns", func(t *testing.T) { + for i, p := range successPatterns { + testNewConverter(t, i, p) } + }) - if actual != nil && p.expected != nil && *actual != *p.expected { - t.Errorf("pattern: %d want: %v [%T], actual: %v [%T]", i, *p.expected, *p.expected, *actual, *actual) + t.Run("errorPatterns", func(t *testing.T) { + for i, p := range errorPatterns { + testNewConverter(t, i, p) } + }) +} + +func testNewConverter(t *testing.T, i int, p pattern) { + t.Helper() + actual, err := newConverter(p.dirname, p.input, p.output) + if err != nil && p.isError == false { + t.Fatalf("pattern[%d]: %v want: NO ERROR, actual: %v", i, p, err) } + if err == nil && p.isError == true { + t.Fatalf("pattern[%d]: %v want: ERROR, actual: NO ERROR", i, p) + } + if (actual == nil && p.expected != nil) || (actual != nil && p.expected == nil) { + t.Fatalf("pattern[%d]: %v want: isNil(%t), actual: isNil(%t)", i, p, p.expected == nil, actual == nil) + } + + if actual != nil && p.expected != nil && *actual != *p.expected { + t.Fatalf("pattern[%d]: %v want: %v [%T], actual: %v [%T]", i, p, *p.expected, *p.expected, *actual, *actual) + } + } From 31020f1c6dfd3c845045d3332412244dddc09e09 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Thu, 16 Jul 2020 16:19:07 +0900 Subject: [PATCH 13/14] Done writing test code --- kadai2/segakazzz/.gitignore | 4 +- kadai2/segakazzz/README.md | 44 ++++++++++- kadai2/segakazzz/cover.out | 63 +++++++++++++++ kadai2/segakazzz/imgconv/imgconv.go | 19 +++-- kadai2/segakazzz/imgconv/imgconv_test.go | 97 +++++++++++++++++++++++- kadai2/segakazzz/main.go | 12 ++- 6 files changed, 222 insertions(+), 17 deletions(-) create mode 100644 kadai2/segakazzz/cover.out diff --git a/kadai2/segakazzz/.gitignore b/kadai2/segakazzz/.gitignore index 2b6ec85..2a79263 100644 --- a/kadai2/segakazzz/.gitignore +++ b/kadai2/segakazzz/.gitignore @@ -1,4 +1,6 @@ .DS_Store testdata/*.jpg testdata/*.png -testdata/out \ No newline at end of file +testdata/out +testdata/test +testdata/error \ No newline at end of file diff --git a/kadai2/segakazzz/README.md b/kadai2/segakazzz/README.md index dc83a51..8399caa 100644 --- a/kadai2/segakazzz/README.md +++ b/kadai2/segakazzz/README.md @@ -1,6 +1,6 @@ # Try 1: io.Reader と io.Writer について調べてみよう -## 標準パッケージでどのように使われているか +## 標準パッケージでどのように使われているか(回答) ### io.Reader/io.Writer とは @@ -38,7 +38,7 @@ type Writer interface { - os.Stdout - os.File -## io.Reader と io.Writer があることでどういう利点があるのか具体例を挙げて考えてみる +## io.Reader と io.Writer があることでどういう利点があるのか具体例を挙げて考えてみる(回答) ### 1. どこからデータを読み込み、どこへ書き出すかについて自由に実装ができる @@ -171,6 +171,40 @@ func main() { - テーブル駆動テストを行う - テストヘルパーを作ってみる +### 回答 + +以下のコマンドでテストの実行が可能です。今回は 85.2%程カバーすることができました。 + +``` +$ go test --cover ./imgconv --coverprofile cover.out +ok github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv 6.727s coverage: 85.2% of statements +``` + +また、以下でコードのどこがテストされたか、ブラウザに表示をさせて確認をすることができます。 + +``` +$ go tool cover -html=cover.out +``` + +### 感想 + +- t.Helper()は、名前からもっとたくさんの情報を出力してくれるのかと期待してしまいましたが、呼び出し元の位置がわかるだけでした。 +- 小さな関数からテストを書いていくより、親となる関数からテストを書いて、カバーした内容を確認しながら、カバーしきれない分のテストを足していったほうが効率的だとおもいました。 +- err の状況を作り出す方法がわからずに、カバーしきれなかった部分もあるので、100%のカバレッジを目指すには、こういった点の調査も必要になると思いました。 + +```golang +switch c.output { + case "png": + e = png.Encode(out, img) + case "jpg": + e = jpeg.Encode(out, img, nil) + } + if e != nil { + return e + } +} +~~~ + ### 参考文献 - カバレッジとは https://www.techmatrix.co.jp/t/quality/coverage.html @@ -178,5 +212,9 @@ func main() { - テストヘルパーとは https://qiita.com/atotto/items/f6b8c773264a3183a53c ``` -testComputeにt.Helper()の1行を追加します。すると、testCompute内で発生したエラーは呼び元のTestComputeのどの行で失敗したのかを表示するようになります。 + +testCompute に t.Helper()の1行を追加します。すると、testCompute 内で発生したエラーは呼び元の TestCompute のどの行で失敗したのかを表示するようになります。 + +``` + ``` diff --git a/kadai2/segakazzz/cover.out b/kadai2/segakazzz/cover.out new file mode 100644 index 0000000..6a49203 --- /dev/null +++ b/kadai2/segakazzz/cover.out @@ -0,0 +1,63 @@ +mode: set +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:26.63,28.16 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:32.2,33.16 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:37.2,38.12 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:28.16,31.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:33.16,36.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:41.84,48.15 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:54.2,54.16 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:61.2,61.21 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:64.2,64.72 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:49.20,50.33 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:51.10,52.26 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:55.20,56.35 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:57.10,58.27 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:61.21,63.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:68.37,70.14 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:73.2,74.14 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:77.2,77.12 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:70.14,72.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:74.14,76.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:80.61,83.16 3 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:86.2,86.26 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:92.2,92.25 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:83.16,85.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:86.26,87.17 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:87.17,89.4 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:95.61,97.14 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:100.2,100.29 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:108.2,108.12 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:97.14,99.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:100.29,101.34 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:101.34,103.16 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:103.16,105.5 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:111.62,116.26 5 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:120.2,121.14 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:125.2,125.15 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:129.2,130.26 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:135.2,135.14 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:139.2,139.15 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:143.2,146.17 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:153.2,153.14 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:156.2,156.18 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:162.2,162.14 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:165.2,165.12 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:116.26,118.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:121.14,123.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:125.15,127.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:130.26,132.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:132.8,134.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:135.14,137.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:139.15,141.3 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:147.13,148.27 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:149.13,150.26 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:153.14,155.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:157.13,158.27 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:159.13,160.33 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:162.14,164.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:168.54,170.24 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:173.2,173.22 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:170.24,172.3 1 0 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:176.52,178.24 2 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:181.2,181.21 1 1 +github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv/imgconv.go:178.24,180.3 1 0 diff --git a/kadai2/segakazzz/imgconv/imgconv.go b/kadai2/segakazzz/imgconv/imgconv.go index 3b29106..390e04b 100644 --- a/kadai2/segakazzz/imgconv/imgconv.go +++ b/kadai2/segakazzz/imgconv/imgconv.go @@ -3,7 +3,6 @@ package imgconv import ( "errors" - "flag" "fmt" "image" "image/jpeg" @@ -24,14 +23,7 @@ type converter struct { // RunConverter converts all image files in the directory which you indicate with -d option. // If the process is completed succeessfully, you will see the list of output files and "Done!" // message in the standard output. -func RunConverter() error { - var ( - dir = flag.String("d", ".", "Indicate directory to convert") - in = flag.String("i", "jpg", "Indicate input image file's extension") - out = flag.String("o", "png", "Indicate output image file's extension") - ) - - flag.Parse() +func RunConverter(dir *string, in *string, out *string) error { c, err := newConverter(*dir, *in, *out) if err != nil { // log.Fatal(err) @@ -86,11 +78,18 @@ func (c *converter) Convert() error { } func (c *converter) getSourceFiles() ([]os.FileInfo, error) { + var outputFiles []os.FileInfo files, err := ioutil.ReadDir(c.dirname) if err != nil { return nil, err } - return files, nil + for _, f := range files { + if !f.IsDir() { + outputFiles = append(outputFiles, f) + } + } + + return outputFiles, nil } func (c *converter) convertFiles(files []os.FileInfo) error { diff --git a/kadai2/segakazzz/imgconv/imgconv_test.go b/kadai2/segakazzz/imgconv/imgconv_test.go index c6342b9..8a12cfe 100644 --- a/kadai2/segakazzz/imgconv/imgconv_test.go +++ b/kadai2/segakazzz/imgconv/imgconv_test.go @@ -12,13 +12,34 @@ type pattern struct { isError bool } +func TestRunConverter(t *testing.T) { + t.Helper() + t.Run("Success", func(t *testing.T) { + dir, input, output := "../testdata", "jpg", "png" + testRunConverter(t, &dir, &input, &output, false) + }) + t.Run("Error", func(t *testing.T) { + dir, input, output := "../testdata", "gif", "png" + testRunConverter(t, &dir, &input, &output, true) + }) +} + +func testRunConverter(t *testing.T, dir *string, input *string, output *string, isError bool) { + t.Helper() + if err := RunConverter(dir, input, output); isError && err == nil { + t.Fatalf("Error is expected but not found") + } else if !isError && err != nil { + t.Fatalf("Error is not expected but found %s", err) + } +} + func TestNewConverter(t *testing.T) { successPatterns := []pattern{ {"testdata", "png", "jpg", &converter{dirname: "testdata", input: "png", output: "jpg"}, false}, {"testdata", "jpg", "png", &converter{dirname: "testdata", input: "jpg", output: "png"}, false}, } errorPatterns := []pattern{ - {"testdata", "jpg", "gif", &converter{}, true}, + {"testdata", "jpg", "gif", nil, true}, {"testdata", "gif", "jpg", nil, true}, {"testdata", "jpg", "jpg", nil, true}, } @@ -54,3 +75,77 @@ func testNewConverter(t *testing.T, i int, p pattern) { } } + +func TestGetSourceFiles(t *testing.T) { + t.Run("Dir Exists", func(t *testing.T) { + testGetSourceFiles(t, &converter{dirname: "../testdata/test/4", input: "jpg", output: "png"}, 4, false) + testGetSourceFiles(t, &converter{dirname: "../testdata/test/2", input: "jpg", output: "png"}, 2, false) + }) + t.Run("Dir Not Exists", func(t *testing.T) { + testGetSourceFiles(t, &converter{dirname: "../notfound", input: "jpg", output: "png"}, 0, true) + }) +} + +func testGetSourceFiles(t *testing.T, c *converter, expected int, isError bool) { + t.Helper() + files, err := c.getSourceFiles() + if isError && err == nil { + t.Fatalf("converter[%v] Error is expected. But not found", *c) + } + if !isError && err != nil { + t.Fatalf("Error is not expected but found %s", err) + } + if actual := len(files); actual != expected { + t.Fatalf("converter[%v] File count is not expected. expected: %d actual: %d", *c, expected, actual) + } +} + +func TestConvertSingle(t *testing.T) { + t.Run("File Found", func(t *testing.T) { + testConvertSingle(t, &converter{dirname: "../testdata", input: "jpg", output: "png"}, "001.jpg", false) + testConvertSingle(t, &converter{dirname: "../testdata", input: "png", output: "jpg"}, "001.png", false) + }) + t.Run("Output folder exists", func(t *testing.T) { + testConvertSingle(t, &converter{dirname: "../testdata", input: "jpg", output: "png"}, "001.jpg", false) + testConvertSingle(t, &converter{dirname: "../testdata/test/2", input: "png", output: "jpg"}, "001.png", false) + }) + t.Run("File Not Found", func(t *testing.T) { + testConvertSingle(t, &converter{dirname: "../testdata", input: "jpg", output: "png"}, "111.jpg", true) + }) + +} + +func testConvertSingle(t *testing.T, c *converter, fname string, isError bool) { + t.Helper() + err := c.convertSingle(fname) + if err == nil && isError { + t.Fatalf("pattern[%v] Error is expected but not found", *c) + } + if err != nil && !isError { + t.Fatalf("pattern[%v] Error is not expected but found %s", *c, err) + } +} + +func TestConvertFiles(t *testing.T) { + t.Helper() + t.Run("Success", func(t *testing.T) { + testConvertFiles(t, &converter{dirname: "../testdata", input: "png", output: "jpg"}, false) + }) + // t.Run("Error", func(t *testing.T) { + // testConvertFiles(t, &converter{dirname: "../testdata", input: "gif", output: "jpg"}, true) + // }) +} + +func testConvertFiles(t *testing.T, c *converter, isError bool) { + t.Helper() + files, err := c.getSourceFiles() + if err != nil { + t.Fatalf("error found %s", err) + } + if err := c.convertFiles(files); isError && err == nil { + t.Fatalf("pattern[%v] Error is expected but not found", c) + } else if !isError && err != nil { + t.Fatalf("pattern[%v] Error is not expected but found %s", c, err) + } + +} diff --git a/kadai2/segakazzz/main.go b/kadai2/segakazzz/main.go index 081416d..c6ec770 100644 --- a/kadai2/segakazzz/main.go +++ b/kadai2/segakazzz/main.go @@ -1,13 +1,21 @@ package main import ( + "flag" "os" - "github.com/gopherdojo/dojo8/kadai1/segakazzz/imgconv" + "github.com/gopherdojo/dojo8/kadai2/segakazzz/imgconv" ) func main() { - err := imgconv.RunConverter() + var ( + dir = flag.String("d", ".", "Indicate directory to convert") + in = flag.String("i", "jpg", "Indicate input image file's extension") + out = flag.String("o", "png", "Indicate output image file's extension") + ) + + flag.Parse() + err := imgconv.RunConverter(dir, in, out) if err != nil { os.Exit(1) } From 84d4ceab64200b5d6fd478929ca89019ce7942e2 Mon Sep 17 00:00:00 2001 From: Kazue Sasatani Date: Thu, 16 Jul 2020 16:21:54 +0900 Subject: [PATCH 14/14] Updated README.md --- kadai2/segakazzz/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/kadai2/segakazzz/README.md b/kadai2/segakazzz/README.md index 8399caa..05e1fa7 100644 --- a/kadai2/segakazzz/README.md +++ b/kadai2/segakazzz/README.md @@ -190,7 +190,7 @@ $ go tool cover -html=cover.out - t.Helper()は、名前からもっとたくさんの情報を出力してくれるのかと期待してしまいましたが、呼び出し元の位置がわかるだけでした。 - 小さな関数からテストを書いていくより、親となる関数からテストを書いて、カバーした内容を確認しながら、カバーしきれない分のテストを足していったほうが効率的だとおもいました。 -- err の状況を作り出す方法がわからずに、カバーしきれなかった部分もあるので、100%のカバレッジを目指すには、こういった点の調査も必要になると思いました。 +- err の状況を作り出す方法がわからずに、カバーしきれなかった部分もありました。(例えば下のような箇所)100%のカバレッジを目指すには、こういった点をカバーするために png.Encode でエラーが出るための条件について調査などが必要になります。 ```golang switch c.output { @@ -203,7 +203,7 @@ switch c.output { return e } } -~~~ +``` ### 参考文献 @@ -212,9 +212,5 @@ switch c.output { - テストヘルパーとは https://qiita.com/atotto/items/f6b8c773264a3183a53c ``` - testCompute に t.Helper()の1行を追加します。すると、testCompute 内で発生したエラーは呼び元の TestCompute のどの行で失敗したのかを表示するようになります。 - -``` - ```