diff --git a/kadai2/tanaka0325/README.md b/kadai2/tanaka0325/README.md
new file mode 100644
index 0000000..620b539
--- /dev/null
+++ b/kadai2/tanaka0325/README.md
@@ -0,0 +1,33 @@
+# 課題2
+
+## 【TRY】io.Readerとio.Writer
+
+```
+- io.Readerとio.Writerについて調べてみよう
+ - 標準パッケージでどのように使われているか
+ - io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる
+```
+
+### io.Readerとio.Writerについて調べてみよう
+
+#### 標準パッケージでどのように使われているか
+
+身近なものでいうと `fmt` の `Fprint` 系の関数は `io.Writer` を引数にとり、そこに対して書き込みを行っている。
+例えば `Println` は `io.Writer` として `os.Stdout` を `Fprintln` に渡して処理を行っている。
+
+標準バッケージでは上記のほかにも、画像やファイルやhttpのリクエスト/レスポンスなど「何か読み書きできるやつ」を抽象化して扱えるように `io.Reader`, `io.Writer` が使われている。
+
+#### io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる
+
+- 「読み書きできるやつ」と抽象化することができるので、例えば書き込みをする関数を1つ作れば複数の構造体に対応できる
+ - `io.Writer` がない場合、「ファイルに対して書き込みする関数」「画像に対して書き込みする関数」のように構造体ごとに関数を準備しなくてはならない
+- 抽象化することにより、具体的な構造体に依存しなくなるため、テスト時に差し替えることができモックなどでテストがしやくすなる
+ - 例えば画像を扱うテストをしたい時に、実際に画像を準備する必要がない
+
+
+## 感想
+
+- どういう構成にするのが良いのか考えながら何度もいじっていたらどんどん深みにハマり、何が良いのか全然わからなくなってしまった...
+- `main` パッケージにはテストを書くほどの処理は書かないで複雑な処理を書きたいなら別途パッケージを切るべきかな?と思ったので今回は `main` パッケージのテストを書いていないがそれで良いのかわからず
+ - 現状で `main` には受け取ったオプション、引数まわりの処理などをしているので、もしパッケージを作るなら `cli` みたいなパッケージを作ろうと思ってますが、今くらいの規模であれば作らなくてよいかなと思いました
+
diff --git a/kadai2/tanaka0325/imgconv/cmd/imgconv/main.go b/kadai2/tanaka0325/imgconv/cmd/imgconv/main.go
new file mode 100644
index 0000000..66a6b02
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/cmd/imgconv/main.go
@@ -0,0 +1,146 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv"
+)
+
+var (
+ args []string
+
+ from string
+ to string
+ dryRun bool
+)
+
+const (
+ fromUsageText = "before extension"
+ toUsageText = "after extension"
+ dryRunUsageText = "with dry-run"
+)
+
+var allowedExts = map[string]bool{
+ "png": true,
+ "jpg": true,
+ "jpeg": true,
+ "gif": true,
+ "bmp": true,
+ "tiff": true,
+ "tif": true,
+}
+
+func init() {
+ flag.StringVar(&from, "from", "jpg", fromUsageText)
+ flag.StringVar(&from, "f", "jpg", fromUsageText+" (short)")
+ flag.StringVar(&to, "to", "png", toUsageText)
+ flag.StringVar(&to, "t", "png", toUsageText+" (short)")
+ flag.BoolVar(&dryRun, "dry-run", false, dryRunUsageText)
+ flag.BoolVar(&dryRun, "n", false, dryRunUsageText+" (short)")
+ flag.Parse()
+
+ args = flag.Args()
+}
+
+func main() {
+ // validate options
+ if ok := isAllowedFileType(from); !ok {
+ onExit(fmt.Errorf("%s is invalid filetype", from))
+ }
+ if ok := isAllowedFileType(to); !ok {
+ onExit(fmt.Errorf("%s is invalid filetype", to))
+ }
+
+ // validate arguments
+ dirnames := uniq(args)
+ for _, dirname := range dirnames {
+ ok, err := isDir(dirname)
+ if err != nil {
+ onExit(err)
+ }
+ if !ok {
+ onExit(fmt.Errorf("%s is not a directory", dirname))
+ }
+ }
+
+ paths, err := getTargetFilepaths(dirnames, from)
+ if err != nil {
+ onExit(err)
+ }
+
+ for _, path := range paths {
+ param := imgconv.ConvertParam{
+ Path: path,
+ FileHandler: imgconv.NewFile(),
+ BeforeFormat: imgconv.NewImage(from),
+ AfterFormat: imgconv.NewImage(to),
+ }
+
+ if !dryRun {
+ if err := imgconv.Do(param); err != nil {
+ onExit(err)
+ }
+ } else {
+ e := len(param.Path) - len(from)
+ fmt.Printf("%s => %s%s\n", path, path[:e], to)
+ }
+ }
+}
+
+func isAllowedFileType(ft string) bool {
+ return allowedExts[ft]
+}
+
+func onExit(err error) {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+}
+
+func uniq([]string) []string {
+ m := map[string]bool{}
+ u := []string{}
+
+ for _, v := range args {
+ if !m[v] {
+ m[v] = true
+
+ u = append(u, v)
+ }
+ }
+
+ return u
+}
+
+func isDir(path string) (bool, error) {
+ fi, err := os.Stat(path)
+ if err != nil {
+ return false, err
+ }
+
+ return fi.IsDir(), nil
+}
+
+func getTargetFilepaths(ds []string, ext string) ([]string, error) {
+ var names []string
+
+ for _, n := range ds {
+ if err := filepath.Walk(n, func(name string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if filepath.Ext(name) == "."+ext {
+ names = append(names, name)
+ }
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ }
+
+ return names, nil
+}
diff --git a/kadai2/tanaka0325/imgconv/cover.html b/kadai2/tanaka0325/imgconv/cover.html
new file mode 100644
index 0000000..8758e79
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/cover.html
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ not tracked
+
+ not covered
+ covered
+
+
+
+
+
+
package imgconv
+
+import (
+ "io"
+ "os"
+)
+
+type FileHandler interface {
+ Open(string) (io.ReadCloser, error)
+ Create(string) (io.WriteCloser, error)
+}
+
+type File struct{}
+
+func (File) Open(n string) (io.ReadCloser, error) { return os.Open(n) }
+func (File) Create(n string) (io.WriteCloser, error) { return os.Create(n) }
+
+func NewFile() File {
+ return File{}
+}
+
+
+
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 ImageFormater interface {
+ Decoder
+ Encoder
+ GetExt() string
+}
+
+type ImageFormat struct {
+ Ext string
+}
+
+// ImagePng is type for png format.
+type PNG ImageFormat
+
+func (PNG) Decode(r io.Reader) (image.Image, error) { return png.Decode(r) }
+func (PNG) Encode(w io.Writer, i image.Image) error { return png.Encode(w, i) }
+func (p *PNG) GetExt() string { return p.Ext }
+
+// JPEG is type for jpeg format.
+type JPEG ImageFormat
+
+func (JPEG) Decode(r io.Reader) (image.Image, error) { return jpeg.Decode(r) }
+func (JPEG) Encode(w io.Writer, i image.Image) error { return jpeg.Encode(w, i, nil) }
+func (j *JPEG) GetExt() string { return j.Ext }
+
+// GIF is type for gif format.
+type GIF ImageFormat
+
+func (GIF) Decode(r io.Reader) (image.Image, error) { return gif.Decode(r) }
+func (GIF) Encode(w io.Writer, i image.Image) error {
+ return gif.Encode(w, i, &gif.Options{NumColors: 256})
+}
+func (g *GIF) GetExt() string { return g.Ext }
+
+// BMP is type for bmp format.
+type BMP ImageFormat
+
+func (BMP) Decode(r io.Reader) (image.Image, error) { return bmp.Decode(r) }
+func (BMP) Encode(w io.Writer, i image.Image) error { return bmp.Encode(w, i) }
+func (b *BMP) GetExt() string { return b.Ext }
+
+// TIFF is type for tiff format.
+type TIFF ImageFormat
+
+func (TIFF) Decode(r io.Reader) (image.Image, error) { return tiff.Decode(r) }
+func (TIFF) Encode(w io.Writer, i image.Image) error { return tiff.Encode(w, i, nil) }
+func (t *TIFF) GetExt() string { return t.Ext }
+
+func NewImageFormat(ext string) ImageFormater {
+ switch ext {
+ case "png":
+ return &PNG{Ext: ext}
+ case "jpg", "jpeg":
+ return &JPEG{Ext: ext}
+ case "gif":
+ return &GIF{Ext: ext}
+ case "bmp":
+ return &BMP{Ext: ext}
+ case "tiff", "tif":
+ return &TIFF{Ext: ext}
+ }
+
+ return nil
+}
+
+
+
// Imgconv package is to convert images file format.
+package imgconv
+
+import "io"
+
+// ConvertParam is parameter to convert image format.
+type ConvertParam struct {
+ Path string
+ FileHandler FileHandler
+ BeforeFormat ImageFormater
+ AfterFormat ImageFormater
+}
+
+// Do is func to convert image format.
+func Do(param ConvertParam) (rerr error) {
+ r, err := param.FileHandler.Open(param.Path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ n := buildAfterPath(param.Path, param.BeforeFormat.GetExt(), param.AfterFormat.GetExt())
+ w, err := param.FileHandler.Create(n)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ err := w.Close()
+ if err != nil {
+ rerr = err
+ }
+ }()
+
+ if err := convert(r, param.BeforeFormat, w, param.AfterFormat); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func buildAfterPath(path, beforeExt, afterExt string) string {
+ e := len(path) - len(beforeExt)
+ return path[:e] + afterExt
+}
+
+func convert(r io.Reader, d Decoder, w io.Writer, e Encoder) error {
+ img, err := d.Decode(r)
+ if err != nil {
+ return err
+ }
+
+ if err := e.Encode(w, img); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+
+
+
+
+
diff --git a/kadai2/tanaka0325/imgconv/cover.out b/kadai2/tanaka0325/imgconv/cover.out
new file mode 100644
index 0000000..187b117
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/cover.out
@@ -0,0 +1,42 @@
+mode: set
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/file.go:15.54,15.75 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/file.go:16.54,16.77 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/file.go:18.21,20.2 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:35.53,35.77 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:36.53,36.80 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:37.53,37.69 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:42.54,42.79 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:43.54,43.87 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:44.54,44.70 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:49.53,49.77 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:50.53,52.2 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:53.31,53.47 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:58.53,58.77 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:59.53,59.80 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:60.53,60.69 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:65.54,65.79 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:66.54,66.87 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:67.54,67.70 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:69.47,70.13 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:83.2,83.12 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:71.13,72.24 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:73.21,74.25 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:75.13,76.24 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:77.13,78.24 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/format.go:79.21,80.25 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:15.42,17.16 2 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:20.2,24.16 4 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:27.2,27.15 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:34.2,34.77 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:38.2,38.12 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:17.16,19.3 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:24.16,26.3 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:27.15,29.17 2 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:29.17,31.4 1 0
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:34.77,36.3 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:41.62,44.2 2 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:46.68,48.16 2 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:52.2,52.41 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:56.2,56.12 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:48.16,50.3 1 1
+github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv/imgconv.go:52.41,54.3 1 1
diff --git a/kadai2/tanaka0325/imgconv/export_test.go b/kadai2/tanaka0325/imgconv/export_test.go
new file mode 100644
index 0000000..91de432
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/export_test.go
@@ -0,0 +1,4 @@
+package imgconv
+
+var ConvertFunc = convert
+var BuildAfterPathFunc = buildAfterPath
diff --git a/kadai2/tanaka0325/imgconv/file.go b/kadai2/tanaka0325/imgconv/file.go
new file mode 100644
index 0000000..a92a39d
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/file.go
@@ -0,0 +1,20 @@
+package imgconv
+
+import (
+ "io"
+ "os"
+)
+
+type FileHandler interface {
+ Open(string) (io.ReadCloser, error)
+ Create(string) (io.WriteCloser, error)
+}
+
+type File struct{}
+
+func (File) Open(n string) (io.ReadCloser, error) { return os.Open(n) }
+func (File) Create(n string) (io.WriteCloser, error) { return os.Create(n) }
+
+func NewFile() File {
+ return File{}
+}
diff --git a/kadai2/tanaka0325/imgconv/file_test.go b/kadai2/tanaka0325/imgconv/file_test.go
new file mode 100644
index 0000000..7fea260
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/file_test.go
@@ -0,0 +1,17 @@
+package imgconv_test
+
+import (
+ "testing"
+
+ "github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv"
+)
+
+func TestImgconvNewFile(t *testing.T) {
+ var f interface{}
+ f = imgconv.NewFile()
+
+ got, ok := f.(imgconv.File)
+ if !ok {
+ t.Errorf("expected type: File, but got %T", got)
+ }
+}
diff --git a/kadai2/tanaka0325/imgconv/format.go b/kadai2/tanaka0325/imgconv/format.go
new file mode 100644
index 0000000..352f440
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/format.go
@@ -0,0 +1,84 @@
+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 ImageConverter interface {
+ Decoder
+ Encoder
+ GetExt() string
+}
+
+type Image struct {
+ Ext string
+}
+
+// ImagePng is type for png format.
+type PNG Image
+
+func (PNG) Decode(r io.Reader) (image.Image, error) { return png.Decode(r) }
+func (PNG) Encode(w io.Writer, i image.Image) error { return png.Encode(w, i) }
+func (p *PNG) GetExt() string { return p.Ext }
+
+// JPEG is type for jpeg format.
+type JPEG Image
+
+func (JPEG) Decode(r io.Reader) (image.Image, error) { return jpeg.Decode(r) }
+func (JPEG) Encode(w io.Writer, i image.Image) error { return jpeg.Encode(w, i, nil) }
+func (j *JPEG) GetExt() string { return j.Ext }
+
+// GIF is type for gif format.
+type GIF Image
+
+func (GIF) Decode(r io.Reader) (image.Image, error) { return gif.Decode(r) }
+func (GIF) Encode(w io.Writer, i image.Image) error {
+ return gif.Encode(w, i, &gif.Options{NumColors: 256})
+}
+func (g *GIF) GetExt() string { return g.Ext }
+
+// BMP is type for bmp format.
+type BMP Image
+
+func (BMP) Decode(r io.Reader) (image.Image, error) { return bmp.Decode(r) }
+func (BMP) Encode(w io.Writer, i image.Image) error { return bmp.Encode(w, i) }
+func (b *BMP) GetExt() string { return b.Ext }
+
+// TIFF is type for tiff format.
+type TIFF Image
+
+func (TIFF) Decode(r io.Reader) (image.Image, error) { return tiff.Decode(r) }
+func (TIFF) Encode(w io.Writer, i image.Image) error { return tiff.Encode(w, i, nil) }
+func (t *TIFF) GetExt() string { return t.Ext }
+
+func NewImage(ext string) ImageConverter {
+ switch ext {
+ case "png":
+ return &PNG{Ext: ext}
+ case "jpg", "jpeg":
+ return &JPEG{Ext: ext}
+ case "gif":
+ return &GIF{Ext: ext}
+ case "bmp":
+ return &BMP{Ext: ext}
+ case "tiff", "tif":
+ return &TIFF{Ext: ext}
+ }
+
+ return nil
+}
diff --git a/kadai2/tanaka0325/imgconv/format_test.go b/kadai2/tanaka0325/imgconv/format_test.go
new file mode 100644
index 0000000..a2fa0d9
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/format_test.go
@@ -0,0 +1,39 @@
+package imgconv_test
+
+import (
+ "testing"
+
+ "github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv"
+)
+
+func TestImgconvNewImage(t *testing.T) {
+ tests := []struct {
+ args string
+ expected string
+ }{
+ {args: "png", expected: "png"},
+ {args: "jpg", expected: "jpg"},
+ {args: "jpeg", expected: "jpeg"},
+ {args: "gif", expected: "gif"},
+ {args: "bmp", expected: "bmp"},
+ {args: "tiff", expected: "tiff"},
+ {args: "tif", expected: "tif"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.args, func(t *testing.T) {
+ f := imgconv.NewImage(tt.args)
+ got := f.GetExt()
+ if got != tt.expected {
+ t.Errorf("expected = %+v, but got = %+v", tt.expected, got)
+ }
+ })
+ }
+
+ t.Run("expected args", func(t *testing.T) {
+ got := imgconv.NewImage("pdf")
+ if got != nil {
+ t.Errorf("expected = nil, but got = %+v", got)
+ }
+ })
+}
diff --git a/kadai2/tanaka0325/imgconv/go.mod b/kadai2/tanaka0325/imgconv/go.mod
new file mode 100644
index 0000000..554fa07
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/go.mod
@@ -0,0 +1,5 @@
+module github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv
+
+go 1.14
+
+require golang.org/x/image v0.0.0-20200618115811-c13761719519
diff --git a/kadai2/tanaka0325/imgconv/go.sum b/kadai2/tanaka0325/imgconv/go.sum
new file mode 100644
index 0000000..83da957
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/go.sum
@@ -0,0 +1,4 @@
+github.com/gopherdojo/dojo8 v0.0.0-20200703052727-6a79d18126bf h1:lpYevjFQMxI5VNBc3WXV6Z5pDDrdppdDKwmeBoyt5BE=
+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/kadai2/tanaka0325/imgconv/imgconv.go b/kadai2/tanaka0325/imgconv/imgconv.go
new file mode 100644
index 0000000..18e56e8
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/imgconv.go
@@ -0,0 +1,57 @@
+// Imgconv package is to convert images file format.
+package imgconv
+
+import "io"
+
+// ConvertParam is parameter to convert image format.
+type ConvertParam struct {
+ Path string
+ FileHandler FileHandler
+ BeforeFormat ImageConverter
+ AfterFormat ImageConverter
+}
+
+// Do is func to convert image format.
+func Do(param ConvertParam) (rerr error) {
+ r, err := param.FileHandler.Open(param.Path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ n := buildAfterPath(param.Path, param.BeforeFormat.GetExt(), param.AfterFormat.GetExt())
+ w, err := param.FileHandler.Create(n)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ err := w.Close()
+ if err != nil {
+ rerr = err
+ }
+ }()
+
+ if err := convert(r, param.BeforeFormat, w, param.AfterFormat); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func buildAfterPath(path, beforeExt, afterExt string) string {
+ e := len(path) - len(beforeExt)
+ return path[:e] + afterExt
+}
+
+func convert(r io.Reader, d Decoder, w io.Writer, e Encoder) error {
+ img, err := d.Decode(r)
+ if err != nil {
+ return err
+ }
+
+ if err := e.Encode(w, img); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/kadai2/tanaka0325/imgconv/imgconv_test.go b/kadai2/tanaka0325/imgconv/imgconv_test.go
new file mode 100644
index 0000000..decdd0a
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/imgconv_test.go
@@ -0,0 +1,251 @@
+package imgconv_test
+
+import (
+ "fmt"
+ "image"
+ "io"
+ "os"
+ "testing"
+
+ "github.com/gopherdojo/dojo8/kadai2/tanaka0325/imgconv"
+)
+
+func TestImgconvDo(t *testing.T) {
+ tests := []struct {
+ name string
+ args imgconv.ConvertParam
+ isErr bool
+ }{
+ {
+ name: "error: Open failed",
+ args: func() imgconv.ConvertParam {
+ mf := newMockFileHandler(
+ func(s string) (io.ReadCloser, error) { return nil, fmt.Errorf("err") },
+ nil,
+ )
+
+ return imgconv.ConvertParam{
+ Path: "path",
+ FileHandler: mf,
+ }
+ }(),
+ isErr: true,
+ },
+ {
+ name: "error: Create failed",
+ args: func() imgconv.ConvertParam {
+ mf := newMockFileHandler(
+ func(s string) (io.ReadCloser, error) { return mockCloser{}, nil },
+ func(s string) (io.WriteCloser, error) { return nil, fmt.Errorf("err") },
+ )
+
+ mbi := newMockImage(
+ func(r io.Reader) (image.Image, error) { return mockStdImage{}, nil },
+ nil,
+ func() string { return "jpeg" },
+ )
+
+ mai := newMockImage(
+ nil,
+ func(w io.Writer, i image.Image) error { return nil },
+ func() string { return "png" },
+ )
+
+ return imgconv.ConvertParam{
+ Path: "path",
+ FileHandler: mf,
+ BeforeFormat: mbi,
+ AfterFormat: mai,
+ }
+ }(),
+ isErr: true,
+ },
+ {
+ name: "error: convert failed",
+ args: func() imgconv.ConvertParam {
+ mf := newMockFileHandler(
+ func(s string) (io.ReadCloser, error) { return mockCloser{}, nil },
+ func(s string) (io.WriteCloser, error) { return mockCloser{}, nil },
+ )
+
+ mbi := newMockImage(
+ func(r io.Reader) (image.Image, error) { return mockStdImage{}, nil },
+ nil,
+ func() string { return "jpeg" },
+ )
+
+ mai := newMockImage(
+ nil,
+ func(w io.Writer, i image.Image) error { return fmt.Errorf("err") },
+ func() string { return "png" },
+ )
+
+ return imgconv.ConvertParam{
+ Path: "path",
+ FileHandler: mf,
+ BeforeFormat: mbi,
+ AfterFormat: mai,
+ }
+ }(),
+ isErr: true,
+ },
+ {
+ name: "ok",
+ args: func() imgconv.ConvertParam {
+ mf := newMockFileHandler(
+ func(s string) (io.ReadCloser, error) { return mockCloser{}, nil },
+ func(s string) (io.WriteCloser, error) { return mockCloser{}, nil },
+ )
+
+ mbi := newMockImage(
+ func(r io.Reader) (image.Image, error) { return mockStdImage{}, nil },
+ nil,
+ func() string { return "jpeg" },
+ )
+ mai := newMockImage(
+ nil,
+ func(w io.Writer, i image.Image) error { return nil },
+ func() string { return "png" },
+ )
+
+ return imgconv.ConvertParam{
+ Path: "path",
+ FileHandler: mf,
+ BeforeFormat: mbi,
+ AfterFormat: mai,
+ }
+ }(),
+ isErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := imgconv.Do(tt.args)
+ if (tt.isErr && err == nil) || (!tt.isErr && err != nil) {
+ t.Errorf("expected err = %+v, but got err = %+v", tt.isErr, err)
+ }
+ })
+ }
+}
+
+func TestImgconv_convert(t *testing.T) {
+ type args struct {
+ Reader io.Reader
+ Decoder imgconv.Decoder
+ Writer io.Writer
+ Encoder imgconv.Encoder
+ }
+
+ tests := []struct {
+ name string
+ args args
+ isErr bool
+ }{
+ {
+ name: "error: Decode failed",
+ args: func() args {
+ mi := newMockImage(
+ func(r io.Reader) (image.Image, error) { return nil, fmt.Errorf("err") },
+ nil,
+ nil,
+ )
+ return args{
+ os.Stdin,
+ mi,
+ nil,
+ nil,
+ }
+ }(),
+ isErr: true,
+ },
+ {
+ name: "error: Encode failed",
+ args: func() args {
+ me := newMockImage(
+ func(r io.Reader) (image.Image, error) { return mockImage{}, nil },
+ nil,
+ nil,
+ )
+
+ md := newMockImage(
+ nil,
+ func(w io.Writer, i image.Image) error { return fmt.Errorf("err") },
+ nil,
+ )
+ return args{
+ os.Stdin,
+ me,
+ os.Stdout,
+ md,
+ }
+ }(),
+ isErr: true,
+ },
+ {
+ name: "ok",
+ args: func() args {
+ me := newMockImage(
+ func(r io.Reader) (image.Image, error) { return mockImage{}, nil },
+ nil,
+ nil,
+ )
+
+ md := newMockImage(
+ nil,
+ func(w io.Writer, i image.Image) error { return nil },
+ nil,
+ )
+
+ return args{
+ os.Stdin,
+ me,
+ os.Stdout,
+ md,
+ }
+ }(),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := imgconv.ConvertFunc(tt.args.Reader, tt.args.Decoder, tt.args.Writer, tt.args.Encoder)
+ if (tt.isErr && err == nil) || (!tt.isErr && err != nil) {
+ t.Errorf("expected err = %+v, but got err = %+v", tt.isErr, err)
+ }
+ })
+ }
+}
+
+func TestImgconv_buildAfterPath(t *testing.T) {
+ type args struct {
+ path string
+ beforeExt string
+ afterExt string
+ }
+
+ tests := []struct {
+ name string
+ args args
+ expected string
+ }{
+ {
+ name: "ok",
+ args: args{
+ path: "test.gif",
+ beforeExt: "gif",
+ afterExt: "png",
+ },
+ expected: "test.png",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := imgconv.BuildAfterPathFunc(tt.args.path, tt.args.beforeExt, tt.args.afterExt)
+ if got != tt.expected {
+ t.Errorf("expected = %+v, but got = %+v", tt.expected, got)
+ }
+ })
+ }
+}
diff --git a/kadai2/tanaka0325/imgconv/mocks_test.go b/kadai2/tanaka0325/imgconv/mocks_test.go
new file mode 100644
index 0000000..0cab6ee
--- /dev/null
+++ b/kadai2/tanaka0325/imgconv/mocks_test.go
@@ -0,0 +1,61 @@
+package imgconv_test
+
+import (
+ "image"
+ "image/color"
+ "io"
+)
+
+type mockCloser struct{}
+
+func (mockCloser) Read(p []byte) (int, error) { return 0, nil }
+func (mockCloser) Close() error { return nil }
+func (mockCloser) Write(p []byte) (int, error) { return 0, nil }
+
+type openFunc func(string) (io.ReadCloser, error)
+type createFunc func(string) (io.WriteCloser, error)
+
+type mockFileHandler struct {
+ mockOpen openFunc
+ mockCreate createFunc
+}
+
+func (m mockFileHandler) Open(s string) (io.ReadCloser, error) { return m.mockOpen(s) }
+func (m mockFileHandler) Create(s string) (io.WriteCloser, error) { return m.mockCreate(s) }
+
+func newMockFileHandler(of openFunc, cf createFunc) mockFileHandler {
+ return mockFileHandler{
+ mockOpen: of,
+ mockCreate: cf,
+ }
+}
+
+type decodeFunc func(io.Reader) (image.Image, error)
+type encodeFunc func(io.Writer, image.Image) error
+type getExtFunc func() string
+
+type mockImage struct {
+ mockDecode decodeFunc
+ mockEncode encodeFunc
+ mockGetExt getExtFunc
+}
+
+func (m mockImage) Decode(r io.Reader) (image.Image, error) { return m.mockDecode(r) }
+func (m mockImage) Encode(w io.Writer, i image.Image) error {
+ return m.mockEncode(w, i)
+}
+func (m mockImage) GetExt() string { return m.mockGetExt() }
+
+func newMockImage(df decodeFunc, ef encodeFunc, gf getExtFunc) mockImage {
+ return mockImage{
+ mockDecode: df,
+ mockEncode: ef,
+ mockGetExt: gf,
+ }
+}
+
+type mockStdImage struct{}
+
+func (mockStdImage) ColorModel() (c color.Model) { return }
+func (mockStdImage) Bounds() (r image.Rectangle) { return }
+func (mockStdImage) At(int, int) (c color.Color) { return }
diff --git a/kadai2/tanaka0325/imgconv/testdata/images/sample1.jpg b/kadai2/tanaka0325/imgconv/testdata/images/sample1.jpg
new file mode 100644
index 0000000..7a10881
Binary files /dev/null and b/kadai2/tanaka0325/imgconv/testdata/images/sample1.jpg differ
diff --git a/kadai2/tanaka0325/imgconv/testdata/images2/img/sample3.jpg b/kadai2/tanaka0325/imgconv/testdata/images2/img/sample3.jpg
new file mode 100644
index 0000000..7a10881
Binary files /dev/null and b/kadai2/tanaka0325/imgconv/testdata/images2/img/sample3.jpg differ
diff --git a/kadai2/tanaka0325/imgconv/testdata/images2/sample2.jpg b/kadai2/tanaka0325/imgconv/testdata/images2/sample2.jpg
new file mode 100644
index 0000000..7a10881
Binary files /dev/null and b/kadai2/tanaka0325/imgconv/testdata/images2/sample2.jpg differ