Markdown 形式のリンクを生成するツールを作ってみた

きっかけはこれ。

仕様はこんな感じ。

URLを入力すると、Markdownに最適な形式で、webページタイトルとURLを出力します。出力結果はコンソールに表示すると同時に、クリップボードにもコピーされます。
via Markdownのリンクフォーマットを生成するコマンドを自作した

あれ? これ Go 言語でも簡単に作れるんじゃないかな。 ちうわけで作ってみた。

(Windows では mklink はシンボリック・リンクを作るコマンドだと後で気づいたが,後悔先に立たずw

パッケージの使い方としてはこんな感じである。

link, err := mklink.New("https://git.io/vFR5M")
if err != nil {
    fmt.Println(err)
    return
}
fmt.Println(link.Encode(mklink.StyleMarkdown))
// Output:
// [GitHub - spiegel-im-spiegel/mklink: Make Link with Markdown Format](https://github.com/spiegel-im-spiegel/mklink)

コマンドライン・インタフェースはこんな感じ。

$ mklink -h
Usage:
  mklink [flags] [URL [URL]...]

Flags:
  -h, --help           help for mklink
  -i, --interactive    interactive mode
      --log string     output log
  -s, --style string   link style (default "markdown")
  -v, --version        output version of mklink

リンクの形式が markdown だけなのはアレなので -s オプションで選べるようにした。 今のところ markdown, wiki, html, csv の4つに対応している。

-i オプションを付けると対話モードになる。

$ mklink -i
Input 'q' or 'quit' to stop
mklink> https://git.io/vFR5M
[GitHub - spiegel-im-spiegel/mklink: Make Link with Markdown Format](https://github.com/spiegel-im-spiegel/mklink)
mklink>

作成したリンクを標準出力に出力すると同時にクリップボードにもコピーする。 いやぁ,これめっさ便利だわ。

Web Scraping

URL からページのタイトルを取得するには HTML の解析を行うスクレイピング(Web scraping)機能が必要だが,好都合なことに PuerkitoBio/goquery という便利なパッケージが公開されている。

PuerkitoBio/goquery が優れているのは jQuery っぽい仕掛けでとても簡単に HTML の解析ができる点にある。 今回は <head> 要素からタイトルと説明(description)を抜き出すだけだが,こんな感じに記述できる。

//New returns new Link instance
func New(url string) (*Link, error) {
    link := &Link{URL: strings.Trim(url, "\t \n")}
    doc, err := goquery.NewDocument(link.URL)
    if err != nil {
        return link, err
    }
    link.Location = doc.Url.String()

    doc.Find("head").Each(func(_ int, s *goquery.Selection) {
        s.Find("title").Each(func(_ int, s *goquery.Selection) {
            link.Title = strings.Trim(s.Text(), "\t \n")
        })
        s.Find("meta[name='description']").Each(func(_ int, s *goquery.Selection) {
            if v, ok := s.Attr("content"); ok {
                link.Description = strings.Trim(v, "\t \n")
            }
        })
    })

    return link, nil
}

ね, jQuery ぽいでしょ。 まさか,この期に及んで jQuery (ぽいもの)を触ることになるとは思わなかったぜ。 jQuery の本は納戸に仕舞っちゃったんだけどなぁ。

ターミナルの判定

mklink はパイプでも動作する。

$ echo https://git.io/vFR5M | mklink
[GitHub - spiegel-im-spiegel/mklink: Make Link with Markdown Format](https://github.com/spiegel-im-spiegel/mklink)

この時にうっかり -i オプションを付けて(パイプのつもりが)対話モードになっては困るので標準入出力がターミナルかどうかを判定するロジックを入れている。

func isTerminal() bool {
    if !isatty.IsTerminal(os.Stdin.Fd()) && !isatty.IsCygwinTerminal(os.Stdin.Fd()) {
        return false
    }
    if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) {
        return false
    }
    return true
}

この機能は mattn/go-isatty パッケージで実装した1

クリップボードの操作

クリップボードの操作といっても今回は書き込みだけだが atotto/clipboard を使って実装している。

buf := new(bytes.Buffer)
io.Copy(c.writer, io.TeeReader(lnk.Encode(c.linkStyle), buf))
strLink := buf.String()
if c.clipbrdFlag {
    clipboard.WriteAll(strLink)
}
if c.log != nil {
    fmt.Fprint(c.log, strLink)
}

Windows 環境では問題なく動作しているが,他の OS ではどうなのかよく分からない。

ブックマーク


  1. 最初は golang.org/x/crypto/ssh/terminal パッケージを使っていたのだが「たしかもう少し軽いパッケージあったよなぁ」と思ってググったら思い出した。 [return]