imgcat

iTerm2 中我们可以直接查看图片,是不是有点酷炫。

imgcat

为了使图片能够正确的展示,我们需要使数据满足特定的协议格式。

1
ESC ] 1337 ; File = [optional arguments] : base-64 encoded file contents ^G

按照上面的格式我们将数据分为 headerbodyfooter 三个部分,而 headerfooter 相对固定,我们使用 strings.NewReader 封装返回一个 io.Reader ,而中间变化的 base64 编码的数据,我们需要利用 base64.NewEncoder 来对输入的数据进行编码,最后将三个部分利用 io.MultiReader 整合成一个 io.Reader ,然后利用 io.Copy 将数据传输到 w 中,然而 base64.NewEncoder 返回的是一个 io.WriteCloser ,而我们想要的是一个 io.Reader ,这种转化我们可以借助 io.Pipe() 实现,io.Pipe() 会返回一个 PipeReader 和一个 PipeWriter ,当有数据往 PipeWriter 中写时,PipeReader 就可以获得相应的数据,通过这种方式,我们就可以间接地将一个 Writer 转换为一个 Reader 来使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func copy(w io.Writer, r io.Reader) error {
header := strings.NewReader("\033 ] 1337 ; File = inline=1 : ")
footer := strings.NewReader("\a\n")

pr, pw := io.Pipe()
go func() {
defer pw.Close()
wc := base64.NewEncoder(base64.StdEncoding, pw)
_, err := io.Copy(wc, r)
if err != nil {
pw.CloseWithError(err)
return
}

if err := wc.Close(); err != nil {
pw.CloseWithError(err)
return
}
}()

mr := io.MultiReader(header, pr, footer)
_, err := io.Copy(w, mr)

return err
}

为了在下面的 imgcat 方法中使用 io.Copy 接口,我们需要自己实现一个 io.Writer ,在 NewWriter 函数中我们返回自己 writer 结构体,并且同时起了一个goroutine 来获取 PipeWriter 写的数据,同理,PipeWriter 中写入的数据可以从相应的 PipeReader 获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package imgcat

type writer struct {
pw *io.PipeWriter
done chan struct{}
}

func (w *writer) Write(p []byte) (int, error) {
return w.pw.Write(p)
}

func (w *writer) Close() error {
if err := w.pw.Close(); err != nil {
return err
}
<-w.done
return
}

func NewWriter(w io.Writer) io.WriteCloser {
pr, pw := io.Pipe()
wc := &writer{pw, make(chan struct{})}
go func() {
defer close(w.done)
err := copy(w, pr)
pr.CloseWithError(err)
}()
return wc
}

imgcat 方法中主要是通过 io.Copy 将路径 path 对应的文件拷贝到我们上面的 Writer 中,NewWriter 指定 os.Stdout 作为我们的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func imgcat(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

wc := imgcat.NewWriter(os.Stdout)
if _, err = io.Copy(wc, f); err != nil {
return err
}
return wc.Close()
}

func main() {
if len(os.Args) != 2 {
return
}
err := imgcat(os.Args[1])
if err != nil {
fmt.Println(err)
}
}
Pieces of Valuable Programming Knowledges