Skip to content

Kadai2 shuheiktgw #33

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 1 commit 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
2 changes: 2 additions & 0 deletions kadai2/shuheiktgw/converter-cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
vendor
converter-cli
15 changes: 15 additions & 0 deletions kadai2/shuheiktgw/converter-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
converter-cli
===

converter-cli is a command line tool to convert image extension.

## Usage

```
converter-cli [options...] PATH

OPTIONS:
--from value, -f value specifies a image extension converted from (default: .jpg)
--to value, -t value specifies a image extension converted to (default: .png)
--help, -h prints out help
```
112 changes: 112 additions & 0 deletions kadai2/shuheiktgw/converter-cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package main

import (
"flag"
"fmt"
"io"

"github.com/shuheiktgw/dojo4/kadai1/shuheiktgw/converter-cli/converter"
)

const (
ExitCodeOK = iota
ExitCodeExpectedError
ExitCodeUnexpectedError
ExitCodeBadArgs
ExitCodeParseFlagsError
ExitCodeInvalidFlagError
)

const Name = "converter-cli"

var extensions = [4]string{".gif", ".jpeg", ".jpg", ".png"}

// CLI represents CLI interface for converter
type CLI struct {
outStream, errStream io.Writer
}

// Run executes `converter-cli` command and converts images's extension
func (cli *CLI) Run(args []string) int {
var (
from string
to string
)

flags := flag.NewFlagSet(Name, flag.ContinueOnError)
flags.Usage = func() {
fmt.Fprint(cli.outStream, usage)
}

flags.StringVar(&from, "from", ".jpg", "")
flags.StringVar(&from, "f", ".jpg", "")

flags.StringVar(&to, "to", ".png", "")
flags.StringVar(&to, "t", ".png", "")

if err := flags.Parse(args[1:]); err != nil {
return ExitCodeParseFlagsError
}

if !validateExtensions(from) {
fmt.Fprintf(cli.errStream, "Failed to set up converter-cli: invalid extension `%s` is given for --from flag\n"+
"Please choose an extension from one of those: %v\n\n", from, extensions)
return ExitCodeInvalidFlagError
}

if !validateExtensions(to) {
fmt.Fprintf(cli.errStream, "Failed to set up converter-cli: invalid extension `%s` is given for --to flag\n"+
"Please choose an extension from one of those: %v\n\n", to, extensions)
return ExitCodeInvalidFlagError
}

path := flags.Args()
if len(path) != 1 {
fmt.Fprintf(cli.errStream, "Failed to set up converter-cli: invalid argument\n"+
"Please specify the exact one path to a directly or a file\n\n")
return ExitCodeBadArgs
}

files, err := converter.Convert(from, to, path[0])
if err != nil {
if _, ok := err.(converter.Handled); ok {
fmt.Fprintf(cli.errStream, "Failed to execute converter-cli\n"+
"%s\n\n", err)
return ExitCodeExpectedError
}

fmt.Fprintf(cli.errStream, `converter-cli failed because of the following error.

%s

You might encounter a bug with converter-cli, so please report it to https://github.com/xxx/xxxx

`, err)
return ExitCodeUnexpectedError
}

fmt.Fprintf(cli.outStream, "converter-cli successfully converted following files to `%s`.\n", to)
fmt.Fprintf(cli.outStream, "%s\n\n", files)
return ExitCodeOK
}

func validateExtensions(ext string) bool {
for _, e := range extensions {
if ext == e {
return true
}
}

return false
}

var usage = `Usage: converter-cli [options...] PATH

converter-cli is a command line tool to convert image extension

OPTIONS:
--from value, -f value specifies a image extension converted from (default: .jpg)
--to value, -t value specifies a image extension converted to (default: .png)
--help, -h prints out help

`
103 changes: 103 additions & 0 deletions kadai2/shuheiktgw/converter-cli/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)

func TestCLI_Run(t *testing.T) {
cases := []struct {
command string
expectedOutStream string
expectedErrStream string
expectedExitCode int
}{
{
command: "converter-cli",
expectedOutStream: "",
expectedErrStream: "Failed to set up converter-cli: invalid argument\nPlease specify the exact one path to a directly or a file\n\n",
expectedExitCode: ExitCodeBadArgs,
},
{
command: "converter-cli testdata testdata2",
expectedOutStream: "",
expectedErrStream: "Failed to set up converter-cli: invalid argument\nPlease specify the exact one path to a directly or a file\n\n",
expectedExitCode: ExitCodeBadArgs,
},
{
command: "converter-cli --from .svg",
expectedOutStream: "",
expectedErrStream: "Failed to set up converter-cli: invalid extension `.svg` is given for --from flag\nPlease choose an extension from one of those: [.gif .jpeg .jpg .png]\n\n",
expectedExitCode: ExitCodeInvalidFlagError,
},
{
command: "converter-cli --to .svg",
expectedOutStream: "",
expectedErrStream: "Failed to set up converter-cli: invalid extension `.svg` is given for --to flag\nPlease choose an extension from one of those: [.gif .jpeg .jpg .png]\n\n",
expectedExitCode: ExitCodeInvalidFlagError,
},
{
command: "converter-cli testdata",
expectedOutStream: "",
expectedErrStream: "Failed to execute converter-cli\ncould not find files with the specified extension. path: testdata, extension: .jpg\n\n",
expectedExitCode: ExitCodeExpectedError,
},
{
command: "converter-cli testdata/unknown.jpg",
expectedOutStream: "",
expectedErrStream: "Failed to execute converter-cli\nlstat testdata/unknown.jpg: no such file or directory\n\n",
expectedExitCode: ExitCodeExpectedError,
},
{
command: "converter-cli --from .jpeg testdata",
expectedOutStream: "converter-cli successfully converted following files to `.png`.\n[testdata/jpeg-image.jpeg]\n\n",
expectedErrStream: "",
expectedExitCode: ExitCodeOK,
},
}

for i, tc := range cases {
outStream := new(bytes.Buffer)
errStream := new(bytes.Buffer)

cli := CLI{outStream: outStream, errStream: errStream}
args := strings.Split(tc.command, " ")

if got := cli.Run(args); got != tc.expectedExitCode {
t.Errorf("#%d %q exits with %d, want %d", i, tc.command, got, tc.expectedExitCode)
}

if got := outStream.String(); got != tc.expectedOutStream {
t.Errorf("#%d Unexpected outStream has returned: want: %s, got: %s", i, tc.expectedOutStream, got)
}

if got := errStream.String(); got != tc.expectedErrStream {
t.Errorf("#%d Unexpected errStream has returned: want: %s, got: %s", i, tc.expectedErrStream, got)
}

cleanup(t)
}
}

func cleanup(t *testing.T) {
t.Helper()

err := filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if path != "testdata/jpeg-image.jpeg" && path != "testdata" {
return os.Remove(path)
}

return nil
})

if err != nil {
t.Errorf("failed to cleanup testdata: %s", err)
}
}
116 changes: 116 additions & 0 deletions kadai2/shuheiktgw/converter-cli/converter/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Package converter provides a functionality to convert image extension
package converter

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

// Handled interface represents the error is properly handled and expected
type Handled interface {
Handled() bool
}

// HandedError represents the error is properly handled and expected
type HandedError struct {
Message string
}

// Error returns error message for HandedError
func (r *HandedError) Error() string {
return r.Message
}

// Handled suggests that HandedError implemented Handled interface
func (r *HandedError) Handled() bool {
return true
}

// Convert converts file extension in the specified path recursively
func Convert(from, to, path string) ([]string, error) {
filePaths, err := findFilePaths(from, path)
if err != nil {
return nil, err
}

for _, filePath := range filePaths {
if err := convert(to, filePath); err != nil {
return nil, err
}
}

return filePaths, nil
}

func findFilePaths(from, path string) ([]string, error) {
var filePaths []string

err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
if _, ok := err.(*os.PathError); ok {
return &HandedError{Message: err.Error()}
}

if err != nil {
return err
}

if filepath.Ext(path) == from {
filePaths = append(filePaths, path)
}

return nil
})

if err != nil {
return nil, err
}

if len(filePaths) == 0 {
return nil, &HandedError{Message: fmt.Sprintf("could not find files with the specified extension. path: %s, extension: %s", path, from)}
}

return filePaths, nil
}

func convert(to, filePath string) error {
file, err := os.Open(filePath)
defer file.Close()

if err != nil {
return err
}

img, _, err := image.Decode(file)

if err != nil {
return err
}

out, err := os.Create(newFilePath(to, filePath))
defer out.Close()

if err != nil {
return err
}

switch to {
case ".gif":
return gif.Encode(out, img, nil)
case ".jpeg", ".jpg":
return jpeg.Encode(out, img, nil)
case ".png":
return png.Encode(out, img)
default:
return fmt.Errorf("unsupported extension is specified: %s", to)
}
}

func newFilePath(to, filePath string) string {
ext := filepath.Ext(filePath)
return filePath[:len(filePath)-len(ext)] + to
}
Loading