diff --git a/kadai1/tsuchinaga/.gitignore b/kadai1/tsuchinaga/.gitignore new file mode 100644 index 0000000..d7340f0 --- /dev/null +++ b/kadai1/tsuchinaga/.gitignore @@ -0,0 +1,3 @@ +### for IntelliJ +.idea +*.iml diff --git a/kadai1/tsuchinaga/README.md b/kadai1/tsuchinaga/README.md new file mode 100644 index 0000000..d774351 --- /dev/null +++ b/kadai1/tsuchinaga/README.md @@ -0,0 +1,41 @@ +# 課題1 tsuchinaga + +## 要件 + +### 次の仕様を満たすコマンドを作って下さい +* ディレクトリを指定する +* 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) +* ディレクトリ以下は再帰的に処理する +* 変換前と変換後の画像形式を指定できる(オプション) + +### 以下を満たすように開発してください +* mainパッケージと分離する +* 自作パッケージと標準パッケージと準標準パッケージのみ使う + * 準標準パッケージ:golang.org/x以下のパッケージ +* ユーザ定義型を作ってみる +* GoDocを生成してみる +* Go Modulesを使ってみる + +## TODO +* [x] コマンドラインで実行できる +* [x] パラメータでディレクトリを指定できる + * [x] ディレクトリの指定がない場合はディレクトリを指定するようにメッセージを出して終了 + * [x] ディレクトリではなくファイルを指定されたらディレクトリを指定するようメッセージを出して終了 + * [x] 指定されたディレクトリ、ファイルが存在しない場合は存在しない旨をメッセージを出して終了 +* [x] 変換元の画像形式が指定できる + * [x] 指定しなかった場合はJPG + * [x] 指定可能な画像形式はJPG、PNG + * [x] 指定可能でない画像形式が指定されたら指定できる画像形式をメッセージで出して終了 +* [x] 変換後の画像形式が指定できる + * [x] 指定しなかった場合はPNG + * [x] 指定可能な画像形式はJPG、PNG + * [x] 指定可能でない画像形式が指定されたら指定できる画像形式をメッセージで出して終了 + * [x] 変換元と同じ画像形式を指定されたら違い画像形式を選ぶようメッセージを出して終了 +* [x] 指定されたディレクトリの配下にある変換元で指定された画像形式の画像を、変換後で指定された画像形式の画像に変換する + * [x] 指定されたディレクトリの配下にサブディレクトリがあればサブディレクトリ内の画像に対しても変換する + * [x] サブディレクトリ内のディレクトリに対しても同様に処理する = 指定されたディレクトリ配下に対して再帰的に実行する + * [x] 指定されたディレクトリ配下に画像がない、もしくは指定された画像形式のファイルがなければ何もしない +* [x] 画像の変換ができる + * [x] 変換元画像が開けなかったらその旨を表示して次のファイルに進む + * [x] 変換後の画像が作れなかったらその旨を表示して次のファイルに進む + * [x] 変換出来たら変換前のパスと変換後のパスを表示して次のがいるに進む diff --git a/kadai1/tsuchinaga/go.mod b/kadai1/tsuchinaga/go.mod new file mode 100644 index 0000000..0c22f52 --- /dev/null +++ b/kadai1/tsuchinaga/go.mod @@ -0,0 +1,5 @@ +module github.com/gopherdojo/dojo8/kadai1/tsuchinaga + +go 1.14 + +require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 diff --git a/kadai1/tsuchinaga/go.sum b/kadai1/tsuchinaga/go.sum new file mode 100644 index 0000000..3ab73ea --- /dev/null +++ b/kadai1/tsuchinaga/go.sum @@ -0,0 +1,2 @@ +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/kadai1/tsuchinaga/imgconv/converter.go b/kadai1/tsuchinaga/imgconv/converter.go new file mode 100644 index 0000000..9349557 --- /dev/null +++ b/kadai1/tsuchinaga/imgconv/converter.go @@ -0,0 +1,146 @@ +package imgconv + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "io/ioutil" + "os" + "path/filepath" + + "golang.org/x/xerrors" +) + +var ( + ReadDirError = xerrors.New("read dir error") + FileStatError = xerrors.New("file stat error") + OpenFileError = xerrors.New("open source file error") + CreateDestinationFileError = xerrors.New("create destination file error") + ReadImageError = xerrors.New("read image error") + EncodeImageError = xerrors.New("encode image error") + NoImageError = xerrors.New("no image error") +) + +var validFileTypes = map[string]bool{"jpeg": true, "png": true} + +// IsValidFileType - 指定されたファイルタイプが利用可能かを返す +func IsValidFileType(fileType string) bool { + return validFileTypes[fileType] +} + +// IsDir - pathがディレクトリかどうか +func IsDir(path string) (bool, error) { + fi, err := os.Stat(path) + if err != nil { + return false, xerrors.Errorf("%+v: %w", err, FileStatError) + } + return fi.IsDir(), nil +} + +// Do - 変換の実行 +func Do(dir, src, dest string) chan error { + c := converter{ + dirList: []string{dir}, + srcFileType: src, + destFileType: dest, + ch: make(chan error), + } + go func() { + defer close(c.ch) + err := c.exec() + if err != nil { + c.ch <- err + } + }() + return c.ch +} + +// converter - 変換機能の実装 +type converter struct { + dirList []string + srcFileType string + destFileType string + ch chan error +} + +// exec - ディレクトリをたどりながら変換を実行 +func (c *converter) exec() error { + for len(c.dirList) > 0 { + dirPath := c.dirList[0] + c.dirList = c.dirList[1:] + + files, err := ioutil.ReadDir(dirPath) + if err != nil { + return xerrors.Errorf("%+v: %w", err, ReadDirError) + } + + for _, file := range files { + path := filepath.Join(dirPath, file.Name()) + if file.IsDir() { + c.dirList = append(c.dirList, path) + } else { + if err := c.convert(path); err != nil { + c.ch <- err // エラーをチャネルに渡して続きを進める + } + } + } + } + return nil +} + +// convert - 変換処理 +func (c converter) convert(path string) (err error) { + f, err := os.Open(path) + if err != nil { // 開けない + return xerrors.Errorf("%+v: %w", err, OpenFileError) + } + defer f.Close() + + ft, err := getFileType(path) + if err != nil { + return err + } + if ft == c.srcFileType { + img, _, err := image.Decode(f) + if err != nil { + return xerrors.Errorf("%+v: %w", err, ReadImageError) + } + + newFilePath := fmt.Sprintf("%s.%s", path, c.destFileType) + o, err := os.Create(newFilePath) + if err != nil { + return xerrors.Errorf("%+v: %w", err, CreateDestinationFileError) + } + defer func() { + err = o.Close() + }() + + switch c.destFileType { + case "jpeg": + if err = jpeg.Encode(o, img, nil); err != nil { + return xerrors.Errorf("%+v: %w", err, EncodeImageError) + } + case "png": + if err = png.Encode(o, img); err != nil { + return xerrors.Errorf("%+v: %w", err, EncodeImageError) + } + } + } + return nil +} + +// getFileType - 画像ファイルの型を得る +func getFileType(path string) (string, error) { + f, err := os.Open(path) + if err != nil { // 開けない + return "", OpenFileError + } + defer f.Close() + + _, format, err := image.DecodeConfig(f) + if err != nil { // 画像じゃない + return "", NoImageError + } + return format, nil +} diff --git a/kadai1/tsuchinaga/imgconv/converter_test.go b/kadai1/tsuchinaga/imgconv/converter_test.go new file mode 100644 index 0000000..1c5ee69 --- /dev/null +++ b/kadai1/tsuchinaga/imgconv/converter_test.go @@ -0,0 +1,59 @@ +package imgconv + +import ( + "errors" + "testing" +) + +func TestIsValidFileType(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect bool + }{ + {desc: "jpegがtrueで返される", arg: "jpeg", expect: true}, + {desc: "pngがtrueで返される", arg: "png", expect: true}, + {desc: "jpgがfalseで返される", arg: "jpg", expect: false}, + {desc: "gifがfalseで返される", arg: "gif", expect: false}, + {desc: "空文字がfalseで返される", arg: "", expect: false}, + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + actual := IsValidFileType(test.arg) + if test.expect != actual { + t.Errorf("%s 失敗\n期待: %v\n実際: %v\n", t.Name(), test.expect, actual) + } + }) + } +} + +func TestIsDir(t *testing.T) { + t.Parallel() + testTable := []struct { + desc string + arg string + expect1 bool + expect2 error + }{ + {desc: "ディレクトリを指定したらtrue", arg: "../testdata", expect1: true}, + {desc: "ファイルを指定したらfalse", arg: "../testdata/Example.jpg", expect1: false}, + {desc: "存在しないパスを指定したらエラー", arg: "./testdata/NotFound.jpg", expect2: FileStatError}, + {desc: "空文字を指定したらエラー", arg: "", expect2: FileStatError}, + // {desc: "不良セクタを指定したらfalse", arg: "", expect1: false}, // やり方わからない + } + + for _, test := range testTable { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + actual1, actual2 := IsDir(test.arg) + if test.expect1 != actual1 || !errors.Is(actual2, test.expect2) { + t.Errorf("%s 失敗\n期待: %v, %v\n実際: %v, %v\n", t.Name(), test.expect1, test.expect2, actual1, actual2) + } + }) + } +} diff --git a/kadai1/tsuchinaga/main.go b/kadai1/tsuchinaga/main.go new file mode 100644 index 0000000..ea05b7e --- /dev/null +++ b/kadai1/tsuchinaga/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/gopherdojo/dojo8/kadai1/tsuchinaga/imgconv" +) + +func main() { + var dir, src, dest string + flag.StringVar(&dir, "dir", "", "変換する画像のあるディレクトリ") + flag.StringVar(&src, "src", "jpeg", "optional 変換元の画像形式 jpeg|png") + flag.StringVar(&dest, "dest", "png", "optional 変換後の画像形式 jpeg|png") + flag.Parse() + + // validation + if dir == "" { + log.Println("dirの指定は必須です") + os.Exit(2) + } + if isDir, err := imgconv.IsDir(dir); err != nil || !isDir { + log.Printf("%sは存在しないかディレクトリではありません\n", dir) + os.Exit(2) + } + if !imgconv.IsValidFileType(src) { + log.Printf("%sは許可されていない画像形式です\n", src) + os.Exit(2) + } + if !imgconv.IsValidFileType(dest) { + log.Printf("%sは許可されていない画像形式です", dest) + os.Exit(2) + } + if src == dest { + log.Println("srcとdestで違う画像形式を選択してください") + os.Exit(2) + } + + // 変換実行 + ch := imgconv.Do(dir, src, dest) + for err := range ch { + if err != nil { + fmt.Println(err) + } + } +} diff --git a/kadai1/tsuchinaga/testdata/Example.jpg b/kadai1/tsuchinaga/testdata/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai1/tsuchinaga/testdata/Example.jpg differ diff --git a/kadai1/tsuchinaga/testdata/Example.png b/kadai1/tsuchinaga/testdata/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai1/tsuchinaga/testdata/Example.png differ diff --git a/kadai1/tsuchinaga/testdata/subdir1/Example.jpg b/kadai1/tsuchinaga/testdata/subdir1/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai1/tsuchinaga/testdata/subdir1/Example.jpg differ diff --git a/kadai1/tsuchinaga/testdata/subdir1/Example.png b/kadai1/tsuchinaga/testdata/subdir1/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai1/tsuchinaga/testdata/subdir1/Example.png differ diff --git a/kadai1/tsuchinaga/testdata/subdir1/subdir1_1/Example.jpg b/kadai1/tsuchinaga/testdata/subdir1/subdir1_1/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai1/tsuchinaga/testdata/subdir1/subdir1_1/Example.jpg differ diff --git a/kadai1/tsuchinaga/testdata/subdir1/subdir1_1/subdir1_1_1/Example.png b/kadai1/tsuchinaga/testdata/subdir1/subdir1_1/subdir1_1_1/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai1/tsuchinaga/testdata/subdir1/subdir1_1/subdir1_1_1/Example.png differ diff --git a/kadai1/tsuchinaga/testdata/subdir2/subdir2_1/Example.jpg b/kadai1/tsuchinaga/testdata/subdir2/subdir2_1/Example.jpg new file mode 100644 index 0000000..cacc09a Binary files /dev/null and b/kadai1/tsuchinaga/testdata/subdir2/subdir2_1/Example.jpg differ diff --git a/kadai1/tsuchinaga/testdata/subdir2/subdir2_1/Example.png b/kadai1/tsuchinaga/testdata/subdir2/subdir2_1/Example.png new file mode 100644 index 0000000..aa61359 Binary files /dev/null and b/kadai1/tsuchinaga/testdata/subdir2/subdir2_1/Example.png differ