[Go] 画像の拡張子の変更をするCLIコマンドを作る

# はじめに
このまえ、**[Gopher道場](https://gopherdojo.org/)** の自習室とやらに参加させてもらい、前々から気になっていたGopher道場の課題をgithubにて探して、(やっていいのかな…？)演習しました。

その課題1ってのが**画像の拡張子変更** でした。とりあえずアウトプットをしたいので、ここに色々書いていきます。また、使用したパッケージから、`flag`,`os`,`path/filepath`,`image`について、ちょっとだけ解説していきます。

## 環境
```
# go version
go version go1.15.2 linux/amd64

# tree
.
|- conversion
|       |- conversion.go
|- go.mod
|- main.go
```

## 実際のコード
とりあえず、リポジトリは[ここです](https://github.com/marnysan111/gopherdojo-studyroom/tree/kadai1-marny/kadai1/marny)

```go:main.go
package main

import (
	"cvs/conversion"
	"flag"
	"fmt"
	"os"
	"path/filepath"
)

var (
	extension string
	imagepath string
	dirpath   string
)

func main() {

	flag.StringVar(&extension, "e", "jpeg", "拡張子の指定")
	flag.StringVar(&imagepath, "f", "", "変換するファイルのパスの指定")
	flag.StringVar(&dirpath, "d", "", "変換後のファイル名の指定")
	flag.Parse()
	err := conversion.ExtensionCheck(extension)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	err = conversion.FilepathCheck(imagepath)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	err = conversion.DirpathCheck(dirpath)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	f := filepath.Ext(imagepath)
	err = conversion.FileExtCheck(f)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}

	fmt.Println("変換中・・・")

	err = conversion.FileExtension(extension, imagepath, dirpath)
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}
}
```

```go:conversion.go
/*
Conversion は 画像の拡張子の変更を行うためのパッケージです。
*/
package conversion

import (
	"errors"
	"fmt"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"os"

	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
)

const (
	JPEG = "jpeg"
	JPG  = "jpg"
	GIF  = "gif"
	PNG  = "png"
)

// -e で指定した拡張子が対応しているか、判断します。
func ExtensionCheck(ext string) error {
	switch ext {
	case JPEG, JPG, GIF, PNG:
		return nil
	default:
		return errors.New("指定できない拡張子です" + ext)
	}
}

// -f で指定したファイルが存在するか、判断します。
func FilepathCheck(imagepath string) error {
	switch imagepath {
	case "":
		return errors.New("ファイルの指定がされてません")
	default:
		if f, err := os.Stat(imagepath); os.IsNotExist(err) || f.IsDir() {
			return errors.New("ファイルが存在しません" + imagepath)
		} else {
			return nil
		}
	}
}

func DirpathCheck(dirpath string) error {
	switch dirpath {
	case "":
		return errors.New("変換後のファイル名が指定されていません")
	default:
		return nil
	}
}

func FileExtCheck(imagepath string) error {
	switch imagepath {
	case "." + JPEG, "." + JPG, "." + GIF, "." + PNG:
		return nil
	default:
		return errors.New("指定したファイルが対応していません。：" + imagepath)
	}
}

func FileExtension(extension string, imagepath string, dirpath string) error {
	exFile, err := os.Open(imagepath)
	defer exFile.Close()
	if err != nil {
		return errors.New("os.Create失敗")
	}

	output, err := os.Create(dirpath)
	defer output.Close()
	if err != nil {
		return errors.New("output失敗")
	}

	img, _, Err := image.Decode(exFile)
	if Err != nil {
		return errors.New("Decode失敗")
	}

	switch extension {
	case JPEG, JPG:
		err = jpeg.Encode(output, img, nil)
		if err != nil {
			return errors.New("Encode失敗")
		}
		fmt.Println("変換成功")
		return nil
	case GIF:
		err = gif.Encode(output, img, nil)
		if err != nil {
			return errors.New("Encode失敗")
		}
		fmt.Println("変換成功")
		return nil
	case PNG:
		err = png.Encode(output, img)
		if err != nil {
			return errors.New("Encode失敗")
		}
		fmt.Println("変換成功")
		return nil
	}
	return nil
}
```

```go:go.mod
module cvs

go 1.15
```

とりあえず、エラーハンドリングとかは適当にしつつ、ちゃんと動作するようにしました。

# コード解説
今回のコードのテーマとして、
- `go mod` を使う
- `go doc` を使う
- パッケージを分ける

というのを決めました。一応、理由としては、
1. `go mod` により、パッケージの分割を使いやすくする
2. `go doc` により、ドキュメントを作成することにより、可用性をあげる
この２つがあります。が、今回はパス。

今回使用したパッケージについて、色々書いていきます。

画像の拡張子を変更するCLIコマンドのために使用したパッケージを上げると、

1. `flag`
2. `os`
3. `path/filepath`
4. `image`

があります。
それぞれ解説していきます。

## flag
[ドキュメント](https://golang.org/pkg/flag/)
コマンドラインからオプションとして、引数を取得するときに使う

```go:実装したコード(main.go)
var (
	extension string
	imagepath string
	dirpath   string
)
func main() {

	flag.StringVar(&extension, "e", "jpeg", "拡張子の指定")
 // -e "文字列" で、文字列を取得する/デフォルトで"jpeg"が指定されている
	flag.StringVar(&imagepath, "f", "", "変換するファイルのパスの指定")
	flag.StringVar(&dirpath, "d", "", "変換後のファイル名の指定")
	flag.Parse() 
// Parseしないと、変数にパースされない

～～～省略
```

```go:書き方
flag.StringVar(&変数名, "オプションの記号", "デフォルトの値","このオプションの説明")
```
文字列の取得以外も取得はできる(はず)

## os
[ドキュメント](https://golang.org/pkg/os/)
今回は、指定した画像が本当に存在するのか、画像を保存したりに使用しました。

```go:実装したコード(conversion.go)
func FilepathCheck(imagepath string) error {
	switch imagepath {
	case "":
		return errors.New("ファイルの指定がされてません")
	default:
		if f, err := os.Stat(imagepath); os.IsNotExist(err) || f.IsDir() {
			return errors.New("ファイルが存在しません" + imagepath)
		} else {
			return nil
		}
	}
}

//-fで指定したファイルが存在するかを判定している関数です
```

こいつは結構なんでもできる。

## path/filepath
[ドキュメント](https://golang.org/pkg/path/filepath/)
先程紹介した関数の引数の取得してます。stringからファイルパスを取得してます。

```go:実装したコード(main.go)
f := filepath.Ext(imagepath)
err = conversion.FileExtCheck(f)
//imagepathが本当にあるのか、をチェックする
```

## image
[ドキュメント](https://golang.org/pkg/image/)
画像処理系はこいつでできる。

```go:実装したコード(conversion.go)
img, _, Err := image.Decode(exFile)
if Err != nil {
	return errors.New("Decode失敗")
}
//画像をデコードする処理

switch extension {
case JPEG, JPG:
	err = jpeg.Encode(output, img, nil)
	if err != nil {
		return errors.New("Encode失敗")
	}
	fmt.Println("変換成功")
	return nil
case GIF:
	err = gif.Encode(output, img, nil)
	if err != nil {
		return errors.New("Encode失敗")
	}
	fmt.Println("変換成功")
	return nil
case PNG:
	err = png.Encode(output, img)
	if err != nil {
		return errors.New("Encode失敗")
	}
	fmt.Println("変換成功")
	return nil
}
//変換後の拡張子に合わせてエンコードする
```

画像処理はほぼこいつだけでできるっぽい


# まとめ
実際に実行してみると、、、

```sh
# go build -o cvs
# ./cvs -e png -f sample.jpeg -d sample.png
変換中・・・
変換成功
```
って感じで、変換できる。

ちなみに、-d のあとに.pngではなく、適当に書いても、拡張子としては、ちゃんと変換されています。

ブラウザで変換すると、へんなスクリプトを入れられるみたいなのもないし、安全。

やりごたえがあって、楽しかったから、またコマンド作ろうかしら。

では。