FUKULOG

FUKULOG開発者ブログ

FUKULOGの新人エンジニアがサイトの開発や運営について書いています。

Google Apps Script を使って各種有効期限を管理&メールでお知らせするようにすると便利

Posted by @wata_n

GAS(Google Apps Script)を使って、ドメインやSSL証明書などの有効期限を管理&メールでお知らせするようにしたら便利だった件。

更新が必要系サービスで、有効期限直前に慌てて更新手続きすることってありますよね。 ドメインだったり、Apple Developers Program だったり、iOSアプリに必要な証明書だったり。

それらをちゃんと管理しようと思って、最初はGoogleカレンダーでいいじゃん、って思ったんですが、 1画面で利用サービスを一覧出来る感じも欲しいなぁ…と。リマインドしてくれるだけならGoogleカレンダーで十分なんですけどね。

というわけでGAS!便利です!GASって言っても、JavaScriptだし!

有効期限管理表をGoogleスプレッドシートで作る

まずは、こんな表をGoogleスプレッドシートで作ります。

もちろんカラムやカラム名などご自由に。

スクリプトを書く

ここから実際にスクリプトを書いていきます。 上で作ったスプレッドシートの画面で、[ツール]→[スクリプト エディタ]を開きます。 別画面でエディタ画面が立ち上がると思うので、カラム名やメールなどを埋めつつ、下のようなスクリプトを書いていきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function sendMailExpiredServices(){
  /*
   * Config
   */
  // カラム名
  var SERVICE_COL_NAME = 'サービス';
  var LIMIT_COL_NAME   = '期限';
  var ADMIN_COL_NAME   = '管理者';
  var COMMENT_COL_NAME = 'コメント';

  // メール
  var admin = 'nagasawa@example.com'; // 管理者(必須)
  var to    = 'nagasawa@example.com, foo@example.com, bar@example.com';
  var opts  = {
    //cc   : '',
    bcc  : admin,
    reply: admin
  };

  // 何日前に送信するか
  var grace_time = 1000 * 60 * 60 * 24 * 7;

  /*
   * Main
   */
  try {
    var sheet = SpreadsheetApp.getActiveSheet();
    var range = sheet.getDataRange();
    var today = new Date();

    for (var i = 2; i <= sheet.getLastRow(); i++) {
      var subject = "【期限切れ】";
      var body = "の期限が迫っています。\n\n"
      + "------------------------------------------------------------\n";
      var footer = "------------------------------------------------------------\n\n"
      + "更新の手続きをお願いします。\n\n"
      + "※更新後はシートの【期限】も更新してください\n"
      + "<Googleスプレッドシートの共有リンク>\n";
      var flg = false;

      for (var j = 1; j <= sheet.getLastColumn(); j++) {
        var col_name  = range.getCell(1, j).getValue(); // カラム名
        var col_value = range.getCell(i, j).getValue(); // 入力値

        body += "【" + col_name + "】\n";
        body += col_value + "\n\n";

        if ( col_name === SERVICE_COL_NAME ) {
          subject += col_value;
          body = col_value + "\n\n" + body;
        }
        if ( col_name === LIMIT_COL_NAME ) {
          var diff = col_value - today;
          if (diff < grace_time) {
            range.getCell(i, j).setBackgroundColor('#fAA');
            flg = true;
          }
        }
      }
      body += footer;

      if (flg) {
        MailApp.sendEmail(to, subject, body, opts);
      }
    }
  } catch(e) {
    MailApp.sendEmail(admin, "【失敗】有効期限管理表からメール送信中にエラーが発生", e.message);
  }
}

スクリプトを保存(フロッピーボタン)して、試しに実行(再生ボタンみたいなやつ)すると、 スプレッドシートの方で、期限が近づいてるセルが赤くなったと思います。

届いたメール

そして実際に届くメールがこんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Subject: 【期限切れ】iOS Development

iOS Development

の期限が迫っています。

------------------------------------------------------
【サービス】
iOS Development

【期限】
Tue Aug 20 2013 00:00:00 GMT+0900 (JST)

【管理者】
nagasawa

【コメント】
開発用プロビジョニングファイル

------------------------------------------------------

更新の手続きをお願いします。

※更新後はシートの【期限】も更新してください
https://docs.google.com/...

あとは、スクリプトエディタ画面中、[リソース]→[現在のプロジェクトのトリガー…]を開いて、 スクリプトを実行するトリガーとなるイベントの設定をしときます。

[sendMailExpiredServices] [時間主導型] [日タイマー] [午前9時〜10時]

みたいな感じに。

あとは管理したいサービスが増えてもこのシートにどんどん追加していけばおk!

これでもう有効期限直前に慌てるなんてこともないはず! (あとは人間の問題。。)

Xcode で コードフォーマッター Uncrustify を使う

Posted by @wata_n

Source Code Beautifier for C, C++, C#, ObjectiveC, D, Java, Pawn and VALA

最近iOS開発で使い始めて、良い感じな Uncrustify 。 設定に従って、インデントの数やスペースの取り方など、自動でコードを綺麗に整形してくれます。 似たもので、 perl 版で同じようなことをしてくれるツール perltidy とかがあります。

新しいソフトウェアとかでは全然無いんですが、Xcode にスクリプトを設定して任意のタイミングで uncrustify を走らす、 というのが便利だった&インストール〜Xcodeに設定までが一気通貫でまとまった記事があまり無さそうだったので書いてみます。

インストール

homebrew を使っているならこれだけで入ります。 (ちなみに Xcode プラグイン版もありますが、今回は使いません)

1
$ brew install uncrustify

Xcode から実行するスクリプト

スクリプトはホームディレクトリの~/bin/uncrustifyに、設定ファイルは~/.uncrustifyconfigに置いてます。

1
2
3
$ vi ~/bin/uncrustify # スクリプト
$ chmod +x ~/bin/uncrustify
$ vi ~/.uncrustifyconfig # 設定ファイル

スクリプトは、Using Uncrustify directly in Xcode 4! を参考にパスなどを変えてます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/ruby
base_path = ENV['XcodeProjectPath'] + "/.."

puts "running uncrustify for xcode project path: #{base_path}"

if base_path != nil
  paths = `find "#{base_path}" -name "*.m" -o -name "*.h" -o -name "*.mm" -o -name "*.c"`
  paths = paths.collect do |path|
    path.gsub(/(^[^\n]+?)(\n)/, '"\\1"')
  end
  paths = paths.join(" ")
  result = `/usr/local/bin/uncrustify -c ~/.uncrustifyconfig --no-backup #{paths}`;
  puts result
else
  puts "Invalid base path..."
end

設定ファイルは Uncrustifyのセッティング (1) プリプロセッサ編 #uncrustify - Qiita [キータ] が詳しかったので、 使える設定、その説明を見ながら書きます。

Xcode の設定

Xcode でBuildに成功したタイミングで uncrustify を実行する場合です。 [Preferences…] -> [Behaviors] と進んで、Build の Succeeds を選択し、Run の項目に上で作ったスクリプトを指定します。

あとは [Product] -> [Build] すると大量に差分が出ます。 差分を見ながら.uncrustifyconfigに指定した設定に従って整形されているのが確認出来ると思います。

それぞれコーディング中は好きな作法で書いても、設定ファイルさえチームで共有しとけば、第三者が見て綺麗なソースが常に保てて便利ですね。

git hooks を利用したデプロイを導入しました

Posted by @wata_n

最近ちょっと開発環境を見直していて、その中の一つとして git hooks によるデプロイを導入したので紹介してみたいと思います。

もともとバージョン管理には Subversion を使っていて、今回 git に移行したタイミングで、 git-hooks デプロイを導入しました。

svn から git への移行については今回割愛しますが、 git push origin master とやると自動的に本番にデプロイする方法は、 個人で借りてるサーバでも利用している方法だったので、是非ともやりたかったことの一つ。

git hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ tree .git/hooks/
.git/hooks/
├── applypatch-msg.sample
├── commit-msg.sample
├── post-update.sample
├── pre-applypatch.sample
├── pre-commit.sample
├── pre-push.sample
├── pre-rebase.sample
├── prepare-commit-msg.sample
└── update.sample

# 実際に動かすときは
$ mv .git/hooks/post-update.sample .git/hooks/post-update
$ chmod +x .git/hooks/post-update

git には hooks という、updateやreceiveした後など、いくつかのタイミングをフックしてスクリプトを実行出来る仕組みがあります。 svn でもフックスクリプトは使えますが、こちらはコミットに対してフックする感じで、 git の方がタイミングが豊富という印象があります。

これの何が嬉しいかというと、例えば、チームでコード品質やコード規約を守るために、コミットする前にコミットしようとしているコードを必ず validation してNGならコミット中止、 コミット後に diff を通知する、push 受けた際にテストを走らす、などなどをするのに便利です。

今回は push があった際に更新しようとしているブランチごとに実行される post-update を使ってデプロイ作業の自動化を行いました。

デプロイの仕組み

(あ、Gitlab使ってます…)

今回やりたかったこととして、

  • プッシュしたタイミングでtest、staging、本番、の各サーバに即時にソースを反映させたい
  • ブランチ毎にWebサーバを切り替えたい

があります。

一つ目について、

今まで svn 時代には、
①ローカルから中央リポジトリにコミット
②test、stagingサーバにあるワーキングリポジトリ側で中央リポジトリの変更を受け取る(cron に3分毎に svn up するようにセットしていた)
③stagingサーバに ssh で入ってstagingから手で本番へ反映

のようなフローでデプロイを行なっていましたが、これは git hooks (post-update)を使って自動化することで解決しました。

2つ目について、

post-update はタイミング的に post-receive と似ているのですが、複数のブランチへのプッシュがあったときに post-receive が実行されるのが一度だけなのに対して、 post-update はブランチ単位でそれぞれ一度ずつ実行されるという特徴があります。 そして第一引数にブランチ名を受け取るのですが、ブランチ名は post-update の中でこんな風に取得することが出来ます。

1
2
3
4
#!/bin/bash

# push されたブランチ名が BRANCH に入る
BRANCH=$(git rev-parse --symbolic --abbrev-ref $1)

あとは $BRANCH 毎に処理の振り分けをします。

1
2
3
4
5
6
7
8
9
10
11
case "$BRANCH" in
  "develop" | feature*)
      develop, feature用の処理
      ;;
  release* | hotfix*)
      release, hotfix用の処理
      ;;
  "master")
      master用の処理
      ;;
esac

最終的には rsync + ssh でWebサーバへ送りたい、ので一度 $TMPDIR に checkout してます。

1
env GIT_WORK_TREE=$TMPDIR git checkout -f $BRANCH

checkout したディレクトリに移動して rsync + ssh

1
2
cd $TMPDIR
$RSYNC ./ $USER@$HOST:$DESTDIR/

この方法だと、それぞれの各確認用サーバでは常に最後に push されたブランチが当該Webサーバに反映されていることになります。

アイデア次第でまだまだ面倒な作業を自動化出来そうですが、まだ git による運用自体に慣れてないこともあり、しばらくこれでまわしてみて、 今後運用の中で、また何か改善点など出てきたら共有していきたいと思います。

tips

tips というかGitlabを使っていてハマった点なのですが、post-updateスクリプトで tmpdir に git checkout したあと、 勝手にディレクトリが770、ファイルが660というパーミッションになってしまって困った、ということがありました。 post-updateに書いてある内容を手で実行してみてもそんなことにはならず、なんだ?という感じだったのですが、 home/git/.gitolite.rcにこんな記述があり、

.gitolite.rc
1
2
3
4
5
%RC = (
    # if you're using mirroring, you need a hostname.  This is *one* simple
    # word, not a full domain name.  See documentation if in doubt
    # HOSTNAME                  =>  'darkstar',
    UMASK                       =>  0007,

ユーザがログイン後に実行されるコマンド/home/git/gitolite/src/gitolite-shell内で、

gitolite-shell
1
2
3
4
5
6
# call this once you are sure arg-1 is the username and SSH_ORIGINAL_COMMAND
# has been setup (even if it's not actually coming via ssh).
sub main {
    my $id = shift;

    umask $rc{UMASK};

のように指定されているのでした。なのでUMASK => 0002にしてディレクトリが775、ファイルが664となるようにして対応しました。

…と書いたのですが、Gitlab5.0からはgitoliteは使用しないようなので、 気にしなくていいかもしれません。ぐぬぬ。

画面スクロールされたかをGoogle Analyticsのイベントトラッキングに記録する

Posted by @wata_n

画面のスクロール位置を、Google Analyticsのイベントトラッキングに記録する。 記録したい要素のidを配列として関数に渡すと、その要素が画面に表示されたときに記録を残す。

つい最近導入してみたんですが、前回につづきjsネタです。

twitterなどからの流入をなんとか活かせないかと、直帰率を下げる&回遊率を上げるべく、該当ページの改修をすることに。 しかしコンテンツを見直すにも、そもそも何は見られていて、どこで見切られてしまっているのかの情報が無い状態だったので、 こんなjsを書いてGoogle Analyticsで見てみることにしました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$.extend({
  attachScrollEventTracking: function(eventcategory, pagePartsIdList) {
      var pageParts = {};
      var windowHeight = $(window).height();
      
      $.each(pagePartsIdList, function(key, pagePartsId){
          var target = $("#" + pagePartsId);
          if (target.length > 0) {
              pageParts[pagePartsId] = target.offset().top;
          }
      });
      
      var isFinish = false;
      $(document).bind("scroll", function(event) {
          if (!isFinish) {
              var scrollTop = $(document).scrollTop() + windowHeight;
              
              isFinish = true;
              $.each(pageParts, function(pagePartsId, value){
                  if (value < scrollTop) {
                      //console.log(['_trackEvent', eventcategory, 'scroll', pagePartsId, value, true]);
                      _gaq.push(['_trackEvent', eventcategory, 'scroll', pagePartsId, value, true]);
                      delete pageParts[pagePartsId];
                  }
                  isFinish = false;
              });
          }
      });
  }
});

使い方:

1
2
3
4
5
$(document).ready(function() {
  // 記録したい要素のidを指定
  var pageParts = ["section-1", "section-2", "section-3"];
  $.attachScrollEventTracking('tracking_event_category', pageParts);
});

スクロールイベントに対して、指定した要素の位置までスクロールされたかを判定する処理をバインドし、 Google Analyticsに送信しています。結果はどうかな・・・。

手軽に使える感じなので、今後効果測定やページ解析にも使っていきたいと思います。

スマホでhover効果を実現するjQueryプラグイン

Posted by @wata_n

スマホの画面タップ時に要素をhoverさせる CSSは、a:hoverの代わりにa.hoverを使う aタグ以外の場合はclassにtapを指定するとCSSで:hoverの代わりに.hoverを使えるようになる

PCではリンクやボタンにカーソルが乗っかったときや押したときの挙動として、スタイルシートで

1
2
a:hover { color:red; }
a:active { color:red; }

のようにやりますが、スマホサイトではこれが出来ません

そこでFUKULOGのスマホサイトでもそうですが、

jQueryを使ってスマホで:hover効果を実現する | アルパカの具

1
2
3
4
5
6
7
8
jQuery(function($){
    $( 'a, input[type="button"], input[type="submit"], button' )
      .bind( 'touchstart', function(){
        $( this ).addClass( 'hover' );
    }).bind( 'touchend', function(){
        $( this ).removeClass( 'hover' );
    });
});

のようにjsでaタグやボタンを指定して、要素がタッチされたときにhoveractiveなどのclassを付けることで対応していました。

しかしこれだと、そのページにある無数のaタグやボタンのeventに対して個別にコールバック関数をbindしていくことになり、要素が増えれば増えるほど処理に時間がかかってしまうことになります。 そこでbindするのはwindowのeventに対してのみにし、コールバック関数の中で実際にタッチされている要素を判別するようにしたのが、jquery.taphover.jsです。

from jquery.taphover.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(window).bind("touchstart", function(event) {
  var target = event.target || window.target;

  var bindElement = null;
  if (target.tagName == "A" || $(target).hasClass(tapClass)) {
      bindElement = $(target);
  } else if ($(target).parents("a").length > 0) {
      bindElement = $(target).parents("a");
  } else if ($(target).parents("." + tapClass).length > 0) {
      bindElement = $(target).parents("." + tapClass);
  }

  if (bindElement != null) {
      Hover().touchstartHoverElement(bindElement);
  }
});

あらかじめjquery.taphover.jsを全ページで読み込んでおくだけで、あとは必要に応じて、

1
article a.hover { color:red; }

のような感じで、該当する要素に対して適宜スタイルを追加していく、という使い方が出来ます。

今後スマホサイトで全面的に移行していくつもりです。