diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44636f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +kadai1/takata/takata +kadai1/takata/output/1.png +kadai1/takata/output/10.png +kadai1/takata/output/2.png +kadai1/takata/output/3.png +kadai1/takata/output/4.png +kadai1/takata/output/5.png +kadai1/takata/output/6.png +kadai1/takata/output/7.png +kadai1/takata/output/8.png +kadai1/takata/output/9.png +kadai2/takata/takata +kadai2/takata/output/1.png +kadai2/takata/output/10.png +kadai2/takata/output/2.png +kadai2/takata/output/3.png +kadai2/takata/output/4.png +kadai2/takata/output/5.png +kadai2/takata/output/6.png +kadai2/takata/output/7.png +kadai2/takata/output/8.png +kadai2/takata/output/9.png diff --git a/kadai1/takata/README.md b/kadai1/takata/README.md new file mode 100644 index 0000000..2f029e4 --- /dev/null +++ b/kadai1/takata/README.md @@ -0,0 +1,57 @@ +# Kadai1 + + +## 実行方法 + +``` +go build -o converter main.go +./converter -d test -sf jpg -df png + +または + +go run main.go -d test -sf jpg -df png + + +出力結果はoutputディレクトリに格納される +``` + + +## Options + +``` +-d 変換対象の画像が入ったディレクトリ(中にディレクトリを含めてもOK) + +-sf 変換前のフォーマット(jpg,jpeg,png,gifが指定可能) デフォルトはjpg + +-df 変換後のフォーマット(jpg,jpeg,png,gifが指定可能) デフォルトはpng + + +``` + +## 課題の回答 + +### ディレクトリを指定する +`flag.Args()`で受け取る値を対象のディレクトリとしました。 + +### ディレクトリ以下は再帰的に処理する +dirwalkメソッドの中でディレクトリの場合はさらにディレクトリ内のファイルを探すようにしました。 + + +### 指定したディレクトリ以下のJPGファイルをPNGに変換 + +オプションを指定しなかった場合、デフォルトでjpgファイルをpngファイルに変換するようにしました。 + +### mainパッケージと分離する +画像変換処理をconvertパッケージに格納し、mainパッケージへimportして呼び出すようにしました。 + + +### ユーザ定義型を作ってみる +Fileタイプを作成しました。ファイル名と変換元のファイル形式、変換後のファイル形式を格納します。 + +### GoDocを生成してみる + +``` +godoc -http:3000 +``` + +で作成しました。 \ No newline at end of file diff --git a/kadai1/takata/convert/convert.go b/kadai1/takata/convert/convert.go new file mode 100644 index 0000000..cee3b68 --- /dev/null +++ b/kadai1/takata/convert/convert.go @@ -0,0 +1,167 @@ +/* +Package convert provides convert function +to some extension to other extension +*/ +package convert + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +// Convert images with srcFmt in dir to images with dstFmt +func Convert(dir string, srcFmt string, dstFmt string) { + + if !exists(dir) { + panic("ディレトリは存在しません") + } + + fInfo, _ := os.Stat(dir) + if !fInfo.IsDir() { + panic("ディレクトリを指定してください") + } + + files := dirwalk(dir, srcFmt, dstFmt) + + for _, file := range files { + convertImage(file) + } +} + +// find files with some extension in a directory recursively +func dirwalk(dir string, srcFmt string, dstFmt string) []FileInformation { + + files, err := ioutil.ReadDir(dir) + if err != nil { + panic(err) + } + + var paths []FileInformation + for _, file := range files { + if file.IsDir() { + paths = append(paths, dirwalk(filepath.Join(dir, file.Name()), srcFmt, dstFmt)...) + continue + } + name := file.Name() + pos := strings.LastIndex(name, ".") + 1 + if name[pos:] != srcFmt { + continue + } + + path := FileInformation{filepath.Join(dir, name[:pos-1]), srcFmt, dstFmt} + paths = append(paths, path) + } + return paths +} + +// convert file with some extension to file with other extension +func convertImage(src FileInformation) { + + if !isAvailableFormat(src.srcFmt) { + log.Fatal("指定した変換元の画像形式は無効です") + return + } + + if !isAvailableFormat(src.dstFmt) { + log.Fatal("指定した変換先の画像形式は無効です") + return + } + + startConvert(src) +} + +// Encode the targer image +func startConvert(src FileInformation) { + + file, err := os.Open(src.name + "." + src.srcFmt) + if err != nil { + log.Fatal(err) + return + } + defer file.Close() + + img, _, err := decode(file) + if err != nil { + log.Fatal(err) + return + } + + dstFile := makeDstFile(src) + + out, err := os.Create(dstFile) + defer out.Close() + + if !encode(src.dstFmt, out, img) { + log.Fatal("encodeに失敗") + } +} + +// decode file +func decode(file *os.File) (image.Image, string, error) { + return image.Decode(file) +} + +// encode image to dstFormat +func encode(format string, out *os.File, img image.Image) bool { + + switch format { + case "jpeg", "jpg": + jpeg.Encode(out, img, nil) + return true + case "gif": + gif.Encode(out, img, nil) + return true + case "png": + png.Encode(out, img) + return true + default: + return false + } +} + +// make destination file +func makeDstFile(src FileInformation) string { + dstDir := "output" + if _, err := os.Stat(dstDir); os.IsNotExist(err) { + os.Mkdir(dstDir, 0777) + } + return dstDir + "/" + fmt.Sprintf("%s.%s", getFileNameWithoutExt(src.name), src.dstFmt) +} + +// check if this format is avalable +func isAvailableFormat(format string) bool { + lowerFormat := strings.ToLower(format) + switch lowerFormat { + case "jpg", "jpeg", "png", "gif": + return true + default: + return false + + } +} + +// Get file name withour extension +func getFileNameWithoutExt(path string) string { + return filepath.Base(path[:len(path)-len(filepath.Ext(path))]) +} + +// Check if file exists +func exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +// FileInformation contains Name and srcFmt and dstFmt. +type FileInformation struct { + name string // name of a file + srcFmt string // original format of a file + dstFmt string // format to convert +} diff --git a/kadai1/takata/main.go b/kadai1/takata/main.go new file mode 100644 index 0000000..08cb4b0 --- /dev/null +++ b/kadai1/takata/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "convert" + "flag" +) + +// main +func main() { + var ( + dir = flag.String("d", "", "directory to convert imagefiles") + srcFmt = flag.String("sf", "jpg", "src file format") + dstFmt = flag.String("df", "png", "dest file format") + ) + + flag.Parse() + + convert.Convert(*dir, *srcFmt, *dstFmt) +} diff --git a/kadai1/takata/test/1.jpg b/kadai1/takata/test/1.jpg new file mode 100644 index 0000000..8a0d03d Binary files /dev/null and b/kadai1/takata/test/1.jpg differ diff --git a/kadai1/takata/test/2.jpg b/kadai1/takata/test/2.jpg new file mode 100644 index 0000000..c685157 Binary files /dev/null and b/kadai1/takata/test/2.jpg differ diff --git a/kadai1/takata/test/3.jpg b/kadai1/takata/test/3.jpg new file mode 100644 index 0000000..c6b9b84 Binary files /dev/null and b/kadai1/takata/test/3.jpg differ diff --git a/kadai1/takata/test/4.jpg b/kadai1/takata/test/4.jpg new file mode 100644 index 0000000..0916c19 Binary files /dev/null and b/kadai1/takata/test/4.jpg differ diff --git a/kadai1/takata/test/5.jpg b/kadai1/takata/test/5.jpg new file mode 100644 index 0000000..f98ecf5 Binary files /dev/null and b/kadai1/takata/test/5.jpg differ diff --git a/kadai1/takata/test/inner/10.jpg b/kadai1/takata/test/inner/10.jpg new file mode 100644 index 0000000..f98ecf5 Binary files /dev/null and b/kadai1/takata/test/inner/10.jpg differ diff --git a/kadai1/takata/test/inner/6.jpg b/kadai1/takata/test/inner/6.jpg new file mode 100644 index 0000000..8a0d03d Binary files /dev/null and b/kadai1/takata/test/inner/6.jpg differ diff --git a/kadai1/takata/test/inner/7.jpg b/kadai1/takata/test/inner/7.jpg new file mode 100644 index 0000000..c685157 Binary files /dev/null and b/kadai1/takata/test/inner/7.jpg differ diff --git a/kadai1/takata/test/inner/8.jpg b/kadai1/takata/test/inner/8.jpg new file mode 100644 index 0000000..c6b9b84 Binary files /dev/null and b/kadai1/takata/test/inner/8.jpg differ diff --git a/kadai1/takata/test/inner/9.jpg b/kadai1/takata/test/inner/9.jpg new file mode 100644 index 0000000..0916c19 Binary files /dev/null and b/kadai1/takata/test/inner/9.jpg differ diff --git a/kadai2/takata/README.md b/kadai2/takata/README.md new file mode 100644 index 0000000..dcc2f41 --- /dev/null +++ b/kadai2/takata/README.md @@ -0,0 +1,91 @@ +# Kadai1 + + +## 実行方法 + +``` +go build -o converter main.go +./converter -d test -sf jpg -df png + +または + +go run main.go -d test -sf jpg -df png + + +出力結果はoutputディレクトリに格納される +``` + + +## Options + +``` +-d 変換対象の画像が入ったディレクトリ(中にディレクトリを含めてもOK) + +-sf 変換前のフォーマット(jpg,jpeg,png,gifが指定可能) デフォルトはjpg + +-df 変換後のフォーマット(jpg,jpeg,png,gifが指定可能) デフォルトはpng + + +``` + +## 課題1の回答 + +### ディレクトリを指定する +`flag.Args()`で受け取る値を対象のディレクトリとしました。 + +### ディレクトリ以下は再帰的に処理する +dirwalkメソッドの中でディレクトリの場合はさらにディレクトリ内のファイルを探すようにしました。 + + +### 指定したディレクトリ以下のJPGファイルをPNGに変換 + +オプションを指定しなかった場合、デフォルトでjpgファイルをpngファイルに変換するようにしました。 + +### mainパッケージと分離する +画像変換処理をconvertパッケージに格納し、mainパッケージへimportして呼び出すようにしました。 + + +### ユーザ定義型を作ってみる +Fileタイプを作成しました。ファイル名と変換元のファイル形式、変換後のファイル形式を格納します。 + +### GoDocを生成してみる + +``` +godoc -http:3000 +``` + +で作成しました。 + + +## 課題2の回答 + +## io.Readerとio.Writerについて調べてみよう + +### 標準パッケージでどのように使われているか + +サポートしている標準パッケージ +- json +- bytes.Buffer +- bufio.Reader +- os.File +- image +- jpeg +- png +- base64 + +インターフェイスを実装していたり、引数として扱えるようにしている。 + +### io.Readerとio.Writerがあることでどういう利点があるのか + +- io.Readerとio.Writerを満たした構造体(クラス)を同じように扱える +読み込み元や出力先がファイル、画面、バッファ、ネットワークなどのデータの種類に関わらず[]byteでさえあれば全て同じように処理できる。 + +また、例えば引数としてio.Reader型を宣言している場合、 +os.Stdinやos.Fileなどio.Readerを満たしている全ての構造体を同じように扱える。 + +また、テスト時にio.Readerとio.Writerを実装したモックを作成することで、 +入出力先をローカルファイルなどにすることでテストがやりやすくなる。 + + + + diff --git a/kadai2/takata/convert/convert.go b/kadai2/takata/convert/convert.go new file mode 100644 index 0000000..1912e5a --- /dev/null +++ b/kadai2/takata/convert/convert.go @@ -0,0 +1,210 @@ +/* +Package convert provides convert function +to some extension to other extension +*/ +package convert + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" +) + +// Converter is interface to convert all files in dir. +type Converter interface { + Convert(dir string) error +} + +// ImageConverter for images contains from format and to format. +type ImageConverter struct { + From string // original format of a file + To string // format to convert +} + +// FileInfo contains Name and srcFmt and dstFmt. +type FileInfo struct { + name string // name of a file +} + +// Convert images with srcFmt in dir to images with dstFmt +func (i ImageConverter) Convert(dir string) error { + + if !isFileOrDirExists(dir) { + return errors.New("ディレクトリは存在しません") + } + + fInfo, error := os.Stat(dir) + if error != nil { + return errors.Wrapf(error, "os.Stat() with %s", dir) + } + + if !fInfo.IsDir() { + return errors.New("ディレクトリを指定してください") + } + + if !isAvailableFormat(i.From) { + return errors.New("指定した変換元の画像形式は無効です") + } + + if !isAvailableFormat(i.To) { + return errors.New("指定した変換先の画像形式は無効です") + } + + files, error := i.dirwalk(dir) + if error != nil { + return error + } + for _, file := range files { + if error := i.convertImage(file); error != nil { + return error + } + } + return nil +} + +// find files with some extension in a directory recursively +func (i ImageConverter) dirwalk(dir string) ([]FileInfo, error) { + + files, error := ioutil.ReadDir(dir) + if error != nil { + return nil, errors.Wrapf(error, "ioutil.ReadDir() with %s", dir) + } + + var paths []FileInfo + for _, file := range files { + if file.IsDir() { + files, error := i.dirwalk(filepath.Join(dir, file.Name())) + if error != nil { + return nil, error + } + paths = append(paths, files...) + continue + } + name := file.Name() + pos := strings.LastIndex(name, ".") + 1 + if name[pos:] != i.From { + continue + } + + path := FileInfo{filepath.Join(dir, name[:pos-1])} + paths = append(paths, path) + } + return paths, nil +} + +// convert file with some extension to file with other extension +func (i ImageConverter) convertImage(src FileInfo) error { + if error := i.convert(src); error != nil { + return error + } + return nil +} + +// Convert the targer image +func (i ImageConverter) convert(src FileInfo) error { + + fileName := src.name + "." + i.From + file, error := os.Open(fileName) + if error != nil { + return errors.Wrapf(error, "os.Open() with %s", fileName) + } + defer file.Close() + + img, error := i.decode(file) + if error != nil { + return error + } + + dstFile, error := i.makeDstFile(src) + if error != nil { + return error + } + + out, error := os.Create(dstFile) + if error != nil { + return errors.Wrapf(error, "os.Create() with %s", dstFile) + } + defer out.Close() + + if error := i.encode(out, img); error != nil { + return error + } + return nil +} + +// encode image to dstFormat +func (i ImageConverter) encode(out io.Writer, img image.Image) error { + + switch i.To { + case "jpeg", "jpg": + if error := jpeg.Encode(out, img, nil); error != nil { + return errors.Wrapf(error, "jpeg.Encode() with %s", i.To) + } + return nil + case "gif": + if error := gif.Encode(out, img, nil); error != nil { + return errors.Wrapf(error, "gif.Encode() with %s", i.To) + } + return nil + case "png": + if error := png.Encode(out, img); error != nil { + return errors.Wrapf(error, "png.Encode() with %s", i.To) + } + return nil + default: + return errors.New("不正な画像形式を出力先に指定しています") + } +} + +// decode image +func (i ImageConverter) decode(r io.Reader) (image.Image, error) { + img, _, error := image.Decode(r) + if error != nil { + return nil, error + } + return img, nil +} + +// make destination file +func (i ImageConverter) makeDstFile(src FileInfo) (string, error) { + dstDir := "output" + if _, err := os.Stat(dstDir); os.IsNotExist(err) { + os.Mkdir(dstDir, 0777) + } + return filepath.Join(dstDir, fmt.Sprintf("%s.%s", getFileNameWithoutExt(src.name), i.From)), nil +} + +// check if this format is avalable +func isAvailableFormat(format string) bool { + lowerFormat := strings.ToLower(format) + switch lowerFormat { + case "jpg", "jpeg", "png", "gif": + return true + default: + return false + + } +} + +// Get file name withour extension +func getFileNameWithoutExt(path string) string { + return filepath.Base(path[:len(path)-len(filepath.Ext(path))]) +} + +// Check if file exists +func isFileOrDirExists(filename string) bool { + if _, err := os.Stat(filename); os.IsNotExist(err) { + return false + } + return true + +} diff --git a/kadai2/takata/convert/convert_test.go b/kadai2/takata/convert/convert_test.go new file mode 100644 index 0000000..2602752 --- /dev/null +++ b/kadai2/takata/convert/convert_test.go @@ -0,0 +1,164 @@ +package convert + +import ( + "fmt" + "image" + "io/ioutil" + "os" + "testing" + + "github.com/pkg/errors" +) + +type Args struct { + dir string + srcFmt string + dstFmt string +} + +func TestConvert(t *testing.T) { + + argsList := []Args{ + {"../testdata/test", "jpg", "png"}, + {"../testdata/test", "jpeg", "png"}, + {"../testdata/test", "jpeg", "gif"}, + {"../testdata/test2", "png", "gif"}, + {"../testdata/test2", "png", "jpg"}, + {"../testdata/test3", "gif", "jpg"}, + {"../testdata/test3", "gif", "png"}, + {"../testdata/test4", "png", "jpg"}, + } + + for _, arg := range argsList { + + i := ImageConverter{arg.srcFmt, arg.dstFmt} + + t.Run(fmt.Sprintf("convertTest %s to %s", arg.srcFmt, arg.dstFmt), func(t *testing.T) { + err := i.Convert(arg.dir) + if err != nil { + t.Error(err) + } + removeTestOutput("./output") + }) + } +} + +func TestInvalidConvert(t *testing.T) { + + failArgsList := []Args{ + {"../testdata/test", "jpbg", "png"}, + {"../testdata/test", "jpg", "pdf"}, + {"../testdata/test2", "pag", "jpg"}, + {"../testdata/test3", "gof", "jpg"}, + {"../testdata/test2", "png", "gof"}, + } + + for _, arg := range failArgsList { + + i := ImageConverter{arg.srcFmt, arg.dstFmt} + + t.Run(fmt.Sprintf("convertFailTest %s to %s", arg.srcFmt, arg.dstFmt), func(t *testing.T) { + err := i.Convert(arg.dir) + if err == nil { + t.Error("Invalid Convert Result") + } + removeTestOutput("./output") + }) + } +} + +func TestFail_isFileOrDirExists(t *testing.T) { + + t.Run("NG isFileOrDirExists", func(t *testing.T) { + result := isFileOrDirExists("../notfound/1.png") + if result { + t.Error("Invalid Result") + } + }) +} + +func TestFail_dirwalk(t *testing.T) { + + t.Run("NG dirwalk", func(t *testing.T) { + + i := ImageConverter{"png", "jpg"} + _, err := i.dirwalk("../notfound") + + expectedError := errors.New("ioutil.ReadDir() with ../notfound: open ../notfound: no such file or directory") + if err.Error() != expectedError.Error() { + t.Error(err) + } + }) +} + +func TestFail_convert(t *testing.T) { + t.Run("NG convert", func(t *testing.T) { + + i := ImageConverter{"png", "jpg"} + fi := FileInfo{name: "999"} + err := i.convert(fi) + expectedError := errors.New("os.Open() with 999.png: open 999.png: no such file or directory") + if err.Error() != expectedError.Error() { + t.Error(err) + } + }) +} + +func TestFail_encode(t *testing.T) { + t.Run("NG encode", func(t *testing.T) { + + i := ImageConverter{"png", "pdf"} + f := testTempFile(t) + img := testTempImage(t) + err := i.encode(f, img) + expectedError := errors.New("不正な画像形式を出力先に指定しています") + if err.Error() != expectedError.Error() { + t.Error(err) + } + }) +} + +func TestFail_decode(t *testing.T) { + t.Run("NG decode", func(t *testing.T) { + f, error := os.Open("../testdata/test4/1.txt") + if error != nil { + t.Error(error) + } + + i := ImageConverter{"png", "pdf"} + _, error = i.decode(f) + if error != nil { + t.Error(error) + } + }) +} + +func testTempFile(t *testing.T) *os.File { + t.Helper() + tf, err := ioutil.TempFile("", "test") + if err != nil { + t.Error(err) + } + tf.Close() + return tf +} + +func testTempImage(t *testing.T) image.Image { + t.Helper() + + file, error := os.Open("../testdata/test/2.jpg") + if error != nil { + t.Error(error) + } + defer file.Close() + + img, _, error := image.Decode(file) + if error != nil { + t.Error(error) + } + return img +} + +func removeTestOutput(dir string) { + os.RemoveAll(dir) +} diff --git a/kadai2/takata/main.go b/kadai2/takata/main.go new file mode 100644 index 0000000..08cb4b0 --- /dev/null +++ b/kadai2/takata/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "convert" + "flag" +) + +// main +func main() { + var ( + dir = flag.String("d", "", "directory to convert imagefiles") + srcFmt = flag.String("sf", "jpg", "src file format") + dstFmt = flag.String("df", "png", "dest file format") + ) + + flag.Parse() + + convert.Convert(*dir, *srcFmt, *dstFmt) +} diff --git a/kadai2/takata/profile b/kadai2/takata/profile new file mode 100644 index 0000000..30ea87d --- /dev/null +++ b/kadai2/takata/profile @@ -0,0 +1,66 @@ +mode: set +convert/convert.go:39.51,41.29 1 1 +convert/convert.go:45.2,46.18 2 1 +convert/convert.go:50.2,50.20 1 1 +convert/convert.go:54.2,54.32 1 1 +convert/convert.go:58.2,58.30 1 1 +convert/convert.go:62.2,63.18 2 1 +convert/convert.go:66.2,66.29 1 1 +convert/convert.go:71.2,71.12 1 1 +convert/convert.go:41.29,43.3 1 0 +convert/convert.go:46.18,48.3 1 0 +convert/convert.go:50.20,52.3 1 0 +convert/convert.go:54.32,56.3 1 1 +convert/convert.go:58.30,60.3 1 1 +convert/convert.go:63.18,65.3 1 0 +convert/convert.go:66.29,67.50 1 1 +convert/convert.go:67.50,69.4 1 0 +convert/convert.go:75.65,78.18 2 1 +convert/convert.go:82.2,83.29 2 1 +convert/convert.go:101.2,101.19 1 1 +convert/convert.go:78.18,80.3 1 1 +convert/convert.go:83.29,84.19 1 1 +convert/convert.go:92.3,94.27 3 1 +convert/convert.go:98.3,99.30 2 1 +convert/convert.go:84.19,86.20 2 1 +convert/convert.go:89.4,90.12 2 1 +convert/convert.go:86.20,88.5 1 0 +convert/convert.go:94.27,95.12 1 1 +convert/convert.go:105.58,106.43 1 1 +convert/convert.go:109.2,109.12 1 1 +convert/convert.go:106.43,108.3 1 0 +convert/convert.go:113.53,117.18 3 1 +convert/convert.go:120.2,123.18 3 1 +convert/convert.go:127.2,128.18 2 1 +convert/convert.go:132.2,133.18 2 1 +convert/convert.go:136.2,138.47 2 1 +convert/convert.go:141.2,141.12 1 1 +convert/convert.go:117.18,119.3 1 1 +convert/convert.go:123.18,125.3 1 0 +convert/convert.go:128.18,130.3 1 0 +convert/convert.go:133.18,135.3 1 0 +convert/convert.go:138.47,140.3 1 0 +convert/convert.go:145.70,147.14 1 1 +convert/convert.go:148.21,149.56 1 1 +convert/convert.go:152.3,152.13 1 1 +convert/convert.go:153.13,154.55 1 1 +convert/convert.go:157.3,157.13 1 1 +convert/convert.go:158.13,159.50 1 1 +convert/convert.go:162.3,162.13 1 1 +convert/convert.go:163.10,164.81 1 1 +convert/convert.go:149.56,151.4 1 0 +convert/convert.go:154.55,156.4 1 0 +convert/convert.go:159.50,161.4 1 0 +convert/convert.go:169.66,171.18 2 1 +convert/convert.go:174.2,174.17 1 1 +convert/convert.go:171.18,173.3 1 0 +convert/convert.go:178.67,180.51 2 1 +convert/convert.go:183.2,183.98 1 1 +convert/convert.go:180.51,182.3 1 1 +convert/convert.go:187.44,189.21 2 1 +convert/convert.go:190.35,191.14 1 1 +convert/convert.go:192.10,193.15 1 1 +convert/convert.go:199.48,201.2 1 1 +convert/convert.go:204.46,205.53 1 1 +convert/convert.go:208.2,208.13 1 1 +convert/convert.go:205.53,207.3 1 1 diff --git a/kadai2/takata/testdata/test/2.jpg b/kadai2/takata/testdata/test/2.jpg new file mode 100644 index 0000000..c685157 Binary files /dev/null and b/kadai2/takata/testdata/test/2.jpg differ diff --git a/kadai2/takata/testdata/test/inner/6.jpg b/kadai2/takata/testdata/test/inner/6.jpg new file mode 100644 index 0000000..8a0d03d Binary files /dev/null and b/kadai2/takata/testdata/test/inner/6.jpg differ diff --git a/kadai2/takata/testdata/test2/1.png b/kadai2/takata/testdata/test2/1.png new file mode 100644 index 0000000..b2ce8f3 Binary files /dev/null and b/kadai2/takata/testdata/test2/1.png differ diff --git a/kadai2/takata/testdata/test2/inner/8.png b/kadai2/takata/testdata/test2/inner/8.png new file mode 100644 index 0000000..0d84e3c Binary files /dev/null and b/kadai2/takata/testdata/test2/inner/8.png differ diff --git a/kadai2/takata/testdata/test3/1.gif b/kadai2/takata/testdata/test3/1.gif new file mode 100644 index 0000000..b2ce8f3 Binary files /dev/null and b/kadai2/takata/testdata/test3/1.gif differ diff --git a/kadai2/takata/testdata/test3/inner/9.gif b/kadai2/takata/testdata/test3/inner/9.gif new file mode 100644 index 0000000..93ea6cd Binary files /dev/null and b/kadai2/takata/testdata/test3/inner/9.gif differ diff --git a/kadai2/takata/testdata/test4/1.txt b/kadai2/takata/testdata/test4/1.txt new file mode 100644 index 0000000..b2ce8f3 Binary files /dev/null and b/kadai2/takata/testdata/test4/1.txt differ diff --git a/kadai3-1/README.md b/kadai3-1/README.md new file mode 100644 index 0000000..e6ba195 --- /dev/null +++ b/kadai3-1/README.md @@ -0,0 +1,10 @@ +# 課題3-1 + +- タイピングゲームを作ろう +- 標準出力に英単語を出す(出すものは自由) +- 標準入力から1行受け取る +- 制限時間内に何問解けたか表示する + +# 備考 + +テストで標準入力に文字を入力する方法がわからず止まってしまいました。 \ No newline at end of file diff --git a/kadai3-1/takata/main.go b/kadai3-1/takata/main.go new file mode 100644 index 0000000..01d377a --- /dev/null +++ b/kadai3-1/takata/main.go @@ -0,0 +1,95 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "time" +) + +const limitTime = 5 + +var writer io.Writer + +var words []string + +func init() { + writer = os.Stdout + + // TODO: ファイルから読み込むなどを検討する + words = []string{ + "apple", + "banana", + "orange", + "grape", + "Melon", + "Muscat", + "strawberry", + "persimmon", + "kiwi fruit", + "cherry", + } +} + +func start() { + fmt.Fprintln(writer, "Game スタート") + fmt.Fprintf(writer, "制限時間は %v秒です\n", limitTime) +} + +func end() { + fmt.Fprintln(writer, "Game 終了") +} + +func createCh(input io.Reader) <-chan string { + ch := make(chan string) + go func() { + s := bufio.NewScanner(input) + for s.Scan() { + ch <- s.Text() + } + close(ch) + }() + return ch +} + +func run(input io.Reader) int { + + var answer int + + ch := createCh(input) + + for i, w := range words { + + fmt.Fprintf(writer, "%d回目 文字を入力してください -> %s\n", i+1, w) + fmt.Fprint(writer, "> ") + select { + case <-time.After(limitTime * time.Second): + fmt.Fprintln(writer, "時間切れです") + case tw := <-ch: + if judge(w, tw) { + fmt.Fprintln(writer, "正解") + answer++ + } else { + fmt.Fprintln(writer, "残念") + } + } + } + fmt.Fprintf(writer, "正解数: %d\n", answer) + return answer +} + +func judge(expected string, actual string) bool { + + if actual == expected { + return true + } else { + return false + } +} + +func main() { + start() + run(os.Stdin) + end() +} diff --git a/kadai3-1/takata/main_test.go b/kadai3-1/takata/main_test.go new file mode 100644 index 0000000..b6bdb2f --- /dev/null +++ b/kadai3-1/takata/main_test.go @@ -0,0 +1,62 @@ +package main + +import ( + "bytes" + "testing" +) + +var buffer *bytes.Buffer + +func init() { + buffer = &bytes.Buffer{} + writer = buffer +} + +func TestStart(t *testing.T) { + defer buffer.Reset() + start() + expected := "Game スタート\n制限時間は 5秒です\n" + actual := buffer.String() + if actual != expected { + t.Errorf(`expected=%s, actual=%s`, expected, actual) + } +} + +func TestEnd(t *testing.T) { + defer buffer.Reset() + end() + expected := "Game 終了\n" + actual := buffer.String() + if actual != expected { + t.Errorf(`expected=%s, actual=%s`, expected, actual) + } +} + +func TestCreateCh(t *testing.T) { + input := "hello world." + inputBuf := bytes.NewBufferString(input) + ch := createCh(inputBuf) + received := <-ch + if input != received { + t.Errorf(`expected=%s, actual=%s`, input, received) + } +} + +func TestRunNoAnswer(t *testing.T) { + defer buffer.Reset() + expected := 0 + actual := run(buffer) + if actual != expected { + t.Errorf(`expected=%d, actual=%d`, expected, actual) + } +} + +func TestRunOK(t *testing.T) { + defer buffer.Reset() + expected := 1 + // TODO: どうしたら標準入力のテストができるのか? + actual := run(buffer) + if actual != expected { + t.Errorf(`expected=%d, actual=%d`, expected, actual) + } +} diff --git a/kadai3-2/README.md b/kadai3-2/README.md new file mode 100644 index 0000000..998215b --- /dev/null +++ b/kadai3-2/README.md @@ -0,0 +1,13 @@ +# 課題3−2 + +## 分割ダウンロードを行う + +- Rangeアクセスを用いる +- いくつかのゴルーチンでダウンロードしてマージする +- エラー処理を工夫する golang.org/x/sync/errgourpパッケージなどを使ってみる +- キャンセルが発生した場合の実装を行う + +## 現状 + +書き方が全然わからず、調べながら色々な方の実装を参考にやりましたが、 +まだ理解が追いついていない状態です。 \ No newline at end of file diff --git a/kadai3-2/takata/download/download.go b/kadai3-2/takata/download/download.go new file mode 100644 index 0000000..505a9a9 --- /dev/null +++ b/kadai3-2/takata/download/download.go @@ -0,0 +1,84 @@ +package download + +import ( + "bytes" + "context" + "io" + "net/url" + "os" + "time" +) + +type Downloader struct { + url *url.URL + Option + Data +} + +type Data struct { + size uint + bytes [][]byte +} + +type Option interface { + Proc() int + Timeout() time.Duration + Writer() io.Writer + Output() string +} + +func New(u *url.URL, opts Option) *Downloader { + return &Downloader{ + url: u, + Option: opts, + Data: Data{}, + } +} + +func (d *Downloader) URL() *url.URL { + return d.url +} + +func (d *Downloader) Run() error { + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + filesize, err := d.FetchFileSize(d.url.String()) + if err != nil { + return err + } + + d.SetFileSize(filesize) + d.bytes = make([][]byte, d.Proc()) + + errorgroup := d.get(ctx) + if err := errorgroup.Wait(); err != nil { + return err + } + + body := d.Merge() + + f, err := os.Create(d.Output()) + defer f.Close() + + f.Write(body) + + return nil + +} + +// SetFileSize is to set Data file size +func (d *Data) SetFileSize(size uint) { + d.size = size +} + +// Merge data +func (d *Data) Merge() []byte { + return bytes.Join(d.bytes, []byte("")) +} + +// return String merged data +func (d *Data) String() string { + return string(d.Merge()) +} diff --git a/kadai3-2/takata/download/request.go b/kadai3-2/takata/download/request.go new file mode 100644 index 0000000..64ced1b --- /dev/null +++ b/kadai3-2/takata/download/request.go @@ -0,0 +1,83 @@ +package download + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + + "golang.org/x/sync/errgroup" +) + +// Range represents byte range to fetch +type Range struct { + low int + high int + proc int +} + +// NewRange make new byte range to fetch +func NewRange(filesize uint, procs, proc int) *Range { + split := int(filesize) / procs + return &Range{ + low: split * (proc - 1), + high: split * proc, + } +} + +// Low get lowest value of bytes to fetch +func (r *Range) Low() int { + return r.low +} + +// High get highest value of bytes to fetch +func (r *Range) High() int { + return r.high +} + +// FetchFileSize is to fetch content length and set filesize +func (d *Data) FetchFileSize(URL string) (uint, error) { + resp, err := http.Head(URL) + + if err != nil { + return 0, err + } + + return uint(resp.ContentLength), nil +} + +// Get contents concurrently +func (d *Downloader) get(ctx context.Context) *errgroup.Group { + errgroup, ctx := errgroup.WithContext(ctx) + + for i := 0; i < d.Proc(); i++ { + i := i + r := NewRange(d.size, d.Proc(), i+1) + + errgroup.Go(func() error { + req, err := http.NewRequest(http.MethodGet, d.URL().String(), nil) + if err != nil { + return err + } + + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.Low(), r.High())) + req = req.WithContext(ctx) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + + d.bytes[i] = body + return nil + }) + } + + return errgroup +} diff --git a/kadai3-2/takata/main.go b/kadai3-2/takata/main.go new file mode 100644 index 0000000..360651f --- /dev/null +++ b/kadai3-2/takata/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "io" + "log" + "os" + "time" + + "./download" + "./option" +) + +var writer io.Writer + +func init() { + writer = os.Stdout +} + +func main() { + opts, u, err := option.Parse(writer) + + if err != nil { + log.Fatal(err) + } + + d := download.New(u, opts) + start := time.Now() + d.Run() + fmt.Fprintf(d.Writer(), "Duration %f seconds\n", time.Since(start).Seconds()) +} diff --git a/kadai3-2/takata/option/option.go b/kadai3-2/takata/option/option.go new file mode 100644 index 0000000..7fb4a7d --- /dev/null +++ b/kadai3-2/takata/option/option.go @@ -0,0 +1,71 @@ +package option + +import ( + "flag" + "fmt" + "io" + "net/url" + "path/filepath" + "runtime" + "time" +) + +// Option represents CLI options +type Option struct { + proc int + timeout int + output string + writer io.Writer +} + +// DefaultTimeout is default value for request timeout +const DefaultTimeout = 30 + +// Parse CLI options +func Parse(w io.Writer) (*Option, *url.URL, error) { + opts := &Option{ + writer: w, + } + + flag.IntVar(&opts.proc, "p", runtime.NumCPU(), "How many to run") + flag.IntVar(&opts.timeout, "t", DefaultTimeout, "Timeout") + flag.StringVar(&opts.output, "o", "", "output") + + flag.Parse() + + if len(flag.Args()) < 1 { + return nil, nil, fmt.Errorf("url is required") + } + + u, err := url.Parse(flag.Arg(0)) + + if err != nil { + return nil, nil, err + } + + if opts.output == "" { + opts.output = filepath.Base(u.Path) + } + + return opts, u, nil +} + +// Proc is proc getter +func (o *Option) Proc() int { + return o.proc +} + +// Timeout return timeout seconds +func (o *Option) Timeout() time.Duration { + return time.Duration(o.timeout) * time.Second +} + +// Writer return io.Writer +func (o *Option) Writer() io.Writer { + return o.writer +} + +// Output path +func (o *Option) Output() string { + return o.output +} diff --git a/kadai4/README.md b/kadai4/README.md new file mode 100644 index 0000000..6f1531f --- /dev/null +++ b/kadai4/README.md @@ -0,0 +1,5 @@ +# 課題内容 +- おみくじAPIを作ってみよう + - [x] JSON形式でおみくじの結果を返す + - [x] 正月(1/1-1/3)だけ大吉にする + - [x] ハンドラのテストを書いてみる diff --git a/kadai4/takata/main.go b/kadai4/takata/main.go new file mode 100644 index 0000000..67a79b2 --- /dev/null +++ b/kadai4/takata/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "bytes" + crand "crypto/rand" + "encoding/json" + "fmt" + "log" + "math" + "math/big" + "math/rand" + "net/http" + "time" +) + +var box = map[int]string{ + 0: "大吉", + 1: "中吉", + 2: "中吉", + 3: "小吉", + 4: "小吉", + 5: "小吉", + 6: "凶", + 7: "凶", + 8: "大凶", +} + +// Fortune おみくじ結果 +type Fortune struct { + Result string `json:"result"` +} + +func (f *Fortune) setResult(t time.Time) { + var result string + + // 正月の場合は大吉にする + if t.Month() == 1 && (t.Day() >= 1 && t.Day() <= 3) { + result = "大吉" + } else { + result = box[rand.Intn(len(box))] + } + f.Result = result +} + +func init() { + seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + log.Fatalf("Failed to init package. %s", err) + } + rand.Seed(seed.Int64()) +} + +func fortuneHandler(w http.ResponseWriter, r *http.Request) { + f := &Fortune{} + + t := time.Now() + f.setResult(t) + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + if err := enc.Encode(f); err != nil { + http.Error(w, "", http.StatusInternalServerError) + } + fmt.Fprint(w, buf.String()) +} + +func main() { + http.HandleFunc("/fortune", fortuneHandler) + http.ListenAndServe(":9999", nil) +} diff --git a/kadai4/takata/main_test.go b/kadai4/takata/main_test.go new file mode 100644 index 0000000..e75eb6b --- /dev/null +++ b/kadai4/takata/main_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestSetResult(t *testing.T) { + resultMap = map[int]string{ + 0: "小吉", + } + cases := []struct { + name string + now time.Time + expected string + }{ + { + name: "1月1日", + now: time.Date(2018, 1, 1, 0, 0, 0, 0, time.Local), + expected: "大吉", + }, + { + name: "1月2日", + now: time.Date(2018, 1, 2, 0, 0, 0, 0, time.Local), + expected: "大吉", + }, + { + name: "1月3日", + now: time.Date(2018, 1, 3, 0, 0, 0, 0, time.Local), + expected: "大吉", + }, + { + name: "12月31日", + now: time.Date(2017, 12, 31, 0, 0, 0, 0, time.Local), + expected: "小吉", + }, + { + name: "1月4日", + now: time.Date(2018, 1, 4, 0, 0, 0, 0, time.Local), + expected: "小吉", + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + f := Fortune{} + f.setResult(c.now) + + if c.expected != f.Result { + t.Errorf( + "f.setResult(%v) expected = %s, actual = %s", + c.now, c.expected, f.Result, + ) + } + }) + } +} + +func TestFortuneHandler(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/fortune", nil) + fortuneHandler(w, r) + rw := w.Result() + defer rw.Body.Close() + if rw.StatusCode != http.StatusOK { + t.Errorf( + "expected statusCode=%d, actual = %d", + http.StatusOK, rw.StatusCode, + ) + } + b, err := ioutil.ReadAll(rw.Body) + if err != nil { + t.Fatalf("Unexpected error, %d", err) + } + + // JSONデコード + var result Fortune + if err := json.Unmarshal(b, &result); err != nil { + log.Fatal(err) + } + actual := result.Result + expected := "小吉" + if expected != actual { + t.Errorf( + "expected=%s, actual=%s", + expected, actual, + ) + } +}