diff --git a/kadai1/asuke-yasukuni/.gitignore b/kadai1/asuke-yasukuni/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/kadai1/asuke-yasukuni/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/kadai1/asuke-yasukuni/README.md b/kadai1/asuke-yasukuni/README.md new file mode 100644 index 0000000..c1f4a40 --- /dev/null +++ b/kadai1/asuke-yasukuni/README.md @@ -0,0 +1,54 @@ +## dojo7 [課題1] 画像変換コマンドを作ろう + +全体的にシンプルな実装を心がけてみました。 + +**仕様** +- -src ディレクトリパス +- -from 変換対象の拡張子 +- -to 変換先の拡張子 + +**実行例** +```bash +./imgreplacer -src ./testdata/ -from png -to jpg +2019/09/09 00:26:22 [replace start] +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-1.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-2.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-3.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-4.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-5.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-6.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-7.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-1.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-2.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-3.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-4.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-5.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-6.png -> jpg +2019/09/09 00:26:22 [replace file]testdata/test-7.png -> jpg +2019/09/09 00:26:22 [replace end] +``` + +## 回答 +##### 次の仕様を満たすコマンドを作って下さい +- ディレクトリを指定する + - 指定できるようにしました +- 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) + - デフォルトでJPG -> PNG 変換になっています。 + - 元ファイルをリプレイスしています。 +- ディレクトリ以下は再帰的に処理する + - filepath.Walkで再帰的に検索して処理しています +- 変換前と変換後の画像形式を指定できる(オプション) + - -to -from オプションで可能にしました + +##### 以下を満たすように開発してください +- mainパッケージと分離する + - 分離しましたが、どこまで分離すれば良いのか迷ったので一旦画像ファイルの変換部分とバリデーション部分のみ分離しました。 +- 自作パッケージと標準パッケージと準標準パッケージのみ使う + - そうなっているはず +- ユーザ定義型を作ってみる + - 今回はユーザー定義関数で replacer.File を用意してそこにメソッドをはやして処理する形にしてみました。 +- GoDocを生成してみる + - go moduleで実装してたので生成に少し苦労しました。 + - 最終的にjodo7リポジトリ自体をGOPATHのsrcディレクトリ以下に置いて生成しています。 +![pkg_dojo7_kadai1_asuke-yasukuni_replacer_](https://user-images.githubusercontent.com/36254193/64491494-0ad96680-d2a4-11e9-926f-42336b6eb1e1.png) +![pkg_dojo7_kadai1_asuke-yasukuni_validation_](https://user-images.githubusercontent.com/36254193/64491495-0b71fd00-d2a4-11e9-97f5-43f0681f403e.png) diff --git a/kadai1/asuke-yasukuni/cover.html b/kadai1/asuke-yasukuni/cover.html new file mode 100644 index 0000000..bb6f224 --- /dev/null +++ b/kadai1/asuke-yasukuni/cover.html @@ -0,0 +1,138 @@ + + + + + + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + +
+ + + diff --git a/kadai1/asuke-yasukuni/cover.out b/kadai1/asuke-yasukuni/cover.out new file mode 100644 index 0000000..2a961eb --- /dev/null +++ b/kadai1/asuke-yasukuni/cover.out @@ -0,0 +1,8 @@ +mode: set +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:19.94,20.81 1 1 +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:35.2,35.8 1 1 +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:20.81,21.40 1 1 +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:26.3,28.52 2 1 +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:32.3,32.13 1 1 +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:21.40,23.4 1 1 +dojo7/kadai1/asuke-yasukuni/walk/encoder.go:28.52,30.4 1 0 diff --git a/kadai1/asuke-yasukuni/go.mod b/kadai1/asuke-yasukuni/go.mod new file mode 100644 index 0000000..979b114 --- /dev/null +++ b/kadai1/asuke-yasukuni/go.mod @@ -0,0 +1,3 @@ +module github.com/gopherdojo/dojo7/asuke-yasukuni + +go 1.12 diff --git a/kadai1/asuke-yasukuni/imgreplacer b/kadai1/asuke-yasukuni/imgreplacer new file mode 100755 index 0000000..00b340b Binary files /dev/null and b/kadai1/asuke-yasukuni/imgreplacer differ diff --git a/kadai1/asuke-yasukuni/main.go b/kadai1/asuke-yasukuni/main.go new file mode 100644 index 0000000..729d6f5 --- /dev/null +++ b/kadai1/asuke-yasukuni/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + "log" + + "github.com/gopherdojo/dojo7/asuke-yasukuni/replacer" + "github.com/gopherdojo/dojo7/asuke-yasukuni/validation" + "github.com/gopherdojo/dojo7/asuke-yasukuni/walk" +) + +var src = flag.String("src", "", "ファイルパス書いて") +var from = flag.String("from", "jpg", "変換したい画像の拡張子 jpg or png") +var to = flag.String("to", "png", "変換後の拡張子 jpg or png") + +func main() { + flag.Parse() + + // do ext validation + if !validation.Ext(*from) || !validation.Ext(*to) { + log.Fatalf("\x1b[31mfrom:%s to:%s encode is unsupported\x1b[0m\n", *from, *to) + } + + log.Printf("\x1b[33m%s\x1b[0m\n", "[replace start]") + + walker := walk.Walk{File: &replacer.File{}} + files, err := walker.Encoder(src, *from, *to) + if err != nil { + log.Fatal(err) + } + + // encoding result + for _, f := range files { + log.Print(f) + } + + log.Printf("\x1b[33m%s\x1b[0m\n", "[replace end]") +} diff --git a/kadai1/asuke-yasukuni/replacer/encode.go b/kadai1/asuke-yasukuni/replacer/encode.go new file mode 100644 index 0000000..46fe324 --- /dev/null +++ b/kadai1/asuke-yasukuni/replacer/encode.go @@ -0,0 +1,69 @@ +// Replacer is a package that can convert to the specified image format (jpg, png) by generating File structure. +// Supported formats png,jpg +package replacer + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "log" + "os" + "path/filepath" +) + +// A structure that stores image files. +type File struct{} + +// This method encodes an image file into jpg or png. +func (f *File) Encode(path, to string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer fileClose(file) + + img, _, err := image.Decode(file) + if err != nil { + return err + } + + // create output file + outPath := path[:len(path)-len(filepath.Ext(path))] + "." + to + out, err := os.Create(outPath) + if err != nil { + return err + } + defer fileClose(out) + + // select encoder + switch to { + case "jpg": + if err := jpeg.Encode(out, img, &jpeg.Options{Quality: 100}); err != nil { + return err + } + case "png": + if err := png.Encode(out, img); err != nil { + return err + } + default: + // delete fail file + if err := os.Remove(outPath); err != nil { + return err + } + return fmt.Errorf("%s is unsupported extension", to) + } + + // delete original file + if err := os.Remove(path); err != nil { + return err + } + + return nil +} + +func fileClose(file *os.File) { + if err := file.Close(); err != nil { + log.Printf("\x1b[31m%s:%s\x1b[0m\n", "[encode error]", err) + } +} diff --git a/kadai1/asuke-yasukuni/replacer/encode_test.go b/kadai1/asuke-yasukuni/replacer/encode_test.go new file mode 100644 index 0000000..2d52297 --- /dev/null +++ b/kadai1/asuke-yasukuni/replacer/encode_test.go @@ -0,0 +1,30 @@ +package replacer + +import ( + "testing" +) + +func TestEncode(t *testing.T) { + + var testCase = []struct { + Name string + Src string + To string + Result bool + }{ + {"jpg -> png encode", "../testdata/test-single.jpg", "png", true}, + {"png -> jpg encode", "../testdata/test-single.png", "jpg", true}, + {"jpg -> gif not support encode", "../testdata/test-single.jpg", "gif", false}, + {"not found file encode", "../testdata/test-nofile.gif", "png", false}, + } + + f := File{} + for _, tc := range testCase { + t.Run(tc.Name, func(t *testing.T) { + err := f.Encode(tc.Src, tc.To) + if err != nil && tc.Result { + t.Error(err) + } + }) + } +} diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-1.jpg b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-1.jpg new file mode 100644 index 0000000..a9a10ac Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-1.jpg differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-1.png b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-1.png new file mode 100644 index 0000000..d5dc988 Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-1.png differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-2.jpg b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-2.jpg new file mode 100644 index 0000000..a9a10ac Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-2.jpg differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-2.png b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-2.png new file mode 100644 index 0000000..d5dc988 Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/recursiondata/test-2.png differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/test-1.jpg b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-1.jpg new file mode 100644 index 0000000..2ff7ede Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-1.jpg differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/test-1.png b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-1.png new file mode 100644 index 0000000..d5dc988 Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-1.png differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/test-2.jpg b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-2.jpg new file mode 100644 index 0000000..a9a10ac Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-2.jpg differ diff --git a/kadai1/asuke-yasukuni/testdata/multiple_replace/test-2.png b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-2.png new file mode 100644 index 0000000..d5dc988 Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/multiple_replace/test-2.png differ diff --git a/kadai1/asuke-yasukuni/testdata/test-single.jpg b/kadai1/asuke-yasukuni/testdata/test-single.jpg new file mode 100644 index 0000000..dd107d9 Binary files /dev/null and b/kadai1/asuke-yasukuni/testdata/test-single.jpg differ diff --git a/kadai1/asuke-yasukuni/validation/extention.go b/kadai1/asuke-yasukuni/validation/extention.go new file mode 100644 index 0000000..a6e7e9f --- /dev/null +++ b/kadai1/asuke-yasukuni/validation/extention.go @@ -0,0 +1,11 @@ +// Validation package for commands. +package validation + +// Returns true for allowed formats, false otherwise. +func Ext(ext string) bool { + // Because there are few correspondence formats, we do not make map. + if ext == "jpg" || ext == "png" { + return true + } + return false +} diff --git a/kadai1/asuke-yasukuni/validation/extention_test.go b/kadai1/asuke-yasukuni/validation/extention_test.go new file mode 100644 index 0000000..a666d1d --- /dev/null +++ b/kadai1/asuke-yasukuni/validation/extention_test.go @@ -0,0 +1,29 @@ +package validation + +import ( + "testing" +) + +func TestExtValidation(t *testing.T) { + testCases := []struct { + Name string + Ext string + Result bool + }{ + {Name: "validate success", Ext: "png", Result: true}, + {Name: "validate success", Ext: "jpg", Result: true}, + {Name: "validate fail not support", Ext: "gif", Result: false}, + {Name: "validate fail not support", Ext: "pdf", Result: false}, + {Name: "validate fail number", Ext: "23456886", Result: false}, + {Name: "validate fail symbol", Ext: ":;@[]_/23!-^~#/.,", Result: false}, + {Name: "validate fail symbol number", Ext: ":;@po234[]_/23!-^~#/.,", Result: false}, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + if Ext(tc.Ext) != tc.Result { + t.Fatalf("%s %s", "ext", tc.Ext) + } + }) + } +} diff --git a/kadai1/asuke-yasukuni/walk/encoder.go b/kadai1/asuke-yasukuni/walk/encoder.go new file mode 100644 index 0000000..ccbf666 --- /dev/null +++ b/kadai1/asuke-yasukuni/walk/encoder.go @@ -0,0 +1,36 @@ +// Recursive image encoder command implementation. +package walk + +import ( + "fmt" + "os" + "path/filepath" +) + +type File interface { + Encode(path, toExt string) error +} + +type Walk struct { + File File +} + +// Recursively search the directory and perform encoding. +func (w *Walk) Encoder(src *string, fromExt, toExt string) (encodeFiles []string, err error) { + err = filepath.Walk(*src, func(path string, info os.FileInfo, err error) error { + if filepath.Ext(path) != "."+fromExt { + return nil + } + + // Use to output. + encodeFiles = append(encodeFiles, fmt.Sprintf("%s%s -> %s", "[replace file]", path, toExt)) + + if err := w.File.Encode(path, toExt); err != nil { + return err + } + + return nil + }) + + return +} diff --git a/kadai1/asuke-yasukuni/walk/encoder_test.go b/kadai1/asuke-yasukuni/walk/encoder_test.go new file mode 100644 index 0000000..055135b --- /dev/null +++ b/kadai1/asuke-yasukuni/walk/encoder_test.go @@ -0,0 +1,65 @@ +package walk + +import ( + "reflect" + "testing" +) + +type testFile struct{} + +func (t *testFile) Encode(path, toExt string) error { + return nil +} + +func TestWalkEncoder(t *testing.T) { + + src := "../testdata/multiple_replace/" + + testCase := []struct { + Name string + From string + To string + Files []string + }{ + { + Name: "walk jpg -> png", + From: "jpg", + To: "png", + Files: []string{ + "[replace file]../testdata/multiple_replace/recursiondata/test-1.jpg -> png", + "[replace file]../testdata/multiple_replace/recursiondata/test-2.jpg -> png", + "[replace file]../testdata/multiple_replace/test-1.jpg -> png", + "[replace file]../testdata/multiple_replace/test-2.jpg -> png", + }, + }, + { + Name: "walk png -> jpg", + From: "png", + To: "jpg", + Files: []string{ + "[replace file]../testdata/multiple_replace/recursiondata/test-1.png -> jpg", + "[replace file]../testdata/multiple_replace/recursiondata/test-2.png -> jpg", + "[replace file]../testdata/multiple_replace/test-1.png -> jpg", + "[replace file]../testdata/multiple_replace/test-2.png -> jpg", + }, + }, + } + + walker := Walk{File: &testFile{}} + for _, tc := range testCase { + t.Run(tc.Name, func(t *testing.T) { + files, err := walker.Encoder(&src, tc.From, tc.To) + testDeepEqual(t, tc.Files, files, err) + }) + } +} + +func testDeepEqual(t *testing.T, tcFiles, files []string, err error) { + t.Helper() + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(tcFiles, files) { + t.Fatal(files) + } +}