読者です 読者をやめる 読者になる 読者になる

RSCSS という CSS 設計について

(追記)

実践例あった

http://ricostacruz.com/rscss/index.html

追記終わり。


https://github.com/rstacruz/rscss

しばらく CSS とか追ってなかったので、触るにあたって「むやみにCSS書いてたら後で確実に死ぬし、そういえばなんかOOCSSとかあったな」と思っていろいろ調べてたら OOCSS の他にも SMACSS とか BEM とか SuitCSS とか FLOCSS とかなんかいろいろ出てきて大変でした。たしか SMACSS くらいまでは記憶があるんだけど…。

で、どうもどれもしっくり来ないのでさらに調べてみると RSCSS というものを発見。「フレームワークじゃなくてあくまでもアイデア集だよ」ってことらしく、ルールガチガチでもないしなんだかよさ気。

で、RSCSS の README を読みながらメモしたんでせっかくだし公開します。ほぼただ訳しただけなんですが…。英語あまり得意ではないので間違いなど指摘いただけると助かります。

Components

検索フォームのようなひとかたまりをひとつのコンポーネントとして考える

Components の命名規則

Components は ダッシュで区切られた 少なくとも2つの単語 からなる。

  • Like ボタン ( .like-button )
  • 検索フォーム ( .search-form )
  • ニュースカード ( .article-card )

Elements

Elements とは Component を構成する内部要素。

Elements の命名規則

それぞれの Component は通常、複数の Element を持つ。 Element の名前は 1単語 にする。

  .search-form {
    > .field { /* ... */ }
    > .action { /* ... */ }
  }

Element のセレクタ

可能な限り子セレクタ > を使う。Componentのネスト防止になるし、子孫セレクタよりパフォーマンスがいい。

  .article-card {
    .title     { /* これでもいいけど */ }
    > .author  { /* ✓ こっちのほうがいい */ }
  }

複数の単語が使いたい

2つ以上の単語からなる名前をつけたい場合は、ダッシュやアンダースコアを使わないで単純に連結する。

  .profile-box {
    > .firstname { /* ... */ }
    > .lastname { /* ... */ }
    > .avatar { /* ... */ }
  }

タグセレクタを避ける

可能な限りクラス名を使用する。タグセレクタはよいものだが、若干のパフォーマンス上のペナルティがあり、また説明的ではないかもしれない。

  .article-card {
    > h3    { /* ✗ これは避ける */ }
    > .name { /* ✓ こうするほうがいい */ }
  }

Variant

Component も Element もそれぞれ variant を持つことができる。

Variant の命名

variant のためのクラス名にはダッシュ ( - ) を prefix としてつける

  .like-button {
    &.-wide { /* ... */ }
    &.-short { /* ... */ }
    &.-disabled { /* ... */ }
  }

Element の variant

Element もまた variant をもつことができる

  .shopping-card {
    > .title { /* ... */ }
    > .title.-small { /* ... */ }
  }

ダッシュ・プレフィックス

ダッシュは variant のプレフィックス(接頭辞)として使用できる

  • Element との曖昧さ防止
  • CSSのクラス名はアルファベットそして _ または - で始めることができる
  • ダッシュはアンダースコアよりタイプしやすい
  • it kind of resembles switches in UNIX commands ( gcc -O2 -Wall -emit-last)

(ダッシュやアンダースコアではじまるクラス名ってOKなの知らなかった)

In CSS, identifiers (including element names, classes, and IDs in selectors) can contain only the characters [a-zA-Z0-9] and ISO 10646 characters U+00A0 and higher, plus the hyphen (-) and the underscore (_); they cannot start with a digit, two hyphens, or a hyphen followed by a digit. Identifiers can also contain escaped characters and any ISO 10646 character as a numeric code (see next item). For instance, the identifier "B&W?" may be written as "B&W\?" or "B\26 W\3F".

http://www.w3.org/TR/CSS21/syndata.html#characters

ネストした Component

<div class='article-link'>
  <div class='vote-box'>
    ...
  </div>
  <h3 class='title'>...</h3>
  <p class='meta'>...</p>
</div>

時には Component をネストする必要がでてくる

ネストした Component の Variant

Component は何らかの方法で他のComponentにネストする必要が生じるかもしれない。ネストしたコンポーネントを、それを内包するコンポーネントに突っ込むような変更は避ける。

.article-header {
  > .vote-box > .up { /* ✗ これは避ける */ }
}

かわりに、ネストした Component に Variant を追加し、それを含む component からそれを適用するのが好ましい。

<div class='article-header'>
  <div class='vote-box -highlight'>
    ...
  </div>
  ...
</div>
.vote-box {
  &.-highlight > .up { /* ... */ }
}

ネストした Component の単純化

しばしば、ネストしたコンポーネントマークアップが汚くなる

<div class='search-form'>
  <input class='input' type='text'>
  <button class='search-button -red -large'></button>
</div>

CSSプリプロセッサーの @extend によって単純化することができる

<div class='search-form'>
  <input class='input' type='text'>
  <button class='submit'></button>
</div>
.search-form {
  > .submit {
    @extend .search-button;
    @extend .search-button.-red;
    @extend .search-button.-large;
  }
}

Layout

ポジションに関するプロパティを避ける

Component は異なるコンテキストで再利用できるべきなので、以下のようなプロパティを書くのを避ける

  • Positioning (position, top, left, right, bottom)
  • Floats (float, clear)
  • Margins (margin)
  • Dimensions (width, height) *

固定寸法

アバターやロゴのような width/height が固定される要素については例外とする

親要素でポジションを定義する

これらを定義したいなら、それらが所属するコンテキストに定義する。下記の例で言うと、 list コンポーネントに適用するのであって、 .article-card 自身に定義するのではない。

  .article-list {
    & {
      @include clearfix;
    }

    > .article-card {
      width: 33.3%;
      float: left;
    }
  }

  .article-card {
    & { /* ... */ }
    > .image { /* ... */ }
    > .title { /* ... */ }
    > .category { /* ... */ }
  }

Helpers

._unmargin { margin: 0 !important; }
._center { text-align: center !important; }
._pull-left { float: left !important; }
._pull-right { float: right !important; }

汎用クラスはアンダースコアで始まる名前で別ファイルに置かれ、値を上書きする。通常、 !important でタグ付けされる。慎重に使うこと。

Helper の命名

アンダースコアをクラス名のプレフィックスとして付ける。これは component に定義された modifier と容易に見分けることができる。アンダースコアはちょっと不格好で内部的な副作用がある。Helperの使い過ぎには嫌気が差すだろう。

  <div class='order-graphs -slim _unmargin'>
  </div>

Helper の整理

helpers というひとつのファイルに収める。複数ファイルに分割することもできるが、たくさんの helper を最小限に維持するのが好ましい。

CSS structure

ファイル単位でひとつの Component

それぞれの Component はそれぞれひとつのファイルに収める

  /* css/components/search-form.scss */
  .search-form {
    > .button { /* ... */ }
    > .field { /* ... */ }
    > .label { /* ... */ }

    // variants
    &.-small { /* ... */ }
    &.-wide { /* ... */ }
  }

Use glob matching

 @import 'components/*';

過剰なネストを避ける

できるだけ1階層のネストにとどめる。ネストし過ぎは簡単に迷子になる。

  /* ✗ Avoid: 3 levels of nesting */
  .image-frame {
    > .description {
      /* ... */

      > .icon {
        /* ... */
      }
    }
  }

  /* ✓ Better: 2 levels */
  .image-frame {
    > .description { /* ... */ }
    > .description > .icon { /* ... */ }
  }

落とし穴

ネストした Component で死亡

ネストした Component が似たような名前の Element を持つ場合に気をつける。

<article class='article-link'>
 <div class='vote-box'>
    <button class='up'></button>
    <button class='down'></button>
    <span class='count'>4</span>
  </div>

  <h3 class='title'>Article title</h3>
  <p class='count'>3 votes</p>
</article>
.article-link {
  > .title { /* ... */ }
  > .count { /* ... (!!!) */ }
}

.vote-box {
  > .up { /* ... */ }
  > .down { /* ... */ }
  > .count { /* ... */ }
}

この場合、 .article-link > .count> セレクタを持っておらず、おそらく .vote-box .count に適用される。これは子セレクタが好ましい理由のひとつ。

懸念事項

  • ダッシュとか最悪: このルールを無視して普通の単語をつかってもいい。ただ、 Component - Element - Variant という考えは忘れないで欲しい

  • 2単語なんて思いつかない: alert のような、いくつかの Component は表現上の理由から1単語であることを要する。これらはサフィックス(接尾辞)を使用することでブロック要素だということをより明確にすることを検討したほうがよい。

  • .alert-box

  • .alert-card
  • .alert-block

Or for inlines:

  • .link-button
  • .link-span

その他のリソース

その他のソリューション

BEM

BEM は素晴らしいけどシンタックスがちょっとキモいという人もいると思う。RSCSSはBEMとはシンタックスが違うだけで、慣習にはほぼ従っている。

<!-- BEM -->
<form class='site-search site-search--full'>
  <input  class='site-search__field' type='text'>
  <button class='site-search__button'></button>
</form>
<!-- rscss -->
<form class='site-search -full'>
  <input  class='field' type='text'>
  <button class='button'></button>
</form>

用語

同様のコンセプトはその他のCSS設計にもみられる

RSCSS BEM SMACSS
Component Block Module
Element Element Sub-Component
Layout ? Layout
Variant Modifier Sub-Module & State

まとめ

  • コンポーネント(Component) で考える。名前付けは2単語で。( .screenshot-image )
  • コンポーネント要素(Element) を持つ。名前付けは1単語で。( .blog-post .title )
  • バリアント(Variant) の名前はダッシュを接頭辞(プレフィックス)としてつける ( .shop-banner.-with-icon )
  • コンポーネントはネストしていい
  • 物事をシンプルにするために拡張していいことを覚えておいて。

DjangoでImageFieldを持つフォームのテスト

django

DjangoでImageFieldを持ったFormがあるとします。こんな感じ。ModelFormでもいい(というか実際のコードはそっちで書いてる)。

class UploadForm(forms.Form):
    title = forms.CharField()
    photo = forms.ImageField()

これをテストする際にStringIOで適当に作ったダミーを食わせたら「画像じゃないよ!」って怒られた。ダミー画像をテスト用に置いておくのもなんだかなぁ…と思ってたら、そうだ、PIL使ってるんだからPILで生成すればいいじゃないか。

#-*- coding:utf-8 -*-
import io

from PIL import Image
from django.test import TestCase
from django.core.files.uploadedfile import SimpleUploadedFile


class UploadFormTest(TestCase):
    def _get_form_class(self):
        from .forms import UploadForm
        return UploadForm

    def _make_dummy_image(self):
        file_obj = io.BytesIO()
        im = Image.new('RGBA', size=(10, 10), color=(256, 0, 0))
        im.save(file_obj, 'png')
        file_obj.name = 'test.png'
        file_obj.seek(0)
        return file_obj

    def test_it(self):
        img = self._make_dummy_image()
        Form = self._get_form_class()
        form = Form(
            data={'title': 'test title'},
            files={'photo': SimpleUploadedFile(
                img.name,
                img.read(),
                content_type='image/png',
            )},
        )
        self.assertTrue(form.is_valid())

ちなみに環境は Python 3.3 + Django 1.7.0.a です。ソースを追ってみると、form.is_valid() を呼ぶと内部で self.ImageField.to_python() が呼ばれ、さらにそのメソッド内で from django.utils.image import Image; Image.open() しているんですね。

というわけで一年以上ぶりの記事でした。

cajonでjavascriptを分割管理

javascript

JavaScript module loader for the browser that can load CommonJS/node and AMD modules

requirejs/cajon · GitHub

cajon(カホンって読むっぽい。ペルー発祥の打楽器の名前らしい)なんてものを見つけた。require.js上に構築されてるとかなんとか。require.jsもよく知らないんだけど、これはこれで簡単だしいいかも。

構成

.
├── index.html
└── js
            ├── cajon.js
            ├── controller.js
            ├── jquery.js
            ├── lib.js
            ├── model.js
            ├── template.js
            └── view.js

index.html

<!DOCTYPE HTML>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>cajon test</title>
  <script src="js/cajon.js"></script>
  <script>
    require.config({
      baseUrl: 'js'
    });
    require(['jquery', 'lib'], function($, lib) {
        $.each(lib, function() {
          $('<li>').text(this.say()).appendTo('#hoge');
        });
    });
  </script>
</head>
<body>
  <ul id="hoge">
  </ul>
</body>
</html>

lib.js

module.exports = {
  template: require('./template'),
  model: require('./model'),
  controller: require('./controller'),
  view: require('./view')
};

model.js

module.exports = {
  say: function() {
    return "I'm model.js"
  }
};

view.js

module.exports = {
  say: function() {
    return "I'm view.js"
  }
};

controller.js

module.exports = {
  say: function() {
    return "I'm controller.js"
  }
};

template.js

module.exports = {
  say: function() {
    return "I'm template.js"
  }
};

ブラウザでhtmlを開くと下のように表示される。

f:id:kk6:20120922000441p:plain

ちゃんとlib.js経由でmodel.js/view.js/controller.js/template.jsがそれぞれ読み込まれているのがわかる。

Ubuntu serverに入れてるDropboxが変なメッセージを出すようになった

linux

ちょっと前くらいからこんなメッセージが出るようになった

$ Unable to monitor entire Dropbox folder hierarchy. Please run "echo 100000 | sudo tee /proc/sys/fs/inotify/max_user_watches" and restart Dropbox to correct the problem.

わけもわからず言われるがままコマンド叩いてたんだけど、毎日ログインVM起動する度に表示されてていい加減鬱陶しくなったので調べた。

/proc/sys/fs/inotify/max_user_watches とは

引用の引用

/proc インターフェース

以下のインターフェースは、inotify で消費されるカーネルメモリの総量を制限するの
に使用できる:

/proc/sys/fs/inotify/max_queued_events

このファイルの値は、アプリケーションが inotify_init(2) を呼び出すときに使用さ
れ、対応する inotify インスタンスについてキューに入れられるイベントの数の上限
を設定する。この制限を超えたイベントは破棄されるが、 IN_Q_OVERFLOW イベントが
常に生成される。

/proc/sys/fs/inotify/max_user_instances

1 つの実ユーザ ID に対して生成できる inotify インスタンスの数の上限を指定す
る。

/proc/sys/fs/inotify/max_user_watches

作成可能な監視対象の数の実 UID 単位の上限を指定する。

lsyncdで上限ファイル数を超えた時の対処策 - Unix的なアレ

だそうです。要するに監視対象のファイル数が上限をオーバーしたってことか。

/etc/sysctl.conf

/proc/sys/fs/inotify/max_user_wathces は直接書き換えても再起動の度にリセットされるらしいので、/etc/sysctl.conf を編集する

こいつはカーネルパラメータを設定するファイルだそうです。そんな事言われたらインフラ知識からっきしな僕は怖くて手が震えてしまうんですががが。
[ThinkIT] 第5回:カーネルをチューニングする (1/4)

管理者権限で開く。

$ sudo vi /etc/sysctl.conf

以下の行を追加する

fs.inotify.max_user_watches = 100000

追加後にこの設定を反映させるために以下のコマンドを叩く

$ sudo sysctl -p

これでたぶん直ったはず。

コミット時にpdb消し忘れてないかチェックするhg hook

python mercurial

チェック用のスクリプト

こんな感じのスクリプト作成

#!/usr/bin/env python

import os
import sys


COMMAND = r"find . -name '*.py' | xargs grep -lr '{pattern}'"
INFO = '{filename}: pdb found.'


def check(pattern):
    return os.popen(COMMAND.format(pattern=pattern)).readlines()


def display_warning(lines):
    for line in lines:
        print >> sys.stderr, INFO.format(filename=line.strip('\n'))


def check_pdb(*args, **kwargs):
    lines = check('pdb.set_trace')
    if lines:
        display_warning(lines)
        sys.exit(1)

~/.hgext/hookpdb/check.py みたいな感じで配置。

.hgrcに追記

[hook]
pretxncommit.pdb = python:~/.hgext/hookpdb/check.py:check_pdb

試してみる

pdb仕込みっぱなしのファイルがある状態でコミットしようとすると…

$ hg ci
./hoge.py: pdb found.
transaction abort!
rollback completed
note: commit message saved in .hg/last-message.txt

うん、できてる。けど、ファイル多いともっさりがキニナルような…

追記

ありがとうございますありがとうございます!
描き直してみました。

#!/usr/bin/env python

import os
import sys
from fnmatch import fnmatch


COMMAND = r"grep -l {pattern} {file}"
INFO = '{filename}: debugger found.'
PATTERNS = ['pdb.set_trace', 'import debug']


def check(repo, file, patterns):
    patterns = ['-e "{ptn}"'.format(ptn=pattern) for pattern in patterns]
    pattern = ' '.join(patterns)
    file = os.path.join(repo.root, file)
    return os.popen(COMMAND.format(pattern=pattern, file=file)).read()


def display_warning(lines):
    for line in lines:
        print >> sys.stderr, INFO.format(filename=line.strip('\n'))


def get_pyfiles(repo, node):
    return [f for f in repo[node].files() if fnmatch(f, '*.py')]


def check_pdb(ui, repo, hooktype, node=None, source=None, **kwargs):
    lines = [f for f in get_pyfiles(repo, node) if check(repo, f, PATTERNS)]
    if lines:
        display_warning(lines)
        sys.exit(1)

さらに修正

リポジトリルートで実行しないとgrep時にnot foundになってしまっていたので絶対パスgrepするように修正しました。

さらにさらに修正

ゆとりな僕はpdbよりもdebugモジュールを使いがちなのでパターンを複数受け取れるように修正しました。もうGistでやれよと思います。

インスタンスに動的にメソッドを追加する

python

追記:
記事を公開してからおんなじような記事あったりするかなと思ってググったら2年前にIanさんがほとんど同じ内容書いてた。
Pythonでメソッドをクラスまたはインスタンスに動的に追加する - Ian Lewis
しかも僕その記事はてブしてたよ…

Interactive Shell で色々試してる時なんかにインスタンスにメソッドを追加したくなる時がある。

>>> class Person(object):
...     def __init__(self, name):
...         self._name = name
...
>>> alice = Person("Alice")
>>> bob = Person("Bob")

こんなクラスがあって、幾つかインスタンスを作っているとする。で、self._nameを取得するメソッドが欲しくなったとしよう。

>>> def get_name(self):
...     return self._name

これを単純にインスタンスのメンバーとして代入してもうまくいかない

>>> alice.get_name = get_name
>>> alice.get_name()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: get_name() takes exactly 1 argument (0 given)

これはalice.get_nameがただの関数だから。インスタンスメソッドではない。

>>> type(alice.get_name)
<type 'function'>

コレジャナイので一旦消します。

>>> del alice.get_name

さて、じゃあどうすればいいかというと

>>> Person.get_name = get_name

というようにクラスの方に追加してやる。ただ、これだと当然ながら全てのインスタンスに影響が出る

>>> alice.get_name()
'Alice'
>>> bob.get_name()
'Bob'

影響が出るというか、実はaliceもbobもget_nameなんて持ってない。持ってないんだけど、クラスの方を見に行ったらそっちにあったからそれを呼び出してるだけ。

>>> del alice.get_name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: get_name
>>> alice.__dict__
{'_name': 'Alice'}

というわけでまあこれでいいんだけど、もしある特定のインスタンスにだけ設定したいなんて場合にはどうすればいいか…というのが今回の本題。結論としては、types.MethodTypeを使う。

>>> del Person.get_name
>>> import types
>>> alice.get_name = types.MethodType(get_name, alice)
>>> alice.get_name()
'Alice'
>>> bob.get_name()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'get_name'

第一引数にメソッドとして登録したい関数、第二引数にインスタンスを渡してやると、それらを紐付けてくれる。これでaliceはget_nameメソッドを持ってるけど、bobは持っていないという状態にすることができる。用途が思いつかないけど気になったから調べてみただけ。

celeryのPeriodic Taskについてのメモ

python Django

必要になったので自分用にメモ。今必要な部分だけざっくりと把握しただけなので間違ったこと書いてたらごめんなさい。

Periodic Taskとは

Periodic Tasks — Celery 3.0.9 documentation

celeryにはPeriodic Taskというタスクをある一定時間ごとに実行してくれる仕組みがある。そのスケジュールを管理してくれるのがcelery beat。

celerybeat: タスクのスケジュールを管理
celeryd: タスクの実行

periodic_taskデコレータ

定期実行タスクをスケジューラーに登録するためにdjangoのsettings.pyにCELERYBEAT_SCHEDULEを設定する必要がある。が、これとは別の方法としてperiodic_taskデコレータを使用する方法があるのでそちらを使うことにする。

from celery.task import periodic_task
import datetime.timedelta as timedelta
...

@periodic_task(run_every=timedelta(minutes=1))
def hoge():
    ...

ソースを見た感じPeriodicTaskクラスは抽象クラスという位置づけのようで、実際に使うには継承してやって具体的な実装をする。その際に必ずrun_everyという属性を設定してやらないと例外を吐くように作られている。

なので、periodic_taskデコレータを使う場合も、引数に run_every=hogehoge というように渡してやらないといけない。

crontab

引数に渡す時間は先ほどのコード例のようにtimedeltaで指定してやる方法もあるが、より柔軟な設定を簡単にするためにcrontabという関数が用意されている。詳しくはドキュメントを参照。

from celery.task import periodic_task
from celery.task.schedule import crontab

@periodic_task(run_every=crontab())  # 毎分実行
def hoge():
    ...

@periodic_task(run_every=crontab(minute="0", hour="0")) 毎日深夜0:00に実行
def fuga():
    ...

タスクの実行

celerybeatはあくまでもスケジュールを管理するだけで、実行まではしてくれない。あくまでも「xxxを実行する時間ですよ!」とcelerydの方に伝えるだけ。celerydが寝ていたら当然タスクは実行されないのでcelerydも一緒に起動しておく必要がある。

$ ./manage.py celerybeat -l DEBUG --settings=setting.xxx
$ ./manage.py celeryd -l DEBUG --settings=setting.xxx

※ タスクの内容にもよるけど、必ずしもDjangoをrunserverしておく必要は無い。

celerybeatを起動時に色々とログが出力される。その中にperiodic_taskデコレータをつけた関数が登録されているのが確認できる。で、時間が来ればそれらが実行される。