diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..600d2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..138a79f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/dojo6 + +go 1.12 diff --git a/kadai1/.gitkeep b/kadai1/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/kadai1/annkara/README.md b/kadai1/annkara/README.md new file mode 100644 index 0000000..38c9e18 --- /dev/null +++ b/kadai1/annkara/README.md @@ -0,0 +1,48 @@ +# 課題1 + +## 次の仕様を満たすコマンドを作って下さい + +- ディレクトリを指定する +- 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) +- ディレクトリ以下は再帰的に処理する +- 変換前と変換後の画像形式を指定できる(オプション) + +## 以下を満たすように開発してください + +- mainパッケージと分離する +- 自作パッケージと標準パッケージと準標準パッケージのみ使う + - 準標準パッケージ:golang.org/x以下のパッケージ +- ユーザ定義型を作ってみる +- GoDocを生成してみる + +--- + +## コマンドの説明 + +`i2i` コマンドは指定されたディレクトリ内の画像ファイルの形式を別の画像形式へと変換し、新規画像ファイルを作成するコマンドです。画像の形式はjpg(jpeg)とpngに対応しています。 + +ディレクトリ内に別のディレクトリが存在した場合には、そのディレクトリ内のファイルも変換対象と見なされます。 + +画像の形式が指定されない場合には、jpg(jpeg)ファイルを対象に、同一ディレクトリ内に新たにpngファイルを作成します。 + +変換前のファイルは削除されません。 + +## 使い方 + +```sh +i2i -b jpg -a png +``` + +--- + +## 実装方針 + +- mainパッケージとpkg/imageパッケージの構成 +- 標準パッケージのみを利用する +- オプションの指定 + flag パッケージを利用して変換前、変換後の画像形式を受け取る。 + 変換前のデフォルト値としてはjpg、変換後はpngを指定する。 +- ディレクトリの再帰処理 + filepath.Walk関数を利用する。 +- 画像変換処理 + image.Decode関数を利用し、指定された拡張子に応じた形式のEncode関数を利用し変換処理を行う。 diff --git a/kadai1/annkara/cmd/i2i/cli.go b/kadai1/annkara/cmd/i2i/cli.go new file mode 100644 index 0000000..ee075f8 --- /dev/null +++ b/kadai1/annkara/cmd/i2i/cli.go @@ -0,0 +1,124 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + + "github.com/dojo6/kadai1/annkara/pkg/image" +) + +const ( + exitCodeOK = 0 + exitCodeErr = 10 +) + +type cli struct { + outStream, errStream io.Writer +} + +func (c *cli) walk(root, before, after string) error { + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + + if err != nil { + return err + } + + n := info.Name() + if strings.HasSuffix(n, before) { + origin, err := os.Open(path) + if err != nil { + return err + } + defer origin.Close() + + // 拡張子を含まない出力用ファイル名 + n := filepath.Base(n[:len(n)-len(filepath.Ext(n))]) + dir := filepath.Dir(path) + out, err := os.Create(filepath.Join(dir, n+"."+after)) + if err != nil { + return err + } + + err = image.Convert(origin, out, after) + if err != nil { + // 変換処理に失敗した場合、不要なファイルが作成されてしまうため、削除する + // ファイルを閉じた後でないと、Windowsの場合削除できないのでここでCloseする + out.Close() + e := os.Remove(filepath.Join(dir, n+"."+after)) + if e != nil { + return e + } + return err + } + } + + return nil + }) + + if err != nil { + return err + } + + return nil +} + +func (c *cli) run(args []string) int { + + log.SetOutput(c.outStream) + + var ( + before string + after string + debug bool + ) + + flags := flag.NewFlagSet(args[0], flag.ContinueOnError) + flags.SetOutput(c.errStream) + flags.Usage = func() { + fmt.Fprint(c.errStream, helpText) + } + + flags.StringVar(&before, "b", "jpg", "変換前の画像形式を指定") + flags.StringVar(&before, "before", "jpg", "変換前の画像形式を指定") + flags.StringVar(&after, "a", "png", "変換後の画像形式を指定") + flags.StringVar(&after, "after", "png", "変換後の画像形式を指定") + flags.BoolVar(&debug, "debug", false, "") + + if err := flags.Parse(args[1:]); err != nil { + if debug { + log.Printf("failed: %+v\n", err) + } + return exitCodeErr + } + + for _, v := range flags.Args() { + err := c.walk(v, before, after) + if err != nil { + if debug { + log.Printf("failed: %+v\n", err) + } + return exitCodeErr + } + } + + return exitCodeOK +} + +var helpText = `Usage: i2i [options] directory + +i2i は指定されたディレクトリ内の画像ファイルを変換するコマンドラインツールです。 +オプションを指定しない場合には、JPEGファイルを対象にPNG形式へと変換します。 +変換後のファイルは同一ディレクトリ内に出力され、変換前のファイルは削除されません。 +JPEGまたはPNG形式をサポートします。 + +Options: + -a, -after 変換後の画像形式を指定 + -b, -before 変換前の画像形式を指定 + -h, -help ヘルプを表示 +` diff --git a/kadai1/annkara/cmd/i2i/cli_test.go b/kadai1/annkara/cmd/i2i/cli_test.go new file mode 100644 index 0000000..f09091b --- /dev/null +++ b/kadai1/annkara/cmd/i2i/cli_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "bytes" + "strings" + "testing" +) + +func TestRun(t *testing.T) { + tests := []struct { + desc string + arg string + expectedStatus int + }{ + { + desc: "正常終了 : コマンド名のみを指定", + arg: "i2i", + expectedStatus: exitCodeOK, + }, { + desc: "正常終了 : ショートオプションを指定", + arg: "i2i -b jpeg -a png", + expectedStatus: exitCodeOK, + }, { + desc: "正常終了 : ロングオプションを指定", + arg: "i2i -before jpeg -after png", + expectedStatus: exitCodeOK, + }, { + desc: "異常終了:不正なオプションを指定", + arg: "i2i -u unkonwn", + expectedStatus: exitCodeErr, + }, + } + + outSteam, errStream := new(bytes.Buffer), new(bytes.Buffer) + cli := &cli{outStream: outSteam, errStream: errStream} + + for _, tc := range tests { + args := strings.Split(tc.arg, " ") + status := cli.run(args) + if status != tc.expectedStatus { + t.Errorf("desc: %v, status should be %v, not %v", tc.desc, tc.expectedStatus, status) + } + } +} diff --git a/kadai1/annkara/cmd/i2i/main.go b/kadai1/annkara/cmd/i2i/main.go new file mode 100644 index 0000000..c43aa11 --- /dev/null +++ b/kadai1/annkara/cmd/i2i/main.go @@ -0,0 +1,13 @@ +package main + +import "os" + +func main() { + + cli := &cli{ + outStream: os.Stdout, + errStream: os.Stderr, + } + os.Exit(cli.run(os.Args)) + +} diff --git a/kadai1/annkara/pkg/image/convertor.go b/kadai1/annkara/pkg/image/convertor.go new file mode 100644 index 0000000..1904ad6 --- /dev/null +++ b/kadai1/annkara/pkg/image/convertor.go @@ -0,0 +1,35 @@ +// Package image provides image convert function. +package image + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "io" +) + +// Convert is the function that converts an image format to another image format. +// origin is original file, target is converted image, and format is target image format. +func Convert(origin io.Reader, target io.Writer, format string) error { + + var err error + img, _, err := image.Decode(origin) + if err != nil { + return err + } + + switch format { + case "jpg", "jpeg": + err = jpeg.Encode(target, img, nil) + case "png": + err = png.Encode(target, img) + default: + err = fmt.Errorf("対応していないフォーマットです") + } + + if err != nil { + return err + } + return nil +} diff --git a/kadai1/annkara/pkg/image/convertor_test.go b/kadai1/annkara/pkg/image/convertor_test.go new file mode 100644 index 0000000..d102687 --- /dev/null +++ b/kadai1/annkara/pkg/image/convertor_test.go @@ -0,0 +1,28 @@ +package image + +import ( + "os" + "path/filepath" + "testing" +) + +var path = filepath.Join("..", "..", "test") + +func TestConvertSuccess(t *testing.T) { + in, err := os.Open(filepath.Join(path, "test.jpg")) + if err != nil { + t.Fatalf("failed test %#v", err) + } + defer in.Close() + + out, err := os.Create(filepath.Join(path, "test.png")) + if err != nil { + t.Fatalf("failed test %#v", err) + } + defer out.Close() + + err = Convert(in, out, "jpg") + if err != nil { + t.Fatalf("failed test %#v", err) + } +} diff --git a/kadai1/annkara/test/test.jpg b/kadai1/annkara/test/test.jpg new file mode 100644 index 0000000..02bfe5d Binary files /dev/null and b/kadai1/annkara/test/test.jpg differ diff --git a/kadai1/annkara/test/test.png b/kadai1/annkara/test/test.png new file mode 100644 index 0000000..834bf32 Binary files /dev/null and b/kadai1/annkara/test/test.png differ diff --git a/kadai2/annkara/README.md b/kadai2/annkara/README.md new file mode 100644 index 0000000..5e84516 --- /dev/null +++ b/kadai2/annkara/README.md @@ -0,0 +1,13 @@ +# 課題2 + +## io.Readerとio.Writerについて調べてみよう + +- 標準パッケージでどのように使われているか +- io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる + +## 1回目の宿題のテストを作ってみて下さい + +- テストのしやすさを考えてリファクタリングしてみる +- テストのカバレッジを取ってみる +- テーブル駆動テストを行う +- テストヘルパーを作ってみる diff --git a/kadai2/annkara/cmd/i2i/cli.go b/kadai2/annkara/cmd/i2i/cli.go new file mode 100644 index 0000000..2a566d1 --- /dev/null +++ b/kadai2/annkara/cmd/i2i/cli.go @@ -0,0 +1,74 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + + "github.com/dojo6/kadai2/annkara/pkg/dir" +) + +const ( + exitCodeOK = 0 + exitCodeErr = 10 +) + +type cli struct { + outStream, errStream io.Writer +} + +func (c *cli) run(args []string) int { + + log.SetOutput(c.outStream) + + var ( + before string + after string + debug bool + ) + + flags := flag.NewFlagSet(args[0], flag.ContinueOnError) + flags.SetOutput(c.errStream) + flags.Usage = func() { + fmt.Fprint(c.errStream, helpText) + } + + flags.StringVar(&before, "b", "jpg", "変換前の画像形式を指定") + flags.StringVar(&before, "before", "jpg", "変換前の画像形式を指定") + flags.StringVar(&after, "a", "png", "変換後の画像形式を指定") + flags.StringVar(&after, "after", "png", "変換後の画像形式を指定") + flags.BoolVar(&debug, "debug", false, "") + + if err := flags.Parse(args[1:]); err != nil { + if debug { + log.Printf("failed: %+v\n", err) + } + return exitCodeErr + } + + for _, v := range flags.Args() { + err := dir.Walk(v, before, after) + if err != nil { + if debug { + log.Printf("failed: %+v\n", err) + } + return exitCodeErr + } + } + + return exitCodeOK +} + +var helpText = `Usage: i2i [options] directory + +i2i は指定されたディレクトリ内の画像ファイルを変換するコマンドラインツールです。 +オプションを指定しない場合には、JPEGファイルを対象にPNG形式へと変換します。 +変換後のファイルは同一ディレクトリ内に出力され、変換前のファイルは削除されません。 +JPEGまたはPNG形式をサポートします。 + +Options: + -a, -after 変換後の画像形式を指定 + -b, -before 変換前の画像形式を指定 + -h, -help ヘルプを表示 +` diff --git a/kadai2/annkara/cmd/i2i/cli_test.go b/kadai2/annkara/cmd/i2i/cli_test.go new file mode 100644 index 0000000..f09091b --- /dev/null +++ b/kadai2/annkara/cmd/i2i/cli_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "bytes" + "strings" + "testing" +) + +func TestRun(t *testing.T) { + tests := []struct { + desc string + arg string + expectedStatus int + }{ + { + desc: "正常終了 : コマンド名のみを指定", + arg: "i2i", + expectedStatus: exitCodeOK, + }, { + desc: "正常終了 : ショートオプションを指定", + arg: "i2i -b jpeg -a png", + expectedStatus: exitCodeOK, + }, { + desc: "正常終了 : ロングオプションを指定", + arg: "i2i -before jpeg -after png", + expectedStatus: exitCodeOK, + }, { + desc: "異常終了:不正なオプションを指定", + arg: "i2i -u unkonwn", + expectedStatus: exitCodeErr, + }, + } + + outSteam, errStream := new(bytes.Buffer), new(bytes.Buffer) + cli := &cli{outStream: outSteam, errStream: errStream} + + for _, tc := range tests { + args := strings.Split(tc.arg, " ") + status := cli.run(args) + if status != tc.expectedStatus { + t.Errorf("desc: %v, status should be %v, not %v", tc.desc, tc.expectedStatus, status) + } + } +} diff --git a/kadai2/annkara/cmd/i2i/main.go b/kadai2/annkara/cmd/i2i/main.go new file mode 100644 index 0000000..c43aa11 --- /dev/null +++ b/kadai2/annkara/cmd/i2i/main.go @@ -0,0 +1,13 @@ +package main + +import "os" + +func main() { + + cli := &cli{ + outStream: os.Stdout, + errStream: os.Stderr, + } + os.Exit(cli.run(os.Args)) + +} diff --git "a/kadai2/annkara/doc/\350\252\262\351\241\2142.md" "b/kadai2/annkara/doc/\350\252\262\351\241\2142.md" new file mode 100644 index 0000000..397ba3c --- /dev/null +++ "b/kadai2/annkara/doc/\350\252\262\351\241\2142.md" @@ -0,0 +1,70 @@ +# 課題2 + +## io.Readerとio.Writerについて調べてみよう + +- 標準パッケージでどのように使われているか +- io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる + +## 標準パッケージでどのように使われているか + +入出力処理を伴うパッケージ内で定義されている構造体の要素、また、その構造体を生成するNewXXX関数などのパラメータとして利用されていることが多いように感じる。 + +io.Writer/io.Readerはともに入出力処理のシンプルなインターフェースを提供し、インターフェースの実装を用途に応じた処理でラッピングすることで、柔軟な入出力処理を実現している印象を受けた。 + +### io.Writer + +[golang/go内でのio.Writerの利用箇所](https://github.com/golang/go/search?q=%22io.Writer%22&unscoped_q=%22io.Writer%22) + +[src/bufio/bufio.goでの利用箇所](https://github.com/golang/go/blob/3ed5a53f9d1c9713c7d2748f7744534e404b80de/src/bufio/bufio.go#L544) + +### io.Reader + +[golang/go内でのio.Readerの利用箇所](https://github.com/golang/go/search?q=%22io.Reader%22&unscoped_q=%22io.Reader%22) + +[src/archive/tar/reader.goでの利用箇所](https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/archive/tar/reader.go#L18) + +## io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる + +### 利点 + +- 実装の切り替えが容易であること + +```Go +//fmt.Fprintf関数は、第一パラメータにio.Writer型を受け取る。 +//そのため、io.Writer型を実装する具象型であればどのような型であっても受け取ることが可能。 +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) + +// 標準出力への出力 +func Printf(format string, a ...interface{}) (n int, err error) { + return Fprintf(os.Stdout, format, a...) +} +// 標準エラー出力への出力 +func usage() { + fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n") + ... +} + +// net/httpパッケージのHandlerインターフェースとResponseWriter型 +// ResponseWriter型がio.Writer型を満たすため、ServeHTTP関数内でResponseWriterを +// 通じてレスポンスを応答することが可能 +type Handler interface { + ServeHTTP(ResponseWriter, *Request) +} +type ResponseWriter interface { + Header() Header + Write([]byte) (int, error) + WriteHeader(statusCode int) +} +// net/httpパッケージのtriv.goからの抜粋 +func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) { + io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch)) +} +// io.WriteString関数の抜粋 +// 第一パラメータがio.Writer型を受け取る +func WriteString(w Writer, s string) (n int, err error) { + if sw, ok := w.(stringWriter); ok { + return sw.WriteString(s) + } + return w.Write([]byte(s)) +} +``` diff --git a/kadai2/annkara/pkg/dir/testdata/test.jpg b/kadai2/annkara/pkg/dir/testdata/test.jpg new file mode 100644 index 0000000..02bfe5d Binary files /dev/null and b/kadai2/annkara/pkg/dir/testdata/test.jpg differ diff --git a/kadai2/annkara/pkg/dir/testdata/test.png b/kadai2/annkara/pkg/dir/testdata/test.png new file mode 100644 index 0000000..17e0f37 Binary files /dev/null and b/kadai2/annkara/pkg/dir/testdata/test.png differ diff --git a/kadai2/annkara/pkg/dir/walker.go b/kadai2/annkara/pkg/dir/walker.go new file mode 100644 index 0000000..d801805 --- /dev/null +++ b/kadai2/annkara/pkg/dir/walker.go @@ -0,0 +1,60 @@ +// Package dir provides directory walk function. +package dir + +import ( + "os" + "path/filepath" + "strings" + + "github.com/dojo6/kadai2/annkara/pkg/image" +) + +// Walk only calls the filepath.Walk fuction and internally calls image.Convert function. +// The root parameter is the target root directory, the before is the file extension before converting image, +// the after is after coverting image. +func Walk(root, before, after string) error { + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + + if err != nil { + return err + } + + n := info.Name() + if strings.HasSuffix(n, before) { + origin, err := os.Open(path) + if err != nil { + return err + } + defer origin.Close() + + // 拡張子を含まない出力用ファイル名 + n := filepath.Base(n[:len(n)-len(filepath.Ext(n))]) + dir := filepath.Dir(path) + out, err := os.Create(filepath.Join(dir, n+"."+after)) + if err != nil { + return err + } + + err = image.Convert(origin, out, after) + if err != nil { + // 変換処理に失敗した場合、不要なファイルが作成されてしまうため、削除する + // ファイルを閉じた後でないと、Windowsの場合削除できないのでここでCloseする + out.Close() + e := os.Remove(filepath.Join(dir, n+"."+after)) + if e != nil { + return e + } + return err + } + } + + return nil + }) + + if err != nil { + return err + } + + return nil +} diff --git a/kadai2/annkara/pkg/dir/walker_test.go b/kadai2/annkara/pkg/dir/walker_test.go new file mode 100644 index 0000000..7770ff2 --- /dev/null +++ b/kadai2/annkara/pkg/dir/walker_test.go @@ -0,0 +1,41 @@ +package dir + +import "testing" + +var directory = "testdata" + +type testcase struct { + desc string + before string + after string + err bool +} + +func TestWalk(t *testing.T) { + + tests := []testcase{ + { + desc: "Succsess", + before: "jpg", + after: "png", + err: false, + }, { + desc: "Fail", + before: "jpg", + after: "gif", + err: true, + }, + } + + for _, tc := range tests { + testWalk(t, tc) + } +} + +func testWalk(t *testing.T, tc testcase) { + + err := Walk(directory, tc.before, tc.after) + if !(err != nil) == tc.err { + t.Errorf("failed test %s: %#v", tc.desc, err) + } +} diff --git a/kadai2/annkara/pkg/image/convertor.go b/kadai2/annkara/pkg/image/convertor.go new file mode 100644 index 0000000..1904ad6 --- /dev/null +++ b/kadai2/annkara/pkg/image/convertor.go @@ -0,0 +1,35 @@ +// Package image provides image convert function. +package image + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "io" +) + +// Convert is the function that converts an image format to another image format. +// origin is original file, target is converted image, and format is target image format. +func Convert(origin io.Reader, target io.Writer, format string) error { + + var err error + img, _, err := image.Decode(origin) + if err != nil { + return err + } + + switch format { + case "jpg", "jpeg": + err = jpeg.Encode(target, img, nil) + case "png": + err = png.Encode(target, img) + default: + err = fmt.Errorf("対応していないフォーマットです") + } + + if err != nil { + return err + } + return nil +} diff --git a/kadai2/annkara/pkg/image/convertor_test.go b/kadai2/annkara/pkg/image/convertor_test.go new file mode 100644 index 0000000..537ef19 --- /dev/null +++ b/kadai2/annkara/pkg/image/convertor_test.go @@ -0,0 +1,68 @@ +package image + +import ( + "os" + "path/filepath" + "testing" +) + +var directory = "testdata" + +type testcase struct { + desc string + origin string + target string + format string + err bool +} + +func TestConvert(t *testing.T) { + + tests := []testcase{ + { + desc: "jpg to png", + origin: filepath.Join(directory, "test.jpg"), + target: filepath.Join(directory, "test2.png"), + format: "png", + err: false, + }, { + desc: "png to jpg", + origin: filepath.Join(directory, "test.png"), + target: filepath.Join(directory, "test2.jpg"), + format: "jpg", + err: false, + }, { + desc: "Unknow format", + origin: filepath.Join(directory, "test.png"), + target: filepath.Join(directory, "test2.diff"), + format: "gif", + err: true, + }, + } + + for _, tc := range tests { + testConvert(t, tc) + } +} + +func testConvert(t *testing.T, tc testcase) { + in, err := os.Open(tc.origin) + if err != nil { + t.Fatalf("failed test %s: %#v", tc.desc, err) + } + defer in.Close() + + out, err := os.Create(tc.target) + if err != nil { + t.Fatalf("failed test %s: %#v", tc.desc, err) + } + // テスト用に生成されたファイルを削除 + defer os.Remove(tc.target) + defer out.Close() + + err = Convert(in, out, tc.format) + if !((err != nil) == tc.err) { + t.Fatalf("failed test %s: %#v", tc.desc, err) + } + +} diff --git a/kadai2/annkara/pkg/image/testdata/test.jpg b/kadai2/annkara/pkg/image/testdata/test.jpg new file mode 100644 index 0000000..02bfe5d Binary files /dev/null and b/kadai2/annkara/pkg/image/testdata/test.jpg differ diff --git a/kadai2/annkara/pkg/image/testdata/test.png b/kadai2/annkara/pkg/image/testdata/test.png new file mode 100644 index 0000000..834bf32 Binary files /dev/null and b/kadai2/annkara/pkg/image/testdata/test.png differ diff --git a/kadai3-1/annkara/README.md b/kadai3-1/annkara/README.md new file mode 100644 index 0000000..411237d --- /dev/null +++ b/kadai3-1/annkara/README.md @@ -0,0 +1,31 @@ +# 課題3-1 + +## タイピングゲームを作ろう + +- 標準出力に英単語を出す(出すものは自由) +- 標準入力から1行受け取る +- 制限時間内に何問解けたか表示する + +## ヒント + +- 制限時間にはtime.After関数を用いる + - context.WithTimeoutでもよい +- select構文を用いる + - 制限時間と入力を同時に待つ + +--- + +## gotyping + +gotyping はコマンドライン上で実行するタイピングゲームです。 + +## ゲームの開始方法 + +-limitsオプションで制限時間を指定できます。 +秒単位で制限時間を指定します。 +制限時間が指定されない場合には、デフォルトの60秒でゲームが開始されます。 + +```shell +# 制限時間100秒でゲーム開始 +gotyping -limits=100 +``` diff --git a/kadai3-1/annkara/cmd/gotyping/main.go b/kadai3-1/annkara/cmd/gotyping/main.go new file mode 100644 index 0000000..a428114 --- /dev/null +++ b/kadai3-1/annkara/cmd/gotyping/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "log" + "os" + + "github.com/dojo6/kadai3-1/annkara/pkg/gotyping" +) + +func main() { + + // 標準ロガーに日時などの情報を付加しない + log.SetFlags(0) + + var exitCode int + err := gotyping.Run(os.Stdout) + if err != nil { + log.Println(err) + exitCode = 1 + } + os.Exit(exitCode) +} diff --git a/kadai3-1/annkara/pkg/gotyping/gotyping.go b/kadai3-1/annkara/pkg/gotyping/gotyping.go new file mode 100644 index 0000000..f4b6239 --- /dev/null +++ b/kadai3-1/annkara/pkg/gotyping/gotyping.go @@ -0,0 +1,89 @@ +package gotyping + +import ( + "bufio" + "flag" + "fmt" + "io" + "math/rand" + "os" + "time" +) + +type gotyping struct { + outStream io.Writer + limits time.Duration + result int +} + +// Run the gotyping +func Run(outStream io.Writer) error { + + var l int + flag.IntVar(&l, "limits", 60, "制限時間") + flag.Parse() + + g := gotyping{ + outStream: outStream, + limits: time.Duration(l) * time.Second, + } + + err := g.start() + + if err != nil { + return err + } + + return nil +} + +type data struct { + in string + err error +} + +func (g *gotyping) start() error { + + rand.Seed(time.Now().Unix()) + fmt.Fprintln(g.outStream, "=== gotyping start ===") + + in := make(chan data) + go input(os.Stdin, in) + +END: + for { + question := word() + fmt.Fprintln(g.outStream, fmt.Sprintf("> %v", question)) + + select { + case <-time.After(g.limits): + fmt.Fprintln(g.outStream, "=== gotyping finish ===") + break END + case answer := <-in: + if answer.err != nil { + return answer.err + } + if answer.in == question { + g.result++ + } + } + } + + fmt.Fprintf(g.outStream, "Score: %v\n", g.result) + return nil +} + +func word() string { + words := []string{"go", "Java", "C", "ruby", "perl", "assembler"} + return words[rand.Intn(len(words))] +} + +func input(r io.Reader, in chan data) { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + in <- data{in: scanner.Text()} + } + if err := scanner.Err(); err != nil { + in <- data{err: err} + } +}