«Prev || 1 | 2 | 3 |...| 18 | 19 | 20 | 21 | 22 || Next»

<%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 -

複合キーの使い方

複合キーのプラグインを使って,当面のモデル化は大体大丈夫そうです。作業メモとして手順と設定を書いておきます。

プラグインのインストールは
gem install composite_primary_keys

で行います。

environment.rbには
require 'rubygems'
require 'composite_primary_keys'

と書いておきます。

まず,Team回りの設定から。Teamクラスには下のように二つのbelongs_toを設定。
class Team < ActiveRecord::Base
  set_primary_keys :tmember, :tblog
  set_base_name :team
  belongs_to :blog, :foreign_key => 'tblog'
  belongs_to :member, :foreign_key => 'tmember'
end

Blogクラスには以下の2行を追加
  has_many :teams, :class_name=>'Team' ,:foreign_key => 'tblog'
  has_many :members, :class_name=>'Member' ,:through=>:teams

同様にMemberクラスにも
  has_many :teams, :class_name=>'Team' ,:foreign_key => 'tmember'
  has_many :blogs, :class_name=>'Blog' ,:through=>:teams
を追加しました。これでmember.blogsとか,blog.membersといった形で参照できるようになります。

次に,もうちょっと単純ですが,スキン関係とテンプレート関係を設定。nucleus_skin_descテーブルにはスキンのIDと名前,説明が,nucleus_skinテーブルには各パートがスキンのIDとパート名をキーとして入っています。

skin_descの方をSkinモデルにします。
class Skin < ActiveRecord::Base
  set_base_name :skin_desc
  set_primary_key :sdnumber
  has_many :parts, :class_name=>'SkinPart' ,:foreign_key => 'sdesc'
end

skinテーブルをSkinPartモデルにします。
class SkinPart < ActiveRecord::Base
  set_base_name :skin
  set_primary_keys :sdesc, :stype
  belongs_to :skin, :foreign_key => :sdesc
end

使い方はこんな感じ。
「default」スキンを獲得
>> skin = Skin.find_by_sdname('default')
skin = Skin.find_by_sdname('default')
=> #"text/html", "sdnumber"=>"5", "sdincmode"=>"skindir", "sddesc"=>"Nucleus CMS default skin", "sdincpref"=>"default/", "sdname"=>"default"}>

このスキンの「index」パートを獲得
>> indexpart = skin.parts.find_by_stype('index')
indexpart = skin.skin_parts.find_by_stype('index')
=> #
以下略

SkinのIDが分かっているならば直接パートの内容を得ることもできます。
>> indexpart = SkinPart.find([5, 'index'])
indexpart = SkinPart.find([5, 'index'])
=> #
以下略

実際のパートの内容にはindexpart.scontentでアクセスできます。テンプレートも同様です。

追記:
  def part(type)
    SkinPart.find([self.id, type]).scontent
  end

とSkinクラスに追加するとskin.part('index')とかで簡単にパートの内容にアクセスできます。多分この方が楽だし速い。

31 Oct, 2007 | General | | Andy | Leave comment - 0 -

リレーション追加

カテゴリーや,コメント,メンバーといったテーブルについてもモデル化を進めています。
一つ引っかかっているのが「チーム」のテーブル。このテーブルはブログIDとメンバーのID,それとAdminのフィールドという三つのフィールドしかなく,主キーがありません。

Railsではこういったときのブログとメンバーの関係をhas_and_belongs_to_many(HABTM)と言っていますが,このときの中間テーブルに相当するのがチームです。ほかにフィールドがなければ問題ないのですが,Adminのフィールドがあるので,そのままではこのモデルにうまくはまりません。「push_with_attributes」という形でフィールドを追加できるという機能もあるのですが,近い将来廃止されるようなので使えません。主キーがあればhas_many; throughといった機能が使えるようですが,テーブル構造を変えるのは今の段階ではちょっと避けたいところ。

Composite Primary Keysというプラグインを使ってみることにしましたが,これがどこまでうまく働いてくれるかは微妙です。

31 Oct, 2007 | General | | Andy | Leave comment - 0 -

ブログとアイテムのリレーション

テストはまだなんだかうまくいかないところもありますが,RadRailsからのコンソールの使い方がやっと分かったので,そちらでテスト。一応リレーションの設定は動いているようです。アイテムとブログの場合はアイテムがブログにbelongs_to,ブログから見るとアイテムをhas_manyするという関係なので,以下のようにしました。

class Item < ActiveRecord::Base
  set_primary_key :inumber
  set_base_name :item
  belongs_to :blog, :foreign_key => 'iblog'
end

class Blog < ActiveRecord::Base
  set_primary_key :bnumber
  set_base_name :blog
  has_many :items, :class_name=>'Item' ,:foreign_key => 'iblog'
end

両方にiblogを書くのがなんだか解せない感じもしますが,一応動いているのでいいことにしておきます。これで例えばブログからだったら
blog = Blog.find(1)
とか
blog = Blog.find(:first, :conditions=>"bshortname = 'test'")
でブログ・オブジェクトを獲得した後
blog.items
で,アイテムの配列が得られます。
逆にアイテムからだったら
item.blog
でブログ・オブジェクトにアクセスできます。
モデルに関してはこれで他のテーブルのモデル作成とリレーションを設定していけばいいはず。

追記
blog = Blog.find_by_bshortname("test")
でもブログ・オブジェクトが獲得できるはずです。

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

作業メモ

まだリレーションのテストは確認していないけど一応テストは動くようになったので,ここまでの作業メモ。まずはプリフィクスを付けるプラグインは以下のようにして,environment.rbの中でset_prefixでプレフィクスを設定。

# Prefixed
module ActiveRecord
  class Base
    class <<Base
      alias :original_table_name :table_name

      def set_base_name(basename)
        @base_name = basename
      end
      
      def set_prefix(prefix)
        @@nucleus_prefix = prefix
      end
      
      def table_name
        if @base_name 
           @@nucleus_prefix + @base_name.to_s
        else
           original_table_name
        end
      end
     end 
  end
end

後は,各クラスではset_base_nameでベースネームを設定(これってアクセサにすべきだった? ちょっとかっこ悪いけどまあいいや)。
テストの方は モデルクラス_test.rb の中でfixtureとしてプリフィクス付きのテーブル名を指定。あわせてfixturesの中のYAMLのファイル名もテーブル名と同じものに変更。さらにYAMLの中でidとなっているところは,実際のID(inumberなど)に修正。

今日はここまで(受験の神様風,笑)


27 Oct, 2007 | General | | Andy | Leave comment - 0 -
«Prev || 1 | 2 | 3 |...| 18 | 19 | 20 | 21 | 22 || Next»

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