Skip to content
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

[課題1] 画像変換コマンドを作ろう #13

Open
wants to merge 14 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
1 change: 1 addition & 0 deletions kadai1/asuke-yasukuni/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
54 changes: 54 additions & 0 deletions kadai1/asuke-yasukuni/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
## dojo7 [課題1] 画像変換コマンドを作ろう

全体的にシンプルな実装を心がけてみました。

**仕様**
- -src ディレクトリパス
- -from 変換対象の拡張子
- -to 変換先の拡張子

**実行例**
```bash
./imgreplacer -src ./testdata/ -from png -to jpg
2019/09/09 00:26:22 [replace start]
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-1.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-2.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-3.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-4.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-5.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-6.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/recursiondata/test-7.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-1.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-2.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-3.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-4.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-5.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-6.png -> jpg
2019/09/09 00:26:22 [replace file]testdata/test-7.png -> jpg
2019/09/09 00:26:22 [replace end]
```

## 回答
##### 次の仕様を満たすコマンドを作って下さい
- ディレクトリを指定する
- 指定できるようにしました
- 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト)
- デフォルトでJPG -> PNG 変換になっています。
- 元ファイルをリプレイスしています。
- ディレクトリ以下は再帰的に処理する
- filepath.Walkで再帰的に検索して処理しています
- 変換前と変換後の画像形式を指定できる(オプション)
- -to -from オプションで可能にしました

##### 以下を満たすように開発してください
- mainパッケージと分離する
- 分離しましたが、どこまで分離すれば良いのか迷ったので一旦画像ファイルの変換部分とバリデーション部分のみ分離しました。
- 自作パッケージと標準パッケージと準標準パッケージのみ使う
- そうなっているはず
- ユーザ定義型を作ってみる
- 今回はユーザー定義関数で replacer.File を用意してそこにメソッドをはやして処理する形にしてみました。
- GoDocを生成してみる
- go moduleで実装してたので生成に少し苦労しました。
- 最終的にjodo7リポジトリ自体をGOPATHのsrcディレクトリ以下に置いて生成しています。
![pkg_dojo7_kadai1_asuke-yasukuni_replacer_](https://user-images.githubusercontent.com/36254193/64491494-0ad96680-d2a4-11e9-926f-42336b6eb1e1.png)
![pkg_dojo7_kadai1_asuke-yasukuni_validation_](https://user-images.githubusercontent.com/36254193/64491495-0b71fd00-d2a4-11e9-97f5-43f0681f403e.png)
138 changes: 138 additions & 0 deletions kadai1/asuke-yasukuni/cover.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }

</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">

<option value="file0">dojo7/kadai1/asuke-yasukuni/walk/encoder.go (87.5%)</option>

</select>
</div>
<div id="legend">
<span>not tracked</span>

<span class="cov0">not covered</span>
<span class="cov8">covered</span>

</div>
</div>
<div id="content">

<pre class="file" id="file0" style="display: none">// Recursive image encoder command implementation.
package walk

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

type File interface {
Encode(path, toExt string) error
}

type Walk struct {
File File
}

// Recursively search the directory and perform encoding.
func (w *Walk) Encoder(src *string, fromExt, toExt string) (encodeFiles []string, err error) <span class="cov8" title="1">{
err = filepath.Walk(*src, func(path string, info os.FileInfo, err error) error </span><span class="cov8" title="1">{
if filepath.Ext(path) != "."+fromExt </span><span class="cov8" title="1">{
return nil
}</span>

// Use to output.
<span class="cov8" title="1">encodeFiles = append(encodeFiles, fmt.Sprintf("%s%s -&gt; %s", "[replace file]", path, toExt))

if err := w.File.Encode(path, toExt); err != nil </span><span class="cov0" title="0">{
return err
}</span>

<span class="cov8" title="1">return nil</span>
})

<span class="cov8" title="1">return</span>
}
</pre>

</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>
8 changes: 8 additions & 0 deletions kadai1/asuke-yasukuni/cover.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mode: set
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:19.94,20.81 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:35.2,35.8 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:20.81,21.40 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:26.3,28.52 2 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:32.3,32.13 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:21.40,23.4 1 1
dojo7/kadai1/asuke-yasukuni/walk/encoder.go:28.52,30.4 1 0
3 changes: 3 additions & 0 deletions kadai1/asuke-yasukuni/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gopherdojo/dojo7/asuke-yasukuni

go 1.12
Binary file added kadai1/asuke-yasukuni/imgreplacer
Binary file not shown.
38 changes: 38 additions & 0 deletions kadai1/asuke-yasukuni/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"flag"
"log"

"github.com/gopherdojo/dojo7/asuke-yasukuni/replacer"
"github.com/gopherdojo/dojo7/asuke-yasukuni/validation"
"github.com/gopherdojo/dojo7/asuke-yasukuni/walk"
)

var src = flag.String("src", "", "ファイルパス書いて")
var from = flag.String("from", "jpg", "変換したい画像の拡張子 jpg or png")
var to = flag.String("to", "png", "変換後の拡張子 jpg or png")

func main() {
flag.Parse()

// do ext validation
if !validation.Ext(*from) || !validation.Ext(*to) {
log.Fatalf("\x1b[31mfrom:%s to:%s encode is unsupported\x1b[0m\n", *from, *to)
}

log.Printf("\x1b[33m%s\x1b[0m\n", "[replace start]")

walker := walk.Walk{File: &replacer.File{}}
files, err := walker.Encoder(src, *from, *to)
if err != nil {
log.Fatal(err)
}

// encoding result
for _, f := range files {
log.Print(f)
}

log.Printf("\x1b[33m%s\x1b[0m\n", "[replace end]")
}
69 changes: 69 additions & 0 deletions kadai1/asuke-yasukuni/replacer/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Replacer is a package that can convert to the specified image format (jpg, png) by generating File structure.
// Supported formats png,jpg
package replacer

import (
"fmt"
"image"
"image/jpeg"
"image/png"
"log"
"os"
"path/filepath"
)

// A structure that stores image files.
type File struct{}

// This method encodes an image file into jpg or png.
func (f *File) Encode(path, to string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer fileClose(file)

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

// create output file
outPath := path[:len(path)-len(filepath.Ext(path))] + "." + to
out, err := os.Create(outPath)
if err != nil {
return err
}
defer fileClose(out)

// select encoder
switch to {
case "jpg":
if err := jpeg.Encode(out, img, &jpeg.Options{Quality: 100}); err != nil {
return err
}
case "png":
if err := png.Encode(out, img); err != nil {
return err
}
default:
// delete fail file
if err := os.Remove(outPath); err != nil {
return err
}
return fmt.Errorf("%s is unsupported extension", to)
}

// delete original file
if err := os.Remove(path); err != nil {
return err
}

return nil
}

func fileClose(file *os.File) {
if err := file.Close(); err != nil {
log.Printf("\x1b[31m%s:%s\x1b[0m\n", "[encode error]", err)
}
}
30 changes: 30 additions & 0 deletions kadai1/asuke-yasukuni/replacer/encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package replacer

import (
"testing"
)

func TestEncode(t *testing.T) {

var testCase = []struct {
Name string
Src string
To string
Result bool
}{
{"jpg -> png encode", "../testdata/test-single.jpg", "png", true},
{"png -> jpg encode", "../testdata/test-single.png", "jpg", true},
{"jpg -> gif not support encode", "../testdata/test-single.jpg", "gif", false},
{"not found file encode", "../testdata/test-nofile.gif", "png", false},
}

f := File{}
for _, tc := range testCase {
t.Run(tc.Name, func(t *testing.T) {
err := f.Encode(tc.Src, tc.To)
if err != nil && tc.Result {
t.Error(err)
}
})
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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/asuke-yasukuni/testdata/test-single.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions kadai1/asuke-yasukuni/validation/extention.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Validation package for commands.
package validation

// Returns true for allowed formats, false otherwise.
func Ext(ext string) bool {
// Because there are few correspondence formats, we do not make map.
if ext == "jpg" || ext == "png" {
return true
}
return false
}
29 changes: 29 additions & 0 deletions kadai1/asuke-yasukuni/validation/extention_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package validation

import (
"testing"
)

func TestExtValidation(t *testing.T) {
testCases := []struct {
Name string
Ext string
Result bool
}{
{Name: "validate success", Ext: "png", Result: true},
{Name: "validate success", Ext: "jpg", Result: true},
{Name: "validate fail not support", Ext: "gif", Result: false},
{Name: "validate fail not support", Ext: "pdf", Result: false},
{Name: "validate fail number", Ext: "23456886", Result: false},
{Name: "validate fail symbol", Ext: ":;@[]_/23!-^~#/.,", Result: false},
{Name: "validate fail symbol number", Ext: ":;@po234[]_/23!-^~#/.,", Result: false},
}

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
if Ext(tc.Ext) != tc.Result {
t.Fatalf("%s %s", "ext", tc.Ext)
}
})
}
}
Loading