diff --git a/kadai2/akuchii/README.md b/kadai2/akuchii/README.md new file mode 100644 index 0000000..d6d1b46 --- /dev/null +++ b/kadai2/akuchii/README.md @@ -0,0 +1,8 @@ +# io.Readerとio.Writerについて調べてみよう + +## 標準パッケージでどのように使われているか +os.File, os.Stdin, os.Stdout, bytes.buffersなどデータの読み書きを行う処理の部分に使われている + +## io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる +例えばデータを読み書きする処理があったときに、io.Reader, io.Writerがあることで使う側は内部実装を知らなくても使うことができる。 +内部実装を知らずに使えるので、読み書き先をDBからjson, csvなどに切り替えたい時も容易に行うことができる。 diff --git a/kadai2/akuchii/converter/converter.go b/kadai2/akuchii/converter/converter.go new file mode 100644 index 0000000..3cd0d20 --- /dev/null +++ b/kadai2/akuchii/converter/converter.go @@ -0,0 +1,159 @@ +// Package converter converts image extension to target extension +package converter + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + "os" + "path/filepath" +) + +var supportedExts = map[string]bool{ + "png": true, + "jpeg": true, + "jpg": true, + "gif": true, +} + +// Encoder encodes image +type Encoder interface { + Encode(io.Writer, image.Image) error +} + +// Converter converts image to other format of afterExt +type Converter struct { + path string + outDir string + afterExt string + encoder Encoder +} + +// JpegEncoder encodes image to jpeg format +type JpegEncoder struct { +} + +// PngEncoder encodes image to png format +type PngEncoder struct { +} + +// GifEncoder encodes image to gif format +type GifEncoder struct { +} + +// Encode encodes to jpeg +func (c *JpegEncoder) Encode(w io.Writer, img image.Image) error { + return jpeg.Encode(w, img, nil) +} + +// Encode encodes to png +func (c *PngEncoder) Encode(w io.Writer, img image.Image) error { + return png.Encode(w, img) +} + +// Encode encodes to gif +func (c *GifEncoder) Encode(w io.Writer, img image.Image) error { + return gif.Encode(w, img, nil) +} + +// Encode encodes to specific format +func (c *Converter) Encode(w io.Writer, img image.Image) error { + return c.encoder.Encode(w, img) +} + +// Decode decodes input image +func (c *Converter) Decode(r io.Reader) (image.Image, error) { + img, _, err := image.Decode(r) + return img, err +} + +// NewConverter creates new converter instance +func NewConverter(path, outDir, afterExt string) (*Converter, error) { + var encoder Encoder + switch afterExt { + case "jpg", "jpeg": + encoder = &JpegEncoder{} + case "png": + encoder = &PngEncoder{} + case "gif": + encoder = &GifEncoder{} + } + return &Converter{path, outDir, afterExt, encoder}, nil +} + +// Execute reads file from path, convert file extension to afterExt and save it in outDir +func (c *Converter) Execute() error { + if _, ok := supportedExts[c.afterExt]; !ok { + return fmt.Errorf("%s is not supported ext", c.afterExt) + } + + in, err := os.Open(c.path) + if err != nil { + return err + } + + defer in.Close() + + img, err := c.Decode(in) + if err != nil { + return err + } + + err = c.createDstDir() + if err != nil { + return err + } + + dstPath, err := c.getDstPath() + if err != nil { + return err + } + + out, err := os.Create(dstPath) + if err != nil { + return err + } + defer out.Close() + + err = c.Encode(out, img) + if err != nil { + return err + } + return nil +} + +func (c *Converter) getDstDir() (string, error) { + srcAbs, err := filepath.Abs(c.path) + if err != nil { + return "", err + } + return filepath.Join(filepath.Dir(srcAbs), c.outDir), nil +} + +func (c *Converter) createDstDir() error { + dstDir, err := c.getDstDir() + if err != nil { + return err + } + + if _, err := os.Stat(dstDir); os.IsNotExist(err) { + err = os.MkdirAll(dstDir, os.ModePerm) + if err != nil { + return err + } + } + return nil +} + +func (c *Converter) getDstPath() (string, error) { + filenameWithoutExt := filepath.Base(c.path[:len(c.path)-len(filepath.Ext(c.path))]) + dstDir, err := c.getDstDir() + if err != nil { + return "", err + } + dstPath := filepath.Join(dstDir, filenameWithoutExt) + "." + c.afterExt + return dstPath, nil +} diff --git a/kadai2/akuchii/converter/converter_test.go b/kadai2/akuchii/converter/converter_test.go new file mode 100644 index 0000000..2d91882 --- /dev/null +++ b/kadai2/akuchii/converter/converter_test.go @@ -0,0 +1,72 @@ +package converter_test + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/gopherdojo/dojo4/kadai2/akuchii/converter" +) + +func TestConvert_NewConverter(t *testing.T) { + cases := []struct { + afterExt string + expectedEncoder reflect.Type + }{ + {"png", reflect.ValueOf(&converter.PngEncoder{}).Type()}, + {"jpeg", reflect.ValueOf(&converter.JpegEncoder{}).Type()}, + {"gif", reflect.ValueOf(&converter.GifEncoder{}).Type()}, + } + for _, c := range cases { + conv, err := converter.NewConverter("", "", c.afterExt) + if err != nil { + t.Fatalf("failed to crete new converter:%s", err) + } + if reflect.ValueOf(conv.ExportEncoder()).Type() != c.expectedEncoder { + t.Fatalf("invalid encoder") + } + } +} + +func TestConverter_Execute(t *testing.T) { + cases := []struct { + target string + outDir string + afterExt string + hasError bool + }{ + {"test.png", "out", "jpeg", false}, + {"test.jpeg", "out", "gif", false}, + {"test.gif", "out", "png", false}, + {"test.webp", "out", "png", true}, + {"test.png", "out", "webp", true}, + {"not_exist_file.png", "out", "jpeg", true}, + } + + for _, c := range cases { + srcPath := filepath.Join("..", "testdata", c.target) + filenameWithoutExt := testGetFileNameWithoutExt(t, c.target) + dstPath := filepath.Join("..", "testdata", c.outDir, filenameWithoutExt) + "." + c.afterExt + conv, err := converter.NewConverter(srcPath, c.outDir, c.afterExt) + if err != nil { + t.Fatalf("fail to create converter: %s", err) + } + err = conv.Execute() + if err != nil && !c.hasError { + t.Fatalf("fail to convert image: %s", err) + } + if _, err := os.Stat(dstPath); os.IsNotExist(err) && !c.hasError { + t.Fatalf("fail to generate converted image: %s", err) + } + if !c.hasError { + os.Remove(dstPath) + } + } +} + +func testGetFileNameWithoutExt(t *testing.T, target string) string { + t.Helper() + + return target[:len(target)-len(filepath.Ext(target))] +} diff --git a/kadai2/akuchii/converter/export_test.go b/kadai2/akuchii/converter/export_test.go new file mode 100644 index 0000000..5d715f3 --- /dev/null +++ b/kadai2/akuchii/converter/export_test.go @@ -0,0 +1,5 @@ +package converter + +func (c *Converter) ExportEncoder() Encoder { + return c.encoder +} diff --git a/kadai2/akuchii/main.go b/kadai2/akuchii/main.go new file mode 100644 index 0000000..75ac147 --- /dev/null +++ b/kadai2/akuchii/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/gopherdojo/dojo4/kadai2/akuchii/converter" +) + +// extension before convert +var beforeExt string + +// extension after convert +var afterExt string + +// source directory +var srcDir string + +// destination directory +var dstDir string + +// exit code +const ( + ExitCodeOK int = iota // 0 + ExitCodeError // 1 +) + +func init() { + flag.StringVar(&beforeExt, "b", "jpeg", "extension before convert") + flag.StringVar(&afterExt, "a", "png", "extension before convert") + flag.StringVar(&srcDir, "s", "", "source directory to convert files") + flag.StringVar(&dstDir, "d", "out", "destination directory to convert files") +} + +func main() { + flag.Parse() + + err := validateArgs() + if err != nil { + fmt.Println(err) + os.Exit(ExitCodeError) + } + + fmt.Printf("start to convert ext before: %s, after: %s\n", beforeExt, afterExt) + err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { + if filepath.Ext(path) == "."+beforeExt { + conv, err := converter.NewConverter(path, dstDir, afterExt) + if err != nil { + fmt.Println(err) + os.Exit(ExitCodeError) + } + + err = conv.Execute() + if err != nil { + fmt.Println(err) + os.Exit(ExitCodeError) + } + fmt.Printf("%s is converted to %s\n", path, afterExt) + } + return nil + }) + if err != nil { + fmt.Println(err) + os.Exit(ExitCodeError) + } + fmt.Println("convert is finished") + os.Exit(ExitCodeOK) +} + +// validateArgs validates input arguments +func validateArgs() error { + if beforeExt == "" || afterExt == "" || srcDir == "" || dstDir == "" { + flag.PrintDefaults() + return errors.New("empty arg is not allowed") + } + + if _, err := os.Stat(srcDir); os.IsNotExist(err) { + return err + } + + if beforeExt == afterExt { + return fmt.Errorf("before and after ext is same: %s", beforeExt) + } + return nil +} diff --git a/kadai2/akuchii/testdata/out/.gitkeep b/kadai2/akuchii/testdata/out/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/kadai2/akuchii/testdata/test.gif b/kadai2/akuchii/testdata/test.gif new file mode 100644 index 0000000..ac37093 Binary files /dev/null and b/kadai2/akuchii/testdata/test.gif differ diff --git a/kadai2/akuchii/testdata/test.jpeg b/kadai2/akuchii/testdata/test.jpeg new file mode 100644 index 0000000..97f9e84 Binary files /dev/null and b/kadai2/akuchii/testdata/test.jpeg differ diff --git a/kadai2/akuchii/testdata/test.png b/kadai2/akuchii/testdata/test.png new file mode 100644 index 0000000..fd588d9 Binary files /dev/null and b/kadai2/akuchii/testdata/test.png differ diff --git a/kadai2/akuchii/testdata/test.webp b/kadai2/akuchii/testdata/test.webp new file mode 100644 index 0000000..7175cb1 Binary files /dev/null and b/kadai2/akuchii/testdata/test.webp differ