diff --git a/kadai2/phamvanhung2e123/Makefile b/kadai2/phamvanhung2e123/Makefile new file mode 100644 index 0000000..2ca19a2 --- /dev/null +++ b/kadai2/phamvanhung2e123/Makefile @@ -0,0 +1,11 @@ +clean: + @echo "====> Remove installed binary" + rm -f bin/kadai2 + +install: + @echo "====> Build hget in ./bin " + go build -o bin/kadai2 + +test: + @echo "====> Remove installed binary" + go test diff --git a/kadai2/phamvanhung2e123/README.md b/kadai2/phamvanhung2e123/README.md new file mode 100644 index 0000000..8fea6e1 --- /dev/null +++ b/kadai2/phamvanhung2e123/README.md @@ -0,0 +1,85 @@ +## 課題1 +* 次の仕様を満たすコマンドを作って下さい + - ディレクトリを指定する + - 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) + - ディレクトリ以下は再帰的に処理する + - 変換前と変換後の画像形式を指定できる(オプション) + +* 以下を満たすように開発してください + - mainパッケージと分離する + - 自作パッケージと標準パッケージと準標準パッケージのみ使う + - 準標準パッケージ:golang.org/x以下のパッケージ + - ユーザ定義型を作ってみる + - GoDocを生成してみる + +#### 課題2 + + - [x] テストのしやすさを考えてリファクタリングしてみる + - [x] テストのカバレッジを取ってみる + ``` + go test -coverprofile=coverage.out + go tool cover -func=coverage.out + go tool cover -html=coverage.out + ``` + - [x] テーブル駆動テストを行う + - [x] テストヘルパーを作ってみる + +#### io.Readerとio.Writerを調べる + +- 標準パッケージでどのように使われているか + - ファイルの読み書きなど入出力の基本使います + - バッファリングして読み書きを行うところを使う + - 多くの標準パッケージがインタフェースを実装していたり、引数として扱える形でサポートしています。 + ``` + json + bytes.Buffer + bufio.Reader + os.File + image + jpeg + png + base64 + ``` +- io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる + - 標準ライブラリに用意されている便利なツールでラップして呼び出すこと + - モックしやすいでテスト書きやすくなります + +## コマンド +* jpeg, png, jpg, gifを対応しました。 +* デコード出来ない場合はログを出して、次の処理へ進みます。 +* GoDocを生成してみる + +## ビルド +``` +$ make install +``` +## テスト +``` +$ make test +``` + +## コマンド使い方 +``` +$./bin/kadai2 [options] [directories] +``` + +### オプション +``` +-i string + Input file type (default "jpg") + +-o string + Output file type (default "png") +``` + +### 例 +``` +$./bin/kadai2 -i jpg -o png fixtures +``` + +## Godoc +``` +$godoc -http=:6060 +``` +以下のURLで読めます。 +`http://localhost:6060/pkg/github.com/gopherdojo/dojo4/kadai2/phamvanhung2e123/converter` diff --git a/kadai2/phamvanhung2e123/converter/converter.go b/kadai2/phamvanhung2e123/converter/converter.go new file mode 100644 index 0000000..04367ee --- /dev/null +++ b/kadai2/phamvanhung2e123/converter/converter.go @@ -0,0 +1,98 @@ +package converter + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "os" + "regexp" +) + +// Converter converts images inside a directory from input type to output type +type Converter struct { + inputType string + outputType string +} + +var regexpPath = regexp.MustCompile("\\.(jpg|jpeg|png|gif)$") +var ( + jpgExt = ".jpg" + jpegExt = ".jpeg" + gifExt = ".gif" + pngExt = ".png" +) + +// New Converter from input type and output type +func New(inputType string, outputType string) Converter { + return Converter{inputType: inputType, outputType: outputType} +} + +// Convert image +func (c *Converter) ConvertImage(path string) (err error) { + img, err := c.readImage(path) + if err != nil { + return err + } + if img == nil { + return nil + } + outputPath := regexpPath.ReplaceAllString(path, "."+c.outputType) + err = c.writeImage(outputPath, img) + if err != nil { + return err + } + return nil +} + +func (c *Converter) readImage(path string) (image.Image, error) { + var image image.Image + fmt.Println("Read file: " + path) + ext := regexpPath.FindString(path) + if ext != "."+c.inputType { + return nil, nil + } + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + inputExt := "." + c.inputType + switch inputExt { + case jpgExt, jpegExt: + image, err = jpeg.Decode(file) + case pngExt: + image, err = png.Decode(file) + case gifExt: + image, err = gif.Decode(file) + default: + return nil, nil + } + if err != nil { + return nil, err + } + return image, nil +} + +func (c *Converter) writeImage(path string, image image.Image) error { + fmt.Println("Write file: " + path) + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + outputExt := "." + c.outputType + switch outputExt { + case jpgExt, jpegExt: + err = jpeg.Encode(file, image, nil) + case pngExt: + err = png.Encode(file, image) + case gifExt: + err = gif.Encode(file, image, nil) + } + if err != nil { + return err + } + return nil +} diff --git a/kadai2/phamvanhung2e123/converter/converter_test.go b/kadai2/phamvanhung2e123/converter/converter_test.go new file mode 100644 index 0000000..bfea726 --- /dev/null +++ b/kadai2/phamvanhung2e123/converter/converter_test.go @@ -0,0 +1,100 @@ +package converter_test + +import ( + "github.com/gopherdojo/dojo4/kadai2/phamvanhung2e123/converter" + "image/gif" + "image/jpeg" + "image/png" + "os" + "testing" +) + +type testCase struct { + description string + path string + inputType string + outputType string + outputFile string +} + +func TestConvert(t *testing.T) { + var testFixtures = []testCase{ + { + description: "Test convert jpg to png", + inputType: "jpg", + outputType: "png", + path: "../fixtures/jpg/input1/gopher1.jpg", + outputFile: "../fixtures/jpg/input1/gopher1.png", + }, + { + description: "Test convert jpeg to png", + inputType: "jpeg", + outputType: "png", + path: "../fixtures/jpeg/input1/gopher1.jpeg", + outputFile: "../fixtures/jpeg/input1/gopher1.png", + }, + { + description: "Test convert png to jpg", + inputType: "png", + outputType: "jpg", + path: "../fixtures/png/input1/gopher1.png", + outputFile: "../fixtures/png/input1/gopher1.jpg", + }, + { + description: "Test convert gif to png", + inputType: "gif", + outputType: "png", + path: "../fixtures/gif/input1/gopher1.gif", + outputFile: "../fixtures/gif/input1/gopher1.png", + }, + { + description: "Test convert gif to jpg", + inputType: "gif", + outputType: "jpg", + path: "../fixtures/gif/input1/gopher1.gif", + outputFile: "../fixtures/gif/input1/gopher1.jpg", + }, + } + + for _, testFixture := range testFixtures { + c := converter.New(testFixture.inputType, testFixture.outputType) + t.Run("Check convert", func(t *testing.T) { + checkConvert(t, c, testFixture.path) + }) + t.Run("Check format", func(t *testing.T) { + checkFormat(t, testFixture.outputFile, testFixture.outputType) + }) + } +} + +func checkConvert(t *testing.T, c converter.Converter, path string) { + t.Helper() + if err := c.ConvertImage(path); err != nil { + t.Errorf("Error: %s", err) + } +} + +func checkFormat(t *testing.T, path string, fileType string) { + t.Helper() + if _, err := os.Stat(path); os.IsNotExist(err) { + t.Errorf("Expected output file %s %s is not exist", path, err.Error()) + } + file, err := os.Open(path) + if err != nil { + t.Errorf("Couldn't open file path: %s, fileType: %s, error: %v", path, fileType, err) + } + defer file.Close() + + switch fileType { + case "jpg", "jpeg": + _, err = jpeg.Decode(file) + case "png": + _, err = png.Decode(file) + case "gif": + _, err = gif.Decode(file) + } + + if err != nil { + t.Errorf("Couldn't decode path: %s, fileType: %s, error: %v", path, fileType, err) + } +} diff --git a/kadai2/phamvanhung2e123/fixtures/gif/input1/gopher1.gif b/kadai2/phamvanhung2e123/fixtures/gif/input1/gopher1.gif new file mode 100644 index 0000000..bf11899 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/gif/input1/gopher1.gif differ diff --git a/kadai2/phamvanhung2e123/fixtures/gif/input2/gopher2.gif b/kadai2/phamvanhung2e123/fixtures/gif/input2/gopher2.gif new file mode 100644 index 0000000..11cbd50 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/gif/input2/gopher2.gif differ diff --git a/kadai2/phamvanhung2e123/fixtures/jpeg/input1/gopher1.jpeg b/kadai2/phamvanhung2e123/fixtures/jpeg/input1/gopher1.jpeg new file mode 100644 index 0000000..dbfb9a5 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/jpeg/input1/gopher1.jpeg differ diff --git a/kadai2/phamvanhung2e123/fixtures/jpeg/input2/gopher2.jpeg b/kadai2/phamvanhung2e123/fixtures/jpeg/input2/gopher2.jpeg new file mode 100644 index 0000000..53d8b0e Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/jpeg/input2/gopher2.jpeg differ diff --git a/kadai2/phamvanhung2e123/fixtures/jpg/input1/gopher1.jpg b/kadai2/phamvanhung2e123/fixtures/jpg/input1/gopher1.jpg new file mode 100644 index 0000000..bcf63e9 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/jpg/input1/gopher1.jpg differ diff --git a/kadai2/phamvanhung2e123/fixtures/jpg/input2/gopher2.jpg b/kadai2/phamvanhung2e123/fixtures/jpg/input2/gopher2.jpg new file mode 100644 index 0000000..7186579 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/jpg/input2/gopher2.jpg differ diff --git a/kadai2/phamvanhung2e123/fixtures/png/input1/gopher1.png b/kadai2/phamvanhung2e123/fixtures/png/input1/gopher1.png new file mode 100644 index 0000000..0e04cf5 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/png/input1/gopher1.png differ diff --git a/kadai2/phamvanhung2e123/fixtures/png/input2/gopher2.png b/kadai2/phamvanhung2e123/fixtures/png/input2/gopher2.png new file mode 100644 index 0000000..8aae466 Binary files /dev/null and b/kadai2/phamvanhung2e123/fixtures/png/input2/gopher2.png differ diff --git a/kadai2/phamvanhung2e123/main.go b/kadai2/phamvanhung2e123/main.go new file mode 100644 index 0000000..72a4e49 --- /dev/null +++ b/kadai2/phamvanhung2e123/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "github.com/gopherdojo/dojo4/kadai2/phamvanhung2e123/converter" + "os" + "path/filepath" +) + +var ( + jpgType = "jpg" + jpegType = "jpeg" + gifType = "gif" + pngType = "png" +) + +var ( + inputType string + outputType string +) + +var validatedTypes = map[string]bool{gifType: true, jpgType: true, jpegType: true, pngType: true} + +func convert(path string, inputType string, outputType string) (err error) { + converter := converter.New(inputType, outputType) + err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + switch { + case err != nil: + return err + case info.IsDir(): + return nil + default: + return converter.ConvertImage(path) + } + }) + return err +} + +func isValidType(imageType string) bool { + _, ok := validatedTypes[imageType] + return ok +} + +func init() { + flag.StringVar(&inputType, "i", jpgType, "Input file type") + flag.StringVar(&outputType, "o", pngType, "Output file type") +} + +func main() { + flag.Parse() + if !isValidType(inputType) || !isValidType(outputType) { + fmt.Println("Please input valid type -i [png, jpeg, gif, png] -o [png, jpeg, gif, png] ") + } + if flag.NArg() == 0 { + fmt.Println("Please input file path") + } + + for i := 0; i < flag.NArg(); i++ { + path := flag.Arg(i) + err := convert(path, inputType, outputType) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } + } +} diff --git a/kadai2/phamvanhung2e123/main_test.go b/kadai2/phamvanhung2e123/main_test.go new file mode 100644 index 0000000..dc2eb32 --- /dev/null +++ b/kadai2/phamvanhung2e123/main_test.go @@ -0,0 +1,105 @@ +package main + +import ( + "image/gif" + "image/jpeg" + "image/png" + "log" + "os" + "testing" +) + +func TestConvert(t *testing.T) { + var testFixtures = []struct { + description string + path string + inputType string + outputType string + outputFiles []string + }{ + { + description: "Test convert jpg to png", + inputType: "jpg", + outputType: "png", + path: "fixtures/jpg", + outputFiles: []string{"fixtures/jpg/input1/gopher1.png", "fixtures/jpg/input2/gopher2.png"}, + }, + { + description: "Test convert jpeg to png", + inputType: "jpeg", + outputType: "png", + path: "fixtures/jpeg", + outputFiles: []string{"fixtures/jpeg/input1/gopher1.png", "fixtures/jpeg/input2/gopher2.png"}, + }, + { + description: "Test convert png to jpg", + inputType: "png", + outputType: "jpg", + path: "fixtures/png", + outputFiles: []string{"fixtures/png/input1/gopher1.jpg", "fixtures/png/input2/gopher2.jpg"}, + }, + { + description: "Test convert gif to png", + inputType: "gif", + outputType: "png", + path: "fixtures/gif", + outputFiles: []string{"fixtures/gif/input1/gopher1.png", "fixtures/gif/input2/gopher2.png"}, + }, + { + description: "Test convert gif to jpg", + inputType: "gif", + outputType: "jpg", + path: "fixtures/gif", + outputFiles: []string{"fixtures/gif/input1/gopher1.jpg", "fixtures/gif/input2/gopher2.jpg"}, + }, + } + for _, testFixture := range testFixtures { + if err := convert(testFixture.path, testFixture.inputType, testFixture.outputType); err != nil { + t.Errorf("Error: %s", err) + } + + for _, file := range testFixture.outputFiles { + if _, err := os.Stat(file); os.IsNotExist(err) { + t.Errorf("Expected output file %s %s is not exist", file, err.Error()) + } + if !isValidFormat(file, testFixture.outputType) { + t.Errorf("Output file %s is in wrong format", file) + } + } + + teardown(testFixture.outputFiles) + } +} + +func teardown(paths []string) { + for _, path := range paths { + if err := os.Remove(path); err != nil { + if os.IsNotExist(err) { + continue + } + log.Fatal(err) + } + } +} + +func isValidFormat(path string, fileType string) bool { + file, err := os.Open(path) + if err != nil { + return false + } + defer file.Close() + + switch fileType { + case "jpg", "jpeg": + _, err = jpeg.Decode(file) + case "png": + _, err = png.Decode(file) + case "gif": + _, err = gif.Decode(file) + } + + if err != nil { + return false + } + return true +}