Golang and the Clipboard (mdclip)

I recently wrote a couple of lines of Go again, mainly to see if I could write a cross platform clone of a two line shell script in Go (which actually makes it like 69 lines, but whatever).

So I wrote mdclip to compile a file from markdown to HTML and put it in my clipboard (so I can paste it into my WordPress editor). Now you could scroll down to the comment box and go like

USE HUGO

or

USE GHOST

or

STOP BLOGGING

and I will stare down into your face and whisper

No.

(watch Watchmen)

Regardless, it was fun to write the little thing again and I actually learned how to use already available binaries on a system with Go.

As many, I am cursed to live my daily life in a world of multiple operating systems, each with their respective… charms and disgusting little pits of dysfunctionality.

Actually I shamelessly stole the part that deals with figuring out the right command for the clipboard from github.com/atotto/clipboard. Fortunately Mac OS has pbcopy and Linux has… well you can install xclip or xsel. I’m sure they both have a place in the hearts of two raging and different camps of Linux users with lots of trenches between them.

Oh by the way, (n)vim is better.

Right, so figuring out the operating system was down to:

var copyCmd *exec.Cmd

// Mac "OS"
if arch == "darwin" {
    copyCmd = exec.Command("pbcopy")
}
// Linux
if arch == "linux" {
    copyCmd = exec.Command("xclip", "-selection", "c")
}

Which is much less advanced than the cool examples in the before referenced project. Another thing I learned is that there apparently is a type for *exec.Cmd (seriously these go dudes have a type for everything), which apparently represents a command you execute, which I totally need for this one.

Another thing I learned was that you can start up all kinds of commands and then pipe things to them, like with an actual |, just controlled by your Go code.

output := blackfriday.MarkdownBasic([]byte(dat))

if _, err := in.Write([]byte(output)); err != nil {
    log.Fatal(err)
}

Wohooo, who could want more? Best thing, it actually works! I can actually use it!

One more drink thing! (you should listen to Alestorm)

In order to get niceness like fenced codeblocks with \ you'll need to use

output := blackfriday.MarkdownCommon(input)

instead of

output := blackfriday.MarkdownBasic(input)

Finally, assuming you want to use this command globally like mdclip my-cool-buttpost you'll need to run go install in the directory where your project lives and where it has a main.go file.

Here's the full code:

package main

import (
    "io/ioutil"
    "log"
    "os"
    "os/exec"
    "path/filepath"
    "runtime"

    "github.com/russross/blackfriday"
)

func main() {
    arch := runtime.GOOS

    if len(os.Args) != 2 {
        log.Fatal("Usage: mdclip filename.md")
    }

    arg := os.Args[1]

    fp, err := filepath.Abs(arg)

    if err != nil {
        log.Fatal(err)
    }

    dat, err := ioutil.ReadFile(fp)
    if err != nil {
        log.Fatal(err)
    }

    output := blackfriday.MarkdownCommon([]byte(dat))
    toClipboard(output, arch)
}

func toClipboard(output []byte, arch string) {
    var copyCmd *exec.Cmd

    // Mac "OS"
    if arch == "darwin" {
        copyCmd = exec.Command("pbcopy")
    }
    // Linux
    if arch == "linux" {
        copyCmd = exec.Command("xclip", "-selection", "c")
    }

    in, err := copyCmd.StdinPipe()

    if err != nil {
        log.Fatal(err)
    }

    if err := copyCmd.Start(); err != nil {
        log.Fatal(err)
    }

    if _, err := in.Write([]byte(output)); err != nil {
        log.Fatal(err)
    }

    if err := in.Close(); err != nil {
        log.Fatal(err)
    }

    copyCmd.Wait()
}
Tagged with: #blackfriday #go #golang #markdown #mdclip #xclip

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!