Categories

open all | close all

Tags


Search

Archive for November 2007

国際化プラグイン

Simple Localizationは,プラグインにも利用できることがわかったので,これを採用することにしました。

30 Nov, 2007 | General | | Andy | Leave comment - 0 -

あまり進んでいませんが

render :partialでフォームを貼り込めることは確認できました。

30 Nov, 2007 | General | | Andy | Leave comment - 0 -

国際化について補足

プラグインを国際化するのにも使えるかどうか。
メールはどうするかっていうのもあるけど,そこまで国際化ツールに期待すると機能が肥大化してしまいそう。

29 Nov, 2007 | General | | Andy | Leave comment - 0 -

さて,フォームをどうしようか

前に,認証,更新系の順に作業する予定と書きましたが,先に更新系をやろうかと思っています。
その前段階としてフォームをどうするかで悩み中。

まず,現状のNucleusはNucleusのパーサーを使ってフォームを生成しています。これをどうするか。
今のRails版のアプローチでは,そのまま移植するのが一番楽ですが,フォーム部分はスキンと独立しているので,Nucleusの<%,%>を使う必然性はありません。なので,この部分はRailsのビュー機能を使い,Partial Renderingで入れていくのがいいのかなと思っています(それで動くかどうかは未テスト)。

それともう1つ,国際化をどうするかという問題があります。RailsのWikiの国際化方法の比較ページを見ていたのですが,決めかねています。世間的にはgettextの評価が高いのかと思っていますが,gemでインストールしなければいけないのがちょっと面倒なのと,「gettextでなくちゃ」というのがどこなのか,よく分かっていないというのがあります。

Railsのプラグインとして使える中では,DBに言語データを入れていくGlobalizeは面倒なので却下。なんとなくよさげかなあと思うのがGloc。ただ,Simple Localizationというのも導入のしきいが低そうなところは好感が持てます。とりあえずSimple Localizationを入れておいて,後から変えることを検討するというのが妥当なところかもしれません。

29 Nov, 2007 | General | | Andy | Leave comment - 0 -

テンプレートのコメントの処理がよく分からない

Nucleusのテンプレートにおけるコメントの処理がよく理解できなくて,先に進めないでいます。なんだかややこしいことをやっているのは分かるのですが。

えーと,これだけでは何なので,今まで書いていなかった,基本的な処理の流れを紹介(といっても大したことはありませんが)。

まず,入ってきたURLを振り分けるのはroutes.rbの役割。ここはこんな感じです(デフォルトブログの場合)。
  # rules for the index page
  map.blog '', :controller => "application", :action => 'index'
  map.blogcategory 'catid/:catid', :controller => "application", :action => 'index'

  # rules for the item page
  map.item 'item/:itemid', :controller => 'application', :action => 'item'
  map.itemcategory 'item/:itemid/catid/:catid', :controller => 'application',  :action => 'item'

  # rules for the archivelist page
  map.archivelist 'archivelist', :controller => 'application', :action => 'archivelist'

  # rules for the member page
  map.member 'member/:memberid', :controller => 'application',  :action => 'member'
  
  # rules for the archive page
  map.archive 'archive/:daymonth', :controller=> 'application', :action => 'archive', \
                    :requirements=>{:daymonth => /\d{4}-d{1,2}(-d{1,2})?/}
  map.archivecategory 'archive/:daymonth/catid/:catid', :controller=> 'application', \
                    :action => 'archive', \
                    :requirements=>{:daymonth => /\d{4}-d{1,2}(-d{1,2})?/}

名前付きのルーティングもできるように,map.connectではなくて,各ルートに名前を付けています。
これを見て分かるように,このあたりは全部applicationというメインのコントローラが処理します。actionのところでスキンタイプごとに振り分ける形。
このほか,ポスト系を処理するactionのコントローラと管理用のadminコントローラ,rss用のコントローラを別途作っていく予定です。

これを受けるapplication.rbは今のところこんな感じ。グローバル変数を多用しているところがちょっと格好悪いのですが。
class ApplicationController < ActionController::Base
private
def set_charset
  headers['Content-Type'] = "text/html; charset=utf-8"
end
# Pick a unique cookie name to distinguish our session data from others'
  session :session_key => '_nonror_session_id'

private
  def setblog
    if !params['blogname']
       $NCGlobals['blog'] = Blog.find(CONF['DefaultBlog'])
    else
       $NCGlobals['blog'] = Blog.find_by_bname(params['blogname']) || Blog.find(CONF['DefaultBlog'])
    end
  end
  
  def setcatid
    if params['catid']
       $NCGlobals['catid'] = params['catid'].to_i
    end
  end

public
  def index
    setblog
    setcatid
    
    $NCGlobals['skin'] = $NCGlobals['blog'].skin
    $NCGlobals['skintype'] = 'index'
    $NCGlobals['item'] = Array.new
    $NCGlobals['controller'] = self

    @contents = $NCGlobals['skin'].part($NCGlobals['skintype'])
    @controller = NCParser::DefaultSkinIndex.new $NCGlobals['controller']
  end

  def item
    setblog
    setcatid
    
    $NCGlobals['skin'] = $NCGlobals['blog'].skin
    $NCGlobals['skintype'] = 'item'
    $NCGlobals['item'] = [Item.find(params['itemid'].to_i)]
    $NCGlobals['controller'] = self

    @contents = $NCGlobals['skin'].part($NCGlobals['skintype'])
    @controller = NCParser::DefaultSkinItem.new $NCGlobals['controller']
    render :action=>'index'
  end

end

今,actionはindexとitemしかありませんが,itemの最後には「render :action=>'index'」というのが入っています。これは表示用のビュー・ファイルはindex.rhtmlを使うということ。全スキンで共通にしています。といってもビュー・ファイルの中身は「<%= parse %>」だけ。これを処理するのはapplication_helper.rbで,この中身もいたってシンプル。
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
  def parse
    @controller.parse(@contents)
  end
end
ここで@controllerというのがパーサのオブジェクトで,それがスキンの中身をパースするという仕組みです。これでパーサが実行した結果が出力されます。これが基本的な流れです。


26 Nov, 2007 | General | | Andy | Leave comment - 0 -

どうもきたない

文字化け問題,Railsのコンソールでは化けているものの,Webの出力は化けていないことが分かり,ますますよく分かりませんが,とりあえず今のままでも困らないので,このまま進めます。

スキン変数とテンプレート変数をちょこちょこと実装しています。相変わらずRubyのnilの扱いにはちょっととまどいがちですが,間違えるパターンが分かってきたので,バグ取りの時間は短くなってきたようです。

URL生成もできているのですが,気になるのはRails開発ブログでDeprecated Controller Methodsの中にurl_for(:#{options}) - use url_for with a named route directlyと書かれていること。
しかし,名前付きの呼び出し方 ...._url はprotectedなので直接コントローラから呼べないのと,_send_を使って無理やり送ってもちょっと動作が違うという問題があります。url_forを使って名前を指定する方法があるのかと,探しているのですが,見つかりません。


25 Nov, 2007 | General | | Andy | Leave comment - 0 -

文字化けわけ分からん

DBをmysqldumpして,エディタで見てみたらちゃんとUTF-8でデータが入っています。
それなのにRailsでは文字化け。
$KCODEもdatabase.ymlのencodingも指定しているのになあ…
ほかに何かあったっけ?


22 Nov, 2007 | General | | Andy | Leave comment - 0 -

そんなわけで

RadRailsのSubversion対応はできました(Thanks to hsurさん)。で,結局夕べは眠くて,コントローラをローカル変数で渡したときに動かないというバグだけ取って寝ました。昨日は睡眠時間4時間オーバー。

全体の進捗を書いておくと,パーサの基本部分はできていて,後はスキンやテンプレートでparse_●●というのを書いていけばいい状態。次に与えられたURLに対して動作を振り分けるところを作っていて,それに対応するURL生成をテストしているところ。これがうまく行ってcreateLink系の部分ができると基本的にブラウズはできる形になります(検索除く)。

その後は,認証系を作り,コメントなどの更新系の作業に移る計画です。

21 Nov, 2007 | General | | Andy | Leave comment - 0 -

もろもろ

●パーサをクラスでなくてモジュールにするかという件。パーサが複数になって連動したりすると名前のコンフリクトとか絶対に起きるだろうなと思い,やっぱり却下。今はコントローラをグローバル変数に入れているのが,事実上は問題なくてもちょっと気持ち悪いところ。そこをパラメータで渡してインスタンス変数にしようかと。でも,なぜかエラーが出ます。グローバル変数はあまり使いたくないのだけど,プラグインで様々な機能を入れていくときに結局必要になるのではないかという気もします。

●プログラムを書くのは夜中12時くらいから2時くらいまでなのですが,デバグなどしているとついつい時間オーバーしてしまいがち。朝7時に起きることを考えると3時までには寝るようにしないときついのですが。さらに,夜1回1時間くらい寝てしまってから,起きて作業をすると,目が冴えてしまって,今度は眠りに着くのが大変。夕べも結局睡眠時間が3時間くらいに。こうなると昼間の仕事に影響が出てしまうので,一回寝てしまった日はプログラム書かないとか,デバッグしないとか,ルールを決めないと体壊しそうです。

●Subversionの使い方が,相変わらずよく分かっていません。今はローカルにtrunkだのbranchだののツリーを作って,サーバーのリポジトリと同期させ,trunkの下だけをInstantRailsの下にコピーして作業しています。作業内容をツリーに入れるときに,どこを更新したかちゃんと覚えていないので,更新忘れのファイルなどが出現しがちです。それから,他の場所で更新したものを反映するときも,ローカルのツリーでどこが変わったのかを忘れてしまい,作業ディレクトリに反映し忘れてしまうことがあります。RadRails(Eclipse)にはSubversionの機能があるというのですが,使い方が分かっていません。作業ディレクトリから直接リポジトリと同期させるのでしょうか。

●このブログ,カテゴリーもちゃんと作っていないいい加減な構成。基本的に作業メモだからいいんだけど,実際には動作チェックもここのデータを使っているので,もうちょっとまじめに作っておかないとちょっと大変。MCやTagEXも入れようかなあ。あ,既に入っているのだけどつかってないだけでした。

20 Nov, 2007 | General | | Andy | Leave comment - 5 -

実装方針で迷い

URLによってディスパッチ先を決めていくのは,割と簡単な作業なので,淡々と進めています。
それと並行して,各スキンタイプによる処理を加え始めているのですが,ちょっと迷い。

今のNucleusの場合,スキンタイプごとに,サポートしているスキン変数を配列に持っていて,それを参照して実行するかどうかを決めています。その部分の実装をどうするか。Nucleusと同じにしてもいいのですが,あまり格好よくありません。スキンタイプごとにサブクラスを作ってそこで対応するのがスマートですが,今度は複数のスキンタイプでサポートしているときにどうするか,という問題があります。

全部のタイプでサポートしているならば親クラスで定義してしまえばそれで済むのですが,そうでない場合,各スキンタイプに書くのは無駄です。Railsにはdelegateという機能があって,特定のメソッドをインスタンス変数に実行させることができますが,そのためだけに,他のスキンタイプ用のオブジェクトをインスタンス変数に持つのもどうなのか。まあ,それでもこれが一番きれいな気がするので,多分それにするでしょう。

これに関連してもう1つの実装として考えているのがパーサの処理部分をクラスではなくモジュールにしてしまって,コントローラにmix-inすること。これから先,Railsのコントローラで提供されている様々な機能(例えばキャッシュ)を利用していくためには,処理をなるべくコントローラ内でするのがいいのではないか,という発想です。

モジュールにした場合に機能的に問題があるのかないのか。その場合,サブクラスとして考えている分も,単なる別のモジュールという扱いになって,それぞれmix-inすることになります。一応それでも動きそうな気はするのですが,検証はしていません。まだ,モジュールの扱い方には慣れていないので,ちょっと危険な感じもします。

19 Nov, 2007 | General | | Andy | Leave comment - 0 -

メモ

後で,クラスごとの役割分担を考えるときに必要かと思って,「Ruby on Rails入門」をみながらページスイッチの機能を一応入れてみようとしてみたのですが,うまく動かず。

少し調べていたら,この問題の解決にはつながっていないのですが,そもそもこのPaginationの機能自体がなくなって,will_paginateあるいはpaginating_findプラグインを使うとのこと。will_paginateの方が標準推奨のようですが,吐き出すSQLがいけてないらしく,どちらを使うかちょっと悩ましいところです。

どちらもモデルに組み込む形なので,使うのは比較的簡単そうな気がします。ということで,これは先送り。

Ruby on Rails入門―優しいRailsの育て方
Ruby on Rails入門―優しいRailsの育て方
西 和則
秀和システム
2006/08
¥ 2,940 (定価)
¥ 2,940 (Amazon価格)
29pt (Amazonポイント)
 (私のおすすめ度)
★★★☆ (Amazonおすすめ度)
単行本
通常24時間以内に発送
(価格・在庫状況は12月4日 23:22現在)



18 Nov, 2007 | General | | Andy | Leave comment - 0 -

一応パースはしています

文字化け問題は完全には解決していませんが,とりあえずいくつかのMySQLのうちちゃんと動くものもあったので,問題は当面たなあげということで進めます。

ということで,一応進捗はここまで。

画像サンプル
テンプレート変数など,まだ少ししか実装していないので<%,%>だらけですが,それでもタイトルや本文が表示されると,すこし格好が付きました。

リンク系をちゃんとしないと,トップページを表示する以外,何もできないので,次はコントローラのディスパッチの設定をして,それにあわせてURL生成をするという作業に移ります。

コードは暫定的なのばかりなので,特に載せませんが,テンプレートのパーツにアクセスする部分を変えたので,そこだけ書いておきます。

  def [] partname
    begin
      @parts[partname].to_s
    rescue
      if !@parts
        @parts = Hash.new
         partsarray = TemplatePart.find(:all, :conditions=>["tdesc = ?", self.tdnumber])
         partsarray.each do |part|
           @parts[part['tpartname']]=part['tcontent']
        end
      end
      @parts[partname].to_s
    end
  end

これでテンプレート・オブジェクトに対してtemplate['ITEM']みたいな形でパーツを取ってこれます。ブログを展開するとき,連続していくつかのパーツを使うため,一回のDBアクセスでそのテンプレートの内容を全部取り込むようにしました。Rubyは例外処理が簡単に書けるので,こういうのはやりやすいです(これくらいなら例外処理使わなくても構いませんが,この方が読みやすいと思います)。スキンの場合は,一回のアクセスで使うパーツはたいてい1つなので,今のところこの処理は入れていません。ただ,スペシャル・スキン・パーツを多用するようになってきたら,入れた方がいいかもしれません。

それから,パートのデータを返すところにわざわざto_sを付けているのは,そのパートのデータがないときに,nilの代わりに空文字列を返すためです。こうしておかないと,文字列にnilを連結させてしまいエラーを起こすことになります。

17 Nov, 2007 | General | | Andy | Leave comment - 0 -

文字化けorz

パースはなんとかしてくれるようになってきたものの,文字化け問題が再発していることが判明しました。phpMyAdmin上ではちゃんと見えているのですが。

ちなみに
show variables like 'char%'
すると
character_set_client utf8
character_set_connection utf8
character_set_database utf8
character_set_filesystem binary
character_set_results utf8
character_set_server latin1
character_set_system utf8
とでます。サーバーのlatin1というのが怪しいのかなあ。
(default-character-set=utf8を設定したらutf8になりましたが文字化けは変わらず)
PHPにしろMySQLにしろ,文字コードの自動変換ほど余計なお世話な機能はないと思います。

こういうので作業が止まるのが一番嫌です。

16 Nov, 2007 | General | | Andy | Leave comment - 0 -

「落ちる」RadRails

Railsの開発はRadRailsを使っているのですが,昨晩は編集中に3回もハングアップしてへこみました。しかも毎回,マシンをリセットするしかないという派手な落ち方。

RadRailsのエディタは括弧やクォーテーション・マークなど補完してくれるのですが,間違ってクォーテーション・マークを入れてしまい,すぐに戻って消そうとするとそこでハングします。昨日編集していたファイルの特殊事情なのかどうかは不明です。

後から考えたら2回ハングして時点で,RadRails使わず,テキスト・エディタに切り替えればよかったです。

14 Nov, 2007 | General | | Andy | Leave comment - 0 -

URL周り

今日はちょっとコードは書くのを休んで,NucleusのURL周りを調べていました。実はあんまりちゃんと
知らなかったりして(^^;)。

スキンタイプパラメータblogidパラメータcategory修飾ページング
indexなし指定可(省略時デフォルト)
itemitemid指定可(省略時デフォルト)×
archivelistblogidあるいはなし指定なし×
search検索文字列指定可(省略時デフォルト)×
membermemberid指定可(省略時デフォルト)××
archive年月指定可(省略時デフォルト)
errorURLとしては使われない指定なし××
imagepopupmediaファイルの位置指定可(省略時デフォルト)××


スキンタイプと関連するパラメータ周りをまとめるとこのようになります(多分)。一番右のページングは,ページスイッチを付ける意味があるかどうか。ページスイッチの機能はコアに持たせる予定なので。

これ見ると,archivelistのところだけブログの指定方法が変則ですね。

URL周りはNucleusコアとの互換性はそれほど重視していません。FancyURLに似た形にはすると思いますが違うところも出てくるかもしれません(要は実装しやすい形でということで)。最終的にはCustomURLと互換にするつもりです。

注:archiveとarchivelistで逆になっているところなどがあったので修正しています。

13 Nov, 2007 | General | | Andy | Leave comment - 0 -

<%image%>と<%popup%>

コードは書いていますが,まだ動作テストしていないので,あらましは明日あたり。
テンプレートなどの細かい実装に移っているので,ようやく今のNucleusのコードを調べたりしています。

パーサ書いているときに気付いたのですが,今のNucleusのパーサーの作りってかなりいい加減。
実は<%と%>って全く区別されていないってご存知でしょうか。試しにスキン中で
%>parsedinclude(head.inc)<%
などと書いてしまっても問題なく動きます。今回のパーサはネストを実現したかったこともあり,そのあたりはきちんと見ています。

あと,一般にはNucleusのパーサってスキンとテンプレートの2段階だと思われていると思いますが,実際にはその下に,アイテム・レベルとイメージ,ポップアップ,メディア処理の2段階が入り,計4段階で動いています。Nucleus3.3ではプラグインのアイテム変数が利用できるようになっていますが,アイテムにはもともと<%image%>などを処理するためにパーサを適用していたので,そこを少し変えるだけでこの機能は追加できました(記憶になかったけど,この部分は僕が書いたコードが採用されているようです)。

最後の段階のイメージなどの処理はテンプレートの中のIMAGE_CODEなどの内容がパースされます。ただし,他の段階のようなフルレベルのパースではなく,同じ名前の変数と置き換えるくらいの単純なやり方です。ちなみにこのテンプレートを書き換えるとプラグインを使わなくてもLightBox.jsを呼び出す程度のものは実現できます。このあたりの自由度は,あまり活用されていない気がしますが,どうでしょう。

というわけで,このあたりの処理を,今は書いています。

12 Nov, 2007 | General | | Andy | Leave comment - 0 -

ifの処理つきパーサーのコード

ほぼ完全版(のつもり)。これで,各パーサーのクラスはスキン変数やテンプレート変数に相当する部分のparse_●●を書いていけばいいはずです。

ああっと,まだプラグイン呼び出すとこ書いてないけど,parse_●●がなかったときの処理としてrescue節に入れてあげればいいでしょう。そもそもまだプラグインのアーキテクチャを考えていないので,そこは後から加えます。


# ParserBase
require 'strscan'

module NCParser

  class Base

     yaml = YAML.load_file("config/database.yml")
     ENV['RAILS_ENV'] ||= 'development'
    @@execmode = ENV['RAILS_ENV']
    
    def initialize (left = '<%', right = '%>', nest = false)
      @left = Regexp.new left
      @leftlen = left.length
      @right = Regexp.new right
      @rightlen = right.length
      @nest = nest
    end
    
    def parse(str)
      if @nest
         parse_with_nest(str)
      else
         parse_without_nest(str)
      end
    end

    
    def parse_with_nest(str)
       parenleft = StringScanner.new(str)
       parenright = StringScanner.new(str)
       length = str.length
       nestlevel = 0
       result = Array.new
       ifstack = Array.new
       ifstack[0] = Array.new
       @ifsituation = ifstack[0]
       currentpos = 0
      if parenleft.search_full(@left,true,false)
         leftpos = parenleft.pos
      else
         leftpos = length+1
      end
      if parenright.search_full(@right,true,false)
         rightpos = parenright.pos
      else
         rightpos = length+1
      end
      while (leftpos <= length) || (rightpos <= length)
        if nestlevel == 0 || leftpos < rightpos
          if @ifsituation.size == 0 || @ifsituation.last == 1
            if ! result[nestlevel]
                result[nestlevel] = str[currentpos , (leftpos-@leftlen-currentpos)]
            else
                result[nestlevel] += str[currentpos , (leftpos-@leftlen-currentpos)]
            end
          end
            ifstack.push Array.new
          @ifsituation = ifstack.last
            nestlevel += 1
            currentpos = leftpos
          if parenleft.search_full(@left,true,false)
              leftpos = parenleft.pos
          else
              leftpos = length+1
          end  
        else
          if @ifsituation.size == 0 || @ifsituation.last == 1
            if ! result[nestlevel]
                result[nestlevel] = str[currentpos , (rightpos-@rightlen-currentpos)]
            else
                result[nestlevel] += str[currentpos , (rightpos-@rightlen-currentpos)]
            end
          end
           result[nestlevel-1] += self.exec result[nestlevel]
           result.pop
           nestlevel -= 1
           ifstack.pop
          @ifsituation = ifstack.last
           currentpos = rightpos
          if parenright.search_full(@right,true,false)
             rightpos = parenright.pos
          else
             rightpos = length+1
          end
        end
      end
       result[0] += str[currentpos .. length]
    end

    def parse_without_nest(str)
       parenleft = StringScanner.new(str)
       parenright = StringScanner.new(str)
       length = str.length
       result = ''
       currentpos = 0
       leftpos = 0
       rightpos = 0
       @ifsituation = Array.new
       
      while (leftpos <= length) || (rightpos <= length)
         if parenleft.search_full(@left,true,false)
           leftpos = parenleft.pos
        else
           leftpos = length+1
        end
        begin
          if parenright.search_full(@right,true,false)
             rightpos = parenright.pos
          else
             rightpos = length+1
          end
        end while leftpos > rightpos
         if leftpos<=length && rightpos<=length
          if @ifsituation.size == 0 || @ifsituation.last == 1
              result += str[currentpos,(leftpos-@leftlen-currentpos)].to_s
          end
           result += self.exec str[leftpos..rightpos-@rightlen-1]
           currentpos = rightpos
           parenleft.pos = rightpos-1
        else
           result += str[currentpos,length-currentpos]
        end
      end
       result
     end
 
    def exec str
      str.rstrip!
      str.lstrip!
      str =~ /^(\w+)(?:\((.*)\))?$/m
      command = $1
      paramall = $2 ? $2 : ""
      params = paramall.split(/[\t\r\n\f\v]*,[\t\r\n\f\v]*/)
      params.unshift paramall
      begin
         send('do_'+command, params)
      rescue
        begin
           if @ifsituation.size == 0 || @ifsituation.last == 1
               send('parse_'+command, params)
           else
              ''
           end
        rescue
            debugout('<%'+command+'('+paramall+')%>')
        end
      end
    end
    
    def debugout str
      if @@execmode == 'production'
         ''
      else
         str
      end
    end

    def do_if(params)
      if self.check_conditions(params)
        @ifsituation.push(1)
      else
        @ifsituation.push(0)
      end
      ''
    end
    
    def do_ifnot(params)
      if self.check_conditions(params)
        @ifsituation.push(0)
      else
        @ifsituation.push(1)
      end
      ''
    end
    
    def do_else(params)
      case @ifsituation.last
      when 0
         @ifsituation[-1,1] = 1
      when 1
         @ifsituation[-1,1] = 2
      end
       ''
    end

    def do_elseif(params)
      case @ifsituation.last
      when 0
          if self.check_conditions(params)
            @ifsituation[-1,1] = 1
          else
            @ifsituation[-1,1] = 0
          end
      when 1
         @ifsituation[-1,1] = 2
       end
       ''
    end
    
    def do_elseifnot(params)
      case @ifsituation.last
      when 0
          if self.check_conditions(params)
            @ifsituation[-1,1] = 0
          else
            @ifsituation[-1,1] = 1
          end
      when 1
         @ifsituation[-1,1] = 2
       end
       ''
    end
    
    def do_endif(params)
      @ifsituation.pop
      ''
    end
    
  end
end


09 Nov, 2007 | General | | Andy | Leave comment - 0 -

前の記事は大分うそでした

「Ruby on Rails入門~優しいRailsの育て方」を読んでいたら,プラグインの作り方が少し載っていて,モジュールにする際にはモジュールをディレクトリ名にすると書いてありました。先日の動かなかった原因もそこにあったようです。つまりRailsの規約に合っていなかったわけ。

parser_base/lib/の下に/parser/base.rbを作ってそこに入れたところ,どこにもrequireなしでちゃんと動くようになりました。テストもリネームしないといけないけどそちらは未検証。

それと,parsedincludeの中からのparseが動かないというのもバグでした。

括弧とパラメータがないときの処理がおかしかったのが原因。Rubyは暗黙の型変換がないのでnilには結構悩まされます。


05 Nov, 2007 | General | | Andy | Leave comment - 0 -

遅々とした進行

先日作ったParser::Baseを継承してデフォルト・パーサーのクラスを作ったのはいいものの,問題はプラグインのロード順。Baseの方を先に読み込んでもらわないといけないのに,どうしてもうまくrequireできない。いろいろ調べるうちに,ようやくRailsのフォーラムでenvironment.rbの中に
config.plugins = [ "prefixed", "parser_base", "default_parser" ]
などと書くことで動くことが判明。ここまでかなり不毛な時間を過ごしました。パーサーにもいくつかバグがあったので,それを取りつつ,いちおう動くようになってきました。
例えばDefaultParserのクラスは

class DefaultParser <Parser::Base
  def exec str
    str.rstrip!
    str.lstrip!
    str =~ /^(\w+)(?:\((.*)\))?$/m
    command = $1
    paramall = $2
    params = paramall.split(/[\t\r\n\f\v]*,[\t\r\n\f\v]*/)
    params.unshift paramall
    begin
      self.send(command, params)
    rescue
       command+'('+paramall+')'
    end
  end

  def parsedinclude fname
    contents = $skin.file(fname[1])
    self.parse contents
  end
end

としているのですが,ここでなぜかparsedincludeの中でパースしてくれません。self.parseの行を取るとincludeはできているのですが。ちょっと不思議。

05 Nov, 2007 | General | | Andy | Leave comment - 0 -

パーサのベース部分

あんまりRubyっぽくない書き方だけど,今のところほかに思いつかないのでそのまま。
aがnilの場合にa += b といった書き方ができないのが痛い。PHPだったら勝手に空文字列扱いにしてくれるのに。

パーサはネストありのものしか今のところ作っていませんが,ネストなしは簡単なので,まあいいでしょう。ただ,ちゃんとデバッグしてないです。正しくないフォーマットのものが来たら,問題起こす可能性大。無駄な検索をしていないので,実行速度は悪くないのではないかと思っています(ベンチマークはしてないけど)。

module Parser
  class Base
    def initialize (left = '<%', right = '%>', nest = true)
      @left = Regexp.new left
      @leftlen = left.length
      @right = Regexp.new right
      @rightlen = right.length
      @nest = nest
    end
    
    def parse(str)
      if @nest
         parse_with_nest(str)
      else
         parse_without_nest(str)
      end
    end

    
    def parse_with_nest(str)
       parenleft = StringScanner.new(str)
       parenright = StringScanner.new(str)
       length = str.length
       nestlevel = 0
       result = Array.new
       currentpos = 0
       parenleft.search_full(@left,true,false)
       parenright.search_full(@right,true,false)
       leftpos = parenleft.pos ? parenleft.pos : length
       rightpos = parenright.pos ? parenright.pos : length
      while (leftpos != length) || (rightpos != length)
        if nestlevel == 0 || leftpos < rightpos
          if ! result[nestlevel]
              result[nestlevel] = str[currentpos .. (leftpos-@leftlen-1)]
          else
              result[nestlevel] += str[currentpos .. (leftpos-@leftlen-1)]
          end
            nestlevel += 1
            currentpos = leftpos
          if parenleft.search_full(@left,true,false)
              leftpos = parenleft.pos
          else
              leftpos = length
          end  
        else
          if ! result[nestlevel]
              result[nestlevel] = str[currentpos .. (rightpos-@rightlen-1)]
          else
              result[nestlevel] += str[currentpos .. (rightpos-@rightlen-1)]
          end
           result[nestlevel-1] += self.exec result[nestlevel]
           result.pop
           nestlevel -= 1
           currentpos = rightpos
          if parenright.search_full(@right,true,false)
             rightpos = parenright.pos
          else
             rightpos = length
          end
        end
      end
       result[0] += str[currentpos .. length]
    end
    
    def exec str
       'exec(' + str + ')'
    end
    
  end
end


03 Nov, 2007 | General | | Andy | Leave comment - 0 -

設定テーブルへのアクセス

nucleus_configテーブルには数値の主キーがないので,ActiveRecordによる自動処理は期待できません。ただ,ここはもう項目が決まっているので,一気に読み込んでハッシュとしてアクセスできるようにしてしまいます。

class Configurations < ActiveRecord::Base
  set_base_name :config

  @@configs = Hash.new
  self.find_by_sql(["SELECT * FROM "+ self.table_name + " WHERE 1"]).each { |pair| 
    @@configs[pair['name']]=pair['value']
  }
  
  
  def self.[] name
    @@configs[name]
  end
end

こういった書き方が正しいのかどうかはよく分かりませんが,これでConfigurations['DefaultBlog']といった形でアクセスできるようになりました。

まだ,プラグインなど残っているテーブルはありますが,すぐには必要ないので,コントローラとパーサに移りたいと思います。

PS. クラス名が長いのでCONFに修正しようと思います。

01 Nov, 2007 | General | | Andy | Leave comment - 0 -

© 2007 yoursite.com | Designed by DesignsByDarren
Ported to Nucleus CMS: Suvoroff