diff --git a/kadai1/fujiokayu/Makefile b/kadai1/fujiokayu/Makefile new file mode 100644 index 0000000..c57ee0a --- /dev/null +++ b/kadai1/fujiokayu/Makefile @@ -0,0 +1,5 @@ +LDFLAGS := -ldflags="-s -w" + +.PHONY: build +build: + go build $(LDFLAGS) \ No newline at end of file diff --git a/kadai1/fujiokayu/README.md b/kadai1/fujiokayu/README.md new file mode 100644 index 0000000..65093f1 --- /dev/null +++ b/kadai1/fujiokayu/README.md @@ -0,0 +1,37 @@ +# myConverter + +## how to build +``` +make +``` + +## usage + +``` +# convert +./myConverter [-from ext] [-to ext] directory + +# show help +./myConverter -h + +# example +./myConverter -from png -to jpg testdir +``` + +## Specification +- 引数で指定されたディレクトリを再帰的に走査し、-from オプションで指定された画像形式のファイルを -to オプションで指定された画像形式のファイルに変換します。 + - オプションで指定できるフォーマットは gif、jpg(jpeg)、png のみです。 + - オプションを指定しなかった場合は jpg -> png に変換します。 +- 変換後のファイルは変換元のファイルと同じディレクトリに出力されます。 +- 変換元のファイルは削除されず、そのまま残ります。 +- 変換中にディスクサイズが足りなくなった場合の挙動は(恐らく)処理系定義になります。 + +## learning memo +- ディレクトリの再帰的な探索は [path/filepath.Walk](https://golang.org/pkg/path/filepath/#Walk) を使うのが一番近道だと思った。 + - しかし、filepath.Walk 内で呼び出せる関数が [WalkFunc](https://golang.org/pkg/path/filepath/#WalkFunc) に限定されているようで、かつ WalkFunc の型が決まっていたので少し使い辛かった。 + - 一方で、これは[先月の Software Design](https://gihyo.jp/magazine/SD/archive/2019/201905) で見た Generator Pattern を試すと Go らしくなるのでは考えた。 + - [同じことを考える人](https://gist.github.com/sethamclean/9475737)が既に居たので、参考にした。 +- ~~flag パッケージに少し使い辛さを感じる。~~ + - ~~特に、フラグ無し引数が一つ入ると以降のフラグも全て Parse できなくなるのが少し使いづらい。~~ + - sh-tatsuno さんのコードで使われていた [NewFlagSet](https://golang.org/pkg/flag/#NewFlagSet) を使うともっと柔軟な操作が可能になるようでした。 + 勉強になりました。 diff --git a/kadai1/fujiokayu/args/args.go b/kadai1/fujiokayu/args/args.go new file mode 100644 index 0000000..8377275 --- /dev/null +++ b/kadai1/fujiokayu/args/args.go @@ -0,0 +1,58 @@ +package args + +import ( + "flag" + "fmt" + "io" + "log" + "os" +) + +//Args struct has parsed args. +type Args struct { + DecodeType string + EncodeType string + RootFolderName []string +} + +func usage() { + _, err := io.WriteString(os.Stderr, usageString) + if err != nil { + log.Fatal(err) + } + flag.PrintDefaults() +} + +const usageString = `Usage of myConverter: + # convert + ./myConverter [-from ext] [-to ext] directory + + # example + ./myConverter -from png -to jpg testdir + + # args +` + +// ParseArgs is the constructor of struct "args" +func ParseArgs() (*Args, error) { + flag.Usage = usage + arg1 := flag.String("from", "jpg", "original file type to convert") + arg2 := flag.String("to", "png", "file type you want to convert") + + flag.Parse() + + // フォルダが指定されているかチェックする + var err error + folder := flag.Args() + if len(folder) == 0 { + err = fmt.Errorf("specify target directory") + } + + newArgs := &Args{ + DecodeType: *arg1, + EncodeType: *arg2, + RootFolderName: flag.Args(), + } + + return newArgs, err +} diff --git a/kadai1/fujiokayu/converter/converter.go b/kadai1/fujiokayu/converter/converter.go new file mode 100644 index 0000000..47a4164 --- /dev/null +++ b/kadai1/fujiokayu/converter/converter.go @@ -0,0 +1,80 @@ +/* +Package converter + +converter は特定の画像形式のファイルを変換するためのパッケージです。 + +How to use + +string 型の3つの引数を指定して Convert 関数を呼び出してください。 + Convert(filePath string, decodeType string, encodeType string) + +利用できるファイル形式は gif、jpg(jpeg)、png だけです。 +*/ +package converter + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "os" + "path" + "strings" +) + +//dec: Decode filePath file +func dec(filePath string, decodeType string) (image.Image, error) { + reader, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer reader.Close() + + switch decodeType { + case "gif": + return gif.Decode(reader) + case "jpeg", "jpg": + return jpeg.Decode(reader) + case "png": + return png.Decode(reader) + } + return nil, err +} + +//enc: Encode m to encodeType +func enc(filePath string, encodeType string, m image.Image) error { + writer, err := os.Create(strings.TrimSuffix(filePath, path.Ext(filePath)) + "." + encodeType) + if err != nil { + return err + } + defer writer.Close() + + switch encodeType { + case "gif": + return gif.Encode(writer, m, nil) + case "jpeg", "jpg": + return jpeg.Encode(writer, m, nil) + case "png": + return png.Encode(writer, m) + } + return nil +} + +//Convert : convert decodeType file to encodeType file +func Convert(filePath string, decodeType string, encodeType string) error { + fmt.Println("Converting", filePath) + + m, err := dec(filePath, decodeType) + if err != nil { + return err + } + + err = enc(filePath, encodeType, m) + if err != nil { + return err + } + + fmt.Println("Converted", filePath) + return nil +} diff --git a/kadai1/fujiokayu/converter/converter_test.go b/kadai1/fujiokayu/converter/converter_test.go new file mode 100644 index 0000000..72725be --- /dev/null +++ b/kadai1/fujiokayu/converter/converter_test.go @@ -0,0 +1,31 @@ +package converter + +import ( + "os" + "testing" +) + +const decodeFile string = "../testdata/cat.jpg" +const encodeFile string = "../testdata/cat.png" + +func Test_Convert(t *testing.T) { + err := Convert(decodeFile, "jpg", "png") + if err != nil { + t.Fatal("failed test: Convert error") + } + + // 変換後のファイルが存在するかチェック + info, err := os.Stat(encodeFile) + if err != nil { + t.Fatal("failed test: File not generated") + } + // 変換後のファイルサイズが0バイトではないかチェック + if info.Size() <= 0 { + t.Fatal("failed test: Encoded file is invalid") + } + // テストで生成したファイルを削除する。 + err = os.Remove(encodeFile) + if err != nil { + t.Fatal("failed to remove the file") + } +} diff --git a/kadai1/fujiokayu/go.mod b/kadai1/fujiokayu/go.mod new file mode 100644 index 0000000..c75bdee --- /dev/null +++ b/kadai1/fujiokayu/go.mod @@ -0,0 +1,3 @@ +module myConverter + +go 1.12 diff --git a/kadai1/fujiokayu/main.go b/kadai1/fujiokayu/main.go new file mode 100644 index 0000000..9e26504 --- /dev/null +++ b/kadai1/fujiokayu/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "log" + "myConverter/args" + "myConverter/converter" + "myConverter/walker" + "path/filepath" + "strings" +) + +// フォルダ探索によって見つかったファイルの拡張子が、decodeType と一致した場合に converter を起動する。 +func execute(filePath string, decodeType string, encodeType string) { + // filepath.Ext が抽出する拡張子は "." を含むため、オプションで指定された拡張子と揃えるために "." を除去する。 + ext := strings.ToLower(strings.Trim(filepath.Ext(filePath), ".")) + + if ext == strings.ToLower(decodeType) { + err := converter.Convert(filePath, ext, encodeType) + if err != nil { + log.Fatal(err) + } + } +} + +func main() { + args, err := args.ParseArgs() + if err != nil { + log.Fatal(err) + } + + folder := strings.Join(args.RootFolderName, " ") + + ch, err := walker.Walk(folder) + if err != nil { + log.Fatal(err) + } + for filePath := range ch { + execute(filePath, args.DecodeType, args.EncodeType) + } +} diff --git a/kadai1/fujiokayu/testdata/a/b/test b/kadai1/fujiokayu/testdata/a/b/test new file mode 100644 index 0000000..e69de29 diff --git a/kadai1/fujiokayu/testdata/c/test b/kadai1/fujiokayu/testdata/c/test new file mode 100644 index 0000000..e69de29 diff --git a/kadai1/fujiokayu/testdata/cat.jpg b/kadai1/fujiokayu/testdata/cat.jpg new file mode 100644 index 0000000..bccff43 Binary files /dev/null and b/kadai1/fujiokayu/testdata/cat.jpg differ diff --git a/kadai1/fujiokayu/testdata/d/e/test b/kadai1/fujiokayu/testdata/d/e/test new file mode 100644 index 0000000..e69de29 diff --git a/kadai1/fujiokayu/walker/walker.go b/kadai1/fujiokayu/walker/walker.go new file mode 100644 index 0000000..42fcb99 --- /dev/null +++ b/kadai1/fujiokayu/walker/walker.go @@ -0,0 +1,43 @@ +package walker + +import ( + "fmt" + "os" + "path/filepath" +) + +// assertDir: 引数の文字列が有効なディレクトリか検証する +func assertDir(path string) error { + info, err := os.Stat(path) + if err == nil { + if !info.IsDir() { + err = fmt.Errorf("directory is not valid") + } + } + return err +} + +// Walk :指定されたディレクトリを再帰的に操作し、見つかったファイルをチャネルで返す。 +func Walk(rootPath string) (chan string, error) { + + ch := make(chan string) + err := assertDir(rootPath) + if err != nil { + return ch, err + } + + // Todo: Add error handling + go func() { + err = filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + ch <- path + } + return nil + }) + defer close(ch) + }() + return ch, err +} diff --git a/kadai1/fujiokayu/walker/walker_test.go b/kadai1/fujiokayu/walker/walker_test.go new file mode 100644 index 0000000..a722bf3 --- /dev/null +++ b/kadai1/fujiokayu/walker/walker_test.go @@ -0,0 +1,24 @@ +package walker + +import ( + "fmt" + "testing" +) + +func Test_Walk(t *testing.T) { + ch, err := Walk("../testdata") + if err != nil { + t.Fatal(err) + } + + count := 0 + for filePath := range ch { + fmt.Println(filePath) + count++ + } + + if count != 4 { + fmt.Println(len(ch)) + t.Fatal("failed test") + } +}