-
Notifications
You must be signed in to change notification settings - Fork 17
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
Kadai2 akuchii #28
base: master
Are you sure you want to change the base?
Kadai2 akuchii #28
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# io.Readerとio.Writerについて調べてみよう | ||
|
||
## 標準パッケージでどのように使われているか | ||
os.File, os.Stdin, os.Stdout, bytes.buffersなどデータの読み書きを行う処理の部分に使われている | ||
|
||
## io.Readerとio.Writerがあることでどういう利点があるのか具体例を挙げて考えてみる | ||
例えばデータを読み書きする処理があったときに、io.Reader, io.Writerがあることで使う側は内部実装を知らなくても使うことができる。 | ||
内部実装を知らずに使えるので、読み書き先をDBからjson, csvなどに切り替えたい時も容易に行うことができる。 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// Package converter converts image extension to target extension | ||
package converter | ||
|
||
import ( | ||
"fmt" | ||
"image" | ||
"image/gif" | ||
"image/jpeg" | ||
"image/png" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
var supportedExts = map[string]bool{ | ||
"png": true, | ||
"jpeg": true, | ||
"jpg": true, | ||
"gif": true, | ||
} | ||
|
||
// Encoder encodes image | ||
type Encoder interface { | ||
Encode(io.Writer, image.Image) error | ||
} | ||
|
||
// Converter converts image to other format of afterExt | ||
type Converter struct { | ||
path string | ||
outDir string | ||
afterExt string | ||
encoder Encoder | ||
} | ||
|
||
// JpegEncoder encodes image to jpeg format | ||
type JpegEncoder struct { | ||
} | ||
|
||
// PngEncoder encodes image to png format | ||
type PngEncoder struct { | ||
} | ||
|
||
// GifEncoder encodes image to gif format | ||
type GifEncoder struct { | ||
} | ||
|
||
// Encode encodes to jpeg | ||
func (c *JpegEncoder) Encode(w io.Writer, img image.Image) error { | ||
return jpeg.Encode(w, img, nil) | ||
} | ||
|
||
// Encode encodes to png | ||
func (c *PngEncoder) Encode(w io.Writer, img image.Image) error { | ||
return png.Encode(w, img) | ||
} | ||
|
||
// Encode encodes to gif | ||
func (c *GifEncoder) Encode(w io.Writer, img image.Image) error { | ||
return gif.Encode(w, img, nil) | ||
} | ||
|
||
// Encode encodes to specific format | ||
func (c *Converter) Encode(w io.Writer, img image.Image) error { | ||
return c.encoder.Encode(w, img) | ||
} | ||
|
||
// Decode decodes input image | ||
func (c *Converter) Decode(r io.Reader) (image.Image, error) { | ||
img, _, err := image.Decode(r) | ||
return img, err | ||
} | ||
|
||
// NewConverter creates new converter instance | ||
func NewConverter(path, outDir, afterExt string) (*Converter, error) { | ||
var encoder Encoder | ||
switch afterExt { | ||
case "jpg", "jpeg": | ||
encoder = &JpegEncoder{} | ||
case "png": | ||
encoder = &PngEncoder{} | ||
case "gif": | ||
encoder = &GifEncoder{} | ||
} | ||
return &Converter{path, outDir, afterExt, encoder}, nil | ||
} | ||
|
||
// Execute reads file from path, convert file extension to afterExt and save it in outDir | ||
func (c *Converter) Execute() error { | ||
if _, ok := supportedExts[c.afterExt]; !ok { | ||
return fmt.Errorf("%s is not supported ext", c.afterExt) | ||
} | ||
|
||
in, err := os.Open(c.path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer in.Close() | ||
|
||
img, err := c.Decode(in) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = c.createDstDir() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
dstPath, err := c.getDstPath() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
out, err := os.Create(dstPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer out.Close() | ||
|
||
err = c.Encode(out, img) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (c *Converter) getDstDir() (string, error) { | ||
srcAbs, err := filepath.Abs(c.path) | ||
if err != nil { | ||
return "", err | ||
} | ||
return filepath.Join(filepath.Dir(srcAbs), c.outDir), nil | ||
} | ||
|
||
func (c *Converter) createDstDir() error { | ||
dstDir, err := c.getDstDir() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if _, err := os.Stat(dstDir); os.IsNotExist(err) { | ||
err = os.MkdirAll(dstDir, os.ModePerm) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (c *Converter) getDstPath() (string, error) { | ||
filenameWithoutExt := filepath.Base(c.path[:len(c.path)-len(filepath.Ext(c.path))]) | ||
dstDir, err := c.getDstDir() | ||
if err != nil { | ||
return "", err | ||
} | ||
dstPath := filepath.Join(dstDir, filenameWithoutExt) + "." + c.afterExt | ||
return dstPath, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package converter_test | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/gopherdojo/dojo4/kadai2/akuchii/converter" | ||
) | ||
|
||
func TestConvert_NewConverter(t *testing.T) { | ||
cases := []struct { | ||
afterExt string | ||
expectedEncoder reflect.Type | ||
}{ | ||
{"png", reflect.ValueOf(&converter.PngEncoder{}).Type()}, | ||
{"jpeg", reflect.ValueOf(&converter.JpegEncoder{}).Type()}, | ||
{"gif", reflect.ValueOf(&converter.GifEncoder{}).Type()}, | ||
} | ||
for _, c := range cases { | ||
conv, err := converter.NewConverter("", "", c.afterExt) | ||
if err != nil { | ||
t.Fatalf("failed to crete new converter:%s", err) | ||
} | ||
if reflect.ValueOf(conv.ExportEncoder()).Type() != c.expectedEncoder { | ||
t.Fatalf("invalid encoder") | ||
} | ||
} | ||
} | ||
|
||
func TestConverter_Execute(t *testing.T) { | ||
cases := []struct { | ||
target string | ||
outDir string | ||
afterExt string | ||
hasError bool | ||
}{ | ||
{"test.png", "out", "jpeg", false}, | ||
{"test.jpeg", "out", "gif", false}, | ||
{"test.gif", "out", "png", false}, | ||
{"test.webp", "out", "png", true}, | ||
{"test.png", "out", "webp", true}, | ||
{"not_exist_file.png", "out", "jpeg", true}, | ||
} | ||
|
||
for _, c := range cases { | ||
srcPath := filepath.Join("..", "testdata", c.target) | ||
filenameWithoutExt := testGetFileNameWithoutExt(t, c.target) | ||
dstPath := filepath.Join("..", "testdata", c.outDir, filenameWithoutExt) + "." + c.afterExt | ||
conv, err := converter.NewConverter(srcPath, c.outDir, c.afterExt) | ||
if err != nil { | ||
t.Fatalf("fail to create converter: %s", err) | ||
} | ||
err = conv.Execute() | ||
if err != nil && !c.hasError { | ||
t.Fatalf("fail to convert image: %s", err) | ||
} | ||
if _, err := os.Stat(dstPath); os.IsNotExist(err) && !c.hasError { | ||
t.Fatalf("fail to generate converted image: %s", err) | ||
} | ||
if !c.hasError { | ||
os.Remove(dstPath) | ||
} | ||
} | ||
} | ||
|
||
func testGetFileNameWithoutExt(t *testing.T, target string) string { | ||
t.Helper() | ||
|
||
return target[:len(target)-len(filepath.Ext(target))] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package converter | ||
|
||
func (c *Converter) ExportEncoder() Encoder { | ||
return c.encoder | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/gopherdojo/dojo4/kadai2/akuchii/converter" | ||
) | ||
|
||
// extension before convert | ||
var beforeExt string | ||
|
||
// extension after convert | ||
var afterExt string | ||
|
||
// source directory | ||
var srcDir string | ||
|
||
// destination directory | ||
var dstDir string | ||
|
||
// exit code | ||
const ( | ||
ExitCodeOK int = iota // 0 | ||
ExitCodeError // 1 | ||
) | ||
|
||
func init() { | ||
flag.StringVar(&beforeExt, "b", "jpeg", "extension before convert") | ||
flag.StringVar(&afterExt, "a", "png", "extension before convert") | ||
flag.StringVar(&srcDir, "s", "", "source directory to convert files") | ||
flag.StringVar(&dstDir, "d", "out", "destination directory to convert files") | ||
} | ||
|
||
func main() { | ||
flag.Parse() | ||
|
||
err := validateArgs() | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(ExitCodeError) | ||
} | ||
|
||
fmt.Printf("start to convert ext before: %s, after: %s\n", beforeExt, afterExt) | ||
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { | ||
if filepath.Ext(path) == "."+beforeExt { | ||
conv, err := converter.NewConverter(path, dstDir, afterExt) | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(ExitCodeError) | ||
} | ||
|
||
err = conv.Execute() | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(ExitCodeError) | ||
} | ||
fmt.Printf("%s is converted to %s\n", path, afterExt) | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
fmt.Println(err) | ||
os.Exit(ExitCodeError) | ||
} | ||
fmt.Println("convert is finished") | ||
os.Exit(ExitCodeOK) | ||
} | ||
|
||
// validateArgs validates input arguments | ||
func validateArgs() error { | ||
if beforeExt == "" || afterExt == "" || srcDir == "" || dstDir == "" { | ||
flag.PrintDefaults() | ||
return errors.New("empty arg is not allowed") | ||
} | ||
|
||
if _, err := os.Stat(srcDir); os.IsNotExist(err) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これだと画像ファイルは存在するけど読込できないパターン(例えば壊れてるとかパーミッションが無いとか)に対応できなさそう? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 確認してみます! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
指定された |
||
return err | ||
} | ||
|
||
if beforeExt == afterExt { | ||
return fmt.Errorf("before and after ext is same: %s", beforeExt) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このパッケージ名にした理由が気になります。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://tech.mercari.com/entry/2018/08/08/080000
の記事を参考にして、
converter
ではなくconverter_test
にしました。converter
と別パッケージにすることでパッケージを使う人と同じ状況(exportされたものしかアクセスされない)でテストを書くことができるのが良い、という理解です。見直してたらpackage名と変数名が被ってたので変数名変えましたa8e7884