2015年2月23日月曜日

Xcodeでgithubを利用

ネットでよく見る話題ですが、サクッといくこともあれば、苦労している記事もあります。自分の場合は苦労しました ^^;) どうもsshキーの登録を手動でしないといけないようなんですが、その辺が詳しく書いてあるものがありません。基本的にはコマンドラインで一回手動でやれば自動的にキーは記録してくれますが、ちょっとメモしておきます。(まあ、こんなのは昔のeclipseでもよくありました。少し前から全部GUIでできるようになったと思います。)

1.Xcodeでプロジェクトの作成
とりあえず何かプロジェクトを作成しましょう。あと重要なことですが、最初にローカルでGit管理するかという問い合わせがひっそりとあります。(デフォルトがオフです)これのチェックを入れておくことを忘れずに。(後からGit管理する方法もあるようですが、Xcodeのバージョンがあがるとどうなるか分からないので、面倒くさければ新規に作り直した方が早いと思います)

2.remoteリポジトリの登録
まずgithubに管理用のリポジトリを作成しておきます。(これ重要。いろんなツールを見てきましたが、新規に作成するリポジトリまで作成してくれる統合ツールは見た事ありません。おそらくgithubが新規リポジトリ作成のAPI(URL経由のね)は作ってないんでしょう。そうしないと、いたずらされかねませんしね。)
XcodeのSourceControlからConfigureで、remoteとしてgithubの先ほど作成したリポジトリを登録しておきます。

3.最初のcommitをローカルにする
とりあえず、ローカルのリポジトリにcommitしておきます。

4,remoteにpushする
ここが一番の難関です。Xcodeから行おうとすると、いかにもIDとパスワードを聞いてきて、うまくいきそうに見えますが失敗します。最初はプロジェクト以下の.gitにsshパスワードが入っていませんから。
ツールを使ってもいいんですが、最初だけは手動でgitコマンドでpushします。

$ git push -u origin master

このときgithubのユーザID、パスワードを聞かれますがここでsshキーが.git以下に登録されますから、あとはXcodeでも自由に操作できるようになります。

毎回思うことですが、gitって便利だけどどうしてもコマンドラインからの入力が避けれないな〜。(コマンドラインでbranchの操作とか、差分見ようと思うと地獄なんですが、どうして公式にはGUI作ってくれないのか。そのあたりはLinusさんの主義なんですかね。GUIあたりは周りに任して、自分は本質の部分だけに集中するとか。自分はコマンドライン、そんなに気にならないほうだからいいけど、若いもんは使ってくれないよ。emacsで編集、make、debugまでするのは会社ではもう自分だけじゃなかろうか。今時のは、統合開発ツールあるのが当たり前と思ってるし。自分としてはツールの裏で何動いているのかわからない方が怖いんだが。)

2015年2月22日日曜日

c++でcurlを使う:couchDBへのアクセス

c++でcouchDBにアクセスしようと思ったら、curlライブラリを組み込んでやりましょう。今回はとりあえず自分のメモ用にサンプル載せておきます。

#include <string> 
#include <iostream> 
#include <curl/curl.h> 

using namespace std;

size_t callbackWrite(char *ptr, size_t size, size_t nmemb, string *stream)
{
  int dataLength = size * nmemb;
  stream->append(ptr, dataLength);
  return dataLength;
}

int main()
{
  CURL *curl;
  CURLcode ret;

  curl = curl_easy_init();
  string chunk;

  if (curl == NULL) {
    cerr << "curl_easy_init() failed" << endl;
    return 1;
  }

  curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:5984/environment/");
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callbackWrite);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk);
  ret = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  if (ret != CURLE_OK) {
    cerr << "curl_easy_perform() failed." << endl;
    return 1;
  }

  cout << chunk << endl;

  return 0;
}

couchDBにenvironmentというデータベースを事前に作っておきます。これでうまくレスポンスが返ってきました。JSON形式で返ってきますので、後は前回のpicojsonでハンドリングするだけです。

2015年2月21日土曜日

JSONデータのハンドリング:c++

さてこれまでブラウザ上でJSONデータの扱いを調べてきましたが、これらは入力データの編集用と思って調べてきました。こっからが本題のc++によるJSONデータの扱いです。

もともと、本職はc++プログラムがほとんどなんで、いつも入出力のGUIに悩まされていました。それをCouchDBで楽したいというところで、色々調べていましたがだいたい目処がついてきたので、さてそれをc++プログラムでどうやってハンドリングしたらいいのか?というところまできました。

c++でJSONのパーサはいくつか見つかりましたが、picojsonがヘッダファイル一つというシンプルさでなかなか秀逸です。早速これを試してみました。

まず試験データとしてのJSONファイル(test3.json)です。

{"message":"success",
  "number":[1, 2],
  "person":{"name":"hoge", "age":38},
  "follow":[
 {"name":"hoge", "age":38},
 {"name":"huga", "age":42}
  ],
  "returnCode":0}

わざと複雑な階層構造を持たせています。このファイル名を引数として動くサンプルが以下です。

#include 
#include 
#include "picojson.h"

int main(int argc, char** argv)
{
  picojson::value v;
  std::ifstream infile;
  std::string inStr = "";

  infile.open(argv[1]);
  while (!infile.eof()) {
    std::string work = "";
    infile >> work;
    inStr += work;
  }
  infile.close();

  std::string err = parse(v, inStr);

  if (!err.empty()) {
    std::cerr << picojson::get_last_error() << std::endl;
    return 1;
  }
  
  // dump json object
  std::cout << "---- dump input ----" << std::endl;
  std::cout << v << std::endl;

  // accessors
  std::cout << "---- analyzing input ----" << std::endl;
  if (v.is()) {
    std::cout << "input is an object" << std::endl;
    //const picojson::object& o = v.get();
    picojson::object& o = v.get();
    for (picojson::object::const_iterator i = o.begin(); i != o.end(); ++i) {
      std::cout << i->first << "  " << i->second << std::endl;
    }
    
    std::cout << std::endl;

    // 一番外側のobjectの取得
    std::string& s1 = o["message"].get();
    //picojson::array& s1 = o["number"].get();
    std::cout << s1 << std::endl;

    std::cout << std::endl;

    // 値の変更
    o["message"] = (picojson::value)std::string("fail");

    // データの追加
    o.insert(std::map::value_type("piyopiyo", 10.0));

    for (picojson::object::const_iterator i = o.begin(); i != o.end(); ++i) {
      std::cout << i->first << "  " << i->second << std::endl;
    }
    
    std::cout << std::endl;

    // 内部のobject値の取得
    picojson::object& o1 = o["person"].get();
    std::cout << o1["name"].get() << " " << o1["age"].get() << std::endl;

    std::cout << std::endl;

    // array値の取得
    picojson::array& a1 = o["follow"].get();
    o1 = a1[1].get();
    std::cout << o1["name"].get() << " " << o1["age"].get() << std::endl;

    std::cout << std::endl;

    // serialize(JSON形式の文字列に戻す。JavaScriptのstringify()と同じ。)
    std::string str = picojson::value(o).serialize();
    std::cout << str << std::endl;

  }
  
  return 0;
}

実行すると以下の様な出力をだしてきます。

---- dump input ----
{"follow":[{"age":38,"name":"hoge"},{"age":42,"name":"huga"}],"message":"success","nu\
mber":[1,2],"person":{"age":38,"name":"hoge"},"returnCode":0}
---- analyzing input ----
input is an object
follow  [{"age":38,"name":"hoge"},{"age":42,"name":"huga"}]
message  "success"
number  [1,2]
person  {"age":38,"name":"hoge"}
returnCode  0

success

follow  [{"age":38,"name":"hoge"},{"age":42,"name":"huga"}]
message  "fail"
number  [1,2]
person  {"age":38,"name":"hoge"}
piyopiyo  10
returnCode  0

hoge 38

huga 42

{"follow":[{"age":38,"name":"hoge"},{"age":42,"name":"huga"}],"message":"fail","numbe\
r":[1,2],"person":{"age":42,"name":"huga"},"piyopiyo":10,"returnCode":0}

うまく動いていますね。階層構造の扱いも、値の変更も思い通りになります。
ちょっと面倒くさいのが、いちいちpicojson::value形式で読みこんだ後、データの操作のためにpicojson::object形式にしないといけないこと。JavaScriptでも同じような考え方でしたが何でですかね?picojsonの実装はヘッダファイル一つなので、少し読んでみると、picojson::valueではvector形式で読み込んでいるようですが、すぐにmap形式にしてしまいます。確かにこれならアクセスするのが簡単で、処理速度も早いんですがデータ項目の順番がkeyでソートされてしまい順番が変わってしまいます。JSONとしては問題ないんですが、デバッグ時に少し悩みそうです。

後、このサンプルではparse()関数を使っていますが、picojson::valueは、>>を使って直接読み込むことができます。わざわざサンプル用に、stringでファイルを読み込み、parse()関数を使うように変更しました。こっちの方がエラーの扱いがわかりやすいからなんですがね。

picojsonなかなかいいんですが、一つ難点を言えば、c++のtemplate機能を使い倒しているため、エラーが出てくると、わけのわからないエラーメッセージを出してくるのがつらいです。c++の機能なんで、しかたないんですが。


2015年2月14日土曜日

JSON形式のハンドリング

couchdbとアクセスするためJSONを扱えるよう、JavaScript/jQueryによるJSONの処理方法を調べてます。

$.ajax()でGETすると、JSON形式でcouchdbからの応答をObjectの型式で読み込みます。データにアクセスするのは楽なのですが、デバッグ用に文字列で全体を表示しようと思うとすごいめんどくさい。調べてみると、stringify()という関数がよくでてきます。以下の様なスプクリプトをstringify.jsとか保存しておいてjQueryを拡張して使います。

(function($) {
 $.extend({
  stringify: function stringify(obj) {
   
   var t = typeof (obj);
   if (t != "object" || obj === null) {
    // simple data type
    if (t == "string") {
     obj = '"' + obj + '"';
    }
    return String(obj);
   }
   else {
    var n, v, json = [];
    var arr = (obj && $.isArray(obj));
     
    for (n in obj) {
     v = obj[n];
     t = typeof(v);
     if (obj.hasOwnProperty(n)) {
      if (t == "string") {
       v = '"' + v + '"';
      }
      else if (t == "object" && v !== null) {
       v = jQuery.stringify(v);
      }
      json.push((arr ? "" : '"' + n + '":') + String(v));
     }
    }
    return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
   }
  }
 });
})(jQuery);

皆んな困ってるようで、ググってると結構いろんな所ででてきます。今度は、$.ajax()でPUTしようとすると、送信データの形式がObjectではなくJSON形式の文字列で渡さないといけません。 これではjQueryにstringify()がなぜ実装されていないのか訳がわかりません。 さらにググると、JavaScript1.7からJSON.stringify()というのが実装されているのがわかり、納得しました。結局、ライブラリとして作ったファイルは一瞬で必要なくなったかの様に思えたのですが、話はこれで終わりませんでした。

ところがどっこい、IE8だとこのJSONというオブジェクトが実装されてません。ブラウザに実装されているJavaScriptのバージョンをチェックするスクリプトがあったので、調べてみると1.3です!(IEだと正確にはJScriptですが)会社関係だとシステムが対応してるのがまだIE8というところが多いと聞いています。(XPがダメになった時に、IE6からやっとあがったばっかりという所が多いと思います) 結局、当面はstringify.jsを使うようにしたほうがいいようです。
ちなみに以下に、そのスクリプトを示します。


<SCRIPT Language="JavaScript">
// Java Script 1.0以降で動作する関数
function funcGetVer() { return "1.0" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.1">
// Java Script 1.1以降で動作する関数
function funcGetVer() { return "1.1" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.2">
// Java Script 1.2以降で動作する関数
function funcGetVer() { return "1.2" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.3">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.3" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.4">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.4" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.5">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.5" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.6">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.6" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.7">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.7" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.8">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.8" ; }
</SCRIPT>

<SCRIPT Language="JavaScript1.9">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "1.9" ; }
</SCRIPT>

<SCRIPT Language="JavaScript2.0">
// Java Script 1.3以降で動作する関数
function funcGetVer() { return "2.0" ; }
</SCRIPT>

<SCRIPT Language="JavaScript">
// Java Script のバージョンを取得して表示する関数
  function funcVerDisp()
{
var strVersion = funcGetVer() ;
alert( 'このブラウザーの Java Scriptのバージョンは\n\t' + strVersion + ' です。\n' ) ;
}
</SCRIPT>

なんか同じ関数を何度も定義してますが、SCRIPTでのlanguage指定がキモです。ここで対応するバージョンを指定していますが、同じ関数定義をすると最後のものが有効になります。そこでhtml内のbuttonからonclickでfuncVerDisp()をCallしてやるようにすれば、バージョン名をダイアログで表示してくれます。ちなみに私が普段使いしているchromeは1.7でした。(最初にこのテクニックを紹介していたサイトでは、SCRIPTの中で、Type="javascript"というtype属性をつけていましたが、それではうまく動きませんでした。調べてみるとtype属性はスクリプトバージョンには関係しないので、返ってそれ以降の指定が無効になってしまうようです。)

結局、stringify()の使い方ですが、以下のように使います。

var jsonStr = '{"message":"success", "returnCode":0}';
json = $.parseJSON(jsonStr);

var str = $.stringify(json);

最初にJSON形式の文字列をjQueryの関数でObject形式にしていますが、JavaScript1.7だと、JSON.parse()という同様のものがあります。しかしこれもIE8では使えないので、色んな環境で動かしたいならjQueryにまかせた方がいいようです。

2015年1月31日土曜日

CouchDB JavaScriptからの読み込みについて

CouchDBのUIを作ってみたくなり、htmlを書いてみます。せっかくなのでjQueryを使って、Ajaxを試して見たいと思ったのですが、ここからが苦難の始まりでした。

簡単なサンプルを書いてみたんですが、どうしてもNo 'Access-Control-Allow-Origin' header is present on the requested resource.というエラーがコンソールに出てきて失敗します。(ChromeのJavaScriptコンソールで確認しています)色々、調べてみたんですが、エラーの意味はXSSを防ぐため、ドメインが異なるJavaScriptを実行したり、読み込もうとするとでるエラーのようです。(数年前、これおを悪用したサイトの改竄が流行った記憶があります)しかし自分はローカル環境、あるいは家庭内の別PCで動かしているcouchdbから読もうとしているんですが、このエラーから逃げれません。
そこで、jQueryの問題なのかcouchdbの問題なのか区別するため、素に戻って単純にJavaScriptでローカルPCに置いたjsonデータを読むプログラムを書いてみました。

   function loadJson(id) {
    $('#msg').append("これから読みます:");

    var url = "http://localhost/~hogehoge/test1.json";
    var xmlHttp;

    xmlHttp = new XMLHttpRequest();
    xmlHttp.open("GET", url, false);
    xmlHttp.send(null);

    $('#msg').append(xmlHttp.responseText);

    $('#msg').append("うまくいった?");

   }

(これをやるために前回「ウェブ共有」の設定を調べました)
ところがMac上のchromeからでは相変わらず同じエラーが出ます。幾ら何でもこれはおかしいだろうと、調べてみるとchromeではセキュリティを厳しくしていて、ローカルPC上のファイルをhttp経由で読もうが、同じチェックをしていてエラーを出すそうです。ちなみに同じものをSafariで実行してみたら、あっさり動きました。(ちなみに同様のスクリプトをjQueryで書くと、Safariでもエラーがでます。どうもjQuery内部で同様のチェックをしているようで、簡単に自分のとこだけで試してみようと思っているのに難儀なことです)

さてCouchDBのUIを作ろうと思ったらなんとかcouchdbの設定で'Access-Control-Allow-Origin' のヘッダをつけるようにしないといけません。
現状、使用しているcouchdbは1.6ですが、1.4のドキュメントには以下の設定があるのがわかりました。

local.iniに以下の設定を追加。

[httpd]
enable_cors = true

[cors]
origins = *

特に[cors]の項目はデフォルトの設定ファイルには存在しておらず大丈夫かと思いましたが、うまくいきました。(ただこの設定は「全て許可する」という意味なんで、あまり推奨できませんが…本当は単にローカルPC上だけで動かしたいだけなら、origins=http://localhostとかするのがいいと思います。でもこれだと、http://127.0.0.1でアクセスすると拒否されてしまい、難しいところです。)
さて実際にcouchDBからデータを読み込んでみます。以下のプログラムで試してみました。(htmlにはボタンをつけて、以下のスクリプトを実行するようにしています)

   function readCouch(id) {
    $('#msg').append("これから読みます:");
    var url;
    url = 'http://localhost:5984/kakeibo';
    $.ajax({
     type: "GET",
     url: url,
     dataType:'json',
    }).done(function(data) {
     alert('読み込み');
     var str = parseJson(data);
     $('#msg').append(str);
    }).fail(function(xhr, textStatus, errorThrown){
     alert('error!!');
     console.log("NG:" + xhr.status);
     console.log("NG:" + textStatus.status);
     $('#msg').append("失敗しました");
    });

    $('#msg').append("うまくいった?");
   }

   function parseJson(json) {
    var ret = 'db_name : ' + json.db_name + '';
    ret = ret + 'doc_count : ' + json.doc_count;
    return ret;
   }

chromeで実行すると以下のようになります。(html上に、<p id=msg>が付けてあり、そこに追記するようにしています)


うまく読めましたね。

2015年1月23日金曜日

Mac Yosemiteでウェブ公開をする(apache2を動かす)

Lionから「設定」に「ウェブ共有」がなくなったそうです。まあ、昔からあまりに簡単にhttpdを起動できるので危ないな〜とは思ってましたが、いざなくなると困ります。Yosemiteではapacheを手動スタートしないといけません。(Lionsは試してなくて、Mavericksは使いましたが、そのときは試してませんでした。いきなりYosemiteに来たんで、気づかなかった)

$ sudo apachectl start

(passwdが聞かれます)

これで、http://localhost/をブラウザで開けるようになります。
この時のhomeは、/Library/WebServer/Documents/になります。(システムルートです)ただこれでは作業上面倒くさいので、従来の自分のhomeのSitesをホームにします。(この後設定ファイルを色々アンコメントしますが、デフォルトでそうなのか、あるいは古いバージョンからYosemiteにUpしたせいなのか正確なところはわかりません。自分はUpしてますので、設定しようと思ったファイルがすでにあるものの内容に問題があり、以下の通りに修正したので所々これでいいのか悩みました)

まず/etc/apache2/httpd.confでmod_userdirを有効にします。

$ sudo vi /etc/apache2/httpd.conf

mod_userdir.soをロードする行がコメントアウトされているので、アンコメントする。
(166行目付近の以下をアンコメントする。)

LoadModule userdir_module libexec/apache2/mod_userdir.so

# User home directories
Include /private/etc/apache2/extra/httpd-userdir.conf

(他のHPみると、phpのモジュールとかもコメントアウトされてしまうとか。そのあたりは必要に応じてということになりますが、基本安全側にコメントされてしまうようです。)

次にユーザーディレクトリを作成します。(これは昔のバージョンのものが自分は残っていました)

$ mkdir ~/Sites

ルート権限で、/etc/apache2/users/ユーザ名.confというファイルを作る。(書式がYosemiteのapache2.4になってから変わってます。以前は2.2だったそうで、サイトによって内容が若干変わってたりします。ただ少なくとも昔のままではダメなことは確かです。)

<Directory “/Users/ユーザ名/Sites/”>
DirectoryIndex index.html index.php
AllowOverride All
Options all
Require all granted
</Directory>

以下のファイルを修正し、アンコメントする。
$ sudo vi /etc/apache2/extra/httpd-userdir.conf

Include /private/etc/apache2/users/*.conf

これでapache2を再スタートすれば、http://localhost/~ユーザ名 で「ウェブ共有」できるようになります。(いくつかのHPでは、ユーザ名の頭の「~」が記されなくてうまくいかず、Yosemiteから変わったんだろうかと悩みましたが結局ここは昔のままのURLです。)
一度起動すれば、以降はマシンを再起動しても自動的に起動してくれます。


なんで急に「ウェブ公開」なんてやろうと思ったかというと、couchDBのUIをhtmlで作ってみたくなり、jQueryとかいじり始めたらどうもうまくいかず、基本のurlでのアクセスから試してみないとわからない、ということになったんです。ただ、ここからまた別の苦闘が始まるのですが、それはまた今度に。

2015年1月12日月曜日

CouchDB Replicatorについて

もう少しCouchDBの基本的な動作を確認します。今度はReplicator(複写)についてです。 作成したDBのBackUpや、あるいは複製を取る機能としてFutonにはReplicatorの機能があります。localにBackUpを保存してもいいんですが、折角なので別のPCにもCouchDBを設定して試してみます。(ちょっと別に興味があったので、MacBookAirにHomebrew入れてCouchDBをインストールしてみました) 

Replicatorの画面は以下の様な感じです。ここで簡単にkakeiboをMacBookAirに新規インストールしたCouchDBにコピーを作ります。(なお、先にMacBookAir側で内容は空でいいんですがコピー先のDBを作成しておく必要があります。どうもいきなり作成はしてくれないようです。PS:remoteから取ってくる場合は新規に作成するかと聞かれて、OKすれば自動的に作成してくれるようです。)


うまくいくとEvent欄にsessionの動作ログが表示されます。

さてここで実験です。localとMacBookAir側でわざとDBを異なる状態にしてReplicatorを動かしたらどうなるかを見てみます。まず、それぞれを以下の状態にします。(赤字がそれぞれ追加したドキュメントです)

local側

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900
5/14       魚            まぐろ       1000


MacBookAir側

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900
5/13     果物           みかん       600

相互にReplicateしてみると、それぞれ追加したドキュメントが相手に入ってくれます。


Replicate後

date    category      item       price
5/10     果物           バナナ       300
5/10       魚               鯛         1000
5/11     果物           みかん       300
5/11     果物           バナナ       300
5/12       魚               鯛           900
5/13     果物           みかん       600
5/14       魚            まぐろ       1000

その後、local側で5/13のドキュメントを削除した後、最後MacBookAir側にReplicateしてもMacBookAir側の5/13のドキュメントはすぐには削除されません。単に複写をしているだけではないようです。(しばらく時間がたってからReplicateすると相手のDBにも最新の状況が反映されます)

このあたりの動きとして関連して、先のReplicateするときボタンの横にCotinuousトグルがあるので、それをOnにしてReplicateすると連続して同期してくれます。Statusで動作していることが確認できます。

(ただこれは、local→MacBookAirの方向のReplicateなので、localの変更は自動的にMacBookAir側に反映されますが、MacBookAirの変更はlocalに自動的には反映してくれません。)
なお、この設定はマシンを再起動すると消えてしまいます。恒常的にReplicateさせるには別途設定が必要なようです。(当たり前か)