From 3063dd6b36d21aa7377d5db34a61a83ddc8e4287 Mon Sep 17 00:00:00 2001 From: norun111 Date: Sat, 24 Apr 2021 00:18:04 +0900 Subject: [PATCH] first-commit --- kadai1/cmd/cvt/Makefile | 8 ++ kadai1/cmd/cvt/main.go | 27 +++++ kadai1/go.mod | 6 + kadai1/go.sum | 2 + kadai1/internal/cvt/cvt.go | 201 ++++++++++++++++++++++++++++++++ kadai1/internal/cvt/cvt_test.go | 151 ++++++++++++++++++++++++ kadai1/pkg/errof/errors.go | 41 +++++++ kadai1/pkg/testutil/testutil.go | 33 ++++++ 8 files changed, 469 insertions(+) create mode 100644 kadai1/cmd/cvt/Makefile create mode 100644 kadai1/cmd/cvt/main.go create mode 100644 kadai1/go.mod create mode 100644 kadai1/go.sum create mode 100644 kadai1/internal/cvt/cvt.go create mode 100644 kadai1/internal/cvt/cvt_test.go create mode 100644 kadai1/pkg/errof/errors.go create mode 100644 kadai1/pkg/testutil/testutil.go diff --git a/kadai1/cmd/cvt/Makefile b/kadai1/cmd/cvt/Makefile new file mode 100644 index 00000000..cb919789 --- /dev/null +++ b/kadai1/cmd/cvt/Makefile @@ -0,0 +1,8 @@ +SERVICE_NAME=cvt +PROJECT_ROOT=$(shell git rev-parse --show-toplevel) + +build: + go build -o $(PROJECT_ROOT)/bin/$(SERVICE_NAME) + +exec: + $(PROJECT_ROOT)/bin/$(SERVICE_NAME) -i=pkg/testdata -o=pkg/png -be=jpg -ae=png diff --git a/kadai1/cmd/cvt/main.go b/kadai1/cmd/cvt/main.go new file mode 100644 index 00000000..fd1955b5 --- /dev/null +++ b/kadai1/cmd/cvt/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "flag" + "fmt" + "github.com/gopherdojo/gopherdojo-studyroom/kadai1/internal/cvt" + "log" +) + +var inputDir, outputDir, beforeExt, afterExt string +var removeSrc bool + +func init() { + flag.StringVar(&inputDir, "i", "", "input dir") + flag.StringVar(&outputDir, "o", "", "output dir") + flag.StringVar(&beforeExt, "be", ".jpg", "before ext") + flag.StringVar(&afterExt, "ae", ".png", "after ext") + flag.BoolVar(&removeSrc, "rm", false, "remove src") + flag.Parse() +} + +func main() { + c := cvt.NewImageCvtGlue(inputDir, outputDir, beforeExt, afterExt, removeSrc) + if err := c.Exec(); err != nil { + log.Fatalf("Failed to execute image convert", fmt.Sprintf("%+v", err)) + } +} diff --git a/kadai1/go.mod b/kadai1/go.mod new file mode 100644 index 00000000..67475deb --- /dev/null +++ b/kadai1/go.mod @@ -0,0 +1,6 @@ +module "github.com/gopherdojo/gopherdojo-studyroom/kadai1" +go 1.16 + +require ( + github.com/pkg/errors v0.9.1 +) diff --git a/kadai1/go.sum b/kadai1/go.sum new file mode 100644 index 00000000..7c401c3f --- /dev/null +++ b/kadai1/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/kadai1/internal/cvt/cvt.go b/kadai1/internal/cvt/cvt.go new file mode 100644 index 00000000..bc457f59 --- /dev/null +++ b/kadai1/internal/cvt/cvt.go @@ -0,0 +1,201 @@ +package cvt + +import ( + "fmt" + "github.com/gopherdojo/gopherdojo-studyroom/kadai1/pkg/errof" + "github.com/pkg/errors" + "image" + "image/gif" + "image/jpeg" + "image/png" + "os" + "path/filepath" + "runtime" + "strings" +) + +type ImageCvtGlue struct { + InputDir string + OutputDir string + BeforeExt string + AfterExt string + RemoveSrc bool +} + +func NewImageCvtGlue( + inputDir, + outputDir, + beforeExt, + afterExt string, + removeSrc bool, +) *ImageCvtGlue { + if strings.Index(beforeExt, ".") == -1 { + beforeExt = fmt.Sprintf(".%s", beforeExt) + } + if strings.Index(afterExt, ".") == -1 { + afterExt = fmt.Sprintf(".%s", afterExt) + } + return &ImageCvtGlue{ + InputDir: inputDir, + OutputDir: outputDir, + BeforeExt: beforeExt, + AfterExt: afterExt, + RemoveSrc: removeSrc, + } +} + +// Exec: +func (c *ImageCvtGlue) Exec() (err error) { + var srcPaths []string + if srcPaths, err = c.pathWalk(); err != nil { + return err + } + if err = c.convert(srcPaths); err != nil { + return err + } + return nil +} + +// convert: +func (c *ImageCvtGlue) convert(srcPaths []string) (err error) { + for _, srcPath := range srcPaths { + var file *os.File + if file, err = os.Open(srcPath); err != nil { + return errors.Wrap(errof.ErrOpenSrcFile, err.Error()) + } + // イメージのデコード + var img image.Image + if img, _, err = image.Decode(file); err != nil { + return errors.Wrap(errof.ErrDecodeImage, err.Error()) + } + // イメージ出力先パス + var dstPath string + if dstPath, err = c.getDstPath(file.Name()); err != nil { + return err + } + // イメージファイルの作成 + var dst *os.File + if dst, err = os.Create(dstPath); err != nil { + return errors.Wrap(errof.ErrCreateDstFile, err.Error()) + } + // 作成したイメージファイルにデコードしたイメージをエンコード + if err = encodeImage(dstPath, dst, img); err != nil { + return err + } + // 変換前のファイルを削除(オプション) + if c.RemoveSrc { + if err = os.Remove(srcPath); err != nil { + return errors.Wrap(errof.ErrRemoveSrcFile, err.Error()) + } + } + if err = file.Close(); err != nil { + return errors.Wrap(errof.ErrCloseSrcFile, err.Error()) + } + if err = dst.Close(); err != nil { + return errors.Wrap(errof.ErrCloseSrcFile, err.Error()) + } + } + return nil +} + +// pathWalk: +func (c *ImageCvtGlue) pathWalk() (srcPaths []string, err error) { + rootDir := getRootDir() + if err = filepath.Walk(filepath.Join(rootDir, c.InputDir), func(srcPath string, info os.FileInfo, err error) error { + if err != nil { + return errors.Wrap(errof.ErrWalkingSrcPath, err.Error()) + } + // ファイルが存在しているパスかどうかを確認 + var isFile bool + if isFile, err = isFileExist(srcPath); err != nil { + return err + } + // ファイルかつ指定した拡張子であれば配列に格納 + if isFile && filepath.Ext(srcPath) == c.BeforeExt { + srcPaths = append(srcPaths, srcPath) + } + return nil + }); err != nil { + return srcPaths, err + } + return srcPaths, nil +} + +// getDstPath: +func (c *ImageCvtGlue) getDstPath(path string) (dstPath string, err error) { + fileNameWithoutExt := filepath.Base(path[:len(path)-len(filepath.Ext(path))]) + if c.OutputDir == "" { + dirPath := filepath.Dir(path) + return fmt.Sprintf("%s%s", filepath.Join(dirPath, fileNameWithoutExt), c.AfterExt), nil + } + rootDir := getRootDir() + dirPath := filepath.Join(rootDir, c.OutputDir) + var isDir bool + if isDir, err = isDirExist(dirPath); err != nil { + return "", err + } + // 出力先ディレクトリが存在していなければ新規作成 + if !isDir { + if err := os.MkdirAll(dirPath, 0777); err != nil { + return "", errors.Wrap(errof.ErrCreateDirectory, err.Error()) + } + } + return fmt.Sprintf("%s%s", filepath.Join(dirPath, fileNameWithoutExt), c.AfterExt), nil +} + +// encodeImage: +func encodeImage(dstPath string, dst *os.File, img image.Image) (err error) { + switch filepath.Ext(dstPath) { + case ".png": + if err = png.Encode(dst, img); err != nil { + return errors.Wrap(errof.ErrEncodePNGImg, err.Error()) + } + case ".jpg", ".jpeg": + if err = jpeg.Encode(dst, img, &jpeg.Options{Quality: jpeg.DefaultQuality}); err != nil { + return errors.Wrap(errof.ErrEncodeJPGImg, err.Error()) + } + case ".gif": + if err = gif.Encode(dst, img, nil); err != nil { + return errors.Wrap(errof.ErrEncodeGIFImg, err.Error()) + } + } + return nil +} + +// getRootDir: +func getRootDir() string { + _, b, _, _ := runtime.Caller(0) + cvtDir := filepath.Dir(b) + internalDir := filepath.Dir(cvtDir) + return filepath.Dir(internalDir) +} + +// isFileExist: +func isFileExist(filepath string) (isFile bool, err error) { + var fileInfo os.FileInfo + if fileInfo, err = os.Stat(filepath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(errof.ErrGetSrcFileInfo, err.Error()) + } + if fileInfo.IsDir() { + return false, nil + } + return true, nil +} + +// isDirExist: +func isDirExist(filepath string) (isDir bool, err error) { + var fileInfo os.FileInfo + if fileInfo, err = os.Stat(filepath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, errors.Wrap(errof.ErrGetDirInfo, err.Error()) + } + if fileInfo.IsDir() { + return true, nil + } + return false, nil +} diff --git a/kadai1/internal/cvt/cvt_test.go b/kadai1/internal/cvt/cvt_test.go new file mode 100644 index 00000000..3ee89c95 --- /dev/null +++ b/kadai1/internal/cvt/cvt_test.go @@ -0,0 +1,151 @@ +package cvt + +import ( + "github.com/gopherdojo/gopherdojo-studyroom/kadai1/pkg/testutil" + "github.com/pkg/errors" + "os" + "path/filepath" + "testing" +) + +// TestConvert: +func TestConvert(t *testing.T) { + rootDir := getRootDir() + vectors := map[string]struct { + outputDir string + beforeExt string + afterExt string + srcPaths []string + expected []string + wantErr error + }{ + "OK": { + beforeExt: ".jpg", + afterExt: ".png", + srcPaths: []string{ + filepath.Join(rootDir, "pkg/testdata/fixture/1000.jpg"), + }, + expected: []string{ + filepath.Join(rootDir, "pkg/testdata/fixture/1000.png"), + }, + }, + "CASE_SPECIFIED_OUTPUT_DIR_OK": { + outputDir: "pkg/testdata/secondfixture", + beforeExt: ".jpg", + afterExt: ".png", + srcPaths: []string{ + filepath.Join(rootDir, "pkg/testdata/fixture/1000.jpg"), + }, + expected: []string{ + filepath.Join(rootDir, "pkg/testdata/secondfixture/1000.png"), + }, + }, + } + for k, v := range vectors { + imageCvtGlue := NewImageCvtGlue("", v.outputDir, v.beforeExt, v.afterExt, false) + err := imageCvtGlue.convert(v.srcPaths) + if errors.Cause(err) != v.wantErr { + t.Errorf("test %s, convert() = %v, want %v", k, errors.Cause(err), v.wantErr) + } + // 出力先が指定されている際は生成されたディレクトリごと削除 + if v.outputDir != "" { + testutil.RemoveAllTestSrc(filepath.Join(rootDir, v.outputDir)) + } else { + // 生成されたテストファイルを削除 + for _, expect := range v.expected { + testutil.RemoveTestFile(expect) + } + } + } +} + +// TestPathWalk: +func TestPathWalk(t *testing.T) { + var currentDir string + var err error + if currentDir, err = os.Getwd(); err != nil { + t.Errorf("failed get current dir: %v", err) + } + vectors := map[string]struct { + inputDir string + beforeExt string + srcPaths []string + expected []string + wantErr error + }{ + "OK": { + inputDir: "internal/cvt/walktest", + beforeExt: ".jpg", + srcPaths: []string{ + filepath.Join(currentDir, "walktest/test001.jpg"), + filepath.Join(currentDir, "walktest/test002.jpg"), + filepath.Join(currentDir, "walktest/test003.png"), + }, + expected: []string{ + filepath.Join(currentDir, "walktest/test001.jpg"), + filepath.Join(currentDir, "walktest/test002.jpg"), + }, + }, + } + for k, v := range vectors { + testutil.PrepareTestSrc("walktest", v.srcPaths) + imageCvtGlue := NewImageCvtGlue(v.inputDir, "", v.beforeExt, "", false) + actual, err := imageCvtGlue.pathWalk() + if errors.Cause(err) != v.wantErr { + t.Errorf("test %s, pathWalk() = %v, want %v", k, errors.Cause(err), v.wantErr) + } + // 実際に取得した配列長とテストケースの配列長が異なる場合 + if len(v.expected) != len(actual) { + t.Errorf("the length of arrays are different test: %s length of expected %d length of actual %d", k, len(v.expected), len(actual)) + } + for idx, expected := range v.expected { + if expected != actual[idx] { + t.Errorf("test: %s expected %s actual %s", k, expected, actual[idx]) + } + } + testutil.RemoveAllTestSrc(filepath.Join(currentDir, "walktest")) + } +} + +// TestGetDstPath: +func TestGetDstPath(t *testing.T) { + var currentDir string + var err error + if currentDir, err = os.Getwd(); err != nil { + t.Errorf("failed get current dir: %v", err) + } + vectors := map[string]struct { + outputDir string + afterExt string + filePath string + expected string + wantErr error + }{ + "CASE_NOT_SPECIFIED_OUTPUT_DIR_OK": { + outputDir: "", + afterExt: ".png", + filePath: filepath.Join(currentDir, "walktest/test003.jpg"), + expected: filepath.Join(currentDir, "walktest/test003.png"), + }, + "CASE_SPECIFIED_OUTPUT_DIR_OK": { + outputDir: "internal/cvt/testdir", + afterExt: ".png", + filePath: filepath.Join(currentDir, "walktest/test003.jpg"), + expected: filepath.Join(currentDir, "testdir/test003.png"), + }, + } + for k, v := range vectors { + imageCvtGlue := NewImageCvtGlue("", v.outputDir, "", v.afterExt, false) + + actual, err := imageCvtGlue.getDstPath(v.filePath) + if errors.Cause(err) != v.wantErr { + t.Errorf("test %s, getDstPath() = %v, want %v", k, errors.Cause(err), v.wantErr) + } + if actual != v.expected { + t.Errorf("test: %s expected %s actual %s", k, v.expected, actual) + } + if v.outputDir != "" { + testutil.RemoveAllTestSrc(filepath.Join(currentDir, "walktest")) + } + } +} diff --git a/kadai1/pkg/errof/errors.go b/kadai1/pkg/errof/errors.go new file mode 100644 index 00000000..6bcce163 --- /dev/null +++ b/kadai1/pkg/errof/errors.go @@ -0,0 +1,41 @@ +package errof + +type UserErr string + +func (e UserErr) Error() (msg string) { + var ok bool + if msg, ok = ErrCodeNames[e]; !ok { + return string(e) + } + return msg +} + +var ErrCodeNames = map[UserErr]string{ + ErrWalkingSrcPath: "指定されたディレクトリのトラバースに失敗しました", + ErrCreateDirectory: "指定されたディクトリの作成に失敗しました", + ErrGetSrcFileInfo: "指定されたファイル情報の取得に失敗しました", + ErrGetDirInfo: "指定されたディレクトリ情報の取得に失敗しました", + ErrOpenSrcFile: "指定されたファイルの展開に失敗しました", + ErrCloseSrcFile: "指定されたファイルを閉じるのに失敗しました", + ErrRemoveSrcFile: "指定されたファイルの削除に失敗しました", + ErrDecodeImage: "指定されたイメージのデコードに失敗しました", + ErrCreateDstFile: "指定されたファイルの作成に失敗しました", + ErrEncodePNGImg: "pngファイルのエンコードに失敗しました", + ErrEncodeJPGImg: "jpgファイルのエンコードに失敗しました", + ErrEncodeGIFImg: "gifファイルのエンコードに失敗しました", +} + +var ( + ErrWalkingSrcPath UserErr = "ErrWalkingSrcPath" + ErrCreateDirectory UserErr = "ErrCreateDirectory" + ErrGetSrcFileInfo UserErr = "ErrGetSrcFileInfo" + ErrGetDirInfo UserErr = "ErrGetDirInfo" + ErrOpenSrcFile UserErr = "ErrOpenFile" + ErrCloseSrcFile UserErr = "ErrCloseSrcFile" + ErrRemoveSrcFile UserErr = "ErrRemoveSrcFile" + ErrDecodeImage UserErr = "ErrDecodeImage" + ErrCreateDstFile UserErr = "ErrCreateDstFile" + ErrEncodePNGImg UserErr = "ErrEncodePNGImg" + ErrEncodeJPGImg UserErr = "ErrEncodeJPGImg" + ErrEncodeGIFImg UserErr = "ErrEncodeGIFImg" +) diff --git a/kadai1/pkg/testutil/testutil.go b/kadai1/pkg/testutil/testutil.go new file mode 100644 index 00000000..c144d1a7 --- /dev/null +++ b/kadai1/pkg/testutil/testutil.go @@ -0,0 +1,33 @@ +package testutil + +import ( + "log" + "os" +) + +// PrepareTestSrc: +func PrepareTestSrc(dirPath string, srcPaths []string) { + var err error + if err := os.MkdirAll(dirPath, 0777); err != nil { + log.Fatal(err) + } + for _, srcPath := range srcPaths { + if _, err = os.Create(srcPath); err != nil { + log.Fatal(err) + } + } +} + +// RemoveAllTestSrc: +func RemoveAllTestSrc(srcPath string) { + if err := os.RemoveAll(srcPath); err != nil { + log.Fatal(err) + } +} + +// RemoveTestFile: +func RemoveTestFile(srcPath string) { + if err := os.Remove(srcPath); err != nil { + log.Fatal(err) + } +}