Studio3104::BLOG.new

uninitialized constant Studio3104 (NameError)

ゆるく `my.cnf` の比較などが出来る gem を作った

複数my.cnf を比較して差異を知りたいが、通常のファイル同士の比較(diff) ではわかりにくいしそもそも知りたい情報を得るのはつらい。
ということがあり作成していた書き捨てのスクリプトがあったのだが、整理してテストまで書いて rubygems に放流した。

mycnf | RubyGems.org | your community gem host

studio3104/mycnf · GitHub

mycnf.gem

機能概要

parse

my.cnf のファイルパスを食わせると parse して hash を返す

generate

上記の parse のフォーマットの hash を食わせると my.cnf を文字列で返す

compare

上記の parse のフォーマットの hash を複数食わせると、比較しやすいフォーマットで hash を返す
(ファイルパスを複数食わせて同様の結果を返す compare_files もある)

diff

上記の compare の結果から、差異のあるパラメタのみを select して返す
(ファイルパスを複数食わせて同様の結果を返す diff_files もある)

使い方

こんな感じの my.cnf があったとする。

$ cat /etc/my.cnf.1
[client]
port            = 3306
socket          = /var/lib/mysql/mysql.sock

[mysql]
no_auto_rehash

[mysqld]
datadir         = /var/lib/mysql
port            = 3306
socket          = /var/lib/mysql/mysql.sock
$ cat /etc/my.cnf.2
[client]
port            = 3308
socket          = /var/lib/mysql/mysql.sock

[mysql]
no_auto_rehash
safe-updates

[mysqld]
datadir         = /var/lib/mysql
port            = 3308
socket          = /var/lib/mysql/mysql.sock
parse
MyCnf.parse('/etc/my.cnf.1')
{
   client: {
     port: 3306, socket: '/var/lib/mysql/mysql.sock'
   },
   mysql: {
     no_auto_rehash: ''
   },
   mysqld: {
     datadir: '/var/lib/mysql', port: 3306, socket: '/var/lib/mysql/mysql.sock'
   }
}
generate
MyCnf.generate({
  client: {
    port: 3306,
    socket: '/var/lib/mysql/mysql.sock'
  }
})
[client]
port = 3306
socket = /var/lib/mysql/mysql.sock
compare
MyCnf.compare(MyCnf.parse('/etc/my.cnf.1'), MyCnf.parse('/etc/my.cnf.2'))
MyCnf.compare_files('/etc/my.cnf.1', '/etc/my.cnf.2')
{
  client: {
    port: [ 3306, 3308 ],
    socket: [ '/var/lib/mysql/mysql.sock', '/var/lib/mysql/mysql.sock' ]
  },
  mysql: {
    no_auto_rehash: [ '', '' ],
    safe_updates: [ nil, '' ]
  },
  mysqld: {
    datadir: [ '/var/lib/mysql', '/var/lib/mysql' ],
    port: [ 3306, 3308 ],
    socket: [ '/var/lib/mysql/mysql.sock', '/var/lib/mysql/mysql.sock' ]
  }
}
diff
MyCnf.diff(MyCnf.parse('/etc/my.cnf.1'), MyCnf.parse('/etc/my.cnf.2'))
MyCnf.diff_files('/etc/my.cnf.1', '/etc/my.cnf.2')
{
  client: {
    port: [ 3306, 3308 ]
  },
  mysql: {
    safe_updates: [ nil, '' ]
  },
  mysqld: {
    port: [ 3306, 3308 ]
  }
}

出来ないこと(0.0.1 現在)

暗黙のデフォルト値との比較は出来ない

たとえば、innodb_buffer_pool_size の値が明示されいるものとそうでないものを比較した場合は以下のような結果が返る。

$ cat /tmp/my.a.cnf
[mysqld]
server_id = 1
innodb_buffer_pool_size = 128M
$ cat /tmp/my.b.cnf
[mysqld]
server_id = 2
$ pry
[1] pry(main)> require 'mycnf'
=> true
[2] pry(main)> MyCnf.diff_files('/tmp/my.a.cnf', '/tmp/my.b.cnf')
=> {:mysqld=>{:server_id=>[1, 2], :innodb_buffer_pool_size=>["128M", nil]}}

MySQL 5.6 系において、innodb_buffer_pool_size のデフォルト値は 128MB であるが、そういったデフォルト値を考慮した比較をする実装にはなっていない。
あくまで明示的に記述されているパラメタのみの比較を行う。

!include, !includedir の先に定義してある設定を追いかける

すべてのメソッドにおいて、!include, !includedir の先で定義してあるパラメタを追うことは出来ない。
あくまで引数に与えたファイル及び hash の中だけで完結する範囲で処理を行います。

さて

こういうのって書き捨てて終わっちゃうことが多くて、あとでまた使いたいときに見つからないとかあったりして困ることがある。
ので、まぁこの程度ならすぐまた書けるべって思わなくもないけど、整理して次の機会にもまたすぐに使えるようにしておいた。

はてなブログおみくじ2014

はてなブログおみくじ2014

Validation Night で focuslight-validator の紹介をしてきたよ

良いイベントでございました。
LT の機会をいただきありがとうございました。

Validation Night - connpass

しゃべったこと

focuslight-validator という、おもに HTTP リクエストパラメタのヴァリデーションのために使うモジュールの紹介をしました。
LT なのに 40p 超のスライド作っちゃって、ガーッと駆け足でやったけど 90s くらいオーバーして迷惑をかけてしまった。

focuslight-validator

focuslight/focuslight-validator · GitHub

スライドにも書いたけど、汎用的に使えるヴァリデーションモジュールです。
名前から察すると、Focuslight 専用なの?って思わなくもない*1けど、Rails でも Padrino でも使える(と思う。使ったことはない。)汎用的なヴァリデーションモジュールなのです。

もともと Focuslight に含まれていたものを切り出してもらったという感じでございます。

要望をぺろっとつぶやいてたら音速で切りだされてリリースされてた。
で、

というやりとりがあったのにもかかわらずずっと放置してた README の p-r を昨日の深夜に出して、さっき無事に取り込んでもらいました。

update README.md by studio3104 · Pull Request #3 · focuslight/focuslight-validator · GitHub

使い方がわかったらあとは使うだけだよ!!!!!
便利なのでぜひ使ったらいいと思う。

感想とか

なんか Java とか Rails の人はコントローラ層でのヴァリデーションをあまりしない人が多いという話をちらほら聞いた。
自分は Sinatra でしかウェブアプリケーションを書いたことがないので、そこらへんの文化とかよく知らないけど、自分の常識、認識との乖離を感じた。
これについては気持ちが乗ったら別のエントリで私見を述べたい。気持ちが乗ったらね。
まぁとりあえず Sinatra でなんか作ることがあって、コントローラ層でのヴァリデーションをしたくなったら focuslight-validator を使ってみたらいいのではないかと思う。

スライド

こちらのいけてるスライドは Deckset で作りました。

*1:実際に懇親会で聞かれたし

sinatra ですぐにアプリケーションの実装に入れるようにするための準備

背景

自分が仕事で書く WEB アプリケーションは多くの場合が小粒で、何か書く場合には sinatra を使っています。
さらにテンプレートエンジンは slim で、ビューが必要な場合は twitter bootstrap を使って書きます。
で、新規で何か書き始める時に、それっぽいディレクトリ構成を作って、twitter bootstrap とか jquery とかをダウンロードして解凍してそれっぽいところに設置してー(もしくは既存プロジェクトをディレクトリごとコピーしてきて要らないファイル消してネームスペース変更してー)、とかっていうローテクな感じのことを毎回手動で行っていて、すっと実装に入れない!めんどくせー!ってなることが多いので、いったん整備してみました。

studio3104/ore-no-sinatra-skelton · GitHub

構成

javascript/css ライブラリ群は bower で管理して、自分で書く javascript/css は coffee/sass で assets 配下に記述し、grunt によって自動コンパイルされます。
いずれも public 配下に勝手に設置されます。

ore-no-sinatra-skelton
├── Gemfile
├── Gemfile.lock
├── Gruntfile.coffee
├── Procfile
├── assets
│   ├── application.coffee
│   └── application.scss
├── bower.json
├── config.ru
├── lib
│   ├── skelton
│   │   └── app.rb
│   └── skelton.rb
├── package.json
└── views
    ├── index.slim
    └── layout.slim

とりあえず試すだけならこんな感じでやればすぐに sinatra まわりの実装に入れるでしょう

$ git clone https://github.com/studio3104/ore-no-sinatra-skelton.git
$ cd ore-no-sinatra-skelton
$ npm install -g grunt-cli
$ npm install
$ bundle
$ bundle exec foreman start

これで、localhost:9292 でアプリケーションが起動します。
あとは lib, views, assets 配下のファイルを編集して開発していくだけです。

少し詳しく解説

bower

bower は javascript/css ライブラリ群 のパッケージマネージャで、bower.json に記述されたパッケージを Search Bower packages から取得してきて bower_components ディレクトリ配下にインストールして管理してくれます。

しかしただそれだけであって、例えば javascript のファイルを public/js 配下に、css のファイルを public/css に設置するなど、そういったことを柔軟にやってくれるわけではありません。
そういったことは、後述する grunt によって解決します。

bower.json 全体

dependencies に必要なライブラリ名とバージョンを記述します。
exportsOverride は、後述する gruntgrunt-bower-task から利用する場合に、dependencies に記述されたそれぞれのライブラリの中からどのファイルが必要なのか明示するために記述します。

{
  "name": "ore no sinatra skelton",
  "version": "0.0.1",
  "dependencies": {
    "vue": "latest",
    "underscore": "latest",
    "jquery": "latest",
    "bootstrap": "latest",
    "bootswatch": "latest",
    "Font-Awesome": "latest"
  },
   "exportsOverride": {
     "jquery": {
       "js": "dist/*"
     },
     "underscore": {
       "js": [ "underscore-min.js", "underscore-min.map" ]
     },
     "vue": {
       "js": "dist/vue.min.js"
     },
     "bootstrap": {
       "js": "dist/js/bootstrap.min.js",
       "css": [ "dist/css/*.min.css", "dist/css/*.map" ],
       "fonts": "dist/fonts/*"
     },
     "bootswatch": {
       "css": "journal/bootstrap.min.css"
     },
     "Font-Awesome": {
       "css": "css/font-awesome.min.css",
       "fonts": "fonts/*"
     }
   }
}

bower 単体での利用

後述する grunt などと組み合わせてではなく、単体で利用する場合は、上記のような bower.json を用意するか、 bower init コマンドによって対話的に bower.json を作成した後、 bower install コマンドを実行します。
bower install [package name] コマンドを実行すると、[package name] のみインストールすることも出来ます。
また、 bower install [package name] --save コマンドを実行することで、[package name] の情報が bower.json に記述されます。

今回は単体利用の目的ではないので、詳しく知りたい場合は後述の参考リンクを参照してください。

grunt

grunt は javascript で振る舞いを記述するタスクランナーで、プラグインとの組み合わせによって、例えば指定したディレクトリ配下の css ファイルを minify したり結合したり、 例えば altJS で書かれたファイルをコンパイルして指定ディレクトリに設置したりなどが出来ます。

Gruntfile.coffee 全体

この Gruntfile.coffee を設置して grunt コマンドを実行すると以下の様になります。
(npm install -g grunt-cli と、リポジトリ内の package.json を設置しての npm install も必要です)

  • bower install を実行し、必要なファイルを適切に public 配下に設置
  • assets 配下にある
  • assets 配下を監視し、ファイルの変更を検知したらコンパイルして適切に配置
module.exports = (grunt) ->
  grunt.initConfig
    coffee:
      compile:
        options:
          bara: true
        files: [
          expand: true
          cwd: 'assets'
          src: [ '**/*.coffee' ]
          dest: 'public/js/'
          ext: '.js'
        ]

    compass:
      dist:
        options:
          sassDir: 'assets'
          cssDir: 'public/css/'

    cssmin:
      my_target:
        files: [
          expand: true,
          cwd: 'public/css',
          src: [ '*.css', '!*.min.css' ],
          dest: 'public/css/',
          ext: '.min.css'
        ]

    bower:
      install:
        options:
          targetDir: 'public'
          layout: (type, component, bower_path) ->
            path = if component == 'bootswatch' && type == 'css'
                     "#{type}/#{component}"
                   else if component == 'bootstrap' && type == 'fonts'
                     "css/#{type}"
                   else
                     type
            path
          install: true
          cleanTargetDir: true
          cleanBowerDir: false

    esteWatch:
      options:
        dirs: [ 'assets' ]
      'coffee': (path) -> [ 'coffee' ]
      'scss': (path) -> [ 'compass', 'cssmin' ]

  grunt.loadNpmTasks 'grunt-bower-task'
  grunt.loadNpmTasks 'grunt-contrib-coffee'
  grunt.loadNpmTasks 'grunt-contrib-compass'
  grunt.loadNpmTasks 'grunt-contrib-cssmin'
  grunt.loadNpmTasks 'grunt-este-watch'
  grunt.registerTask 'make', [ 'bower', 'coffee', 'compass', 'cssmin' ]
  grunt.registerTask 'default', [ 'make', 'esteWatch' ]

grunt-bower-task

Gruntfile.coffeebower の部分だけ抜粋。

    bower:
      install:
        options:
          targetDir: 'public'
          layout: (type, component, bower_path) ->
            path = if component == 'bootswatch' && type == 'css'
                     "#{type}/#{component}"
                   else if component == 'bootstrap' && type == 'fonts'
                     "css/#{type}"
                   else
                     type
            path
          install: true
          cleanTargetDir: true
          cleanBowerDir: false
targetDir

bower install したファイルをどこに設置するか指定。

layout

どのようなレイアウトで targetDir 配下に設置するか指定。
byType, byComponent から選ぶか、自前で定義する。

  • byType で実行した場合のレイアウトの例
public
├── css
│   ├── Font-Awesome
│   │   └── font-awesome.min.css
│   ├── application.css
│   ├── bootstrap
│   │   ├── bootstrap-theme.css.map
│   │   ├── bootstrap-theme.min.css
│   │   ├── bootstrap.css.map
│   │   └── bootstrap.min.css
│   └── bootswatch
│       └── bootstrap.min.css
├── fonts
│   ├── Font-Awesome
│   │   ├── FontAwesome.otf
│   │   ├── fontawesome-webfont.eot
│   │   ├── fontawesome-webfont.svg
│   │   ├── fontawesome-webfont.ttf
│   │   └── fontawesome-webfont.woff
│   └── bootstrap
│       ├── glyphicons-halflings-regular.eot
│       ├── glyphicons-halflings-regular.svg
│       ├── glyphicons-halflings-regular.ttf
│       └── glyphicons-halflings-regular.woff
└── js
    ├── application.js
    ├── bootstrap
    │   └── bootstrap.min.js
    ├── jquery
    │   ├── jquery.js
    │   ├── jquery.min.js
    │   └── jquery.min.map
    ├── underscore
    │   ├── underscore-min.js
    │   └── underscore-min.map
    └── vue
        └── vue.min.js
  • byComponent で実行した場合のレイアウトの例
public
├── Font-Awesome
│   ├── css
│   │   └── font-awesome.min.css
│   └── fonts
│       ├── FontAwesome.otf
│       ├── fontawesome-webfont.eot
│       ├── fontawesome-webfont.svg
│       ├── fontawesome-webfont.ttf
│       └── fontawesome-webfont.woff
├── bootstrap
│   ├── css
│   │   ├── bootstrap-theme.css.map
│   │   ├── bootstrap-theme.min.css
│   │   ├── bootstrap.css.map
│   │   └── bootstrap.min.css
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.eot
│   │   ├── glyphicons-halflings-regular.svg
│   │   ├── glyphicons-halflings-regular.ttf
│   │   └── glyphicons-halflings-regular.woff
│   └── js
│       └── bootstrap.min.js
├── bootswatch
│   └── css
│       └── bootstrap.min.css
├── css
│   └── application.css
├── jquery
│   └── js
│       ├── jquery.js
│       ├── jquery.min.js
│       └── jquery.min.map
├── js
│   └── application.js
├── underscore
│   └── js
│       ├── underscore-min.js
│       └── underscore-min.map
└── vue
    └── js
        └── vue.min.js
  • 自前で定義する場合

上述の2例では、vue/js/vue.min.jsjs/vue/vue.min.js というようなスタイルになり、少々冗長な感じになってしまうので、自前で無名関数を定義してあげ、パスを返すようにしてあげれば、そのとおりの場所に設置してもらえる。

javascript ファイルは public/js 配下にフラットに並べたいし、css ファイルは public/css 配下にフラットに並べたいことが多いと思う。

例えば、このように定義してあげると、

         layout: (type, component, bower_path) ->
            path = if component == 'bootswatch' && type == 'css'
                     "#{type}/#{component}"
                   else if component == 'bootstrap' && type == 'fonts'
                     "css/#{type}"
                   else
                     type
            path

(bootswatch に含まれる css ファイルは bootstrap.min.css というファイル名で、bootstrap のそれとカブってしまっていて上書きされてしまうので、byType のレイアウトで設置)
(bootstrapbootstrap.min.css../fonts/ でフォントファイルを参照しているので、public/css/fonts 配下に設置)

このようになる

public
├── css
│   ├── application.css
│   ├── bootstrap-theme.css.map
│   ├── bootstrap-theme.min.css
│   ├── bootstrap.css.map
│   ├── bootstrap.min.css
│   ├── bootswatch
│   │   └── bootstrap.min.css
│   ├── font-awesome.min.css
│   └── fonts
│       ├── glyphicons-halflings-regular.eot
│       ├── glyphicons-halflings-regular.svg
│       ├── glyphicons-halflings-regular.ttf
│       └── glyphicons-halflings-regular.woff
├── fonts
│   ├── FontAwesome.otf
│   ├── fontawesome-webfont.eot
│   ├── fontawesome-webfont.svg
│   ├── fontawesome-webfont.ttf
│   └── fontawesome-webfont.woff
└── js
    ├── application.js
    ├── bootstrap.min.js
    ├── jquery.js
    ├── jquery.min.js
    ├── jquery.min.map
    ├── underscore-min.js
    ├── underscore-min.map
    └── vue.min.js
cleanTargetDir

targetDir を初期化してからタスクを実行するかどうか指定。
上記の例だと、public ディレクトリが実行前に削除され、再作成されます。
(これめっちゃコワイんだけど、targetDir に '.' を指定して cleanTargetDir を true にして実行するとプロジェクトがディレクトリごと消滅します。.git とかも全部消える。コワイ。)

cleanBowerDir

特に指定のない場合は bower install によって bower_components ディレクトリにインストールされますが、そこを実行時に初期化するかどうかを指定する。

foreman

grunt-este-watch を実行するようにした状態で grunt コマンドを実行すると、フォアグラウンドで grunt が起動しっぱなしになってしまうので、rack アプリケーションを起動するために別のコンソールを起動しなくてはならなくなってしまいます。
そうしなくてよいように、以下のような Procfile を用意して、bundle exec foreman start します。

application: bundle exec rackup
grunt: grunt

これであとはもう lib, views, assets 配下のファイルを編集して開発していくだけ。

やろうとしてやめたこととか

最初は、 views/layout.slimjs/application.jscss/application.min.css だけ読み込ませるように記述して、requirejsbrowserify などを利用して自前で書いた javascriptbower でインストールしたライブラリをいい感じにガッチャンコしたひとつのファイルを作成する、わざわざ scss ファイルをインストールして compass を利用して自前で書いた cssbower でインストールしたライブラリをいい感じにガッチャンコしたひとつのファイルを作成する、などということを試みました。
が、やめました。
意図しない挙動などのトラブルに対処するにあたり、ガッチャンコされて minify されてしまっていると、どのライブラリ起因の問題なのか切り分けるときに一個ずつ読み込ませないようにして探るとか出来なくてとても大変です。
使うライブラリを link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" だったり script src="/js/vue.min.js" みたいにそれぞれ書けばいいじゃん。
javascript/css のファイルをテンプレートでそれぞれ一つずつ読み込ませたいというだけのために頑張ることじゃないよなぁ。ということで。

あと、「requirejsオワコンなんで browserify 使いましょう」とか突然若者に言われたので、両方それなりに調べて使えるようにしたけど、いずれを使うにしても目的と天秤にかけたらとにかくコストが高すぎると感じた。
現在ナウいものを選択したとしても、来年にはそれがオワコン化してるかもしれないし、本業の領域ではない分野のトレンドを追いかけ続けてオワコンじゃないものに切り替えていくのは、やはり今回の目的からすると異常な高コストでしかなかった。

なぜ今 coffee script なのか

coffee scriptオワコン。本命 TypeScript, 対抗は Haxe, Dreamy だけどおもしろいのが Dart だ。」
みたいな意見を数人から頂戴した。

coffee script は利用者が減ってきていて、コミュニティも以前のような活発さがなくなっているとのことだが、それは現状自分にとってはあまりリスクではなく、解説記事やサンプルコードなど、そんなに古くないものがまだまだいくらでもインターネット上に存在しているので、困難に対処しやすいという観点でもまだまだメリットが多い。
それに自分としては「文字列内での変数展開、簡潔なイテレーション記法、省略記法が使える」など、「簡潔な記述が出来る」ということだけ求めているので、情報の多さ(特に母国語での)という観点から coffee script でとりあえず書いてみようということにした。

もしメンテを継続していくプロジェクトにおいて coffee script を使わなくなったとしても、ゴリゴリ javascript を書いてるわけではないので他の altjs で書き換えるのはさほどコストをかけずに出来るだろうし、変換されたナマの javascript をベースにして開発を継続すれば良いのかなと思っております。

参考

昨今の自分用Webアプリケーションひな形 - naoyaのはてなダイアリー
昨今のWebアプリケーションのひな形その2 - Grunt - naoyaのはてなダイアリー
Bower入門(基礎編) - from scratch
Bower入門(応用編) - from scratch
jade, sass/compass, coffeescript, bowerで静的Webサイトを作るGrunt.js秘伝のタレ - Qiita

vue.js でチェックボックスによってセレクトボックスの enabled/disabled を切り替える

タイトル通りの挙動を、このエントリを書いている現在で javascript がまったく得意ではない自分が jquery で書くとこんな感じになります。
きっともっとまともな書き方があるんでしょうけども、非常に冗長な感じですね。

vue.js で書いてみた

めっちゃスッキリした。
el で指定したセレクタの配下がスコープになるので、上記の jquery のように挙動ごとにセレクタを指定しなくて良くて(きっともっとまともな書き方があるんでしょうけども)スッキリ書けて良い。

vue.js でチェックボックスによって表示/非表示を切り替える

タイトル通りの挙動を、このエントリを書いている現在で javascript がまったく得意ではない自分が jquery で書くとこんな感じになります。
きっともっとまともな書き方があるんでしょうけども、非常に冗長な感じですね。

vue.js で書いてみた

めっちゃスッキリした。
el で指定したセレクタの配下がスコープになるので、上記の jquery のように挙動ごとにセレクタを指定しなくて良くて(きっともっとまともな書き方があるんでしょうけども)スッキリ書けて良い。

tmux 1.9 でペイン分割時にカレントディレクトリを維持

tmux 1.8 (それ以前のバージョンは知らない) では、ペイン分割時に現在フォーカスのあるペインのカレントディレクトリで新しいペインを作ってくれたが、どうやら 1.9 からはそうではなくなった模様。

だいぶ不便なので、.tmux.conf をこのようにした。
( | や - での分割はデフォルトではないので注意です )

# ペインを縦分割
unbind %
bind | split-window -h -c "#{pane_current_path}"
if-shell '[[ "`tmux -V`" =~ 1\.8 ]]' 'bind | split-window -h'

# ペインを横分割
unbind '"'
bind - split-window -v -c "#{pane_current_path}"
if-shell '[[ "`tmux -V`" =~ 1\.8 ]]' 'bind - split-window -v'

split-window コマンドのオプション -c#{pane_current_path} を渡してあげるようにする。
1.8 でこの設定を食わすと、逆にカレントディレクトリを維持してくれなくなるので、if-shell でバージョンを確認して -c 以降を渡さないようにしている。