2014/07/18

Recent entries from same category

  1. RapidJSON や simdjson よりも速いC言語から使えるJSONライブラリ「yyjson」
  2. コメントも扱える高機能な C++ 向け JSON パーサ「jsoncpp」
  3. C++ で flask ライクなウェブサーバ「clask」書いた。
  4. C++ 用 SQLite3 ORM 「sqlite_orm」が便利。
  5. zsh で PATH に相対パスを含んだ場合にコマンドが補完できないのは意図的かどうか。

先日、github で crow という、python の flask からインスパイアされた C++ 製 micro web framework を見つけました。

ipkn/crow - GitHub
https://github.com/ipkn/crow

ルーティングの書き方が flask ぽく、かつモダンな C++ な書き方だったの気に入りました。

crow::Crow app;

CROW_ROUTE(app, "/")
    .name("hello")
([]{
    return "Hello World!";
});
app.port(18080)
    .multithreaded()
    .run();

何か作ってみたくなったので、lingr の bot を書いてみました。crow には JSON パーサ(シリアライザ)が同梱されているので API サーバを作るのも簡単そうです。

#include "crow.h"
#include "json.h"

#include <iostream>
#include <sstream>
#include <regex>

static std::vector<std::string>
str_split(const string s, const string d) {
  std::vector<string> r;
  std::string::size_type n;
  std::string b = s;
  while ((n = b.find_first_of(d)) != b.npos) {
    if(n > 0) r.push_back(b.substr(0, n));
    b = b.substr(n + 1);
  }
  if(b.length() > 0) r.push_back(b);
  return r;
}

static std::string&
str_replace(std::string& s, const std::string p, const std::string r) {
  std::string::size_type n(s.find(p));
  while (n != std::string::npos) {
    s.replace(n, p.length(), r);
    n = s.find(p, n + r.length());
  }
  return s;
}

class logger : public crow::ILogHandler {
public:
  void log(string message, crow::LogLevel level) override {
    cerr << message;
  }
};

int
main() {
  std::vector<std::pair<const std::regex, const std::string>> resp;
  std::ifstream ifs("pattern.txt");
  if (ifs.fail()) return 1;
  string buf;
  while(ifs && getline(ifs, buf)) {
    auto t = str_split(buf, "\t");
    if (t.size() != 2continue;
    std::cout << "Loading pattern: " << t[0] << "=" << t[1] << std::endl;
    resp.push_back(make_pair(std::regex(t[0]), t[1]));
  }
  ifs.close();

  crow::Crow app;

  CROW_ROUTE(app, "/")
    ([]{
        return "lingr-cppbot";
    });

  CROW_ROUTE(app, "/lingr")
    ([&](const crow::request& req){
      auto x = crow::json::load(req.body);
      if (!x) return crow::response(400);

      std::stringstream os;
      for (const auto& e : x["events"]) {
        std::string t = e["message"]["text"].s();
        for (const auto& r : resp) {
          std::smatch matches;
          if (!regex_match(t, matches, r.first)) continue;
          std::string output = r.second;
          for (std::smatch::size_type i = 0; i < matches.size(); ++i) {
            std::stringstream ss;
            ss << "$" << i;
            str_replace(output, ss.str(), matches[i]);
          }
          os << output;
          break;
        }
      }
      return crow::response{os.str()};
    });

  crow::logger::setLogLevel(crow::LogLevel::INFO);
  crow::logger::setHandler(std::make_shared<logger>());

  uint16_t port;
  std::stringstream ss;
  ss << getenv("PORT");
  ss >> port;
  if (port == 0) port = 8080;
  app.port(port).multithreaded().run();
}

// vim:set et:

仕組みは至って簡単で、カレントディレクトリにある pattern.txt という、以下の様なファイルを読み込み、正規表現(タブ文字で区切られた左側)に対する置換文字列(右側)を設定します。

^あー$  いー
^いー$  うー
^うー$  えー
^えー$  おー
^おー$  もうええやろ!
^(.+)(大好き|だいすき)  $1は俺の物だ。勝手に呼び捨てにするな。

起動して、正規表現にマッチする発言を受信すると、置換文字列をサブマッチなどで入れ替えて返答します。

cppbot

サブマッチ置換が使えるので、ある程度人間っぽい発言も可能ですし、gh:mattn という文字列から https://github.com/mattn という返答をさせてアンカーを作る等の応用例もあります。(参考資料)

crow は内部でスレッドを使っており、複数同時リクエストにも応答出来る様です。色々遊べそうなので試してみてはいかがでしょうか。

Posted at by | Edit