CORDEA blog

Android application engineer

OCaml で Subcommand をパースする

Arg module のドキュメント読んでもどうすればいいかいまいち分からなかったので
OCaml で Subcommand とそのオプションを Arg module でパースする方法をメモしておきます。

何か間違っているところなどあればコメント、ツイートなど下さい。

実装

parse_dynamic を使用する

let usage = ""

let options = ref []

let hoge_options = [
    ("-hoge",
        Arg.Unit(fun () -> print_endline "hoge option"),
        "");
]

let huge_options = [
    ("-huge",
        Arg.Unit(fun () -> print_endline "huge option"),
        "");
]

let parse arg =
    if !Arg.current = 1 then
        match arg with
        | "hoge" -> options := hoge_options
        | "huge" -> options := huge_options
        | _ -> raise (Arg.Bad "Invalid subcommand")

let () =
    Arg.parse_dynamic options parse usage
;;

確認

$ ocamlbuild demo.native
$ ./demo.native hoge -hoge
hoge option
$ ./demo.native hoge -huge
demo.native: unknown option '-huge'.

  -help  Display this list of options
  --help  Display this list of options
$ ./demo.native huge -huge
huge option
$ ./demo.native huge -hoge
demo.native: unknown option '-hoge'.

  -help  Display this list of options
  --help  Display this list of options
$ ./demo.native demo
demo.native: Invalid subcommand.

  -help  Display this list of options
  --help  Display this list of options

参考

Io で Unicode エスケープ形式から元に戻す

Io language で API を叩いていて、
"\u3042" とか出てきて元に戻す必要があったので調べた

結論

私の見た限りでは、一つ method 呼べばなんとかなるようには見えなかったので、以下のようにしてとりあえず解決した
簡単な方法あったら教えてください

str matchesOfRegex("u[a-f0-9]{4}") replace(x, ("0x" .. (x string exSlice(1))) toBase(10) asNumber asCharacter)

str は元の文字列
こんな感じ

Io> str := "\u3042\u3042\u3042\u3042\u3042\u3042"
==> u3042u3042u3042u3042u3042u3042
Io> str matchesOfRegex("u[a-f0-9]{4}") replace(x, ("0x" .. (x string exSlice(1))) toBase(10) asNumber asCharacter)
==> ああああああ

解説っぽいやつ

最初の方は不要だと思うので途中から

x string exSlice(1)

ここの x には "u3042" が入っているはずなので、slice で u を捨てる (replace でよかったかも)
そのあと、16 進数から 10 進数に変換したいので

"0x" .. ...

ここで "0x" を足して "0x3042" とし、
その上で 10 進数に変換 (toBase(10)) する

そうするとここで得られるのは "12354" という文字列 (Sequence)
このまま asCharacter に渡すと怒られるので、Number に変換する (asNumber)

asCharacter は渡された値に対して有効な UCS マッピングがあればそれにあたる文字を返してくれる
なので例えば先ほどの 12354 を渡すと "あ" が返ってくる

Io> 12354 asCharacter
==>

Factor で http-request の response body が byte-array になった時

あるサービスの API レスポンスで body が byte-array になったので調べた

結論

charset が指定されていなくて byte-array になったら適切な形で decode する
utf-8 なら

http-get drop body>> utf8 decode json> .

詳細

サーバー側で charset が指定されていない場合は content-encoding が binary になるので
response body は byte-array になります.

例えば,以下のようなコードを書いた場合

"http://example.com" http-get drop .
charset が指定されていない場合
T{ response
    { version "1.1" }
    { code 200 }
    { message "OK " }
    { header
        H{
            { "connection" "close" }
            { "date" "Sun, 29 Jan 2017 08:48:16 GMT" }
            { "content-length" "12" }
            { "x-content-type-options" "nosniff" }
            { "server" "WEBrick/1.3.1 (Ruby/2.4.0/2016-12-24)" }
            { "content-type" "application/json" }
        }
    }
    { cookies { } }
    { content-type "application/json" }
    { content-encoding binary }
    { body B{ 123 34 115 116 97 116 117 115 34 58 49 125 } }
}
charset が指定されている場合
T{ response
    { version "1.1" }
    { code 200 }
    { message "OK " }
    { header
        H{
            { "connection" "close" }
            { "date" "Sun, 29 Jan 2017 08:47:25 GMT" }
            { "content-length" "12" }
            { "x-content-type-options" "nosniff" }
            { "server" "WEBrick/1.3.1 (Ruby/2.4.0/2016-12-24)" }
            { "content-type" "application/json; charset=utf-8" }
        }
    }
    { cookies { } }
    { content-type "application/json" }
    { content-charset "utf-8" }
    { content-encoding utf8 }
    { body "{\"status\":1}" }
}

charset が指定されていないと,いざ body 取って json として読み込もうとした場合に

Generic word json> does not define a method for the byte-array class. 

で怒られます.

こういう場合は

"http://example.com" http-get drop body>> utf8 decode json> .

のように decode して読み込む.

以上,charset 大事という話でした.

Nim の pegs モジュールを使う

この記事は Nim Advent Calendar 2016 24 日目の記事です。

クリスマスとか年末に絡めた何かを考えていたのですが、思いつかなかったので普通に書きます。

PEG とは

PEG について、解説的なものを入れようかと思ったのですが、
Wikipedia がとても詳しく、私が生半可な知識で書くのも良くないのでそちらをご覧ください。
https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar

使ってみる

使い方は PEG に慣れていればなんの抵抗もないはずですが、
私のように正規表現しか書いたことのない人間には馴染みの薄い物です。

では先日使用した json をそのまま流用して試してみます。
使用した jsonjson.org の JSON Example の一番上のやつです。
http://json.org/example.html
この json を example.json として保存し、使用しています。

"SortAs": "SGML",

json からkey, value (この場合は SortAs:SGML )を得たい場合
PEG ではこのように書くことができます

'"' {( \w )+} '":' \s+ '"' {( \w )+} '"' ','?

 

"Abbrev": "ISO 8879:1986",

この場合は

'"' {( \w )+} '":' \s+ '"' {( \w / \s / ':' )+} '"' ','?

となります。


これらの規則は非終端記号として定義できます。

grammer <- '"' {( \w )+} '":' \s+ '"' {( \w / \s / ':' )+} '"' ','?

key, value 部分をまとめて以下のような記述にもできます。

grammer <- '"' cap '":' \s+ '"' cap '"' ','?
cap <- {( \w / \s / ':' )+}


最後に、json 全体をパースしてみます

rule <- '{' \s+
    '"glossary":' \s+ '{' \s+
        title
        '"GlossDiv":' \s+ '{' \s+
            title
            '"GlossList":' \s+ '{' \s+
                '"GlossEntry":' \s+ '{' \s+
                    (glossDef / pair)*

glossDef <- '"GlossDef":' \s+ '{' \s+ pair '},' \s+
pair <- '"' cap '":' \s+ '"' cap '"' ','? \s+
title <- '"title":' \s+ '"' cap '",' \s+
cap <- {( \w / \s / [.,-:;] )+}


このままだと nim ではなく PEG の話になるので申し訳程度にコードと結果を載せておきます
ちなみに今回の json でしか機能せず汎用性は全くありませんので...

import pegs

const peg = """
rule <- '{' \s+
    '"glossary":' \s+ '{' \s+
        title
        '"GlossDiv":' \s+ '{' \s+
            title
            '"GlossList":' \s+ '{' \s+
                '"GlossEntry":' \s+ '{' \s+
                    (glossDef / pair)*

glossDef <- '"GlossDef":' \s+ '{' \s+ pair '},' \s+
pair <- '"' cap '":' \s+ '"' cap '"' ','? \s+
title <- '"title":' \s+ '"' cap '",' \s+
cap <- {( \w / \s / [.,-:;] )+}
"""
let str = "example.json".readFile()
if str =~ peg(peg):
    for i in 0..(matches.len - 1):
        if i < 2:
            echo "title: ", matches[i]
        else:
            if matches[i] != nil:
                if i mod 2 == 0:
                    echo "key: ", matches[i]
                else:
                    echo "value: ", matches[i]

この出力は以下の通りです。

title: example glossary
title: S
key: ID
value: SGML
key: SortAs
value: SGML
key: GlossTerm
value: Standard Generalized Markup Language
key: Acronym
value: SGML
key: Abbrev
value: ISO 8879:1986
key: para
value: A meta-markup language, used to create markup languages such as DocBook.
key: GlossSee
value: markup

参考

nim で json を扱う話

この記事は Nim Advent Calendar 2016 18 日目の記事です。

さて、ネタが被ったような気がしないでもないのですが
nim で json 扱うのが楽ですよって話をします。

json の扱いの簡単さはとても大切ですね。

今回例として使用した jsonjson.org の JSON Example の一番上のやつです。
http://json.org/example.html
この json を example.json として保存し、使用しています。

Version

Nim Compiler Version 0.15.2 (2016-12-18) [MacOSX: amd64]

json module を使う

json module を使えば json を簡単に扱うことができます

json -> JsonNode
import json

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        jsonObj = parseJson jsonStr
    echo jsonObj["glossary"]["title"].str
  • Result
example glossary
JsonNode -> json
import json

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        jsonObj = parseJson jsonStr
    echo $jsonObj
  • Result
{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossL...

簡単ですが、用途によってはどこか物足りません。
やっぱり object に詰めて欲しいですよね。

そこで出てくるのが marshal module です。

marshal module を使う

marshal module を使えば json <-> object が実現できます
準備として example.nim として以下を用意しました

type
    Base = object of RootObj

    Example* = object of Base
        glossary*: Glossary

    Glossary* = object of Base
        title*: string
        GlossDiv*: GlossDiv

    GlossDiv* = object of Base
        title*: string
        GlossList*: GlossList

    GlossList* = object of Base
        GlossEntry*: GlossEntry

    GlossEntry* = object of Base
        ID*: string
        SortAs*: string
        GlossTerm*: string
        Acronym*: string
        Abbrev*: string
        GlossDef*: GlossDef
        GlossSee*: string

    GlossDef* = object of Base
        para*: string
        GlossSeeAlso*: seq[string]
json -> object
import example, marshal

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        ex = to[Example] jsonStr
    echo ex.glossary.title
  • Result
example glossary
object -> json
import example, marshal

when isMainModule:
    let
        ex = Example()
    echo $$ex
  • Result
{"glossary": {"title": null, "GlossDiv": {"title": null, "GlossL...

自分で object に詰める

さきほどの marshal module はとてもシンプルなもので
例えば json の key と object の key を変えたい場合などに対応することはできません。(できなかったはず...)

そのため、json の key に proc などがあるとつらい感じになります。
そういう場合は、自分で object に詰めてしまうのが楽だと思います。

今回の json は string と array しか出てこないので実装はこの程度でした

import json, sequtils

proc `[]`(t: JsonNode, key: string): JsonNode =
    result = if t.hasKey(key): json.`[]`(t, key) else: nil

proc to[T: object](node: JsonNode, data: var T) =
    for k, v in data.fieldPairs:
        when v is string:
            v = node[k].getStr
        elif v is seq:
            if node[k] == nil:
                v = @[]
            else:
                v = node[k].elems.map(proc(x: JsonNode): string = x.str)
        else:
            node[k].to v
  • Usage
import example, json, sequtils

...

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        jsonObj = parseJson jsonStr
    var ex: Example
    jsonObj.to ex
    echo ex

実装も簡単ですし、依存も減るし、諸々の仕様に柔軟に対応できるので
状況次第では自分で実装してしまうのも悪くないかなと思っています。

それでは。

Jenkins の Android アプリ "Butler" を作った

Jenkins のクライアントアプリを作成しましたのでご報告。
もちろんオープンソースですが、今回ストアには公開していません。


github.com



ストアに公開しなかった理由についてですが、
アプリ的にではなく、API 的に公開できる品質にならなそうだと判断しました。
具体的には、ユーザー一覧など、多すぎるとタイムアウトしてしまう点です。
調べた限りではページング等できなさそうだったので、ここの改善は難しいと考えています。
そこらへんできる方法あれば教えていただけますと幸いです。

特徴

screen shot は GitHub をみてください。

アプリ的特徴

  • ビルド、プロジェクト、コンピューター、ユーザーの確認
  • ビルドの実行 (一部対応)

特徴

  • Kotlin
  • Data binding

まとめ

今買うの私的な挑戦は、Kotlin + Data binding の組み合わせを用いることで、
RxKotlin を使用しなかったことです。

Rx は流行りもあって広まっていますし、使用する人も増えていますが、
設計如何では、恐ろしいコードが生成される傾向があります。

そういうのをいくつかみてきたので、最近では Rx を使わない道を模索しており、今回のアプリはその一環です。
とはいえ、今回は MVVM を意識した作り (Data binding を使用しているので当たり前といえば当たり前ですが) なので
Rx を使ってもそれほど酷くはならなかったかもしれません。

あと、Kotlin は Observable とかあるので、使わなくても十分対応できるよねってところがあります。

Mackerel Client もまるごと書き直したい...


ストア公開する予定がないため、使いたい方はビルドして使ってみてください。apk もどこかに置く予定はあります。

Windows で perl5 の DBI モジュール使って嵌った話

perl5 の DBI モジュールを使用する際で
data_source の指定パスに日本語含まれてる場合に嵌った。


環境

>perl --version

This is perl 5, version 22, subversion 1 (v5.22.1) built for MSWin32-x64-multi-t
hread
(with 1 registered patch, see perl -V for more detail)

Copyright 1987-2015, Larry Wall

Binary build 2201 [299574] provided by ActiveState http://www.ActiveState.com
Built Jan  4 2016 12:12:58

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

結論

connect する際に utf8 にエンコードするといける
アプローチがあってるかどうかは謎

良いアプローチあったら教えてください


 

テスト

#!/usr/bin/perl
use strict;
use warnings;
use utf8;

use Encode qw/encode decode/;
use DBI;

my $file_enc = "cp932";

binmode(STDIN,":encoding($file_enc)");
binmode(STDOUT,":encoding($file_enc)");
binmode(STDERR,":encoding($file_enc)");

my $db_loc = $ARGV[0];

my $data_source = "dbi:SQLite:$db_loc";
my $dbh = DBI->connect($data_source);

$dbh->disconnect;
print "できた";
結果
>perl test.pl ほんとやめて\test.db
DBI connect('\x{0082}U\x{0082}n\x{0082}A\x{0082}a\x{0082}s\x{0082}A\test.db','',
...) failed: unable to open database file at test.pl line 21.
Can't call method "disconnect" on an undefined value at test.pl line 23.

修正

#!/usr/bin/perl
use strict;
use warnings;
use utf8;

use Encode qw/encode decode/;
use DBI;

my $file_enc = "cp932";

binmode(STDIN,":encoding($file_enc)");
binmode(STDOUT,":encoding($file_enc)");
binmode(STDERR,":encoding($file_enc)");

my $db_loc = $ARGV[0];

my $utf8_db_loc = decode($file_enc, $db_loc);
$utf8_db_loc = encode("utf8", $utf8_db_loc);

my $data_source = "dbi:SQLite:$utf8_db_loc";
my $dbh = DBI->connect($data_source);

$dbh->disconnect;
print "できた";
結果
>perl test.pl ほんとやめて\test.db
できた


python2 でこの類は慣れてるつもりだったけど
perl5 は格が違った
もう Windows + 日本語は本当につらい