diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d3982c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +*.swp +build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9993f93 --- /dev/null +++ b/Makefile @@ -0,0 +1,92 @@ +.DEFAULT_GOAL := help + +GOCMD := env GO111MODULE=on go +GOMOD := $(GOCMD) mod +GOBUILD := $(GOCMD) build +GOINSTALL := $(GOCMD) install +GOCLEAN := $(GOCMD) clean +GOTEST := $(GOCMD) test +GOGET := $(GOCMD) get +NAME := dango +CURRENT := $(shell pwd) +BUILDDIR := ./build +BINDIR := $(BUILDDIR)/bin +PKGDIR := $(BUILDDIR)/pkg +DISTDIR := $(BUILDDIR)/dist + +VERSION := $(shell git describe --tags --abbrev=0) +LDFLAGS := -X 'main.version=$(VERSION)' +GOXOSARCH := "darwin/amd64 darwin/arm64 windows/386 windows/amd64 linux/386 linux/amd64" +GOXOUTPUT := "$(PKGDIR)/$(NAME)_{{.OS}}_{{.Arch}}/{{.Dir}}" + +export GO111MODULE=on + +.PHONY: deps +## Install dependencies +deps: + $(GOMOD) download + +.PHONY: devel-deps +## Install dependencies for develop +devel-deps: deps + sh -c '\ + tmpdir=$$(mktemp -d); \ + cd $$tmpdir; \ + $(GOGET) \ + golang.org/x/tools/cmd/goimports \ + golang.org/x/lint/golint \ + github.com/Songmu/make2help/cmd/make2help \ + github.com/mitchellh/gox \ + github.com/tcnksm/ghr; \ + rm -rf $$tmpdir' + +.PHONY: build +## Build binaries +build: deps + $(GOBUILD) -ldflags "$(LDFLAGS)" -o $(BINDIR)/$(NAME) . + +.PHONY: cross-build +## Cross build binaries +cross-build: + rm -rf $(PKGDIR) + gox -osarch=$(GOXOSARCH) -ldflags "$(LDFLAGS)" -output=$(GOXOUTPUT) . + +.PHONY: package +## Make package +package: cross-build + rm -rf $(DISTDIR) + mkdir $(DISTDIR) + pushd $(PKGDIR) > /dev/null && \ + for P in `ls | xargs basename`; do zip -r $(CURRENT)/$(DISTDIR)/$$P.zip $$P; done && \ + popd > /dev/null + +.PHONY: release +## Release package to Github +release: package + ghr $(VERSION) $(DISTDIR) + +.PHONY: test +## Run tests +test: deps + $(GOTEST) -v ./... + +.PHONY: lint +## Lint +lint: devel-deps + go vet ./... + golint -set_exit_status ./... + +.PHONY: fmt +## Format source codes +fmt: deps + find . -name "*.go" -not -path "./vendor/*" | xargs goimports -w + +.PHONY: clean +clean: + $(GOCLEAN) + rm -rf $(BUILDDIR) + +.PHONY: help +## Show help +help: + @make2help $(MAKEFILE_LIST) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bd90ce --- /dev/null +++ b/README.md @@ -0,0 +1,154 @@ +[English](README.md) | [ๆ—ฅๆœฌ่ชž](README_ja.md) + +# ๐Ÿก dango + +๐Ÿก`dango` is a program that concatenates and splits standard input. + +## Description + +`dango` concatenates and splits the standard input. + +`dango` concatenates and splits standard input by bytes, characters, words, or line breaks. +`dango` reads the standard input and whenever it reads a specified number of elements, it puts them on a line and prints them to the standard output. + +## Usage + +``` +$ cat << EOF | dango +โ” +๐ŸŸ  +๐ŸŸก +๐ŸŸข +โ” +EOF +โ”๐ŸŸ ๐ŸŸก๐ŸŸขโ” +``` + +Concatenate or split by number of bytes. + +``` +$ echo 'Hello World!' | dango -b -n 1 +H +e +l +l +o + +W +o +r +l +d +! + +$ echo 'Hello World!' | dango -b -n 5 +Hello + Worl +d! +``` + +Concatenate or split by number of characters. + +``` +$ echo '่Šฑใ‚ˆใ‚Šๅ›ฃๅญ' | dango -c -n 1 +่Šฑ +ใ‚ˆ +ใ‚Š +ๅ›ฃ +ๅญ + +$ echo '่Šฑใ‚ˆใ‚Šๅ›ฃๅญ' | dango -c -n 3 +่Šฑใ‚ˆใ‚Š +ๅ›ฃๅญ +``` + +Concatenate or split by word count. + +``` +$ echo 'Hello World!' | dango -w -n 1 +Hello +World! + +$ echo 'Hello World!' | dango -w +HelloWorld! +``` + +Concatenate or split by the number of line breaks. + +``` +$ cat << EOF | dango -l -n 1 +Hello +World! +EOF +Hello +World! + +$ cat << EOF | dango -l +Hello + +World + +! +EOF +HelloWorld! +``` + +Specify a delimiter to concatenate or split. + +``` +$ echo 'abcdefg' | dango -c -d ' ' +a b c d e f g + +$ cat << EOF | dango -d ' ' +Hello +World! +EOF +Hello World! +``` + +Show help. + +``` +$ dango -help +# ... +``` + +## Installation + +### Developer + +Go 1.16 or later. + +``` +$ go install github.com/ebc-2in2crc/dango@latest +``` + +Go 1.15. + +``` +$ go get github.com/ebc-2in2crc/dango/... +``` + +### User + +Download from the following url. + +- [https://github.com/ebc-2in2crc/dango/releases](https://github.com/ebc-2in2crc/dango/releases) + +## Contribution + +1. Fork this repository +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Add some feature') +4. Rebase your local changes against the master branch +5. Run test suite with the `make test` command and confirm that it passes +6. Run `make fmt` +7. Create new Pull Request + +## License + +[MIT](https://github.com/ebc-2in2crc/wareki/blob/master/LICENSE) + +## Author + +[ebc-2in2crc](https://github.com/ebc-2in2crc) diff --git a/README_ja.md b/README_ja.md new file mode 100644 index 0000000..7182df0 --- /dev/null +++ b/README_ja.md @@ -0,0 +1,154 @@ +[English](README.md) | [ๆ—ฅๆœฌ่ชž](README_ja.md) + +# ๐Ÿก dango + +๐Ÿก `dango` ใฏๆจ™ๆบ–ๅ…ฅๅŠ›ใ‚’้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ™ใ‚‹ใƒ—ใƒญใ‚ฐใƒฉใƒ ใงใ™ใ€‚ + +## Description + +`dango` ใฏๆจ™ๆบ–ๅ…ฅๅŠ›ใ‚’้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ—ใพใ™ใ€‚ + +`dango` ใฏๆจ™ๆบ–ๅ…ฅๅŠ›ใ‚’ใƒใ‚คใƒˆใ€ๆ–‡ๅญ—ใ€ๅ˜่ชžใ‚ใ‚‹ใ„ใฏๆ”น่กŒใซใ‚ˆใฃใฆ้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ—ใพใ™ใ€‚ +`dango` ใฏๆจ™ๆบ–ๅ…ฅๅŠ›ใ‚’่ชญใฟๅ–ใฃใฆใ€่ชญใฟๅ–ใฃใŸ่ฆ็ด ใŒๆŒ‡ๅฎšใ—ใŸๆ•ฐใซใชใ‚‹ใ”ใจใซใใ‚Œใ‚’1่กŒใซใพใจใ‚ใฆใ€ๆจ™ๆบ–ๅ‡บๅŠ›ใซๅ‡บๅŠ›ใ—ใพใ™ใ€‚ + +## Usage + +``` +$ cat << EOF | dango +โ” +๐ŸŸ  +๐ŸŸก +๐ŸŸข +โ” +EOF +โ”๐ŸŸ ๐ŸŸก๐ŸŸขโ” +``` + +ใƒใ‚คใƒˆๆ•ฐใง้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ™ใ‚‹ใ€‚ + +``` +$ echo 'Hello World!' | dango -b -n 1 +H +e +l +l +o + +W +o +r +l +d +! + +$ echo 'Hello World!' | dango -b -n 5 +Hello + Worl +d! +``` + +ๆ–‡ๅญ—ๆ•ฐใง้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ™ใ‚‹ใ€‚ + +``` +$ echo '่Šฑใ‚ˆใ‚Šๅ›ฃๅญ' | dango -c -n 1 +่Šฑ +ใ‚ˆ +ใ‚Š +ๅ›ฃ +ๅญ + +$ echo '่Šฑใ‚ˆใ‚Šๅ›ฃๅญ' | dango -c -n 3 +่Šฑใ‚ˆใ‚Š +ๅ›ฃๅญ +``` + +ๅ˜่ชžๆ•ฐใง้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ™ใ‚‹ใ€‚ + +``` +$ echo 'Hello World!' | dango -w -n 1 +Hello +World! + +$ echo 'Hello World!' | dango -w +HelloWorld! +``` + +ๆ”น่กŒใฎๆ•ฐใง้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ™ใ‚‹ใ€‚ + +``` +$ cat << EOF | dango -l -n 1 +Hello +World! +EOF +Hello +World! + +$ cat << EOF | dango -l +Hello + +World + +! +EOF +HelloWorld! +``` + +ใƒ‡ใƒชใƒŸใ‚ฟใƒผใ‚’ๆŒ‡ๅฎšใ—ใฆ้€ฃ็ตใ—ใŸใ‚Šๅˆ†ๅ‰ฒใ™ใ‚‹ใ€‚ + +``` +$ echo 'abcdefg' | dango -c -d ' ' +a b c d e f g + +$ cat << EOF | dango -d ' ' +Hello +World! +EOF +Hello World! +``` + +ใƒ˜ใƒซใƒ—ใ€‚ + +``` +$ dango -help +# ... +``` + +## Installation + +### Developer + +Go 1.16 ไปฅ้™ใ€‚ + +``` +$ go install github.com/ebc-2in2crc/dango@latest +``` + +Go 1.15ใ€‚ + +``` +$ go get github.com/ebc-2in2crc/dango/... +``` + +### User + +ๆฌกใฎ URL ใ‹ใ‚‰ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ—ใพใ™ใ€‚ + +- [https://github.com/ebc-2in2crc/dango/releases](https://github.com/ebc-2in2crc/dango/releases) + +## Contribution + +1. Fork this repository +2. Create your feature branch (git checkout -b my-new-feature) +3. Commit your changes (git commit -am 'Add some feature') +4. Rebase your local changes against the main branch +5. Run test suite with the `make test` command and confirm that it passes +6. Run `make fmt` +7. Create new Pull Request + +## License + +[MIT](https://github.com/ebc-2in2crc/dango/blob/main/LICENSE) + +## Author + +[ebc-2in2crc](https://github.com/ebc-2in2crc) diff --git a/cli.go b/cli.go new file mode 100644 index 0000000..c14629b --- /dev/null +++ b/cli.go @@ -0,0 +1,100 @@ +package main + +import ( + "flag" + "fmt" + "io" + "os" +) + +type cli struct { + inStream io.Reader + outStream, errStream io.Writer +} + +type dangoOptions = struct { + size int + delimiter string + maxBufferSize int + lines bool + words bool + characters bool + bytes bool + version bool + help bool +} + +var options dangoOptions + +func init() { + flag.IntVar(&options.maxBufferSize, "B", 0, "Maximum size used to buffer a token") + flag.BoolVar(&options.bytes, "b", false, "Concat or split standard input by bytes") + flag.BoolVar(&options.characters, "c", false, "Concat or split standard input by characters") + flag.StringVar(&options.delimiter, "d", "", "Element delimiter (default \"\")") + flag.BoolVar(&options.lines, "l", false, "Concat or split standard input by lines") + flag.IntVar(&options.size, "n", 0, "Number of elements in each line") + flag.BoolVar(&options.version, "version", false, "Show version") + flag.BoolVar(&options.help, "help", false, "Show this help message") + flag.BoolVar(&options.words, "w", false, "Concat or split standard input by words") + + flag.Usage = func() { + fmt.Printf(`NAME: + dango - concat or split standard input + +USAGE: + dango [options] + +DESCRIPTION: + dango concat or split standard input. + + e.g. + $ cat << EOF | dango -l -n 2 + first + second + EOF + firstsecond + + $ cat << EOF | dango -w + first second + EOF + first + second + +OPTIONS: +`) + flag.PrintDefaults() + } +} + +func (c *cli) run(args []string) int { + c.parseFlag(args) + + if options.version { + _, _ = fmt.Fprintf(c.outStream, "dango version %s\n", version) + return 0 + } + if options.help { + flag.Usage() + return 0 + } + + d := dango{ + reader: c.inStream, + writer: c.outStream, + delimiter: []byte(options.delimiter), + } + err := d.run() + if err != nil { + msg := fmt.Sprintf("%+v\n", err) + _, _ = c.errStream.Write([]byte(msg)) + return 1 + } + + return 0 +} + +func (c *cli) parseFlag(args []string) { + os.Args = args + options = dangoOptions{} + flag.Parse() +} diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..d0ed9c8 --- /dev/null +++ b/cli_test.go @@ -0,0 +1,105 @@ +package main + +import ( + "bytes" + "strings" + "testing" +) + +type param struct { + args string + stdin string + exitCode int + stdout string + stderr string +} + +func TestSplitLines(t *testing.T) { + params := []param{ + {args: "-l -n 1", stdin: "first\nsecond\n", exitCode: 0, stdout: "first\nsecond\n"}, + {args: "-l -n 2", stdin: "first\nsecond\n", exitCode: 0, stdout: "firstsecond\n"}, + {args: "-l -n 2", stdin: "first\n\nsecond\n", exitCode: 0, stdout: "first\nsecond\n"}, + {args: "-l -n 2 -d _", stdin: "first\nsecond\n", exitCode: 0, stdout: "first_second\n"}, + {args: "-l -n 3", stdin: "first\nsecond\nthird\n", exitCode: 0, stdout: "firstsecondthird\n"}, + {args: "-l -n 3", stdin: "first\n\nsecond\n\nthird\n", exitCode: 0, stdout: "firstsecond\nthird\n"}, + } + + testRun(t, params) +} + +func testRun(t *testing.T, params []param) { + for _, p := range params { + c, outStream, errStream := newCLI(p.stdin) + args := strings.Split(" "+p.args, " ") + + exitCode := c.run(args) + + if exitCode != p.exitCode { + t.Errorf("run(%s): Output = %q; want %q", args, exitCode, p.exitCode) + } + if outStream.String() != p.stdout { + t.Errorf("run(%s): Output = %q; want %q", args, outStream.String(), p.stdout) + } + if errStream.String() != p.stderr { + t.Errorf("run(%s): Err output = %q; want %q", args, errStream.String(), p.stderr) + } + } +} + +func newCLI(stdin string) (c cli, outStream, errStream *bytes.Buffer) { + inStream := bytes.NewBufferString(stdin) + outStream = bytes.NewBuffer(nil) + errStream = bytes.NewBuffer(nil) + c = cli{inStream: inStream, outStream: outStream, errStream: errStream} + return +} + +func TestSplitWords(t *testing.T) { + params := []param{ + {args: "-w -n 1", stdin: "first\nsecond\n", exitCode: 0, stdout: "first\nsecond\n"}, + {args: "-w -n 1", stdin: "first second\n", exitCode: 0, stdout: "first\nsecond\n"}, + {args: "-w -n 2", stdin: "first\nsecond\n", exitCode: 0, stdout: "firstsecond\n"}, + {args: "-w -n 2", stdin: "first\n\nsecond\n", exitCode: 0, stdout: "firstsecond\n"}, + {args: "-w -n 2 -d _", stdin: "first\nsecond\n", exitCode: 0, stdout: "first_second\n"}, + {args: "-w -n 3", stdin: "first\nsecond\nthird\n", exitCode: 0, stdout: "firstsecondthird\n"}, + {args: "-w -n 3", stdin: "first\n\nsecond\n\nthird\n", exitCode: 0, stdout: "firstsecondthird\n"}, + } + + testRun(t, params) +} + +func TestSplitCharacters(t *testing.T) { + params := []param{ + {args: "-c -n 1", stdin: "12\n", exitCode: 0, stdout: "1\n2\n\n\n"}, + {args: "-c -n 2", stdin: "1\n2\n", exitCode: 0, stdout: "1\n\n2\n\n"}, + {args: "-c -n 2", stdin: "1234\n", exitCode: 0, stdout: "12\n34\n\n\n"}, + {args: "-c -n 2", stdin: "12\n\n34\n", exitCode: 0, stdout: "12\n\n\n\n34\n\n\n"}, + {args: "-c -n 2 -d _", stdin: "1234\n", exitCode: 0, stdout: "1_2\n3_4\n\n\n"}, + {args: "-c -n 2", stdin: "๏ผ‘\n๏ผ’\n", exitCode: 0, stdout: "๏ผ‘\n\n๏ผ’\n\n"}, + {args: "-c -n 3", stdin: "๏ผ‘\n๏ผ’\n", exitCode: 0, stdout: "๏ผ‘\n๏ผ’\n\n\n"}, + } + + testRun(t, params) +} + +func TestSplitBytes(t *testing.T) { + params := []param{ + {args: "-b -n 1", stdin: "12\n", exitCode: 0, stdout: "1\n2\n\n\n"}, + {args: "-b -n 2", stdin: "12\n", exitCode: 0, stdout: "12\n\n\n"}, + {args: "-b -n 2", stdin: "1\n2\n", exitCode: 0, stdout: "1\n\n2\n\n"}, + {args: "-b -n 2", stdin: "1234\n", exitCode: 0, stdout: "12\n34\n\n\n"}, + {args: "-b -n 2", stdin: "12\n\n34\n", exitCode: 0, stdout: "12\n\n\n\n34\n\n\n"}, + {args: "-b -n 2 -d _", stdin: "1234\n", exitCode: 0, stdout: "1_2\n3_4\n\n\n"}, + } + + testRun(t, params) +} + +func TestBufferSize(t *testing.T) { + params := []param{ + {args: "-l -n 1 -B 1", stdin: "a\n", exitCode: 1, stdout: "", stderr: "failed to scan stdin: bufio.Scanner: token too long\n"}, + {args: "-l -n 1 -B 2", stdin: "a\n", exitCode: 0, stdout: "a\n", stderr: ""}, + } + + testRun(t, params) +} diff --git a/dango.go b/dango.go new file mode 100644 index 0000000..a6b654c --- /dev/null +++ b/dango.go @@ -0,0 +1,113 @@ +package main + +import ( + "bufio" + "fmt" + "io" + + "golang.org/x/xerrors" +) + +const ( + startBufSize = 4096 // Size of initial allocation for buffer. +) + +type dango struct { + reader io.Reader + writer io.Writer + delimiter []byte +} + +func (d *dango) run() error { + switch { + case options.bytes: + return d.splitBytes() + case options.lines: + return d.splitLines() + case options.words: + return d.splitWords() + case options.characters: + return d.splitCharacters() + default: + return d.splitLines() + } +} + +func (d *dango) splitBytes() error { + return d.joinTokens(bufio.ScanBytes) +} + +func (d *dango) splitWords() error { + return d.joinTokens(bufio.ScanWords) +} + +func (d *dango) joinTokens(splitFunc bufio.SplitFunc) error { + scanner := bufio.NewScanner(d.reader) + scanner.Split(splitFunc) + if options.maxBufferSize > 0 { + size := startBufSize + if size > options.maxBufferSize { + size = options.maxBufferSize + } + b := make([]byte, size) + scanner.Buffer(b, options.maxBufferSize) + } + + limit := options.size + count := 0 + for scanner.Scan() { + t := scanner.Bytes() + count++ + + if err := d.addDelimiter(count, t); err != nil { + return fmt.Errorf("failed to add delimiter: %w", err) + } + + _, err := d.writer.Write(t) + if err != nil { + return fmt.Errorf("failed to write to stdout: %w", err) + } + if limit > 0 && count >= limit { + _, err := d.writer.Write([]byte("\n")) + if err != nil { + return xerrors.Errorf("failed write LF: %w", err) + } + count = 0 + } + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("failed to scan stdin: %w", err) + } + + if count != 0 && count != limit { + _, err := d.writer.Write([]byte("\n")) + if err != nil { + return xerrors.Errorf("failed write LF: %w", err) + } + } + + return nil +} + +func (d *dango) addDelimiter(count int, t []byte) error { + if count <= 1 { + return nil + } + if len(t) == 1 && t[0] == '\n' { + return nil + } + + _, err := d.writer.Write(d.delimiter) + if err != nil { + return fmt.Errorf("failed to write delimiter to stdout: %w", err) + } + return nil +} + +func (d *dango) splitLines() error { + return d.joinTokens(bufio.ScanLines) +} + +func (d *dango) splitCharacters() error { + return d.joinTokens(bufio.ScanRunes) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9bb6242 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/ebc-2in2crc/dango + +go 1.15 + +require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1ff365f --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..772edd2 --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +import "os" + +var version = "0.0.1" + +func main() { + c := cli{inStream: os.Stdin, outStream: os.Stdout, errStream: os.Stderr} + c.run(os.Args) +} diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..b19c98d --- /dev/null +++ b/todo.md @@ -0,0 +1,14 @@ +# ToDo + +## ใ‚„ใ‚‹ใ“ใจ + +- [x] `d.writer.Write([]byte("\n"))` ใจใ‹ใฎใ‚จใƒฉใƒผใ‚’ใƒใƒณใƒ‰ใƒซใ™ใ‚‹ +- [ ] ใƒ‡ใƒใƒƒใ‚ฐใƒญใ‚ฐใ‚’ๆจ™ๆบ–ใ‚จใƒฉใƒผใซๅ‡บใ™ใจใ‹๏ผŸ (ใ‚ชใƒ—ใ‚ทใƒงใƒณใง) +- [ ] CI ใ‚’ใฉใ†ใ™ใ‚‹ใ‹่€ƒใˆใ‚‹ +- [ ] ใƒชใƒชใƒผใ‚นใฎใ‚„ใ‚Šๆ–นใ‚’่€ƒใˆใ‚‹ใ€‚ใจใ‚Šใ‚ใˆใš Makefile ใงใ„ใคใ‹ GHA ใฎใŒ็„ก้›ฃใใ† +- [x] ่‹ฑ่ชžใฎ README ใ‚’ๆ›ธใ +- [ ] ใƒใƒƒใ‚ธใ‚’ README ใซ้…็ฝฎใ™ใ‚‹ +- [ ] Dockerfile ใ‚’ไฝœใ‚‹ +- [ ] โ†‘ README ใซๆ›ธใ +- [ ] brew ใฎ Formula ใ‚’ๆ›ธใ +- [ ] โ†‘ใ‚’ push ใ™ใ‚‹ \ No newline at end of file