以前にPythonでSlackをエゴサーチするツールSlackegoを作りましたが、Nimでそれを作り直しました。

リポジトリはこちらです。Windowsには対応していません。

Table of Contents

経緯

先週NimでGitHubのリポジトリ検索ツールを作りました。

ただ、これは実用的なものではなく勉強のために作成したモノに過ぎません。
私が新しい言語を学ぶときにほぼ例外なく作成するモノです。

Nimを学んだ大きな理由の1つがPythonで作成モノの置き換えです。
Jumeauxは私が本気で作成中のPython製ツールですが、置き換えには少々ヘビーです。
そこで、先日作成したSlackegoを置き換えることにしました。

本記事の内容

本記事ではSlackegoをNimで実装することで得た知見を紹介します。
いずれもNimを使い始めた方向けの内容になっていると思います。

Slackegoの経緯については下記記事を参照して下さい。

また、使い方はGitHubのREADMEをご覧下さい。

利用パッケージ

Slackegoで利用したパッケージの紹介を簡単にします。
前回説明したものは説明しません。

json

jsonをobjectと相互変換する為に使っています。 間にJsonNodeという型が含まれます。

以下の変換を使用しています。

Before 変換に使ったもの After 主な用途
ファイルパス parseFile JsonNode jsonファイルの読みこみ
string parseJson JsonNode 文字列をjsonに変換したいとき(http get)
object %* JsonNode objectをjsonとしてパースしたいとき
JsonNode getBool bool jsonのbool要素を取り出したいとき
JsonNode getStr string jsonのstring要素を取り出したいとき
JsonNode $ string jsonをワンラインの文字列に変換したいとき (http post)
JsonNode pretty string jsonを人間が読める文字列に変換したいとき

本当はNimYAMLを使いたかったのですがビルドが通らなくて諦めました…

dotenv

環境変数を定義したファイル、.envを読みこんで環境変数にセットする為に使います。

import os
import dotenv

if fileExists(".env"):
  initDotEnv().load()

.envが存在しない場合は無視したいので、osモジュールのfileExistsを使用しています。

uri

RFC3936に従いURIのクエリをパースするため、encodeUrlを使用します。

import uri

# ..中略..

proc search*(word: string, after: DateTime): SearchResult =
  assertToken()

  let
    token = getEnv("SLACK_TOKEN")
    afterDate = after.format("yyyy-MM-dd")
    query = encodeUrl(fmt"{word} after:{afterDate}")

times

日時を扱うためにDateTime型を利用します。

yyyy-MM-dd表記に変換するためformat("yyyy-MM-dd")を使用しています。
$yyyy-MM-ddThh:mmzzzにフォーマットされてしまうので使用できませんでした。

現在時刻から1日+minutes分前のDateTime型を生成するには以下のようにします。

let t: DateTime = now() - 1.days - minutes.minutes

options

みんな(多分)大好きOption型です。

プロシージャ 意味
some(val) valを値としてOptionで包む
none(T) T型の値ナシとしてOptionで包む
isSome 値が存在する場合にtrueを返す
get 値を取得する. 存在しない場合はエラー

Option(val)みたいにしたらvalを判定してsome(val)none(T)のどちらかを返却してくれるともっと楽なのですが..。

今回は使用しませんでしたが、unsafeGet, map, filter, flatten, flatMapも便利ですね。

terminal

ターミナル/コンソールでエラー表示をするために使用しています。
エラーを表示してプログラムを終了させる関数errorutil.nimに作りました。

import os
import terminal

proc error*(msg: string, code: int = 1) =
  styledWriteLine(stderr, fgRed, "Error: ", msg, resetStyle)
  quit(code)

styledWriteLineは標準出力に赤色でメッセージを出力します。

strutils

文字列を数値型に変換するために使用しています。
実際に使用したのはparseFloatですがよく使うプロシージャもあわせて紹介します。

プロシージャ 変換後の型 備考
parseInt int -
parseFloat float -
parseBool bool y,yes,true,1,onがtrue. n,no,false,0,offがfalse
parseEnum enum デフォルト値を指定するとinvalidの時に採用される

他にも文字列を置換するreplaceが使われています。

その他

Slackegoの実装とは関係ありませんがハマったことです。

1..n表記でsequenceを作成する

sequtilstoSeqを使用します。

toSeq(1..100).filterIt(it > 90)

Get propert

Pythonにおけるget propertyのようなものをNimで使う方法です。
Propertiesを使います。

import strutils
import times

type Message* = object
  username*: string
  text*: string
  permalink*: string
  channel*: Channel
  ts: string

proc unixTime*(m: Message): int {.inline.} = m.ts.parseFloat.int
proc dateTime*(m: Message): DateTime {.inline.} = m.unixTime.fromUnix.inZone(local())

上記のMessage.tsは外部モジュールに公開されていません。
代わりにunixTimedateTimeが公開されており、それぞれint型、DateTime型を返却します。

Method call syntaxの記法を使うことでプロシージャ呼び出しではなくプロパティのように表記が可能です。
Method call syntaxはNimが持つ非常に興味深い機能の1つであり、ルーチン呼び出しの糖衣構文です。
これまでにも何度か登場していますが説明するのはこれが初めてになります。

通常の呼び出し方 Method call syntax
days(1) 1.days
parseFloat(“1.23”) “1.23”.parseFloat
int(parseFloat(“1.23”)) “1.23”.parseFloat.int
isZone(fromUnix(123456789), local()) 123456789.fromUnix.isZone(local())

Nimにはclassが無いのでobjectにMethod call syntaxでプロシージャを定義していく流儀なのでしょうか..。

総括

Pythonで作成したSlackegoをNimに移植しました。
yamlからjsonへの変更など移植しきれない部分もありましたが、ほとんどの機能はキレイに移植することができたと思います。

Python版のSlackegoもリポジトリは残してありますので興味がありましたらご覧下さい。

構成や書き方が多少変わっていますが大筋に変更は無いはずです。