Help me hackers!

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第a回。

コレを作ってて2ヶ月近くポストして無かったですけどなにか?

Actually sorry

(@佐々木健介)

komagata a.k.a. DJです。

今回紹介するのはコレ。

Ham CutletをHTML5に対応する – Help me, hackers!

HTMLのインデントを綺麗にする誰得サービスHam Cutlet。内部で使ってるHamlがデフォルトxhtmlなのでhtml5を食わせるとコードが破壊される。それを解決してくれたナイスガイがmat_aki a.k.a. youroomの中の人。ありがとう!

mat_akiのパッチはこのコードに集約される。

app.rb at 236baf240828a805726a49adb8c76fd4f048f802 from mataki’s hamcutlet – GitHub

[cc lang="ruby"] def html2haml(html)
html5 = (doctype = Hpricot(html).children.detect{ |e| e.doctype? }) ? doctype.public_id.nil? : false
haml = Haml::HTML.new(html.gsub(/\t/, ‘ ‘)).render
Haml::Engine.new(haml, :attr_wrapper => ‘”‘, :format => html5 ? :html5 : :x html ).render
end[/cc]

doctypeを見てHTML5なのか、それ以外なのかを判断してる。

[cc lang="html"][/cc]

XHTML 1.0のdoctypeはこんな感じ。PUBLICなんとかがある。

[cc lang="html"][/cc]

HTML5のdoctypeはこんな感じでシンプル。

Hpricotを使って「doctypeがあるけどpublicなんとかが無いものはHTML5」としている。Haml::Engineが元からやって欲しいような気もするが、将来のバージョンではそうなるのかもしれない。

Contributors to komagata/hamcutlet - GitHub

気づいたらHam Cutletもいつのまにやら色んなプログラマーの手が入ってるプロジェクトになってる。kyannyの気付かない内にmat_akiとコラボレーションしてる状態になってるんじゃないかなどと考えると面白い。オマエら一緒のコードいじってるぜ!

この記事のタグ

コメントはまだありません

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第9回。

「あなだのコードがー しぬほどスキダカダー!」(@チャン・ドンゴン)

komagata a.k.a. DJです。

今回紹介するのはコレ。

Ham Cutlet Chrome Extension – Help me, hackers!

HTMLのインデントを綺麗にする誰得サービスHam CutletのGoogle Chrome Extension。ブラウザの「このページのソースを見る」の代わりにインデントを綺麗にしたソースを表示するボタンをOmniBoxの右に追加するExtensionだ。

mongorian_chop a.k.a. 自由人が作ってくれた。ボタンのアイコンはmachida a.k.a. 漫★画太郎。軽いコラボ。

コードはココ。

mongorian-chop’s hamcutlet-extension at master – GitHub

manifest.json

[cc lang="javascript"]{
“name”: “Ham Cutlet Extension”,
“version”: “0.1″,
“description”: “Ham Cutletを使用したHTMLソース表示”,
“background_page”: “background.html”,
“browser_action”: {
“default_icon”: “icon.png”,
“defailt_title”: “Ham Cutlet”
},
“permissions”: [
"tabs"
]
}[/cc]

background.html

[cc lang="html"]





[/cc]

Chrome Extensionの作り方なんて知らないって?それならDJに任せろ!当然の如くDJも知らないからお前らの代わりに泣きながら覚えてやる!

モダンブラウザ・JS好きなら知らぬものはいないid:os0xの連載、続・先取り! Google Chrome Extensions:第1回 Chrome ExtensionsのAPI#1|gihyo.jp … 技術評論社で丁寧に説明されてるのでこれでわかりそうだ。

DJが超訳するとこんな感じだ。

  • manifest.jsonの入ったディレクトリを作る
  • 上のバーの右にあるボタンはbrowser_actionという
  • browser_actionのボタンを押した時の動きはbackground_pageで指定したhtmlに書く
  • Chromeで.crxパッケージを作れる。(単なるzipなので解凍も出来る)(CUI野郎にはConstellation’s crxmake at master – GitHubというパッケージングコマンドもあるらしい。)

作れる気がしてきた!早速コードを見ていこう。

6行目のbrowser_actionでタイトルとアイコンを設定してる。

10行目のpermissionsはクロスドメインでアクセスできるURLを指定するようだが、tabsはなんだろう?

5行目のbackground_pageで実際の動作を書くファイルを指定している。

background.htmlを見ていこう。

10行目のchrome.browserAction.onClicked.addListenerがキモだ。こうやってbrowser_actionをクリックした時の動作を設定するのだろう。

7行目のchrome.tabs.createでURLを指定してタブを開けるらしい。何かグリモンでも思ったけどクロスブラウザ問題が無いとJSってこんなにシンプルに書けるのかという・・・。

URLは以前、「HTMLのインデントを綺麗にするAPI – Help me, hackers!」でkyanny a.k.a. 刺身ブーメランが作ってくれたHam Cutlet APIを使って該当ページのソースをインデントを綺麗にした上で取得している。

これは簡単・・・。グリモンやFF拡張もそうだけど、実用的なものが簡単に作れるのがイイ!ECMAScripter的にもクロスブラウザに悩まされず思いっきりJSを書けるのも気持ちがイイ。

この記事のタグ

コメントはまだありません

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第8回。

「おでのからだはぼどぼどだー!」

komagata a.k.a. DJです。

今週はコレ。

WordPress.orgのプラグイン一覧でダウンロード数に目印をつけて欲しい – Help me, hackers!

登録してくれたのはmonoooki a.k.a. 前田製作所。速攻解決してくれたのはazu_re a.k.a. Firefoxの人(DJ主観)。

ソースコード:

[cc lang="javascript"]// ==UserScript==
// @name WordPress.org highlight counter
// @namespace http://efcl.info/
// @description WordPress.orgのタグ/単語検索のダウンロード数によって色を変える
// @include http://wordpress.org/extend/plugins/tags/*
// @include http://wordpress.org/extend/plugins/search*
// ==/UserScript==
/* TEST URL

http://wordpress.org/extend/plugins/search.php?q=word

http://wordpress.org/extend/plugins/tags/widget

*/
GM_addStyle(<> .GM_downloads_count_50000{
background: #cc0000; color: #fff; padding: 1px 3px; -moz-border-radius: 3px;
}
.GM_downloads_count_10000{
background:#ffb0b0; padding: 1px 3px; -moz-border-radius: 3px;
}
.GM_downloads_count_5000{
background:#ffd792; padding: 1px 3px; -moz-border-radius: 3px;
}
.GM_downloads_count_2000{
background:#f9f49d; padding: 1px 3px; -moz-border-radius: 3px;
}
]]>);
function hilightCounter(node){
var downloadsSpan = $X(‘//span[@class="info-marker"][text()="Downloads"]‘ ,node);
for(var i=0,len=downloadsSpan.length;i var countTextNode = downloadsSpan[i].nextSibling;
var downloadCount = parseInt(countTextNode.textContent.replace(",","","g"), "10");// 数値化
var parDownload = downloadsSpan[i].parentNode;
var span = document.createElement("span");
if(downloadCount > 50000){
span.setAttribute(“class” , “GM_downloads_count_50000″);
}else if(downloadCount > 10000){
span.setAttribute(“class” , “GM_downloads_count_10000″);
}else if(downloadCount > 5000){
span.setAttribute(“class” , “GM_downloads_count_5000″);
}else if(downloadCount > 2000){
span.setAttribute(“class” , “GM_downloads_count_2000″);
}else{
continue;
}
span.textContent = countTextNode.textContent;
parDownload.replaceChild(span , countTextNode);
}
}

document.body.addEventListener(‘AutoPagerize_DOMNodeInserted’,function(evt){
var node = evt.target;
//var requestURL = evt.newValue;
//var parentNode = evt.relatedNode;
hilightCounter(node);
}, false);
hilightCounter(document);

// $X on XHTML
// @target Freifox3, Chrome3, Safari4, Opera10
// @source http://gist.github.com/184276.txt
function $X (exp, context) {
context || (context = document);
var _document = context.ownerDocument || context,
documentElement = _document.documentElement,
isXHTML = documentElement.tagName !== ‘HTML’ && _document.createElement(‘p’).tagName === ‘p’,
defaultPrefix = null;
if (isXHTML) {
defaultPrefix = ‘__default__’;
exp = addDefaultPrefix(exp, defaultPrefix);
}
function resolver (prefix) {
return context.lookupNamespaceURI(prefix === defaultPrefix ? null : prefix) ||
documentElement.namespaceURI || “”;
}

var result = _document.evaluate(exp, context, resolver, XPathResult.ANY_TYPE, null);
switch (result.resultType) {
case XPathResult.STRING_TYPE : return result.stringValue;
case XPathResult.NUMBER_TYPE : return result.numberValue;
case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
// not ensure the order.
var ret = [], i = null;
while (i = result.iterateNext()) ret.push(i);
return ret;
}
}[/cc]

このブログを読んでる人達ならそろそろグリモン見慣れてきたんじゃないか?泣きながらソース読んでたDJも慣れてきたよ。

12行目からはxmlリテラルでstyleを追加。でも、先頭の<>ってのはなんだろう?

61行目の$X関数はXPathのためのラッパー。こうして見ると色々な人が色々なXPathユーティリティーを選んでるのがわかる。$X関数は比較的昔からあって有名な方なんじゃないかと思う。

26行目のhilightCounterで注文通り、ダウンロード数別に色を変えるためにclassを割り当ててる。

41行目はびっくり。AutoPagerizeのイベントにひっかけてる。ブログのソースコードのSyntax HilightingとかJSのonloadでやってる場合に、AutoPagerizeの2ページ目からは反映されなくてガッカリ・・・ってことが多かったんだけどこれを使えばイケる!

これはDJ得した気分。

WordPressのプラグインページは見る人が多いからuserscripts.orgにきちんと登録してある点も素敵だ。

この記事のタグ

3件のコメント

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第7回。

今、流行の24シーズン1を見ているkomagata a.k.a. DJ(@流行に疎い方)です。

今回紹介するコードはコレ。

任意の期間、ブログが更新されないとメールが飛ぶwordpressのplugin – Help me, hackers!

出題したのは@machida a.k.a. お米嫌い。議論に対していきなりコードで回答を示したのが@terakuma a.k.a. 法務系PHPer。

コード:

[cc lang="php"] /*
Plugin Name: Remember The WordPress
Plugin URI: http://github.com/e2esoundcom/Remember-The-WordPress
Description: If you forgot write a new post,this plugin send you E-mail.
Version: 0.2
Author: Yuya Terajima
Author URI: http://www.e2esound.com/
*/

/* When Activate Plugin
==================================================================== */
function rtw_activate() {
$time = time();
$terms = 7; //default terms
$unixtime_per_day = 86400;
$email = get_bloginfo('admin_email');
$subject = "Remember the WordPress!!";
$message = "Blogの更新が滞っているようです。サイトの更新をお願いします。";

if(!get_option('rtw_initialized') || get_option('rtw_initialized') !== $time) {
update_option('rtw_initialized',$time);
}
if(!get_option('rtw_terms')) {
update_option('rtw_terms',$terms * $unixtime_per_day);
}
if(!get_option('rtw_email')) {
update_option('rtw_email',$email);
}
if(!get_option('rtw_subject')) {
update_option('rtw_subject',$subject);
}
if(!get_option('rtw_message')) {
update_option('rtw_message',$message);
}
wp_schedule_event($time + 86400, 'daily', 'rtw_cron');
}
register_activation_hook(__FILE__, 'rtw_activate');

function get_latest_post_time() {
global $wpdb;
$query = "SELECT post_date FROM ".$wpdb->posts.”
WHERE post_status = ‘publish’
ORDER BY `”.$wpdb->posts.”`.`post_date` DESC LIMIT 0,1″;
$query = $wpdb->prepare($query);
return strtotime($wpdb->get_var($query));
}

function rtw_sendmail()
{
$subject = get_option(‘rtw_subject’);
$mailbody = get_option(‘rtw_message’);
$to_email = get_option(‘rtw_email’);

mb_send_mail($to_email,$subject,$mailbody);
}

function rtw_compare_time(){
$unixtime_per_day = 86400;
$latest = get_latest_post_time();
$terms = intval(get_option(‘rtw_terms’)) + intval($latest);
$time = time();

if(intval($terms) < intval($time)){
rtw_sendmail();
}
}
add_action('rtw_cron', 'rtw_compare_time');

/* When New Post or Edited Post.
==================================================================== */

function rtw_new_posted(){
update_option('rtw_initialized',time());
}
add_action('publish_post','rtw_new_posted');

/* When De-Activate Plugin
==================================================================== */

function rtw_deactivate() {
wp_clear_scheduled_hook('rtw_cron');
delete_option('rtw_initialized');
delete_option('rtw_terms');
delete_option('rtw_message');
delete_option('rtw_subject');
delete_option('rtw_email');
}
register_deactivation_hook(__FILE__, 'rtw_deactivate');

/* Admin
==================================================================== */
function rtw_add_admin_menu(){
add_options_page('Remember The WordPress','Remember The WP','administrator',__FILE__,'rtw_add_admin_page');
}
add_action('admin_menu','rtw_add_admin_menu');

function rtw_add_admin_page(){

if(isset($_POST['posted']) === FALSE){
$posted = FALSE;
}elseif(isset($_POST['posted']) === TRUE){
$posted = TRUE;
}

$unixtime_per_day = 86400;
if($posted) {
//Validation
if(preg_match('/[1-3][0-9]|[1-9]/',intval($_POST['terms']) AND intval($_POST['terms']) <= 30)){
update_option('rtw_terms',intval($_POST['terms'] * $unixtime_per_day));
update_option('rtw_email',stripslashes($_POST['email']));
update_option('rtw_subject',stripslashes($_POST['subject']));
update_option('rtw_message',stripslashes($_POST['message']));
$rtw_error = FALSE;
}else{
$rtw_error = TRUE;
}
}
?>

//Admin Page Start
//Updated Message
if($posted === TRUE AND $rtw_error === FALSE) : ?>

設定を保存しました

アラート発生日数は1-30の間の値を入力して下さい。

Remember The WordPress Settings



送信メッセージの「内容」を入力してください。

[/cc]

少々長いがへこたれずに頑張ってみていくことにしよう。(自分自身を鼓舞)

WordPressプラグインの作成方法

前提としてこれはWordPressプラグインなのでその大まかな作り方を紹介しよう。

まず、単体のphpファイルか、もしくは同名のディレクトリにプラグイン用のファイルを作る。
(例:foo_plugin/foo_plugin.php)

プラグインの各種情報は決まったコメントの書き方があるのでそれにしたがってファイル内先頭に書く。これがWordPressのプラグイン管理画面に出てくる情報になる。

一番単純なプラグインとしては、プラグインを管理画面から有効にすると、プラグインのphpファイルがWordPressにincludeされる。よって適当な関数をプラグイン内で定義しておき、テーマの中で使うといったことが可能だ。(モチロン名前はぶつかる可能性がある。プラグイン名をprefixにした関数を作るか、プラグイン名のクラスに閉じ込めることが推奨されている。)

これでWordPressの関数や変数、DBをプラグイン内からでも使うことが出来る。また、プラグイン用にactionやfilterといったフックをかける事ができる。この辺は公式ドキュメントに全関数リファレンスやプラグイン用のフックの説明がちゃんとあるのでPHPが分かれば理解するのは容易だ。(Main Page – WordPress Codex 日本語版

擬似cron

今回のコードを理解する上でもう一つ必要になるのが、wp_schedule_event関数を使った擬似cronの仕組みだ。WordPressにはwp_schedule_event関数でcronのように定期スケジュールを登録することが出来る。こういう処理はcron daemonのように常に起動しているプロセスと連携する必要があり、一見単純なPHPウェブアプリだけでは無理に思えるが、Webからのユーザーリクエストのみをトリガーにcronのようなスケジュール実行を可能にしている。

もちろんWebからのリクエスト頼みなのでアクセスがまったく来なければスケジュールした時間には実行されない擬似的な機能なので厳密な処理には向かない。しかし、一般的なサイトであれば大抵毎分1アクセスぐらいはあるので大体このぐらいの頻度でやって欲しいといった処理なら実用上は問題なさそうだ。

参照:Function Reference/wp schedule event « WordPress Codex

こんないい加減な仕組み使うなんてありえねー!とヤルタイプからは言われそうだが、phpのsession削除は同様の仕組みで行われている。(アクセスが来なければ永久にセッションファイルは消えない)
根本的なこの仕組の是非は兎も角、PHPの流儀に従った実装方法ではあると言える。

参照:PHP: 実行時設定 – Manual

Remenber The WordPress

まず、38行目でregister_activation_hook関数で自分自身のファイルのrtw_activate関数をactivation_hookに登録している。activationとはプラグイン管理画面でそのプラグインを「有効」にしたときのことだ。データベーステーブルを独自に持つようなプラグインでは大抵この場所でテーブルをCREATEしている。(WordPressはORMを提供していないので、各種プラグインのこの場所にはMySQLに特化したSQLが大抵使用されている。WordPressはプラグインエコシステムがキモであるため、コレのせいで他DBへの対応が非常に難しくなっている。)

rtw_activate関数の中でwp_schedule_eventを使ってdailyで実行されるようにrtw_cronを登録している。

あとは、40行目のget_latest_post_time関数で最終投稿日を取得して、管理画面で設定できる「何日更新が無かったらメールを送るか?」という値と比較してmb_send_mailでメールを送っている。

殆どの行数は管理画面用なので長く見えるし大変そうだが、管理画面からプラグインの設定が出来るというのがWordPressユーザーに非常に訴える箇所なので重要だ。

議論にコードで答えた事。(これが結構貴重なんだが)WordPress流儀に則った実装。管理画面まで手を抜かない所。等々、@terakuma a.k.a. 法務系PHPerは素晴らしい。それにRemember The MilkをパクったRemember The WordPressというプラグイン名も素敵だ。

WordPressプラグインで困ったことがあったら@terakuma a.k.a. 法務系PHPerに相談してみたらいいかもしれない!

この記事のタグ

1件のコメント

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第6回。

おい!ワールドカップ見てない癖に適当に話を合わせるのは辞めろ!DJ?モチロン見てたよ。本田のシュートは凄かったよね!・・・くらいやがれ!これが俺のワールドカップに対する知識の全てだ!

komagata a.k.a. DJです。

今回紹介するのはコレ。

無印良品のサイトの表示をデフォルトで「安い順、150件表示」にする – Help me, hackers!

まさに「プログラマーにとって解きたくなるような問題」といったセンスの良い問題を投稿してくれたのは@monoooki a.k.a. 前田製作所

そして解決してくれたのはお馴染み@milk1000cc a.k.a. 牛乳嫌いプログラマ on Rails。(@milk1000ccはDJのストーキング情報によれば最近PONPONというグルーポン情報まとめサイトをRails, Herokuで公開したらしいRails野郎だ。)

二人ともありがとう!

しかし、ちょっと待ってくれ。コイツをどう思う?(@阿部高和)


% heroku console
>> Task.find(106).created_at
=> Tue, 06 Jul 2010 04:16:07 UTC +00:00
>> Task.find(106).comments.correct.first.created_at
=> Tue, 06 Jul 2010 07:02:54 UTC +00:00

「すごく・・・早いです・・・。」(@道下正樹)

タスクが登録されてからわずか3時間足らずで解決されている。(@荒巻)

たしかにDJも「これは解きたくなる問題だな」とは思ってはいたが、あまりにもはやすぐるでしょう?

本題のタスクの内容は、

無印良品ネットストア

こんな感じで無印良品ネットストアのデフォルトは「おすすめ順」「50件表示」になってる。それを「安い順」「150件表示」をデフォルトになるようにして欲しいというタスク。

あるある。確かにハードに使ってるサイトでこういうちょっとしたところを俺仕様にしたいことある。

それを通常ならありえない速さできょうきょ参戦(@ブロントさん)してくれたグリモンコードがコレ。

http://gist.github.com/465095
(右上のrawのリンクをクリックでグリモンスクリプトインストール)

// ==UserScript==
// @name           AutoMujiSort
// @namespace      http://www.milk1000.cc/
// @include        http://www.muji.net/store/cmdty/section/*
// ==/UserScript==

(function() {
    var OLD_PATTERN = { sort: 4, count: 12 };
    var NEW_PATTERN = { sort: 0, count: 150 };

    var $ = function(selector) { return document.querySelector(selector); };

    if ($('select[name="sort"]') && $('select[name="sort"]').value == OLD_PATTERN.sort &&
        $('select[name="count"]') && $('select[name="count"]').value == OLD_PATTERN.count) {
        $('select[name="sort"]').value = NEW_PATTERN.sort;
        $('select[name="count"]').value = NEW_PATTERN.count;
        (typeof unsafeWindow != 'undefined' ? unsafeWindow : window).sort();
    }
})();

きれいなコード。DJ以前はグリモンのコード「嫌だなぁ、嫌だなぁ、怖いなぁ」(@稲川淳二)なんて思ってたけどこの連載のお陰でむしろ好きになってきた。読み易い。

11行目のquerySelectorを$に割り当てるのはECMAScripterお馴染みパンチライン。最初のvarはconstにしたら速くなるのかも?

17行目のunsafeWindowを見てるのは多分ブラウザによって違うところなんだろう。

DJそろそろ気付いてきたんだが、今、タスクの需要と供給は依頼が少ない状態にある。特にJavascriptのタスクは監視してるイカレタHacker達が飢えた獣のように跳びかかり、瞬時に解決されてしまう。

成果物の公開方法やパッケージング方法、環境作りが楽なグリモン・JavascriptはHacker側にとって非常にやりやすい。そういう事情を踏まえた依頼の仕方もセンスが問われている。

センスを磨くか、金を積むか。そういう仕組みになっているらしい。

この記事のタグ

コメントはまだありません

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第5回。

TimeMachineで自動バックアップを取っていても結局クリーンインストール後は手でデータを復旧させる昭和の男、komagata a.k.a. DJです。

今回はコレ、フォームのデフォルトの文字をフォーカスが移ったら消す – Help me, hackers!

コレの経緯はこんな感じだ。

  1. F5V a.k.a. PHPerよりHam CutletにTwitter経由で上記の要望が入る。
  2. DJが要望をHelp me, hacekrs!に登録する。
  3. mongorian_chop a.k.a. 自由人からjQuery Watermark Pluginの情報を貰う。
  4. USTREAMの開発生放送でDJが上記プラグインでの実装に敗北。
  5. 見るに見かねたmongorian_chopが実装してpull request。

100万語の言葉より1つのpull request。コードは偉大だ。

「コード!コード!コード!」

「よし、ならばコードだ。」

今回のキモはココ。

views/index.haml at c7cf55da11f2131568deda5b1d4b7a46742943f8 from mongorian-chop’s hamcutlet – GitHub

$(document).ready(function(){
    $.updnWatermark.attachAll();
    $('.updnWatermark > label').click(function(){
        $(this).toggle();
        $("textarea").select();
    }).blur(function(){
        $(this).toggle();
    });
});

$(document).ready(function(){ … }) はお馴染み、DOMの構築が終わった瞬間に実行してくれるjQueryの書き方だ。$(function() { … }) でも同じ。画像の読み込み等を待たない分、onloadより速いのでjQuerystはドンドン使ってこ!

今回、DJが挫折してたのはjQuery Watermark Pluginをtextareaに使った時に、そのWatermarkのテキスト自身をクリックした場合にWatermakが消えないところだ。

mongorian_chopはそこをちゃんと消すコードを書いてくれた。ありがとう!

jQueryだとちょっと面倒に思えた処理もこんなにスマートな見た目で書けるなんて。DJこの連載のおかげで強制的にJS勉強させられてるね!

DJ、今現在もHelp me, hacekers!上で複数のコード野郎共とコラボ中だ。議論する暇があったらコードを送りつけろ!

この記事のタグ

コメントはまだありません

Macのマイクから獣の吠え声の様なものしか聴こえないkomagata a.k.a. DJです。

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第4回。

今回はコレ、HTMLのインデントを綺麗にするAPI – Help me, hackers!

HTMLのインデントを綺麗にするサービスHam CutletにAPIを付けるというタスク。やってくれたのはkyanny a.k.a. 刺身ブーメラン。

コードはコレ

source = NKF.nkf('-w', expand_tab( open(params[:url]){ |f| f.read } ) )

38行目のココでurlで指定したページのソースを取ってきてタブをスペースに変換。NKFでutf-8に変換。-wはそういうオプションみたいだ。後はそれを表示すればいい。

erb :created, :layout => false

49行目でerbを使ってHTMLを表示している。sinatraではget ‘/’ do; endなどのメソッドは文字列を返せばそれが表示されるというシンプル設計。他でも使ってるhamlメソッドなども文字列を返すメソッドだ。

なのでココは、

@html

とやるだけでもいい。

このタスクのお陰でAPIが出来たので、誰得Chrome拡張などを作ってもらう事が出来た。

他のプログラマーにpushしたりpullしたりするのは何故か訳も無く楽しい。コードを介したコミュニケーションはプログラマーにしか出来ない。kyanny a.k.a. 刺身ブーメランとのコラボレーションも無性にテンション上った。コレは騙されたと思って試してみて欲しい。何かテンション上るんだ。

ちょっとDJ、Windowsプログラマーとか色んなプログラマーにpushしたりpullしたりしたくなって来た。

この記事のタグ

コメントはまだありません

父の日前に「電子書籍リーダーに興味がある」ってiPad送れってことか?
汚い。流石親父汚い。

komagata a.k.a. DJです。

今週はコレ:COOK後にHTMLをクリップボードに保存したい – Help me, hackers!

やってくれたのは@func09 a.k.a. ラペコの中の人だ。

Ham CutletはHTMLのインデントを綺麗にする誰得サービスだ。タグのネストが綺麗に揃うと尋常じゃない行数になることがあるので全選択してコピーするのが非常に面倒臭い。これを解決したいという依頼。(というかほとんどバグかってぐらい苦痛)

コレ、textareaだったら

elem.select()

で完了なんだけど、コードはpre, codeタグで出したいというDJと@machida a.k.a. マークアップエンジニアのわがままで実装方法がわからず心が折れてた。

それをエレガントに解決してくれた@func09のコードがコレ。

* HTTP * Git Read-Only http://github.com/func09/hamcutlet.git This URL has Read-Only access

#created.section
  - if flash[:error]
    .error
      %h2 HTMLに下記の問題があります。
      %p= flash[:error]
      .to_top
        %a{:href => '/'}トップページに戻る

  - else
    %h2 インデントの美しいHTMLが出来ました
    %button.raw plane text
    .source
      %textarea{:style => 'display:none;'}
        =h @html
      %pre
        %code=h @html
:javascript
  $(function(){

    $('.source textarea').height($('.source pre').height());

    $('button.raw').click(function(){
      if ($('.source textarea').css('display') == 'none') {
        $('button.raw').text('html')
        $('.source textarea').fadeIn();
        $('.source pre').hide();
        $('.source textarea').focus().select();
      } else {
        $('button.raw').text('plane text')
        $('.source textarea').hide();
        $('.source pre').fadeIn();
      }
    });
  });

このテンプレはお馴染みのHaml。:javascriptというのはjavascript用のhaml_filterで、この中はHaml記法から離れて自由にJavascriptが書ける。Hamlの構文チェッカーから逃れてホッとするという記法だ。

13行目で非表示のtextareaを作っておいて、22行目以降のボタンのonclickでpre, codeタグ表記とtextarea表記を入れ替えてる。

そうか、選択する時だけtextareaに変えちゃえばいいのか。そういわれてみればたしかにブラウザのwysiwygエディタでよく見る手法だ。頭柔らかいね・・・。

23行目の表示状態をトグル条件にするってのも色々なところでパクろうと密かに思ったよ。

25, 31行目のfadeIn()はDJ、バファリンの半分と同じ成分だと思うな。

自分のコードを書くのも楽しいけど、人のコードにちょっかい出すのも楽しい。githubも素敵だ。

次回からも更にコードにフォーカスして行くのでみんなのコードを晒してくれ、そして読ませてくれ!

この記事のタグ

コメントはまだありません

@dandasoに「プロジェクト」と「タスク」の使い方がよく分からないと言われたので紹介したいと思います。

Help me, hackers!は要は「報酬付き公開BTS」なのでTracやRedmineと似たようなものです。

仕事のまとまり 仕事
Trac Project Ticket
Redmine Project Issue
Help me, hackers! プロジェクト タスク

Help me, hackers!を使う上でプロジェクトは必須ではありません。突発的なタスクは単純なTODOのように登録して問題ありません。

しかし、何かのアプリを作る。何かのWebサービスを作る。などといった大きめの仕事は細かいタスクをプロジェクトという形で一覧できた方が便利です。

Help me, hackers!

例えば、これはHam CutletというWebサービスのプロジェクトですが、これに関係のあるタスクが一覧できるので、未解決のタスクをひたすら潰していくのに便利です。

Help me, hackers!ではプロジェクトもタスクも、編集・削除はいつでも出来るので適当にタスクを登録していて、これはまとめた方が便利かな?と思ったときにプロジェクトを作成すれば十分です。(タスクの登録・編集フォームでプロジェクトを選択できます)

「ドックフードを食べる」ということで僕はRedmineの代わりに使っているので、自分のやるべきタスクがわからなくなったのでプロジェクト機能を作りました。

普通のBTSと違うところは、どうせ自分がやるつもりのタスクでも公開されているので思いがけず解決してもらえることがあることです。Twitter経由で「それはコレ使えばいいんじゃ?」ということを教えてもらうこともあります。

値段は何時でも修正できるので、適当に1000円とか登録しておいて、急に解決してもらって慌てて金額を増やして振込んだりしています。(数百円でも設定しておくと、解決する方にとっては「あ、この人はホントに解決を望んでるんだな」「勝手にこのタスクを解決しちゃっても迷惑にならない」とわかるので少額でも設定しておくのがコツだということに最近気付きました。)

使い方について分からないことがあれば気軽に@komagata@machidaまでメッセージを頂ければ答えます!

コメントはまだありません

毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第2回。

「電子書籍通の俺から言わせてもらえば、電子書籍通の間での最新の流行、”青空文庫形式”。コレ。”青空文庫形式”は縦書きとルビを標準でサポート。しかしコレで保存しても海外ではまったく役に立たないという危険も伴う”諸刃の剣”。まあお前らド素人はAZWでも使ってなさいってこった。」

komagata a.k.a. DJです。

今回紹介するのはコレ。

acts_as_taggable_on_steroidsのタグのValidationのエラーを直したい – Help me, hackers!

毎週火曜日更新とかいってコレ書いてる現在火曜日 23時50分ということからわかるようにDJネタに困ってたんだ。

そこに現れた天使がmilk1000cc a.k.a 牛乳嫌いプログラマ on Rails。

放置出来ないバグなのにうまい直し方がわからずDJが寝かせてたバグをサラっと解決してくれた上にネタまで提供してくれて最高だ。

神とアラン・ケイとmilk1000ccに感謝。

コードはココ:

元はRailsのタグ付けプラグインのacts_as_taggable_on_steroidsだが、Help me, hackers!向けにDJがちょっと修正を加えてある。

そもそも大元のacts_as_taggable自体、Rails初期からのプラグインで_reduxとか_steroidsとか色々派生を産んでるが品質も色々。ポリモーフィック関連が使えるようになってからは単純なacts_as_系は書いても実装は大分楽になったので若干重要性は薄れているが色々なところで使われている。

自分がURLの一部として使われるタグの実装をすることを想像してみて欲しい。DJが追加したかった部分は下記。

  • case insensitive(大文字・小文字を区別しない)
  • 小文字に統一
  • 空白禁止(ハイフン使ってね)

まあ、stackoverflowの仕様のパクリなんだけどね。

class Tag < ActiveRecord::Base
  validates_presence_of :name
  validates_uniqueness_of :name, :case_sensitive => false # デフォルトはtrueなのでfalseにする
  validates_format_of :name, :with => /^\S+$/ # \Sは非空白文字

  def before_validation
    self.name.downcase! # 小文字に合わせる
  end
end

要はこんな感じにTagクラスを定義したいんだけど、Tagクラスはプラグインの中だから直接修正した。(app/models/tag.rbを置いて上書きできるけど、これはクラス読み込み順番の問題でproductionでは機能しない)

ところが、TagクラスのValidationエラーは誰にも拾われる事無くユーザーの前に表れやがる。

そこでmilk1000ccがHackしてくれた部分のキモが13行目のvalidate :validate_tagsと186-199行目のvalidate_tagsメソッドだ。

validate :validate_tags

Railsユーザーにはお馴染みのActiveRecord内での宣言的なメソッド指定。validateで引数のシンボルと同じ名前のメソッドをvalidate時に実行する。

def validate_tags
  error_messages = []
  new_tag_names = @tag_list - tags.map(&:name)

  new_tag_names.each do |new_tag_name|
    tag = Tag.find_or_initialize_with_like_by_name(new_tag_name)
    error_messages << tag.errors.on(:name) unless tag.valid?
  end
  error_messages.flatten!

  error_messages.uniq.each do |msg|
    errors.add :tag_list, msg
  end
end

複数あるタグのエラーメッセージをflatten!してエラーに追加している。これは非常に真っ当な対処方法な気がする。正直DJ、エラーが出ないように適当に変換しちゃおうとしてたよ。(しかも失敗してた)

188行目で

tags.map(&:name)

というコードが出てくるが、これはActiveSupportで追加されるSymbol#to_procを使ったRails以降Rubyで多用されるイディオムで

tags.map {|tag| tag.name }

と同じ意味。

慣れると欠かせないが、初心者キラーとして有名なので「下の書き方でいいじゃねぇか・・・」という声も。しかしRuby1.8.7やRuby1.9以降ではコアに含まれてるので頑張って慣れるべしというのはRubyist豆知識だ。

Javascript続きで来たこの連載もmilk1000ccのお陰でRubyが登場。しかもgistにpatch形式で貼ってくれるという親切設計・・・。Help me, hackers!はまだ収益とか上げてないので5000円しか振り込めないのがDJ情けない。

しかし、FJORD社(総勢2名)の経理を担当する者としてはPayPalに「受領書を印刷」という機能があって助かった。

大事なことだから強調しておこう。

「Help me, hackers!の報奨金支払いは経費で落ちます。」

この記事のタグ

コメントはまだありません