2020/04/12


僕はよく C++ 用の SQLite3 ライブラリを探し歩いていて、見つけるたびに「1行掲示板」を実装してみている。

ウェブサーバは crow というライブラリを使い、SQLite3 の部分だけ差し替えて試すという感じ。初代は MySQL 用に作った。

GitHub - mattn/crow-bbs
https://github.com/mattn/crow-bbs

これを SQLite3 向けに直したのが crow-bbs-sqlite3

GitHub - mattn/crow-bbs-sqlite3
https://github.com/mattn/crow-bbs-sqlite3

さらにそれを sqlpp11 向けに直したのが crow-bbs-sqlite3-sqlpp11

GitHub - mattn/crow-bbs-sqlite3-sqlpp11
https://github.com/mattn/crow-bbs-sqlite3-sqlpp11

sqlpp11 は SQLite3 専用という訳ではなく、connector を使って異なる RDBMS も扱える。

先日、C++ 用 SQLite3 ORM の sqlite_orm を見つけたのでこれも試してみた。

GitHub - fnc12/sqlite_orm: ❤️ SQLite ORM light header only library for modern C++

// SELECT AVG(id) FROM users auto averageId = storage.avg(&User::id); cout << " averageId = " << ave...

https://github.com/fnc12/sqlite_orm

作った物はこちら。

GitHub - mattn/crow-bbs-sqlite3-sqlite_orm
https://github.com/mattn/crow-bbs-sqlite3-sqlite_orm

sqlite_orm は SQLite3 専用ではあるものの、sqlite3 ネイティブライブラリの様に SQLite3 色が濃くなく、sqlpp11 の様に SQL も登場しない。初回のスキーマ構築で struct との bind を行う。

struct Post {
  int id;
  std::string text;
  std::string created;
};
auto storage = sqlite_orm::make_storage("bbs.db",
    sqlite_orm::make_table("bbs",
      sqlite_orm::make_column("id", &Post::id, sqlite_orm::autoincrement(), sqlite_orm::primary_key()),
      sqlite_orm::make_column("text", &Post::text),
      sqlite_orm::make_column("created", &Post::created)
));
storage.sync_schema();

全ての Post を得るのであれば1行で出来る。

auto posts = storage.get_all<Post>();

追加も簡単

Post post = {.text = q, .created = storage.select(sqlite_orm::datetime("now""localtime")).front()};
storage.insert(post);

1行掲示板くらいのアプリであればとても小さく書ける。

#include <memory>
#include <stdexcept>
#include "crow_all.h"
#include <sqlite_orm/sqlite_orm.h>

struct Post {
  int id;
  std::string text;
  std::string created;
};

int
main() {
  auto storage = sqlite_orm::make_storage("bbs.db",
      sqlite_orm::make_table("bbs",
        sqlite_orm::make_column("id", &Post::id, sqlite_orm::autoincrement(), sqlite_orm::primary_key()),
        sqlite_orm::make_column("text", &Post::text),
        sqlite_orm::make_column("created", &Post::created)
  ));
  storage.sync_schema();
  crow::SimpleApp app;
  crow::mustache::set_base(".");

  CROW_ROUTE(app, "/")
  ([&] {
    crow::mustache::context ctx;
    auto posts = storage.get_all<Post>();
    int n = 0;
    for(auto &post : posts) {
      ctx["posts"][n]["id"] = post.id;
      ctx["posts"][n]["text"] = post.text;
      ctx["posts"][n]["created"] = post.created;
      n++;
    }
    return crow::mustache::load("bbs.html").render(ctx);
  });

  CROW_ROUTE(app, "/post").methods("POST"_method)
  ([&](const crow::request& req, crow::response& res) {
    crow::query_string params(std::string("?") + req.body);
    char* q = params.get("text");
    if (q == nullptr) {
      res = crow::response(400);
      res.write("bad request");
      res.end();
      return;
    }

    Post post = {.text = q, .created = storage.select(sqlite_orm::datetime("now""localtime")).front()};
    storage.insert(post);
    res = crow::response(302);
    res.set_header("Location""/");
    res.end();
  });

  app.port(40081)
    //.multithreaded()
    .run();
}

ちょっと触ってみた感じだと SQLite3 を扱うのであれば一番便利かもしれない。

Posted at by



2020/02/10


vim-jp の Slack で「zsh の PATH 環境変数に相対パスを含んでいる場合、補完ができないけど意図的か」という話題が出たので調べてみた。

補完できない様にしているのはこの変更

39104: do not hash relative paths in findcmd() · zsh-users/zsh@b312abc
https://github.com/zsh-users/zsh/commit/b312abc93b3b8eae8feb4a9884b22f519a137c7f

結構古い変更。この変更が行われた理由を追ってみた所、メーリングリストでこの会話が見つかった。

Running 'type' causes false positive hashed command completion

Zsh Mailing List Archive Messages sorted by: Reverse Date , Date , Thread , Author Running 'type' ca...

http://www.zsh.org/mla/workers/2016/msg01583.html
$ zsh -f
% cd $(mtemp -d)
% touch sudofoo; chmod +x $_
% ./sudo<TAB>
<becomes>
% ./sudofoo <^C>
% type -w ./sudo
./sudo: none
% ./sudo<TAB>
./sudo    sudofoo*

That's wrong because ./sudo does not exist.  However, it's hashed:

% print $commands[./sudo]
/usr/bin/./sudo

To confuse matters further, even though "./sudo" is hashed, a subsequent
'type -w ./sudo' will print "none", because the hash node lacks the
HASHED bit in its .flags and the PATH_DIRS option is unset by default.

/usr/bin 対しては ./sudo が存在する為、sudofoo に対する ./sudo からの補完候補に sudo が出てきてしまう、これは混乱を生んでしまう」という物だった。これを回避する為に上記の変更で相対パスはハッシュしない様にしている。ちなみに bash や fish だと相対パスの中のコマンドも補完された。

相対パス上で sudofoo の一部 sudo が補完されてしまったとして困るのは、その相対パス内に危ないファイルを追加してしまった場合だろうと推測するが、そもそも相対パスを PATH に追加したい要件が僕には見つからなかった。おそらく自動的に node_modules/.bin 内のコマンドを扱える様にしたいといった物だと思う。もし zsh でやりたい人は direnv を使って動的に PATH を追加するのが良いと思う。

Posted at by



2019/11/30


以前、mruby から TensorFlow Lite を扱う為の mrbgems、「mruby-tflite」を書きました。

Big Sky :: MRuby の TensorFlow Lite バインディングを書いた。

以前 TensorFlow Lite の Go バインディングを書いたのだけど Big Sky :: TensorFlow Lite の Go binding を書いた。 Google launche...

https://mattn.kaoriya.net/software/lang/c/20190417102631.htm

KaoriYa さんに Coral EdgeTPU をプレゼントして頂いたので、mruby-tflite を Coral EdgeTPU 対応する為の mrbgems を書きました。

GitHub - mattn/mruby-tflite-edgetpu
https://github.com/mattn/mruby-tflite-edgetpu

元の mruby-tflite とはソースを分離してあります。build_config.rb に以下の様に追加して頂くだけで EdgeTPU を扱える様になります。

conf.gem :github => 'mattn/mruby-tflite'
conf.gem :github => 'mattn/mruby-tflite-edgetpu'
conf.gem :github => 'kjunichi/mruby-webcam'
conf.gem :github => 'qtkmz/mruby-gd'

mruby-tflite と mruby-tflite-edgetpu を連携するには以下の様にインタプリタに対してオプション設定する必要があります。

#! ./bin/mruby

# オプションを生成
options = TfLite::InterpreterOptions.new

# Coral EdgeTPU のデバイス一覧から 0 番目を delegate として割り付け
options.add_delegate TfLite::EdgeTPU.new(TfLite::EdgeTPU.devices[0])

model = TfLite::Model.from_file 'mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite'
interpreter = TfLite::Interpreter.new(model, options)
interpreter.allocate_tensors
input = interpreter.input_tensor(0)
output2 = interpreter.output_tensor(1)
output3 = interpreter.output_tensor(2)
wanted_width = input.dim(1)
wanted_height = input.dim(2)
wanted_channel = input.dim(3)
data = Array.new(wanted_height * wanted_width * wanted_channel, 0)
labels = {}
File.read('coco_labels.txt').lines.each do |x|
  kv = x.split('  ')
  labels[kv[0].to_i] = kv[1].strip
end

cam = Webcam.new(ARGV[0]||0)
cam.set_size(wanted_width, wanted_height)
cam.each(true) {|img|
  decoded = GD::Image.new_from_jpeg_data(img)
  (0...wanted_width).each do |x|
    (0...wanted_height).each do |y|
      pixel = decoded.get_pixel(x, y)
      offset = (y * wanted_width + x) * wanted_channel
      data[offset..offset+2] = [decoded.red(pixel), decoded.green(pixel), decoded.blue(pixel)]
    end
  end
  decoded.destroy
  input.data = data
  interpreter.invoke
  result = []
  output3.data.each_with_index do |v, i|
    next if v < 0.6
    result.push([v, output2.data[i]])
  end
  result.sort{|a, b| b[0] <=> a[0] }.take(5).each_with_index do |v, i|
    s = v[0]
    i = v[1]
    puts "#{labels[i]} #{i} #{s}"
  end 
  puts "---"
}

現状、mruby には OpenCV の様に加工した MAT を表示できる mrbgems が存在しない為、kjunichi さんが作っておられる mruby-webcam で画像を取り込み、qtkmz さんの mruby-gd で画像を解析し、そこに何が映っているのかを端末に出力しているだけですが、今後 OpenCV の様な mrbgems が登場すれば mruby でもリアルタイムで画像を解析し、認識したオブジェクトに枠や名称を描画する事も出来る様になるかもしれません。

mruby-tflite-edgetpu
Posted at by