Perl6のインストールを試す
環境
vagrantのCentOS6.x
インストール
git clone https://github.com/rakudo/rakudo.git cd rakudo/ perl Configure.pl --prefix=$HOME/perl6 --gen-moar --gen-nqp --backends=moar make make install
バージョン確認
$ $HOME/perl6/bin/perl6 -v This is perl6 version 2015.07.1-160-gb56d593 built on MoarVM version 2015.07-57-gec051f5 $
環境変数に追加
echo 'export PATH=$PATH:$HOME/perl6/bin' >> ~/.bash_profile source ~/.bash_profile
サンプル実行
sample.pl
say "hello world"; say "aaaaaaa";
↓
$ perl6 sample1.pl hello world aaaaaaa $
make testはできなかった
[vagrant@localhost rakudo]$ make test /usr/bin/perl t/harness --moar t/01-sanity t/04-nativecall Can't locate Test/Harness.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at t/harness line 14. BEGIN failed--compilation aborted at t/harness line 14. make: *** [m-coretest] エラー 2 [vagrant@localhost rakudo]$
※なんか必要だったみたいだけどインストールはできたので保留
perl6の構文の書き方はまた別途見てみる
Amon2のチュートリアルをやってみた
最初はチュートリアルから
基本的なデータベースやルーティングなどを理解する意味でやってみる
手順
雛形を作成して必要なモジュールをインストールしておく
amon2-setup.pl BBS cd BBS/ carton install
sql/sqlite.sql を編集
CREATE TABLE IF NOT EXISTS member ( id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(255) ); CREATE TABLE IF NOT EXISTS sessions ( id CHAR(72) PRIMARY KEY, session_data TEXT ); CREATE TABLE IF NOT EXISTS entry ( entry_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, body varchar(255) not null );
sqlを実行
sqlite3 db/development.db < sql/sqlite.sql
テーブルを確認
$ sqlite3 db/development.db SQLite version 3.6.20 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables entry member sessions sqlite>
config/development.pl で接続用の設定を追加
use File::Spec; use File::Basename qw(dirname); my $basedir = File::Spec->rel2abs(File::Spec->catdir(dirname(__FILE__), '..')); my $dbpath = File::Spec->catfile($basedir, 'db', 'development.db'); +{ 'DBI' => [ "dbi:SQLite:dbname=$dbpath", '', '', +{ sqlite_unicode => 1, } ], };
一度起動する
※hostは’0.0.0.0’にしておく
lib/BBS/DB/Schema.pmにテーブルなどの設定を追加
package BBS::DB::Schema; use strict; use warnings; use utf8; use Teng::Schema::Declare; base_row_class 'BBS::DB::Row'; table { name 'sessions'; pk 'id'; columns qw(session_data); }; table { name 'entry'; pk 'entry_id'; columns qw(entry_id body); }; 1;
lib/BBS/Web/Dispatcher.pmでルーティングの設定
package BBS::Web::Dispatcher; use strict; use warnings; use utf8; use Amon2::Web::Dispatcher::RouterBoom; any '/' => sub { my ($c) = @_; my @entries = $c->db->search( entry => { }, { order_by => 'entry_id DESC', limit => 10, } ); return $c->render( "index.tx" => { entries => \@entries, } ); }; post '/post' => sub { my ($c) = @_; if (my $body = $c->req->param('body')) { $c->db->insert( entry => +{ body => $body, } ); } return $c->redirect('/'); }; 1;
tmpl/index.txでテンプレートを編集
: cascade "include/layout.tx" : override content -> { <form method="post" action="<: uri_for('/post') :>"> <input type="text" name="body" /> <input type="submit" value="Send" /> </form> <ul> <: for $entries -> $entry { :> <li><: $entry.entry_id :>. <: $entry.body :></li> <: } :> </ul> : }
実行
参考
Tutorial - Create BBS site — Amon2 6.00 documentation
http://kimoto.hatenablog.com/entry/2012/07/26/144204
http://amagitakayosi.hatenablog.com/entry/2013/09/12/142900
Amon2 4.01でチュートリアル - マルシテイア
Amon2 Tutrialしてみる - 非開発者がプログラム技術を使ったQAを目指すブログ
Big Sky :: Amon2::LiteでとCPANモジュールで作る「Nopaste」チュートリアル
所感
よく勉強会とかではamon2で作った的なことを聞いていてMojoliciousで作ったようなことを聞いてみないのでやってみました。
まだ、チュートリアルだけなので実感がわかないけどamon2はいろいろなモジュールをたくさん使用している感じでMojolicousのように1つになっていないのでモジュールを使用する場合はcartonで制限したほうが動かすときに安全な感じがしました。
また、いろいろと触ってみて理解していこう
今日はここまで
perlの復習(map)
なぜかたいていはfor文とか使ってしまう
mapとかも使えたほうがイケてる感じがするので調べ直さないと…
map
配列の一覧を返す機能、値に加工などを行うことも可能
書式
map { なんかの処理 } 配列;
※{}で挟まれた部分でなんかの処理を行う
※セミコロンを忘れないようにしないと
例1
#!/usr/bin/env perl use strict; use warnings; my $list = [111, 222, 333, 444, 555]; print "map sample\n"; map { print $_ . "\n"; } @{$list};
↓
$ perl sample1.pl map sample 111 222 333 444 555 $
※単純に値を出力するサンプル
例2
#!/usr/bin/env perl use strict; use warnings; use Data::Dumper; my $list = [111, 222, 333, 444, 555]; print "map sample\n"; my @list2 = map { "[" . $_ . "]"; } @{$list}; # あえてリファレンスで渡すとかわけわからんことしているような… print Dumper(\@list2);
↓
$ perl sample1.pl map sample $VAR1 = [ '[111]', '[222]', '[333]', '[444]', '[555]' ]; $
※配列に値を返す場合は%を指定することをわすれないようにしないといけない
例3
#!/usr/bin/env perl use strict; use warnings; use Data::Dumper; my $list = [111, 222, 333, 444, 555, 111, 333]; print "map sample\n"; my %list2 = map { $_ => "" } @{$list}; # あえてリファレンスで渡すとかわけわからんことしているような… print Dumper(\%list2);
↓
$ perl sample1.pl map sample $VAR1 = { '444' => '', '555' => '', '111' => '', '333' => '', '222' => '' }; $
※なんかちょっと強引だけどハッシュ化することで重複キーを除去とかもできるかもしれない
grepは?
今回はやりませんでした。
私は普通の人より理解が遅いので1つずつやっていかないと混乱してしまう(´・ω・`)
一応こんな感じ
#!/usr/bin/env perl use strict; use warnings; use Data::Dumper; my $list = [111, 222, 333, 444, 555]; print "map sample\n"; my @list2 = grep { if($_ % 2 == 0){ $_ } } @{$list}; # あえてリファレンスで渡すとかわけわからんことしているような… print Dumper(\@list2);
↓
$ perl sample3.pl map sample $VAR1 = [ 222, 444 ]; $
所感
1行で書くのが普通みたいだけどなんか他の情報がまざると間違いやすくなるのでブロック構文のように複数の行にしています。
何度も書けば慣れていくかも…
使いドコロを間違えないようにしないと
perlでtwitterAPI(retweet、favorite)
自分のツイートをリツイートできないことに
やっと気づいた。
だってなんどやっても 403が帰ってくるから…
実装
sample1.pl
#/usr/bin/env perl use strict; use warnings; use Net::Twitter; use utf8; use Data::Dumper; binmode STDOUT, ":utf8"; my $consumer_key = "<consumer_key>"; my $consumer_secret = "<consumer_secret>"; my $access_token = "<access_token>"; my $access_token_secret = "<access_token_secret>"; my $tw_app = Net::Twitter->new({ traits => [qw/API::RESTv1_1 OAuth /], consumer_key => $consumer_key, consumer_secret => $consumer_secret, access_token => $access_token, access_token_secret => $access_token_secret}); if (@ARGV == 2){ my $text = $ARGV[0]; my $mode = $ARGV[1]; my $res; if($mode eq "R"){ $res = $tw_app->retweet($text); }else{ $res = $tw_app->create_favorite($text); } print Dumper($text); print Dumper($res); }else{ print "1:id 2:mode('R':retweet, 'F':favorite)\n"; }
引数
引数はツイートの固有のIDらしい
所感
最初、自分にリツイートできなくて戸惑いましたけど、きちんとリツイートできたので良かったです。
この方法と検索やstreamを併用することで自動リツイートBOTなどを作成できそう。
自分自身に通知代わりになにかを送りたいと思っていたのでそろそろやってみるかも
PerlのExport確認
とりあえずサンプル
lib/Sample1.pm
package Sample1; sub test1{ return "aaa"; } sub test2{ return "bbb"; } sub test3{ return "ccc"; } # 忘れがち 1;
※超基本
sample1.pl
#!/usr/bin/env perl use strict; use warnings; use Sample1; print Sample1::test1() . "\n"; print Sample1::test2() . "\n"; print Sample1::test3() . "\n";
↓
$ perl -Ilib sample1.pl aaa bbb ccc $
まあ、普通に出ます。
sample2.pl
#!/usr/bin/env perl use strict; use warnings; use Sample1; print test1() . "\n"; print test2() . "\n"; print test3() . "\n";
↓
$ perl -Ilib sample2.pl Undefined subroutine &main::test1 called at sample2.pl line 8. $
でません
Exporterを指定
Sample1.pm
package Sample1; use base qw(Exporter); our @EXPORT = qw/test1/; our @EXPORT_OK = qw/test1/; sub test1{ return "aaa"; } sub test2{ return "bbb"; } sub test3{ return "ccc"; } # 忘れがち 1;
@EXPORT、@EXPORT_OKにサブルーチンを指定
sample2.pl
#!/usr/bin/env perl use strict; use warnings; use Sample1; print test1() . "\n"; print test2() . "\n"; print test3() . "\n";
↓
$ perl -Ilib sample2.pl aaa Undefined subroutine &main::test2 called at sample2.pl line 9. $
test1は指定しているので実行できる
EXPORT_TAGSでグループ分け
lib/Sample1.pm
package Sample1; use base qw(Exporter); our @EXPORT = qw/test1/; our @EXPORT_OK = qw/test1 test2/; our %EXPORT_TAGS = ( all => [ @EXPORT, @EXPORT_OK ], test3 => [ qw(test3) ] ); sub test1{ return "aaa"; } sub test2{ return "bbb"; } sub test3{ return "ccc"; } # 忘れがち 1;
EXPORT_TAGSは%定義なので間違わないようにする
sample3.pl
#!/usr/bin/env perl use strict; use warnings; use Sample1 qw(:all); print test1() . "\n"; print test2() . "\n"; print test3() . "\n";
↓
$ perl -Ilib sample3.pl aaa bbb Undefined subroutine &main::test3 called at sample3.pl line 10. $
test1とtest2のサブルーチンを取り込める
sample3_2.pl
#!/usr/bin/env perl use strict; use warnings; use Sample1 qw(:test3); print test3() . "\n"; print test1() . "\n"; print test2() . "\n";
↓
$ perl -Ilib sample3_2.pl "test3" is not exported by the Sample1 module Can't continue after import errors at sample3_2.pl line 6. BEGIN failed--compilation aborted at sample3_2.pl line 6. $
test3は@EXPORT_OKに設定していないのでだめ?
使用するサブルーチンを制限したい場合
実際にモジュール名::サブルーチン名で定義できるので厳密には難しいと思いますけど使用するものを一部に制限することは可能らしい
lib/Sample1.pm
package Sample1; use base qw(Exporter); our @EXPORT = qw/test1/; our @EXPORT_OK = qw/test1 test2/; our %EXPORT_TAGS = ( all => [ @EXPORT, @EXPORT_OK ], test2 => [ qw(test2) ], test3 => [ qw(test3) ] ); sub test1{ return "aaa"; } sub test2{ return "bbb"; } sub test3{ return "ccc"; } # 忘れがち 1;
※test2を用意、test2は@EXPORT_OKにある
sample3_3.pl
#!/usr/bin/env perl use strict; use warnings; use Sample1 qw(:test2); print test2() . "\n"; print test1() . "\n";
↓
$ perl -Ilib sample3_3.pl bbb Undefined subroutine &main::test1 called at sample3_3.pl line 9. $
※test1は呼び出し失敗しているよう
所感
ちょっとむずかしいような気がする。なんかプライベートメソッドみたいなことを期待していたけど、モジュール名を指定することで参照できるのであまりそのへんは難しいかも。
なんかモジュールを作るときに役たつかもしれない。
Mojolicious+Angularjsでwebsocket2
上記の設定を元に設定
nginxの設定ファイル
upstream chat { server 127.0.0.1:8012; } server { listen 80; server_name dev.example.com; root /var/www/tools/oreoreapp/reoreotsubuyakikun/public; access_log /var/log/chat.access.log main; location / { proxy_set_header X-Forwarded-Proto "http"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://chat; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
supervisord
[program:oreoreapp] user=root command=/var/www/tools/oreoreapp/app.sh autostart=true autorestart=true stopsignal=QUIT
/var/www/tools/oreoreapp/app.sh
#!/bin/sh # export export PATH="/var/www/tools/app1/local/bin:/root/.plenv/shims:/root/.plenv/bin:/root/.phpenv/shims:/root/.phpenv/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin" # app start cd /var/www/tools/oreoreapp/reoreotsubuyakikun exec /root/.plenv/shims/hypnotoad -f app.psgi
※なんとなくフォアグラウンドで起動
※実行権限を別途付与
アプリ側の修正として
app.conf
{ hypnotoad => { listen => ['http://*:8012'], workers => 1 } };
※アプリ用のパラメータ設定を用意して
app.psgi
use Mojolicious::Lite; use DateTime; use Encode; use JSON; use utf8; # 設定ファイルを読み込み my $config = plugin('Config', {file => 'app.conf'}); # 接続人数 my $clients = {}; # Template with browser-side code get '/' => 'index'; # WebSocket echo service websocket '/echo' => sub { my $c = shift; my $id = sprintf "%s", $c->tx; $clients->{$id} = $c->tx; # Opened $c->app->log->debug('WebSocket opened.'); # Increase inactivity timeout for connection a bit $c->inactivity_timeout(300); # Incoming message $c->on(message => sub { my ($c, $msg) = @_; $c->app->log->debug('mesage->' . $msg); my $dt = DateTime->now( time_zone => 'Asia/Tokyo'); for (keys %$clients) { my $_msg = "$msg"; $clients->{$_}->send(JSON->new->utf8(0)->encode({ hms => $dt->hms, text => $_msg, })); } }); # Closed $c->on(finish => sub { my ($c, $code, $reason) = @_; $c->app->log->debug("WebSocket closed with status $code."); }); }; app->start;
※app.confを読み込み処理を追加
Mojolicious+Angularjsでwebsocket
angularjsでwebsocket
Controllerだけではうまくいかないことがあるのでfactoryなどが必要になってくる
実装
default.html.ep
<!DOCTYPE html> <!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel="shortcut icon" href="<%= url_for '/favicon.ico' %>"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="utf-8"> <link rel='stylesheet prefetch' href='/css/bootstrap.min.css'> <link rel='stylesheet prefetch' href='/css/main.css'> <script src='/js/jquery-2.1.1.min.js'></script> <script src='/js/bootstrap.min.js'></script> <script src='/js/angular.min.js'></script> <script src='/js/ui-bootstrap-tpls-0.12.0.min.js'></script> <script src='/js/main.js'></script> </head> <body> <div class="header"> <div class="title"> <a href="/"><%= title %></a> </div> </div> <div class="content"> <%= content %> </div> <div class="footer"> <div class="copyright"> @m_shige1979 </div> </div> </body> </html>
このへんはいままでのものを流用しているのでぶっちゃけ手抜きです
index.html.ep
% layout 'default'; % title 'おれおれつぶやき君'; <div class="index" ng-app="myApp"> <div ng-controller="MainCtrl"> <div class="row head"> さみしくなるまでなんかをつぶやけ </div> <div class="row detail"> <input type="hidden" ng-model="EchoScoket.path" ng-init="EchoScoket.path='<%= url_for('echo')->to_abs %>'" /> <div class="col-md-12 block1"> <form class="form" role="form"> <div class="form-group"> <div class="input-group" style="width: 100%;"> <input type="text" class="form-control" placeholder="Message" ng-model="message" ng-keydown="handleKeydown($event)"> </div> </div> <button type="button" class="btn btn-default" ng-click="messageClear()">clear</button> <button type="button" class="btn btn-primary" ng-click="messageSend()">send</button> </form> </div> </div> <div class="row detail"> <div class="col-md-12 block1"> <div class="message_block"> <div class="message_item" ng-repeat="item in EchoScoket.messageList"> 俺:{{item.text}} </div> </div> </div> </div> </div> </div>
※echoのURLをどのようにしてng-modelに割り当てようかと思ったけどng-initでなんとかなりました。
main.js
var myApp = angular.module('myApp', ['ui.bootstrap']); myApp.factory('EchoScoket', function($rootScope, $log){ var service = {}; var ws; service = { // websocket path path: "", // messageList: [], // 接続 connect: function(){ // オブジェクト生成 ws = new WebSocket(service.path); $log.log(ws); // 接続 ws.onopen = function(){ $log.log("websocket connect"); }; // メッセージ受信 ws.onmessage = function(message){ $log.log("websocket connect") var res = JSON.parse(message.data); // 先頭に追加する service.messageList.unshift(res); // すぐに反映されないのでここで同期する $rootScope.$apply(service.messageList); }; // 切断 ws.onclose = function(){ $log.log("websocket disconnect") } }, // メッセージ送信 send: function(message){ if(message != ""){ ws.send(message); } } }; return service; }); myApp.controller('MainCtrl', function($scope, $log, EchoScoket){ $scope.EchoScoket = EchoScoket; $scope.message = ""; // message clear $scope.messageClear = function(){ $scope.message = ""; } // message send $scope.messageSend = function(){ EchoScoket.send($scope.message); $scope.message = ""; } // enter key send $scope.handleKeydown = function(e) { if (e.which == 13) { EchoScoket.send($scope.message); $scope.message = ''; } } // 初回にのみ実行され、websocketの接続処理を実施 $scope.$watch('EchoScoket.path', function(newVal, oldVal) { EchoScoket.connect(); }); });
factoryをシングルトンパターンにしてscopeにすることで値を変更されたことを監視する$watchと連携できるようにしています。
websocketの通信部分は基本そのままで値の反映に誤差があるので$rootScope.$applyで同期をとるようにしました。
app.psgi
use Mojolicious::Lite; use DateTime; use Encode; use JSON; use utf8; # 接続人数 my $clients = {}; # Template with browser-side code get '/' => 'index'; # WebSocket echo service websocket '/echo' => sub { my $c = shift; my $id = sprintf "%s", $c->tx; $clients->{$id} = $c->tx; # Opened $c->app->log->debug('WebSocket opened.'); # Increase inactivity timeout for connection a bit $c->inactivity_timeout(300); # Incoming message $c->on(message => sub { my ($c, $msg) = @_; $c->app->log->debug('mesage->' . $msg); my $dt = DateTime->now( time_zone => 'Asia/Tokyo'); for (keys %$clients) { my $_msg = "$msg"; $clients->{$_}->send(JSON->new->utf8(0)->encode({ hms => $dt->hms, text => $_msg, })); } }); # Closed $c->on(finish => sub { my ($c, $code, $reason) = @_; $c->app->log->debug("WebSocket closed with status $code."); }); }; app->start;
※ベースとしたものは
http://mojolicio.us/perldoc/Mojolicious/Guides/Cookbook#WebSocket-web-service
であとは以前作成したものを流用しました。