Path::Class(::File)のslurp()でバイナリ・ファイルががが

Posted at 2009-12-22T13:28:00+09:00 in Coding

添付ファイル付きのメールを作成・送信するオレオレスクリプトを作ってて、ファイル名とかの扱いが簡単そうだったのでPath::Classを使ってみた。添付ファイルの読み込みもPath::Class(::File)のslurp()でやろうとしたのだけど、バイナリ・ファイルを添付する時にbinmodeして読み込む方法がわからなくちょっと困った。そしてどうやるのが正解なのかまだわからない……。

Path::Class(::File)のslurp()を含むopen()関連のメソッドはIO::Fileを使っているようなので、こういう風に書けばとりあえず目的は達成できるらしい。

use Path::Class;

my $file = file("foo.png");
my $fh = $file->openr();
$fh->binmode();
local $/;
my $content = <$fh>;

slurp()のコード読んで一行追加しただけ。

use Path::Class;

my $file = file("foo.png");
my $content = $file->slurp(binmode => 1);

とか書きたい。

Google Closure Compiler ServiceでJavaScriptファイルをコンパイルするPerlスクリプト

Posted at 2009-12-13T14:08:00+09:00 in Coding

最近になってGoogleのClosure Compiler Serviceを良く使うようになった。今まで使っていたYUI Compressorと比較すると、複数のJavaScriptファイルをまとめて圧縮出来ることと圧縮にとどまらない最適化を行うことも出来ることがメリットで、CSSの圧縮には対応していないことがデメリット。ウェブのUIで圧縮するのは面倒になってきたので、REST APIを利用してウェブのUIと同じようにコメントで設定を記述してコンパイルするPerlスクリプトを書いてみた。タイトル長い!

#!/usr/bin/perl

# gccs.pl - Compile your JavaScript code with Google Closure Compiler Service
# Usage:    gccs.pl < <original file> > <compiled file>
# Author:   Kyo Nagashima <kyo@hail2u.net>, http://hail2u.net/
# License:  MIT license (http://opensource.org/licenses/mit-license.php)
# Modified: 2009-12-12T00:57:02+09:00

use strict;
use warnings;

use JSON;
use LWP::UserAgent;

my @params = (
  "output_info",   "compiled_code",
  "output_format", "json",
);

&main;
exit;

sub main {
  my @code = <STDIN>;
  push @params, "js_code", join("", @code);
  my $idx = 0;
  my $found_metadata = 0;

  while (my $line = $code[$idx++]) {
    if ($line =~ /^\/\/ ==ClosureCompiler==/) {
      $found_metadata = 1;
      last;
    }
  }

  if ($found_metadata) {
    while (my $line = $code[$idx++]) {
      if ($line =~ /^\/\/ ==\/ClosureCompiler==/) {
        last;
      } elsif ($line =~ /^\/\/ @(\S+)\s*(.*)$/) {
        push @params, $1, $2;
      }
    }
  }

  &compile(@params);
}

sub compile {
  my $ua = LWP::UserAgent->new;
  my $res = $ua->post("http://closure-compiler.appspot.com/compile", \@_);

  if ($res->is_success) {
    my $c = from_json($res->decoded_content);

    if (defined $c->{"serverErrors"}) {
      foreach (@{$c->{"serverErrors"}}) {
        warn "Error(" . $_->{"code"} . "): " . $_->{"error"};
      }

      die "Failed to compile";
    } else {
      print $c->{"compiledCode"};
    }
  } else {
    die $res->status_line;
  }
}

使い方は、

$ gccs.pl < original.js > compiled.js

コンパイルするfoobar.jsにはコンパイル設定を書くこともできる(設定を書かなくても圧縮される)。設定は以下のようにコメントとして記述(ウェブUIと同じ)。

// ==ClosureCompiler==
// @code_url          http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
 
$(function () {
  var who = "Google Closure Compiler Service";
  alert("Hello " + who + "!");
});

細かい設定はAPIリファレンスを参照。code_urlはいくつでも指定できる。compilation_levelはデフォルトでSIMPLE_OPTIMIZATIONSに設定されているので省略可能。パラメータは丸投げなのでリファレンスに載っていない適当なパラメータなども設定できてしまうが、実害はないと思う。結果がJSONで返ってくるとみなしてパースしているので、output_formatは指定しない方が良い。また警告やエラーの出力にはまったく対応していないのでoutput_infowarning_levelを指定しても無意味。

foobar.jsには設定だけ書いても構わない。その場合はcode_urlが必須になる。

// ==ClosureCompiler==
// @code_url          http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js
// @code_url          http://hail2u.net/scripts/prettify/lang-css.js
// @code_url          http://hail2u.net/scripts/prettify/loader.js
// ==/ClosureCompiler==

以上のように書いただけでもちゃんとコンパイルされる。


Vimから簡単に使えるように$MYVIMRCには以下のように書いた。

" Compile JavaScript file with Google Closure Compiler Service
" :GCCS [<path>]
command! -nargs=? -complete=file GCCS :call s:CompileScript('<args>')

function! s:CompileScript(file)
  if a:file == ''
    silent normal ggVG
    '<,'>!perl -S gccs.pl
    silent normal V
  else
    execute '!perl -S gccs.pl < ' . expand('%') . ' > ' . a:file
  endif
endfunction

コマンドGCCSにファイルのパスが渡されたらそのファイルに出力、そうでない場合は全選択して差し替えるというもの。

はてなブックマークのブックマークレットをlightbox化

Posted at 2009-12-10T10:00:00+09:00 in Coding

はてなブックマークのデフォルトのブックマークレットiframeでオーバーレイ表示されるが、サイズ的にも位置的にも見づらい気がするので、ユーザースタイルシートをいくつか書いてみた。最初はオーバーレイ表示ではない旧バージョンのブックマークレットを使い、そのページのスタイルシートを書き換えてみたのだけど、見やすいことは見やすいがイマイチな感じだった。そこでもっと単純にオーバーレイ表示のサイズと位置を変更するだけのものを書いたりしてみたが、これもイマイチ。最終的にlightboxっぽく表示してやれば良いんじゃないかと思い、試してみたらなかなか良さそうな感じだった。

Lightbox化したはてなブックマークのブックマークレットのプレビュー

投稿フォームはブラウザ・ウィンドウのど真ん中に表示される。調節するのが面倒だったのでサイズの変更はしていない。ユーザースタイルシートのコードは以下の通り。

.hatena-bookmark-bookmarklet-container {
  padding: 0 !important;
  display: table !important;
  position: fixed !important;
  top: 0 !important;
  right: 0 !important;
  bottom: 0 !important;
  left: 0 !important;
  width: 100% !important;
  height: 100% !important;
  background-color: transparent !important;
}

.hatena-bookmark-bookmarklet-container div {
  padding: 0 !important;
  background-image: none !important;
  text-align: center !important;
}

.hatena-bookmark-bookmarklet-header {
  display: none !important;
}

.hatena-bookmark-bookmarklet-header + div {
  display: table-cell !important;
  vertical-align: middle !important;
  background-color: rgba(0, 0, 0, 0.75) !important;
}

.hatena-bookmark-bookmarklet-container iframe {
  padding: 0 !important;
  background-image: none !important;
}

あらゆるサイトに表示される可能性があるので、@-moz-document domain()等は使わ(え)ない。このユーザースタイルシートで元々の投稿フォームのヘッダにある閉じるボタン(やシェードボタン)が機能しなくなってしまうので、投稿フォームを閉じるにはリロードする必要がある(ヘッダは残してやっても意味が無いようなので削除した)。これだと非常に操作性が悪いので、投稿フォーム以外の半透明な部分をクリックするとフォームが消え、再度ブックマークレットを実行すると再表示されるブックマークレットは書いてみた。表示・非表示のトグル以外はデフォルトのものと同じ。非表示にするだけなのでフォームに入力した内容とかは維持される。

Bookmarklet: ブックマークする

あんまり試してないのでちゃんと動くかわからない……。単純にタイマーで投稿フォームが作成されたかどうか監視して、作成されたら投稿フォームをトグルするコードを仕込むというだけのもの(なのにやたら長い)。もちろん上記ユーザースタイルシートと共に使わないと意味はない。

拡張使え!拡張使え!拡張使え!Chromeで!!1はてなブックマークのGoogle Chrome拡張はベータテスト中です

追記@2009-12-10T13:03:41+09:00

Chromeにはユーザースタイルシート的なものがないので、スタイルを追加する簡単なユーザースクリプトを書いてみた。現在の最新ベータ版(4.0.249.30)ではrawをクリックすると拡張としてインストールされる。試してみた限りではうまく動いている模様

AutoHotkey: ホイール・スクロールを加速

Posted at 2009-12-07T10:20:00+09:00 in Coding

ロジクールのマウス・ユーティリティのSetPointにはホイールによるスクロールを加速するオプションが無い(モデルによって違うのかもしれない)。加速だけするソフトウェアというのもあまりなく、いくつか見つけたものは常用しているソフトウェアと干渉したりFirefoxでうまく動かなかったりしたので、単純なAutoHotkeyスクリプトを書いて誤魔化してみた。

WheelUp::
WheelDown::
  wheeltype := A_ThisHotkey

  if (wheeltype <> A_PriorHotkey || A_TimeSincePriorHotkey > 250) {
    wheelcount = 1
  } else if (wheelcount < 8 * 5) {
    wheelcount++
  }

  count := (wheelcount // 8 * 3) + 1
  ; ToolTip, %count%`, %wheelcount%
  MouseClick, %wheeltype%, , , %count%

  return

最初はユーティリティ等で設定している行数でスクロールし、ホイールを250ms以内に回し続けると段々と4倍→7倍→10倍→13倍→16倍(最大)と加速していく。ホイールを8回回すごとに加速が成されるので、普通にたらたら回した場合は設定行数でスクロールすると思う。

最初はホイールを回した回数=スクロールする行数という形で書いていたが、それだと普通にゆっくりとスクロールさせるのが難しくなってしまったので、段階的に加速していくようにした。AutoPagerizeなページの時など若干スクロールする時に負荷がかかる場合は意図したように動かないのをどうにかしたい。


そういえば(g)Vimでは<A-Enter>にマッピングすることができなくて、FuzzyFinderから<A-Enter>で新しいタブで開くことができなかったのもAutoHotkeyで誤魔化した。

; Redirect Alt + Enter => Ctrl + L for gVim
!Enter::
  WinGet, process_name, ProcessName, A

  if (InStr(process_name, "gvim.exe") > 0) {
    Send, ^L
  } else {
    Send, !{Enter}
  }

  return

Firefoxと挙動を合わせたかっただけ。Vimを使い始めて20種類くらいから6種類に減ったAutoHotkeyによるキー割り当てが、徐々に増えて12種類まで戻ってしまった……。

SimpldR: livedoor Readerをシンプルに

Posted at 2009-11-20T09:38:00+09:00 in Coding

しばらく前からユーザー・スタイルシートとユーザー・スクリプトのコンビネーションでlivedoor Readerをシンプルにして使うようにしていて中々快適なのでまとめてエントリにしてみる。キーボードでサクサク操作できて快適ウハウハとかじゃなくてシンプルな画面でマウス・ホイールを使ってひゅいんひゅいん読む感じ。

SimpldRのプレビュー(Firefox)

ユーザー・スタイルシート

ヘッダの色などを排除するのがメイン。userContent.cssに以下のCSSを追加する。

/* livedoor Reader
----------------------------- */
@-moz-document domain("reader.livedoor.com") {
  * {
    font-family: "Lucida Grande", "Trebuchet MS", sans-serif !important;
  }

  pre, code, kbd, samp, var {
    font-family: "Monaco", "Andale Mono", monospace !important;
  } 

  .body img {
    max-width: 100% !important;
  }

  .channel {
    background-color: #ffffff !important;
  }

  .channel a {
    color: #000000 !important;
  }

  .title {
    font-size: 30px !important;
  }

  .channel_toolbar {
    padding-bottom: 1em !important;
    border-bottom: 1px solid #000000 !important;
    background-color: #ffffff !important;
  }

  .item {
    border-bottom: 1px solid #000000 !important;
  }

  .even {
    background-color: #ffffff !important;
  }

  .hilight {
    background-color: #ffffff !important;
  }

  .item_title {
    font-size: 18px !important;
  }

  .adsWrapper {
    margin: 0 !important;
  }
}

ldR側の設定で「本文の文字サイズ」を15pxにしておくとバランスが良い。display: none;とかはしていないので他のなんかと衝突して困るとかは無いと思う。広告消しとかは自分でどうぞ。あとフォント指定も好きに変えると良いと思う。

ユーザー・スクリプト

ldR: Auto fullscreen
livedoor Readerを自動的にフルスクリーンにする
ldR: Show first feed
livedoor Readerにアクセスした時に最初のフィードを自動的に開く
ldR: Go prev/next feed with wheel
livedoor Readerでホイール・スクロールし切ると自動的に前後のフィードに移動する

一つ目のユーザー・スクリプトで自動的にフルスクリーン、つまり記事表示部分のみにする。他の二つは操作系のものなので、自分好みにカスタマイズしている人は入れる必要は無い。このままだと設定とかにアクセスできないが、ldRにはショートカットキーがあるので、必要な時はzでサイドバー、Shift+zでトップのナビゲーションを表示できる。


以上でFirefoxではいける。PrismGreasemonkey for Prismを組み合わせればアプリケーション化も可能。だがスタンドアローン版のPrismの場合は起動に時間がかかり実用的ではなく、Firefoxの拡張版のPrismの場合はブラウザを起動済みなら起動速度に問題はなくなるが、それならタブで開けば良い話。

そこでGoogle Chromeに頑張ってもらう。2009-11-20現在の最新安定版のChromeではユーザー・スクリプトをサポートしているので、Chrome向けにカスタマイズしたユーザー・スクリプトをインストールし、以下のようなショートカットを作成すればさくっと起動するSimpldRの出来上がり。ユーザー・スクリプトのインストールは続・先取り! Google Chrome Extensions:第2回 User ScriptsとContent Scriptsの「導入」が一番わかりやすい。

SimpldR on Google Chrome

今のChromeでは残念ながらユーザー・スタイルシートはサポートされていない感じ(僕が知らないだけかも)なので、ユーザー・スクリプト内で強引にスタイルを設定してやっている(あって良かったGM_addStyle())。スクリーンショットはChrome 3でのものだがChrome 4 betaでも動作する。

アプリケーションのショートカットの作成は、とりあえず普通にChromeでldRを開いてページ メニューからアプリケーションのショートカットを作成を実行し、デスクトップ等に作られたショートカットのプロパティを開き、スクリーンショットのように

...\chrome.exe" --enable-user-scripts --app=http://reader.livedoor.com/reader/

--enable-user-scriptsを追加してやれば良い。Chromiumとかの話はよく知らないけど似たような感じでできるらしい。


Chromeのユーザー・スクリプトは書き方わかりづらい……。古い情報と新しい情報が混ざってるので混乱する。

CSSのプロパティをソートするPerlスクリプト

Posted at 2009-11-14T14:11:00+09:00 in Coding

CSSを書く時に「セレクタ内でCSS仕様書でのプロパティの出現順序に従ってソートする」という個人的なルールを守っている。何かコーディングにおいて便利な理由があるからというわけではなく、第三者に説明する時に「仕様書の出現順で書いてます!」とかで済ませられるから。今まではファイル全体を処理するオレオレPerlスクリプトで適当にやっていたのだけど、Vimで選択範囲だけをソートとかやりたくなったので、普通に標準入力を読んで結果を標準出力に吐くように書き直した。ついでにCSS3のプロパティとFirefox (Mozilla)やSafari (WebKit)、Opera (Presto)、Internet Explorer (Trident)の独自拡張などへも対応させたりとか。

#!/usr/bin/perl

# Author:   Kyo Nagashima <kyo@hail2u.net>, http://hail2u.net/
#           xaicron, http://blog.livedoor.jp/xaicron/
# License:  MIT license (http://opensource.org/licenses/mit-license.php)
# Modified: 2009-11-17T23:09:27+09:00

use strict;
use warnings;

# CSS2.1
my @property_order = qw(
margin
margin-top
margin-right
margin-bottom
margin-left
padding
padding-top
padding-right
padding-bottom
padding-left
border
border-top
border-bottom
border-right
border-left
border-width
border-top-width
border-right-width
border-bottom-width
border-left-width
border-color
border-top-color
border-right-color
border-bottom-color
border-left-color
border-style
border-top-style
border-right-style
border-bottom-style
border-left-style
display
position
top
right
bottom
left
float
clear
z-index
direction
unicode-bidi
width
min-width
max-width
height
min-height
max-height
line-height
vertical-align
overflow
clip
visibility
content
quotes
counter-reset
counter-increment
list-style
list-style-type
list-style-image
list-style-position
page-break-before
page-break-after
page-break-inside
orphans
widows
color
background
background-color
background-image
background-repeat
background-attachment
background-position
font
font-family
font-style
font-variant
font-weight
font-size
text-indent
text-align
text-decoration
text-shadow
letter-spacing
word-spacing
text-transform
white-space
caption-side
table-layout
border-collapse
border-spacing
empty-cells
cursor
outline
outline-width
outline-style
outline-color
volume
speak
pause-before
pause-after
pause
cue-before
cue-after
cue
play-during
azimuth
elevation
speech-rate
voice-family
pitch
pitch-range
stress
richness
speak-punctuation
speak-numeral
speak-header
);

# CSS3 and vendor extension for CSS3 
push @property_order, qw(
background-clip
-moz-background-clip
-webkit-background-clip
background-origin
-moz-background-origin
-webkit-background-origin
background-size
-moz-background-size
-webkit-background-size
-o-background-size
border-radius
-moz-border-radius
-webkit-border-radius
border-top-right-radius
-moz-border-radius-topright
-moz-border-top-right-radius
border-bottom-right-radius
-moz-border-radius-bottomright
-moz-border-bottom-right-radius
border-bottom-left-radius
-moz-border-radius-bottomleft
-webkit-border-bottom-left-radius
border-top-left-radius
-moz-border-radius-topleft
-moz-border-top-left-radius
border-image
-moz-border-image
-webkit-border-image
border-image-source
border-image-slice
border-image-width
border-image-outset
border-image-repeat
box-break
box-shadow
-moz-box-shadow
-webkit-box-shadow
appearance
-moz-appearance
-webkit-appearance
icon
box-sizing
-moz-box-sizing
-webkit-box-sizing
outline-offset
resize
nav-index
overflow-x
-ms-overflow-x
overflow-y
-ms-overflow-y
overflow-style
-webkit-marquee
marquee-style
-webkit-marquee-style
marquee-loop
marquee-direction
-webkit-marquee-direction
marquee-speed
-webkit-marquee-speed
rotation
rotation-point
opacity
font-stretch
font-size-adjust
src
unicode-range
string-set
border-length
hyphens
hyphenate-resource
hyphenate-before
hyphenate-after
hyphenate-lines
hyphenate-character
text-replace
image-resolution
float-offset
marks
bookmark-level
bookmark-label
bookmark-target
target
target-name
target-new
target-position
text-height
line-stacking
line-stacking-strategy
line-stacking-ruby
line-stacking-shift
dominate-baseline
alignment-baseline
alignment-adjust
baseline-shift
inline-box-align
drop-initial-value
drop-initial-size
drop-initial-after-align
drop-initial-after-adjust
drop-initial-before-align
drop-initial-before-adjust
columns
-webkit-columns
column-width
-moz-column-width
-webkit-column-width
column-count
-moz-column-count
-webkit-column-count
column-gap
-moz-column-gap
-webkit-column-gap
column-rule
-moz-column-rule
-webkit-column-rule
column-rule-color
-moz-column-rule-color
-webkit-column-rule-color
column-rule-style
-moz-column-rule-style
-webkit-column-rule-style
column-rule-width
-moz-column-rule-width
-webkit-column-rule-width
column-span
column-fill
size
page
image-orientation
fit
fit-position
presentation-level
ruby-position
ruby-align
ruby-overhang
ruby-span
grid-columns
grid-rows
voice-volume
voice-balance
rest
rest-before
rest-after
mark
mark-before
mark-after
voice-rate
voice-pitch
voice-pitch-range
voice-stress
voice-duration
phonemes
white-space-collapse
word-break
-ms-word-break
text-wrap
word-wrap
-ms-word-wrap
text-align-last
-ms-text-align-last
text-justify
-ms-text-justify
punctuation-trim
text-emphasis
text-shadow
text-outline
text-indent
hanging-punctuation
box-orient
-moz-box-orient
-webkit-box-orient
box-direction
-moz-box-direction
-webkit-box-direction
box-original-group
-moz-box-ordinal-group
-webkit-box-ordinal-group
box-align
-moz-box-align
-webkit-box-align
box-flex
-moz-box-flex
-webkit-box-flex
box-flex-group
-moz-box-flexgroup
-webkit-box-flex-group
box-pack
-moz-box-pack
-webkit-box-pack
box-lines
-webkit-box-lines
transform
-moz-transform
-webkit-transform
transform-origin
-moz-transform-origin
-webkit-transform-origin
transform-style
-webkit-transform-style
perspective
-webkit-perspective
perspective-origin
-webkit-perspective-origin
backface-visibility
-webkit-backface-visibility
transition
-webkit-transition
transition-property
-webkit-transition-property
transition-duration
-webkit-transition-duration
transition-timing-function
-webkit-transition-timing-function
transition-delay
-webkit-transition-delay
animation
-webkit-animation
animation-name
-webkit-animation-name
animation-duration
-webkit-animation-duration
animation-timing-function
-webkit-animation-timing-function
animation-iteration-count
-webkit-animation-iteration-count
animation-direction
-webkit-animation-direction
animation-play-state
-webkit-animation-play-state
animation-delay
-webkit-animation-delay
);

# Vendor extension
push @property_order, qw(
-moz-background-inline-policy
-moz-binding
-moz-border-bottom-colors
-moz-border-left-colors
-moz-border-right-colors
-moz-border-top-colors
-moz-border-end
-moz-border-end-color
-moz-border-end-style
-moz-border-end-width
-moz-border-start
-moz-border-start-color
-moz-border-start-style
-moz-border-start-width
-moz-float-edge
-moz-force-broken-image-icon
-moz-image-region
-moz-margin-end
-moz-margin-start
-webkit-margin-start
-moz-outline-radius
-moz-outline-radius-bottomleft
-moz-outline-radius-bottomright
-moz-outline-radius-topleft
-moz-outline-radius-topright
-moz-padding-end
-moz-padding-start
-webkit-padding-start
-moz-stack-sizing
-moz-user-focus
-moz-user-input
-moz-user-modify
-webkit-user-modify
-moz-user-select
-webkit-user-select
-moz-window-shadow
-webkit-background-composite
-webkit-border-horizontal-spacing
-webkit-border-vertical-spacing
-webkit-box-reflect
-webkit-column-break-after
-webkit-column-break-before
-webkit-column-break-inside
-webkit-dashboard-region
-webkit-line-break
-webkit-margin-bottom-collapse
-webkit-margin-collapse
-webkit-margin-top-collapse
-webkit-marquee-increment
-webkit-marquee-repetition
-webkit-mask
-webkit-mask-attachment
-webkit-mask-box-image
-webkit-mask-clip
-webkit-mask-composite
-webkit-mask-image
-webkit-mask-origin
-webkit-mask-position
-webkit-mask-position-x
-webkit-mask-position-y
-webkit-mask-repeat
-webkit-mask-size
-webkit-nbsp-mode
-webkit-rtl-ordering
-webkit-tap-highlight-color
-webkit-text-fill-color
-webkit-text-security
-webkit-text-size-adjust
-webkit-text-stroke
-webkit-text-stroke-color
-webkit-text-stroke-width
-webkit-touch-callout
-webkit-transform-origin-x
-webkit-transform-origin-y
-webkit-transform-origin-z
-webkit-user-drag
-o-table-baseline
-ms-background-position-x
-ms-background-position-y
-ms-filter
-ms-ime-mode
-ms-layout-flow
-ms-layout-grid
-ms-layout-grid-char
-ms-layout-grid-line
-ms-layout-grid-mode
-ms-layout-grid-type
-ms-text-autospace
-ms-text-kashida-space
-ms-text-overflow
-ms-text-underline-position
-ms-writing-mode
-ms-interpolation-mode
-ms-scrollbar-3dlight-color
-ms-scrollbar-arrow-color
-ms-scrollbar-base-color
-ms-scrollbar-darkshadow-color
-ms-scrollbar-face-color
-ms-scrollbar-highlight-color
-ms-scrollbar-shadow-color
-ms-zoom
);

# Other CSS extensions (without vendor prefix)
push @property_order, qw(
image-rendering
ime-mode
pointer-events
text-rendering
);

# Create hash: key => property, value => index
# See also: http://blog.livedoor.jp/xaicron/archives/50913074.html
my %property = do {
  my $i = 0;
  map { $_ => $i++ } @property_order;
};

my $css = join "", <STDIN>;
$css =~ s/{(.*?)}/"{".sort_properties($1)."}"/imgse;

print $css;

sub sort_properties {
  # Remove leading line-breaks
  (my $rules = shift) =~ s/^\n+//;

  # Orcish Maneuver
  # my %temp;
  # my @sorted_rules = sort {
  #   ($temp{$a} ||= check_order($a)) <=> ($temp{$b} ||= check_order($b))
  # } split "\n", $rules;

  # Schwartzian Transform
  # See also: http://gist.github.com/236860
  my @sorted_rules
    = map  { $_->[0] }
      sort { $a->[1] <=> $b->[1] }
      map  { [ $_, check_order($_) ] }
      split "\n", $rules;

  return "\n" . join("\n", @sorted_rules) . "\n";
}

sub check_order {
  my $line = shift;
  my $order = 10000;

  if ($line =~ /^\s*(.+?)\s*:/) {
    if (defined $property{$1}) {
      $order = $property{$1};
    }
  # } else {
  #   warn "Unknown property: $1";
  }

  return $order;
}

安定のCSS2.1のプロパティが優先的に上になる。それに続いてCSS3の草案にあるプロパティがCSS Modulesの順に並ぶ。各ブラウザの独自拡張でCSS3の草案にそれに対応するものがある場合(border-radius-moz-border-radius-webkit-border-radiusなど)はその対応するものに続けて並べるようにした。それらに続いてプリフィックスのある各ブラウザの独自拡張プロパティ。一番下に来るのはプリフィックスの無い独自拡張プロパティでime-mode等がこれに当る。

まとめると「CSS2.1 > CSS3 > 独自拡張 > プリフィックスの無い独自拡張」という順序で並ぶことになり、具体的には以下のように並ぶ。

div#header ul#navigation li a {
  /* CSS2.1 */
  margin-left: 6px;
  padding: 0 6px;
  display: block;
  width: auto;
  height: 24px;
  line-height: 24px;
  color: #c9c6c0;
  background-image: url("../images/bg-navigation.png");
  background-repeat: repeat-x;
  /* CSS3 */
  border-radius: 3px;
  -moz-border-radius: 3px;
  -webkit-border-radius: 3px;
  /* Vendor extension */
  -moz-margin-start: 6px;
  /* Vendor extension (without prefix) */
  ime-mode: auto;
}

スクリプトではそれぞれを分けて順にpushしているので新規追加や修正はそれほど大変でもない(と思う)。

CSSのプロパティについては以下を参考にした。

「プロパティの位置がおかしい!」とか「プロパティに抜けがある!」とか「プロパティがかぶっている!」とかは報告してくれるとありがたい


Vimでこのスクリプトを利用する場合は、パスの通ったところにsortcss.plという名前で保存し、セレクタ全体もしくはファイル全体を選択して、

!sortcss.pl

とすれば良い。セレクタ全体を選択(正確には中括弧の間を中括弧を含めて選択)するにはビジュアル・モードでのa}を使えば良いので、以下のようなキーバインドとコマンドを用意しておくと便利。

nmap gso va}:SortCSS<CR>
vmap gso a}:SortCSS<CR>

" Sort CSS properties
" :SortCSS
command! -range=% SortCSS :<line1>,<line2>!perl -S sortcss.pl

これでgsoでソートできる他、:SortCSSでもソートできる(-range=%なので選択していない場合はファイル全体を処理する)。CSSファイル以外ではアクティブにする必要が無いので、$MYVIMRCではなく$HOME/.vim/ftplugin/css.vim (Windowsなら$HOME\vimfiles\ftplugin\css.vim)とかに書いた方が良いかも


プロパティの羅列部分だけを切り出せばCSS用の辞書に早変わり!

追記@2009-11-16T15:31:03+09:00

xaicronさん添削してもらった。なるほどなーということでコピペして更新。

追記@2009-11-16T18:28:19+09:00

重複していたspeak-headerの削除(kitsさんのはてブコメントより)とempty-cellscursorの位置が逆だったのを修正した。

追記@2009-11-17T23:15:06+09:00

xaicronさんからdefined抜けの修正(marginで場合によってはおかしくなることがあった)とシュワルツ変換を噛ませて高速化したものをフィードバックしてもらったので、手元で加えていた未知のプロパティを最後に回す修正とマージして変数名をちょっと変え更新。warnでの未知のプロパティの警告はあったほうが良いのだけど、Vimと一部のシェルの組み合わせでは標準エラー出力もリダイレクトするように(デフォルトでは)なっているのでコメントアウトしておいた。

MEGAUPLOADから簡単にダウンロード

Posted at 2009-10-18T17:45:00+09:00 in Coding

MEGAUPLOADではファイルをダウンロードする時に、4文字のCAPTCHAの入力と45秒の待ち時間を要求される。が、URLにちょっと細工するとそれらをすっ飛ばしてダウンロードできることを知ったので、ユーザースクリプトにしてみた。中身は一行。

URLの細工はものすごく簡単なもので、元のURLが、

http://www.megaupload.com/?d=XXXXXXXX

なら、

http://www.megaupload.com/mgr_dl.php?d=XXXXXXXX

と変更するだけ。こう細工するとCAPTCHAの入力と待ち時間をまとめて飛ばしてダウンロードが始まる(ダウンロードのリンクをクリックする必要すらない)。かなり前からあったみたい。

MEGAUPLOADへのリンクを見つけたら書き換えるユーザースクリプトとかでも良いと思う。書くとしたらこんな感じ?

Array.forEach(document.getElementsByTagName("a"), function (a) {
  if (a.href.match(/^http:\/\/www\.megaupload\.com\/\?d=[a-zA-Z0-9]{8}$/)) {
    a.href = a.href.replace(/\/\?d=/, "/mgr_dl.php?d=");
  }
});

1週間くらい前に知って直ぐにユーザースクリプトを作ったのだけど、動いたり動かなかったりしてたり、今週はずっと腹痛だったりしたのでエントリにしてなかった。腹痛であまり食べられなくてずっと空腹だとなかなかすごい世界が見えることを知った。

Query YQLプラグインでOpen Data Tableをサポートした

Posted at 2009-10-06T15:08:00+09:00 in Coding

YQLではデフォルト多くのテーブルが組み込まれているのだけど、自由に拡張できる仕組みとしてOpen Data Tableというものがある。YQL ConsoleではShow Community Tablesをクリックして有効にすると、Community Open Data Tablesに登録されているGitHubやTwitterのためのテーブルを試すことができる。これらのOpen Data Tableを実際に利用するためにはYQLへのリクエストへenvパラメータとしてenvironmentファイルを指定しなければならない。最近よく利用するようになったのでQuery YQLプラグインでもそれらを利用できるように更新した。

引数の互換性を維持したまま引数を増やしたので気持ち悪いコードになった。statement以外はオブジェクトにして渡すとかの方が良かったかもしれないけど、これ以上増えることも無いだろうと思うのでまぁ良し。びっちり書く場合は以下のようになる。

var statement = "select * from twitter.user.timeline where id='hell2u'";
$.queryYQL(statement, "json", "http://datatables.org/alltables.env", function (data) {
  // do something with "data".
});

第三引数にenvironmentファイルのURLを指定する。Community Open Data Tablesのテーブルを全てインポートする場合のみallというキーワードでも可で、第二引数は省略するとjsonになるので、上記コードは以下のようにも書くことができる。

var statement = "select * from twitter.user.timeline where id='hell2u'";
$.queryYQL(statement, "all", function (data) {
  // do something with "data".
});

Open Data Tableの利用はGoogle検索やTwitterのテーブルの利用が主なのだけど、他にもいくつか面白いものがある。

そのうちテーブル書いてみたい。

そのうちとか言う時は大抵やらないよね。

Amazon Product Advertising APIで返されるProductGroupの日本語訳

Posted at 2009-08-01T12:36:00+09:00 in Coding

Amazon Product Advertising APISearchIndex=Allを使って検索すると、複数のカテゴリの商品が検索結果として返ってくる。検索結果の各商品がどのカテゴリの商品かはItemAttributes/ProductGroupの値を参照すれば良いのだが、「家電&カメラ」が「CE」だったりとそのままではいまいちよくわからない。ということでAmazon.co.jpのカテゴリ分けのラベルに訳した。

{
  "Apparel":           "アパレル&ファッション雑貨",
  "Baby Product":      "ベビー&マタニティ",
  "Book":              "本・漫画・雑誌",
  "CE":                "家電&カメラ",
  "DVD":               "DVD",
  "Grocery":           "食品&飲料",
  "Health and Beauty": "ヘルス&ビューティー",
  "Kitchen":           "ホーム&キッチン",
  "Music":             "ミュージック",
  "Office Product":    "文房具・オフィス用品",
  "Shoes":             "シューズ",
  "Software":          "PCソフト",
  "Sports":            "スポーツ&アウトドア",
  "Toy":               "おもちゃ",
  "VHS":               "ビデオ",
  "Video Games":       "TVゲーム",
  "Watch":             "時計"
}

和書と洋書やミュージックとクラシック音楽、TVゲームとPCゲーム、DVDとBlu-Rayなど判定したいけどそのままではできないものは色々ある。逆にシューズはAmazon.co.jpの検索結果ではアパレル&ファッション雑貨でまとめられているが、Shoesで返ってくるのでシューズと細かく判定できたりする。

またProductGroupの値はSearchIndexに指定する文字列と同じだと、SearchIndex=Allでとりあえず検索させて検索結果のある商品と同じカテゴリだけに絞るとか簡単にできそうで便利だと思うんだけど、そうなってはいない。BrowseNodesResponseGroupパラメータの値に含めればかなり正確にカテゴリ判定ができ、検索をカテゴリで絞るとかもSearchIndexパラメータではなくBrowseNodeパラメータを使ってやれば良いのでやりたいことは実現できるのだけど、レスポンスのBrowseNodesの構造はかなり複雑で、とりあえず大まかなカテゴリ(最上位のカテゴリ)を知りたい(で絞り込みたい)とかには使いづらいような気がする。

この訳データにBrowseNodeIdも持たせてやれば便利かもな。

Aexには導入しておいた。

Aex: Amazon.co.jpの全商品から検索するよ!

Posted at 2009-07-31T15:16:00+09:00 in Coding

Amazon Product Advertising APIItemSearchオペレーションのパラメータとしてSearchIndex=Allが日本でも利用できるようになったらしいので、とりあえずAexという名前でAmazon.co.jpの検索サイトを作ってみた。これでドロップダウンでカテゴリを選択とかいう腐ったUIから解放される。

SearchIndex=Allを指定すると全商品からKeywordsに指定した文字列で検索してくれる。SearchIndex=Blendedとはまったく違い、カテゴリごとに検索結果が分けられて返ってきたりはしない。色々検索してみたところ、Amazon.co.jpでの検索と同じ結果で、概ね期待通りの検索結果が返ってくるようだ(ItemSearchオペレーションのデフォルトではCondition=Newなので中古商品などが含まれないというような違いはあったりもする)。

SearchIndex=Allのみの制限として、検索結果のページングに使用するItemPageの最大値が5(SearchIndex=Booksなどの場合は400)までというのがあるようだ。またSortに指定できる値も、

  1. price
  2. -price
  3. relevancerank
  4. salesrank

の4つだけになっており、名前順でソートなどは不可能。他にも何か制限ありそう……。

ItemAttributes/ProductGroupの値の一覧が(できれば対訳も)欲しい。今まではSearchIndexでカテゴリを絞っていたのでProductGroupの値を意識しないでも何とかなったけど、全カテゴリから検索できるようになるとどのカテゴリの商品かをProductGroupから類推することになるが、ローカライズされていないのでCEとか言われてもわからない。

Amazon アソシエイト・プログラム(アフィリエイト) 公式ブログではProduct Advertising APIをPAAPIって略してるんだな。ぱーぴ!!1

追記@2009-08-01T20:44:44+09:00

IE8ではonhashchange、それ以外のブラウザでは古のsetInterval()を使った簡単なjQueryプラグインを書いて、ブラウザの戻る・進むに対応させた。普通にjQuery history pluginを使えば良かったという……。Firefox 3.xでは、

location.watch("hash", function (obj, oldVal, newVal) {
  $(window).trigger("hashchange");
});

とか初めてObject.watch()を使ってやってみたんだけど、location.hashに値をセットしようとすると#undefinedとかなってわけがわからなくなった。どうせ3.6でonhashchange導入されるから良いか。

追記2009-08-02T15:27:24+09:00

クエリの方式を#q=foo;p=3という形に変えたり、スタイルをモノクロにしたり、バグ直したり。