Skip to content

hokita / 課題1 #5

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions kadai1/hokita/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 課題 1 画像変換コマンドを作ろう

## 課題内容
### 次の仕様を満たすコマンドを作って下さい

- ディレクトリを指定する
- 指定したディレクトリ以下の JPG ファイルを PNG に変換(デフォルト)
- ディレクトリ以下は再帰的に処理する
- 変換前と変換後の画像形式を指定できる(オプション)

### 以下を満たすように開発してください

- main パッケージと分離する
- 自作パッケージと標準パッケージと準標準パッケージのみ使う
- 準標準パッケージ:golang.org/x 以下のパッケージ
- ユーザ定義型を作ってみる
- GoDoc を生成してみる
- Go Modules を使ってみる

## 対応したこと
- 画像を変換
- 現状はjpg, pngのみ
- jpg, png以外はエラー表示
- 画像出力先は対象画像と同じディレクトリ
- 指定したディレクトリが無いとエラーを表示

## 動作
```shell
$ go build -o test_imgconv

$ ./test_imgconv -h
Usage of ./test_imgconv:
-from string
Conversion source extension. (default "jpg")
-to string
Conversion target extension. (default "png")

# testdata内のすべてのjpgファイルをpngに変換する
$ ./test_imgconv testdata
Conversion finished!

# testdata内のすべてのpngファイルをjpgに変換する
$ ./test_imgconv -from png -to jpg testdata
Conversion finished!

# ディレクトリの指定が無い場合はエラー
$ ./test_imgconv
Please specify a directory.

# 存在しないディレクトリの場合はエラー
$ ./test_imgconv non_exist_dir
Cannot find directory.

# 対応していない拡張子の場合はエラー
$ ./test_imgconv -from txt -to jpg testdata
Selected extension is not supported.
```

## 工夫したこと
- png, jpg以外にも拡張子が増えそうなので、`image_type`というinterfaceを作ってみた。
- 拡張子の微妙な違い(jpg, jpeg, JPGなど)にも対応できるようにした。

## わからなかったこと、むずかしかったこと
- go mod initで指定するmodule名に命名規則があるのか。
- 普段オブジェクト指向(その上動的型付け言語)で書いているので、それがgoらしいコードになっているのか不安。
- なんでもかんでも構造体メソッドにしたい願望がでてくる
3 changes: 3 additions & 0 deletions kadai1/hokita/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module imgconv

go 1.14
Empty file added kadai1/hokita/go.sum
Empty file.
77 changes: 77 additions & 0 deletions kadai1/hokita/imgconv/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package imgconv

import (
"errors"
"image"
"os"
"path/filepath"
)

func newConverter(from, to string) (*Converter, error) {
fromImage, err := selectImage("." + from)
if err != nil {
return nil, err
}

toImage, err := selectImage("." + to)
if err != nil {
return nil, err
}

return &Converter{fromImage, toImage}, nil
}

var ErrUnmatchExt = errors.New("ext does not match")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsNotMatchExt関数が公開されているのでこちらは公開しないほうが良いです。


func IsNotMatchExt(err error) bool {
return errors.Is(err, ErrUnmatchExt)
}

type Converter struct {
fromImage ImageType
toImage ImageType
}

func (conv *Converter) Execute(path string) (rerr error) {
// ignore unrelated file
if !conv.fromImage.IsMatchExt(filepath.Ext(path)) {
return ErrUnmatchExt
}

// file open
file, err := os.Open(path)
defer file.Close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer Closeはエラー処理のあと

if err != nil {
return err
}

// convert to image obj
img, _, err := image.Decode(file)
if err != nil {
return err
}

// output file
out, err := os.Create(conv.SwitchExt(path))
defer func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer Closeはエラー処理のあと

if err := out.Close(); err != nil {
rerr = err
}
}()
if err != nil {
return err
}

// output image
if err := conv.toImage.Encode(out, img); err != nil {
return err
}
return nil
}

func (conv *Converter) SwitchExt(path string) string {
ext := filepath.Ext(path)
toExt := conv.toImage.GetMainExt()

return path[:len(path)-len(ext)] + toExt
}
26 changes: 26 additions & 0 deletions kadai1/hokita/imgconv/image_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package imgconv

import (
"errors"
"image"
"io"
)

type ImageType interface {
Encode(w io.Writer, m image.Image) error
IsMatchExt(ext string) bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hasとかぐらいでいい気もしますね。
pngImage.Has(ext)とかになると思うので。
長くてHasExtとか。

GetMainExt() string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらも Ext で十分かなと思います。

}

func selectImage(ext string) (ImageType, error) {
pngImage := PngImage{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

フィールドの初期化を伴わない構造体リテラル(コンポジットリテラル)での初期化は不要です。
構造体もゼロ値で初期化されるため、var pngImg PngImageのように変数を定義されば十分です。

jpegImage := JpegImage{}

if pngImage.IsMatchExt(ext) {
return pngImage, nil
} else if jpegImage.IsMatchExt(ext) {
return jpegImage, nil
}

return nil, errors.New("Selected extension is not supported.")
}
36 changes: 36 additions & 0 deletions kadai1/hokita/imgconv/imgconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package imgconv

import (
"errors"
"os"
"path/filepath"
)

func Call(dir, from, to string) error {
if dir == "" {
return errors.New("Please specify a directory.")
}

if f, err := os.Stat(dir); os.IsNotExist(err) || !f.IsDir() {
return errors.New("Cannot find directory.")
}

converter, err := newConverter(from, to)
if err != nil {
return err
}

err = filepath.Walk(dir,
func(path string, info os.FileInfo, err error) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

引数のエラーのエラー処理。

err = converter.Execute(path)
if !IsNotMatchExt(err) {
return err
}
return nil
})
if err != nil {
return err
}

return nil
}
31 changes: 31 additions & 0 deletions kadai1/hokita/imgconv/jpeg_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package imgconv

import (
"image"
"image/jpeg"
"io"
)

const QUALITY = 100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

定数であってもキャメルケースで書きます。
先頭が大文字だとパッケージ外に公開されます。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPEGのQUALITYっていうのが分かるようにしたほうが良さそうですね。


type JpegImage struct{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPEGで十分だと思います。Goでは略語はすべて大文字か全部小文字にします。


var jpegExt = map[string]bool{
".jpg": true,
".jpeg": true,
".JPG": true,
".JPEG": true,
}

func (JpegImage) Encode(w io.Writer, m image.Image) error {
err := jpeg.Encode(w, m, &jpeg.Options{Quality: QUALITY})
return err
}

func (ji JpegImage) IsMatchExt(ext string) bool {
return jpegExt[ext]
}

func (JpegImage) GetMainExt() string {
return ".jpg"
}
27 changes: 27 additions & 0 deletions kadai1/hokita/imgconv/png_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package imgconv

import (
"image"
"image/png"
"io"
)

type PngImage struct{}

var pngExt = map[string]bool{
".png": true,
".PNG": true,
}

func (PngImage) Encode(w io.Writer, m image.Image) error {
err := png.Encode(w, m)
return err
}

func (pi PngImage) IsMatchExt(ext string) bool {
return pngExt[ext]
}

func (PngImage) GetMainExt() string {
return ".png"
}
39 changes: 39 additions & 0 deletions kadai1/hokita/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"flag"
"fmt"
"imgconv/imgconv"
"os"
)

const (
ExitCodeOk = 0
ExitCodeError = 1
)

var from, to string

func init() {
flag.StringVar(&from, "from", "jpg", "Conversion source extension.")
flag.StringVar(&to, "to", "png", "Conversion target extension.")
}

func main() {
os.Exit(run())
}

func run() int {
flag.Parse()
dir := flag.Arg(0)

err := imgconv.Call(dir, from, to)

if err != nil {
fmt.Fprintln(os.Stderr, err)
return ExitCodeError
}

fmt.Println("Conversion finished!")
return ExitCodeOk
}
Binary file added kadai1/hokita/testdata/dir1/gopher2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added kadai1/hokita/testdata/gopher1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.