デザイナーの為のSass/Compassのすゝめ

三月とはいえすっかりもう桜が咲き、杉花粉で目と鼻を塞がれ
これで耳と口も塞がったらさながら『ライ麦畑でつかまえて』の主人公だななどと思っています。ゆーじろーです。

今回はSass/CompassについてのHowtoを書かせていただきます。

SASS
compass

Continue reading

CakePHPでMongoDBを使う基本

こんにちは、ピンキリエンジニアのひろゆきです。

最近は久しぶりにPHPを書いており、MongoDBも始めてみたので、CakePHPとMongoDBについて少し書いてみたいと思います。

今回はちょこっと触った程度の、CakePHPからMongoDBを使用するための内容になります。

※CakePHPは1.xの時に少し使った、MongoDBはほぼ初心者な状態です

今回使用した環境は

・AmazonEC2のRedhat
・PHP 5.3.3
・Apache 2.2.15
・MongoDB 2.2.3
・CakePHP 2.3

になってます。

これでCakePHPからMongoDBを操作してみたいと思います。

まずはMongoDBをインストールします。

リポジトリの追加

sudo vi /etc/yum.repos.d/10gen.repo

リポジトリ設定

[10gen]
name=10gen Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64
gpgcheck=0
enabled=0

インストール

sudo yum install mongo-10gen-server.x86_64 mongo-10gen.x86_64 --enablerepo=10gen

起動

sudo /etc/init.d/mongod start

これでMongoDBの準備はOKです。

次はこれをCakePHPから操作します。

CakePHPではデフォルトでいくつかのDBを使用でき、簡単にWEBサイトを構築出来ますが、
MongoDBについてはichikawayさんが提供しているPluginを使用することで可能になります。

https://github.com/ichikaway/cakephp-mongodb
※githubから

cloneするかもしくは別の方法で

app/Plugin/MongoDB

に配置します。

次にdatabase.phpを変更してMongoDBへの接続を定義します。

public $default = array(
	'datasource' => 'Mongodb.MongodbSource',
	'host' => 'localhost',
	'database' => 'your_database_name',
	'port' => '27017',
	'encoding' => 'utf8',
);

では、実際に操作をしてみます。

データを登録する

$data = array();
$data['value1'] = 1;
$data['value2'] = array(1,2,3);
$data['value3'] = array('a' => 1, 'b' => 2, 'c' =>3);
$data['value4'] = array('a' => array(1,2,3), 'b' => array(4,5,6));
$this->Sample->save($data);

発行されたクエリ

db.samples.insert( {"value1":1,"value2":[1,2,3],"value3":{"a":1,"b":2,"c":3},"value4":{"a":[1,2,3],"b":[4,5,6]},"modified":"MongoDate(0.39200000 1363326797)","created":"MongoDate(0.39200000 1363326797)","_id":ObjectId ("5142b74d2dc6b69371000002")} , true)

登録された内容

{ "_id" : ObjectId("5142b74d2dc6b69371000002"),
  "value1" : 1, 
  "value2" : [ 1, 2, 3 ], 
  "value3" : { "a" : 1, "b" : 2, "c" : 3 }, 
  "value4" : { "a" : [ 1, 2, 3 ], "b" : [ 4, 5, 6 ] }, 
  "modified" : ISODate("2013-03-15T05:17:21.393Z"), 
  "created" : ISODate("2013-03-15T05:17:21.393Z") }

データを取得する

$this->Sample->find('all');

発行されたクエリ

db.samples.find( [], [] ).sort( [] ).limit( 0 ).skip( 0 )

取得した内容

array(1) { 
 [0]=> array(1) {
  ["Sample"]=> array(7) { 
   ["value1"]=> int(1) 
   ["value2"]=> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } 
   ["value3"]=> array(3) { ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) } 
   ["value4"]=> array(2) { ["a"]=> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } ["b"]=> array(3) { [0]=> int(4) [1]=> int(5) [2]=> int(6) } } 
   ["modified"]=> object(MongoDate)#38 (2) { ["sec"]=> int(1363325551) ["usec"]=> int(106000) } 
   ["created"]=> object(MongoDate)#39 (2) { ["sec"]=> int(1363325551) ["usec"]=> int(106000) } 
   ["id"]=> string(24) "5142b74d2dc6b69371000002"
  }
 }
}

データが作成されました。

今回はidを指定せず自動で生成しています。

データを更新する

$list = $this->Sample->find('all');
$data = $list[0]['Sample'];
$data['value1'] = 100;
$data['value5'] = 'Hello World';
$this->Sample->save($data);

発行されたクエリ

db.samples.update( {"_id":ObjectId ("5142b74d2dc6b69371000002")}, {"$set":{"value1":100,"value2":[1,2,3],"value3":{"a":1,"b":2,"c":3},"value4":{"a":[1,2,3],"b":[4,5,6]},"modified":"MongoDate(0.39200000 1363326797)","created":"MongoDate(0.39200000 1363326797)","value5":"Hello World"}}, {"multiple":false} )

変更後の内容

array(1) {
 [0]=> array(1) {
  ["Sample"]=> array(8) {
   ["created"]=> object(MongoDate)#38 (2) { ["sec"]=> int(1363326797) ["usec"]=> int(392000) } 
   ["modified"]=> object(MongoDate)#39 (2) { ["sec"]=> int(1363326797) ["usec"]=> int(392000) } 
   ["value1"]=> int(100) 
   ["value2"]=> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } 
   ["value3"]=> array(3) { ["a"]=> int(1) ["b"]=> int(2) ["c"]=> int(3) } 
   ["value4"]=> array(2) { ["a"]=> array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } ["b"]=> array(3) { [0]=> int(4) [1]=> int(5) [2]=> int(6) } } 
   ["value5"]=> string(11) "Hello World" 
   ["id"]=> string(24) "5142b74d2dc6b69371000002" } } }

CakePHPを使用しているので、同idがデータに存在する場合は更新にしてくれます。
新しく追加したカラム「value5」もちゃんと反映されています。

データを削除する

$list = $this->Sample->find('all');
$data = $list[0]['Sample'];
$this->Sample->delete($data['id']);

発行されたクエリ

db.samples.remove( {"_id":ObjectId ("5142b74d2dc6b69371000002")} )

指定されたidのデータが削除されました。

上記でCRUDについての操作が行えました。

Pluginのおかげで、これくらいであればサクッと簡単にできちゃいます。

まだまだ基本的な部分を触りながら確認している状況ですので、もう少し慣れたら詳しくメモも含めてログを残したいと思ってます。

MongoDBならではの操作方法についてとか書いておくと良いかなー、と。

以上、ありがとうございました!!

LinuxのシェルコマンドでFuelPHP開発環境を構築

こんにちは。金髪エンジニアのみきあらいです。今日でGeNERACEに入って2ヶ月めになります。
今回はLinuxのシェルコマンドを使ってFuelPHPの開発環境を構築する方法について書きます。

個人で開発環境を設定するのなら一人でコマンドを叩けばいいのですが、複数人で同じことをする場合、
各人がそれぞれコマンドを叩くのはあまり効率的とは言えません。
そこで、「これさえあれば一瞬で開発環境が作れる!」というシェルスクリプトを書きました。

☆下準備

  • GitのリポジトリにFuel PHPをフォークする。
  • DNSにサブドメインを設定する。
  • Apacheが入っているLinuxサーバーを準備する。
  • PHP5.3がApacheで起動していることを確認する。
  • サーバー用のssh鍵を作成する。(Git使用時に必要)
  • configファイル、xxx_setupを書いてサーバー上に置く。(注意:本物のconfigファイルではなく、あくまで別のconfigファイルを作成するためのものなので、拡張子を.confにしないこと!)

xxx_setupの中身

<VirtualHost *:80>
        ServerName 任意の名前(例:xxx)
        DocumentRoot 任意のドキュメントパス(例:/home/xxx/www/public)
        DirectoryIndex 任意のファイル(今回はindex.phpとindex.htmlを設置)
</VirtualHost>

ServerNameに記載した名前でブラウザから参照することができます。

☆今回やりたいこと

  • 作成したconfigファイル、xxx_setupの中身をコピー。
  • コピーしたファイルをもとに”各ユーザー名.conf”を作る。
  • コピー元ファイルでユーザーごとにあわせて置換したい部分を置換。(上記の例だとサーバー名やルートパス内の”xxx”を各ユーザー名に置換)
  • Gitに置いたFuelPHPをサーバーにgit cloneする。
  • Apacheを再起動する。

シェルスクリプトを書きます。
今回は”shelltest.sh”というシェルスクリプトを作成します。

vi shelltest.sh

“shelltest.sh”の編集を行います。
まず、シェルスクリプトを/bin/shで実行させます。

#!/bin/sh

echoの後ろに変数や文字列を指定します。今回はコメントを文字列として表示します。
すでに”ユーザー名.conf”がある場合も考慮し、スーパーユーザー権限で事前に削除します。-rfをつけてエラーなしで削除します。

echo "setting for httpd"
sudo rm -rf /etc/httpd/conf.d/${USER}.conf

事前に作成したxxx_setupを、”ユーザー名.conf”としてコピーします。

sudo cp -rf /etc/httpd/conf.d/xxx_setup /etc/httpd/conf.d/${USER}.conf

コピー元のxxx_setupの中には”xxx”という文字列があり、それを各ユーザー名に変えたいので、置換するためのコマンドを書きます。

sudo sed -i "s/xxx_setup/"${USER}"/g" /etc/httpd/conf.d/${USER}.conf

変数として${USER}と書くと、そのまま${USER}という文字列として置換されてしまうので、${USER}以外をダブルクオーテーションで囲みます。

次に、confで作成したDocumentRootにFuelPHPを配置します。今回は/home/ユーザー名/www以下にFuelPHPを置きます。
/home/ユーザー名/wwwが既にあることを想定して一度エラーなしで削除し、新たにディレクトリを作り直します。

echo "delete old folder"
rm -rf /home/${USER}/www
echo "settings"
mkdir /home/${USER}/www

/home/${USER}/wwwに移動し、Gitに置いたFuelPHPをサーバーにクローンします。
git@github.com:の後ろにはGithubの各人のユーザー名を書くので、変数として$1を引き渡します。

echo "clone git@github.com:"$1"/fuel.git "
cd /home/${USER}/www/
git clone git@github.com:$1/fuel.git

今回はクローンするだけとしましたが、中央リポジトリのリモートリポジトリを作成したい場合は以下のコマンドを書きます。
(例:fuel.gitのクローン元があるGithubのユーザー名=”UserXXX”、リモートリポジトリ名=”upstream”)

git remote add upstream git@github.com:UserXXX/fuel.git

例として”dev”というブランチを作成し、devブランチに切り替えてpullとpushを行います。

git checkout dev
git pull upstream dev
git push origin dev

最後に作成したconfファイルを反映させるためにApacheを再起動させます。
restartだとエラーが起きたときにApache自体が落ちてしまうので、それを避けるためにgracefulコマンドを使います。

echo "graceful httpd"
sudo /etc/rc.d/init.d/httpd graceful

“shelltest.sh”の編集モードを終了します。これで”shelltest.sh”の完成です。

☆”shelltest.sh”の実行!
早速作成した”shelltest.sh”を実行しましょう。引数$1としてLinuxに渡すGitHubの各人のユーザー名を実行時に入力します。

cd shelltest.shが格納されている場所
sh shelltest.sh GitHubの各人のユーザー名

これでシェルスクリプトを叩くだけで、FuelPHPの開発環境ができるようになります!
シェルスクリプト全体はこちらのgistを参照してください。

今回はここまでです。ご覧いただきましてありがとうございます。

参考記事一覧

Andengineで使うスプライトアニメーション素材作成

皆様はじめまして、今月からGeNERACEに参画してます。マークアップエンジニアのユージローです。

さて、いくつか前のポストでも紹介されてる通り、弊社ではAndengineを使用して開発を行っています。

Andengine = java

マークアップエンジニア = html,css,javascript,etc

仕事が無い!(待て

そんなこんなで慣れないjavaを少し触りつつ、僕は現在キャラクターなどの画面素材を担当しています。

Andengineはjavaですので、アニメーションの実装方法もcssアニメーションなどではなく、javaを用いてのアニメーションが主になりますが、スプライトアニメーションは昔ながらのフィルムビデオと同じ形式です。

全てのコマを一枚の画像として書き出さなければなりません。

スプライトシートは縦横に繰り返すものもありますが、単純なものであれば
縦もしくは横のみの繰り返しで済みます。

このスプライトシートですがアナログな作業でやるのは効率的ではないのでツールを使います。
作成ツールも下記のように幾つかあるのですが、Flash Pro CS6からFlashでもスプライトシートの作成が可能になりました。

インストールタイプのツール:http://www.codeandweb.com/texturepacker
ブラウザアプリ:http://draeton.github.com/stitches/

ということで今回はFlash Pro CS6を使用した場合のスプライトシート作成手順です。

ステップ1:Flashアニメの書き出し

Flashアニメの作成方法は端折るとして、ここでは作成したFlashアニメをpng画像に書き出します。キャプチャのようにメニューの『ファイル』➡『書き出し』➡『ムービーの書き出し』を選択します。

ss2013-02-22-12.40.08

フォーマットは今回背景有の為jpegでもgifでも良いのですが、変更で透過させる可能性も考えてpngシーケンスを選択しました。ss2013-02-22-12.58.30

保存するとタイムラインに沿った画像が全て吐き出されこのようになります。
無事に書き出せました。

スクリーンショット-2013-02-22-13.07.54

ステップ2:ライブラリへの取り込み

ここでまたFlash Proで新規ファイルを作成します。
新規ファイルを作成したら、メニューの『ファイル』➡『読み込み』➡『ライブラリに読み込み』を選択し、先ほど書き出したpng画像を全て読み込みます。
スクリーンショット-2013-02-22-13.22.39

ステップ3:スプライトシートの生成

読み込みが完了したら、今度はライブラリウインドウから全ての素材を選択して右クリック➡『スプライトシートを生成』を選択します。

スクリーンショット-2013-02-22-13.55.21

最後です。
スプライトシートの出力と書かれている部分でシートの幅と高さや、素材間の距離、背景色などの調整を行い書き出して完了です。
スクリーンショット-2013-02-22-14.01.50

シートの大きさなどは実際の実装にも影響する為、事前に認識のずれが無いかなどの確認が必要です。

また今回は新規ファイルを作成しましたが、Flashアニメーションの任意のキーフレームシンボルからスプライトを生成することも可能になっています。

ご覧いただきありがとうございました。

Andengineを使ってみる その1

GeNERACE laboを御覧の皆様、はじめまして。

ピンキリエンジニアのひろゆきです。

先日からAndengineを使っていますが、日本語の紹介があんまりないので書いてみます。

少しでもお役に立てば幸いです。

本家はここ→ http://www.andengine.org/

用意したものはAndroid開発環境があるMac、Eclipse、DLしたAndengine。

プロジェクト作成などはこちらを参考にしてください。

では、簡単ではありますが、サンプルを記載します。

※SampleScene.javaを作成して動かしています

文字を出力する。

// フォントを指定
Texture texture = new BitmapTextureAtlas(activity.getTextureManager(), 480, 800, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
Font font = new Font(activity.getFontManager(), texture, Typeface.DEFAULT_BOLD, 30, true, Color.WHITE);
activity.getTextureManager().loadTexture(texture);
activity.getFontManager().loadFont(font);

// 出力する文字を設定
String str = "hello andengine!!";
Text text = new Text(0, 0, font, str, str.length(), new TextOptions(HorizontalAlign.LEFT), activity.getVertexBufferObjectManager());
attachChild(text);

まずはTextureの生成から使用したいFontを指定します。
それをActivityに設定して、Textにて文字を生成します。

画像を表示する

// 使用する画像名
String fileName = "button.png";

// 使用する画像の設定
BitmapTextureAtlas bta = new BitmapTextureAtlas(activity.getTextureManager(), 100, 100, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
activity.getEngine().getTextureManager().loadTexture(bta);
ITextureRegion btr = BitmapTextureAtlasTextureRegionFactory.createFromAsset(bta, activity, fileName, 0, 0);
Sprite sprite = new Sprite(0, 0, btr, activity.getVertexBufferObjectManager());
sprite.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
attachChild(sprite);

ボタン画像を表示する

// 使用する画像名
String btnName = "button.png";
String btnPressName = "button_press.png";

// 使用する画像の設定
BuildableBitmapTextureAtlas bta = new BuildableBitmapTextureAtlas(activity.getTextureManager(), 100, 200);
ITextureRegion trBtn = BitmapTextureAtlasTextureRegionFactory.createFromAsset(bta, activity, btnName);
ITextureRegion trPresseBtn = BitmapTextureAtlasTextureRegionFactory.createFromAsset(bta, activity, btnPressName);
try {
    bta.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 0, 0));
    bta.load();
} catch (TextureAtlasBuilderException e) {</em>
    Debug.e(e);
}

// 通常時と押された時の画像を指定する
ButtonSprite buttonSprite = new ButtonSprite(0, 0, trBtn, trPresseBtn, activity.getVertexBufferObjectManager());
buttonSprite.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
attachChild(buttonSprite);

ボタン画像を押した時に処理を行う

public class SampleScene extends Scene implements ButtonSprite.OnClickListener {

ButtonSprite.OnClickListenerを定義します。

// 識別タグ
buttonSprite.setTag(1);
// リスナー
buttonSprite.setOnClickListener(this);
// 認識するエリア登録
registerTouchArea(buttonSprite);

上記で表示したボタン画像に対して、この処理を加えます。

@Override
public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, float pTouchAreaLocalY) {
    if (pButtonSprite.getTag() == 1) {
        // ボタン画像を大きくする
        buttonSprite.setScale(1.2f);
    }
}

どのボタン画像が押されたかはTagで識別します。

画像を動かす

// Handloerを定義する
public TimerHandler timeHandler = new TimerHandler(1f / 60f, true, new ITimerCallback() {
    @Override
    public void onTimePassed(TimerHandler pTimerHandler) {
        // Y軸に移動させる
        sprite.setPosition(sprite.getX() ,sprite.getY() + 1);
        // 拡大させる
        sprite.setScale(sprite.getScaleX() * 1.01f, sprite.getScaleY() * 1.01f);
        // 透明にする
        sprite.setAlpha(sprite.getAlpha() * 0.99f);
    }
});

これだけでは画像は動きません。
Handlerは登録する必要があります。

registerUpdateHandler(timeHandler);

動きを止めたいときは

unregisterUpdateHandler(timeHandler);

とします。
慣れてないときは、これを忘れて動かない!ということが多々ありました。

Sceneを遷移させる

NextScene scene = new NextScene(activity);
activity.getEngine().setScene(scene);

SceneからActivityを遷移させる

activity.startActivity(new Intent(activity, NextActivity.class));
activity.finish();

現在のActivityを終了して、次のActivityを呼び出します。

他にAndengineの機能は多々ありますが、調べきれていない部分などもありますので、次回に続きたいと思います。

ご覧頂きありがとうございました。

GitとGitHubを使いこなすためのメモ

こんにちは。2月から入った新人のあらいみきです。今日から「爆速」で開発できるエンジニアになるために頻繁にメモを書き残します。
第一弾のテーマはGitです!

macでGitを使いこなすための簡単なメモ。

1. 下準備

☆用意するもの

  • Xcodeがインストールされていて、Command Line Toolsが入っているmacbook

☆インストールするもの

  • Tower

☆GitHubの設定をする。

  • GitHub https://github.com/

☆Git coreをmac portでダウンロード

$ sudo port install git-core

☆Gitのバージョンを確認

$ git version

2. Gitを使う

☆登場人物

  • 中央リポジトリ…メインユーザーが持つプロジェクトのリポジトリ。
  • リモートリポジトリ…各人が中央リポジトリからforkしたリポジトリ。メインユーザーではなく各人のリポジトリとなる。
  • ローカルリポジトリ…リモートリポジトリからローカルへクローンして作るリポジトリ。

☆Gitのおさらい

  • fork…自分のGitのリポジトリに任意のGitのリポジトリをコピーする。
  • pull…任意のリモートリポジトリの変更を自分のところに反映させる。
  • push…自分のリモートリポジトリに、自分のローカルでの変更を反映させる。commitとセットで行うとわかりやすい。
  • commit…自分がローカルで行った変更を確定させる。commitしただけだと変更が自分のリモートリポジトリ反映されないことに注意!(svnとは違う)
  • pull request…自分のリモートリポジトリの変更を中央リポジトリに反映させたい場合は、中央リポジトリに対してpull requestを送る。
  • master…リポジトリの大本命。ここを簡単に変えられないように枝分かれのリポジトリ(=branch)を作る。
  • 中央リポジトリの設定について…Git Flowに似た運用をしているので、中央リポジトリの更新用にリモートリポジトリを追加します。

    中央リポジトリの設定について詳細は後述

☆ローカルにgitのリポジトリを置くディレクトリを作る。

$ mkdir Git用ディレクトリ名

☆GitHubでコピーしたいリポジトリをforkする。

☆forkしたリポジトリのクローンをローカルに作る。

$ cd Git用ディレクトリ名
$ git clone git@xxx/yyy.git
$ git checkout -b ブランチ名

※今回はSSHの鍵認証を使ってるので、HTTPではなくSSHを選択しました。

☆中央リポジトリの設定を行う。
今回は中央リポジトリのリモートブランチとして、”upstream”という名前のリモートブランチを作成しました。

$ git remote add upstream git@フォークしたリポジトリの元のURL

☆ローカルで、クローンしたリポジトリ内のどこかに変更を加える。

☆自分の変更をまずはコミットさせる。

  • Towerの左側のBRANCHESは常にチェックすること。ターゲットになってるブランチだったら、ブランチ名の横に(HEAD)と書いてある。
  • コミットさせたい変更したファイルを選ぶ方法→Statusタブを開いて、任意のファイルのStagedをチェックする。チェックしたら、上部にあるチェック印の”Commit”ボタンをクリック。

これで「コミットは完了」!、、だけどローカルの変更は自分のリモートリポジトリに反映されていない。pushをして、ローカルでの変更を自分のリモートリポジトリに反映させる。

☆変更をpushする。
Towerの上部のpushボタンをクリックし、ブランチの場所を要確認してokボタンをクリックする。
これで自分のリモートリポジトリにも変更した内容が反映させる。
今回はTowerでpushしましたが、ターミナルを使う場合は以下のコマンドを打ちます。

$ git push origin ブランチ名/プロジェクト名

※引数なしのpushをすると、カレントブランチに関わらずローカルブランチと同名のブランチがリモート上にある場合はそれらを一気にpushします。

☆中央リポジトリの最新版を取り込む。

$ git pull upstream ブランチ名

☆pull requestを送る。
GitHub上で自分がフォークしたリポジトリのページに遷移し、Commitsというタブを選択し、自分のpush内容が反映されていることを確認し、pull requestボタンをクリックする。

  • pull(変更を)する側…master
  • pullするもの…pull requestを送る側が変更した箇所

これで中央リポジトリの管理者にpull requestが送信される。

※pull requestを押した後、Tower上で空pushをする。これで自分が行った変更+中央リポジトリの変更を自分のリモートリポジトリに反映することができる。

☆mergeしたいときは?

自分がpull requestを送ったと同時にほかの誰かもpull requestを送ったとき、エラーや競合が起きない範囲で変更を反映させたいところです。

そこでGitHub上でmerge pull requestボタンを押すと、簡単にmergeを行うことができます。さらにmergeされた箇所に対してコメントを書くこともできます。これでソースレビューとマージが同時にできます!

※自分が行った変更と他の人が行った変更がコンフリクトした場合。
例えば他の人と同じファイルを変更していて、pullした際にeclipse上などでファイルを確認したときにエラーが起こっている場合は、自分でeclipe上で変更分を手動でマージする必要があります。その後改めて変更分をpushし、pull requestを送る必要があります。

今回はここまでです。ご覧いただきましてありがとうございます。

参考記事一覧