Skip to content

tsuchinaga / 課題1 #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions kadai1/tsuchinaga/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### for IntelliJ
.idea
*.iml
41 changes: 41 additions & 0 deletions kadai1/tsuchinaga/README.md
Original file line number Diff line number Diff line change
@@ -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] 変換出来たら変換前のパスと変換後のパスを表示して次のがいるに進む
5 changes: 5 additions & 0 deletions kadai1/tsuchinaga/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/gopherdojo/dojo8/kadai1/tsuchinaga

go 1.14

require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
2 changes: 2 additions & 0 deletions kadai1/tsuchinaga/go.sum
Original file line number Diff line number Diff line change
@@ -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=
146 changes: 146 additions & 0 deletions kadai1/tsuchinaga/imgconv/converter.go
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions kadai1/tsuchinaga/imgconv/converter_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
48 changes: 48 additions & 0 deletions kadai1/tsuchinaga/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Binary file added kadai1/tsuchinaga/testdata/Example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added kadai1/tsuchinaga/testdata/Example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added kadai1/tsuchinaga/testdata/subdir1/Example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added kadai1/tsuchinaga/testdata/subdir1/Example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.