root/lang/ruby/wikiforme/trunk/core/wikiforme/format.rb @ 2245

Revision 2245, 12.9 kB (checked in by frsyuki, 5 years ago)

lang/ruby/wikiforme: article.4meにdiv.rbを追加、wfdocにCSSを追加、wfdocで整形にERBを使うようにした

Line 
1
2require 'wikiforme/inline'
3require 'wikiforme/block'
4require 'wikiforme/syntax'
5require 'wikiforme/xml'
6require 'wikiforme/syntax_extend'
7
8
9module Format
10        # replace可能な定数
11        class ConstVariable
12                attr_accessor :obj
13                def initialize(obj = nil)
14                        @obj = obj
15                end
16                def replace(obj = true)
17                        @obj = obj
18                end
19                alias :set :replace
20                def method_missing(name, *args)
21                        @obj.__send__(name, *args)
22                end
23                def inspect
24                        @obj.inspect
25                end
26                def unset
27                        @obj = nil
28                end
29                def set?
30                        !@obj.nil?
31                end
32        end
33
34        def self.reset(block_base, inline_base)
35                @@block_base = block_base
36                @@inline_base = inline_base
37                @@block = Hash.new
38                @@block_ordered = Array.new
39                @@inline = Hash.new
40                @@inline_ordered = Array.new
41                @@escape_proc = Hash.new
42                @@suffix = Hash.new
43                @@default_root = nil
44                @@default_root_text = nil
45                @@init_procs = []
46        end
47        def self.block_elements
48                return @@block, @@block_ordered
49        end
50        def self.inline_elements
51                return @@inline, @@inline_ordered
52        end
53        def self.escape_proc
54                @@escape_proc
55        end
56        def self.suffix
57                @@suffix
58        end
59        def self.default_root
60                @@default_root
61        end
62        def self.default_root_text
63                @@default_root_text
64        end
65        def self.init_procs
66                @@init_procs
67        end
68
69        # ブロック要素を作成
70        def self.block(name = nil, base_element = nil)
71                return @@block if !name
72                # FIXME extendの実装が、記述順に依存してしまっている
73                #       extend元の要素は先に定義されている必要がある
74                name = name.to_sym
75                if @@block.include?(name)
76                        return @@block[name]
77                end
78                if base_element
79                        if !@@block.include?(base_element.to_sym)
80                                # FIXME エラーメッセージ
81                                raise WikiForme::FormatLoadError, "Base element :#{base_element} is not declared: :#{name}"   # MSG
82                        end
83                        element = @@block[name] = Class.new(@@block[base_element.to_sym])
84                else
85                        element = @@block[name] = Class.new(@@block_base)
86                end
87                element.module_eval {
88                        const_set(:BLOCK_ELEMENT, true)
89                        const_set(:BLOCK_GROUP, false)
90                        const_set(:INLINE_ELEMENT, false)
91                        # 要素名: Symbol
92                        const_set(:NAME, name)
93                        # 包含可能要素: SymbolのArray
94                        # CONTAIN_NAMESは継承する
95                        if defined? self::CONTAIN_NAMES
96                                const_set(:CONTAIN_NAMES, self::CONTAIN_NAMES.dup)
97                        else
98                                const_set(:CONTAIN_NAMES, [])
99                        end
100                        const_set(:CONTAIN, []) # ClassのArray
101                        # 包含不可能要素: SymbolのArray
102                        # UNCONTAIN_NAMESは継承する
103                        if defined? self::UNCONTAIN_NAMES
104                                const_set(:UNCONTAIN_NAMES, self::UNCONTAIN_NAMES.dup)
105                        else
106                                const_set(:UNCONTAIN_NAMES, [])
107                        end
108                        const_set(:UNCONTAIN, []) # ClassのArray
109                        # 所属グループの定義: SymbolのArray(@から始まる)
110                        # GROUP_NAMESは継承する
111                        if defined? self::GROUP_NAMES
112                                const_set(:GROUP_NAMES, self::GROUP_NAMES.dup)
113                        else
114                                const_set(:GROUP_NAMES, [])
115                        end
116                        # 親要素: Symbolかnil
117                        # PARENT_COMPLETION_NAMEは継承する
118                        if defined? self::PARENT_COMPLETION_NAME
119                                const_set(:PARENT_COMPLETION_NAME, self::PARENT_COMPLETION_NAME.dup)
120                        else
121                                const_set(:PARENT_COMPLETION_NAME, ConstVariable.new)
122                        end
123                        const_set(:PARENT_COMPLETION, ConstVariable.new)  # Classかnil
124                        # デフォルトシンタックス: 文字列かnilかSymbol(:text, :blank)
125                        const_set(:DEFAULT_SYNTAX, ConstVariable.new)
126                        # multiline要素の終了マーク: 文字列かnil
127                        # MULTILINEは継承する
128                        if defined? self::MULTILINE
129                                const_set(:MULTILINE, self::MULTILINE.dup)
130                        else
131                                const_set(:MULTILINE, ConstVariable.new)
132                        end
133                        # closer要素か否か: trueかnil
134                        # CLOSERは継承する
135                        if defined? self::CLOSER
136                                const_set(:CLOSER, self::CLOSER.dup)
137                        else
138                                const_set(:CLOSER, ConstVariable.new)
139                        end
140                }
141                @@block_ordered.push(element)
142                element
143        end
144
145        # インライン要素を作成
146        def self.inline(name = nil, base_element = nil)
147                return @@block if !name
148                # FIXME extendの実装が、記述順に依存してしまっている
149                #       extend元の要素は先に定義されている必要がある
150                name = name.to_sym
151                if @@inline.include?(name)
152                        return @@inline[name]
153                end
154                if base_element
155                        if !@@inline.include?(base_element.to_sym)
156                                raise WikiForme::FormatLoadError, "Base element :#{base_element} is not declared: :#{name}"   # MSG
157                        end
158                        element = @@inline[name] = Class.new(@@inline[base_element.to_sym])
159                else
160                        element = @@inline[name] = Class.new(@@inline_base)
161                end
162                element.module_eval {
163                        const_set(:BLOCK_ELEMENT, false)
164                        const_set(:BLOCK_GROUP, false)
165                        const_set(:INLINE_ELEMENT, true)
166                        # 要素名: Symbol
167                        const_set(:NAME, name)
168                        # デフォルトシンタックス: 文字列の配列かnil
169                        const_set(:DEFAULT_SYNTAX, ConstVariable.new)
170                        # Regexp要素なら正規表現: Regexpかnil
171                        # REGEXPは継承する
172                        if defined? self::REGEXP
173                                const_set(:REGEXP, self::REGEXP.dup)
174                        else
175                                const_set(:REGEXP, ConstVariable.new)
176                        end
177                }
178                @@inline_ordered.push(element)
179                element
180        end
181
182        # 初期化メソッドを登録
183        def self.initialize(&proc)
184                @@init_procs.push(proc)
185        end
186
187        # デフォルトのルート要素を定義
188        def self.default_root=(sym)
189                @@default_root = sym.to_sym
190        end
191
192        # ルート要素のデフォルトテキストを定義
193        def self.default_root_text=(text)
194                @@default_root_text = text.to_s
195        end
196
197        # escape_*, suffix_* を指定
198        def self.method_missing(name, *args)
199                if name.to_s =~ /escape_([^=]+)=?/
200                        if args.length == 0
201                                if block_given?
202                                        @@escape_proc[$~[1].to_sym] = Proc.new {|rtext|
203                                                yield rtext
204                                        }
205                                end
206                                return @@escape_proc[$~[1].to_sym]
207                        elsif args.length == 1
208                                return @@escape_proc[$~[1].to_sym] = args[0]
209                        end
210                elsif name.to_s =~ /suffix_([^=]+)=?/
211                        if args.length == 0
212                                return @@suffix[$~[1].to_sym]
213                        elsif args.length == 1
214                                return @@suffix[$~[1].to_sym] = args[0].to_s
215                        end
216                end
217                raise NameError, "undefined method `#{name}' for #{self.inspect}:#{self.class}"
218        end
219end
220
221
222class WikiForme::ParserParameters
223        def _set(params, format, block, inline, block_base, inline_base)
224                @params = params || {}
225                @format = format
226                @block  = block
227                @inline = inline
228                @block_base = block_base
229                @inline_base = inline_base
230        end
231        attr_reader :params, :format, :block, :inline, :block_base, :inline_base
232        def [](key)
233                params[key]
234        end
235end
236Parser = WikiForme::ParserParameters.new
237
238
239module WikiForme
240        GROUP_NAME_REGEXP = /@(.*)/
241        FORMAT_BUNDLE_SUFFIX = '4me'
242        FORMAT_BUNDLE_REGEXP = /(.*)\.#{FORMAT_BUNDLE_SUFFIX}$/
243        FORMAT_FILE_REGEXP   = /\.rb$/
244        SYNTAX_FILE_REGEXP   = /\.yaml$/
245end
246
247
248class WikiForme::AssembleScope
249        def initialize(params, block_parser, inline_parser)
250                @params = params
251                @block_parser = block_parser
252                @inline_parser = inline_parser
253        end
254        attr_reader :params, :block_parser, :inline_parser
255end
256
257
258class WikiForme::ProcessScope
259        def initialize(format_type)
260                @format_type = format_type
261        end
262        attr_reader :format_type
263end
264
265
266module WikiForme; class FormatLoader
267        attr_reader :block, :group, :inline, :syntax
268        attr_reader :block_ordered, :inline_ordered
269        attr_reader :escape_proc, :default_root, :default_root_text
270        attr_reader :suffix
271        attr_reader :block_base, :inline_base
272        def initialize(root_bundle)
273                @block_base         = Class.new(BlockElement)
274                @inline_base        = Class.new(InlineElement)
275                Format.reset(@block_base, @inline_base)
276
277                # 記法バンドル内のsyntax.yamlファイルのリスト
278                @syntax_files = []
279
280                # 記法バンドルをロード
281                ::Parser._set(nil,
282                                                                        nil,
283                                                                        nil,
284                                                                        nil,
285                                                                        @block_base,
286                                                                        @inline_base )
287                load_format(root_bundle, 0)
288
289                # ブロック要素のリスト
290                @block,  @block_ordered  = Format.block_elements
291
292                # インライン要素のリスト
293                @inline, @inline_ordered = Format.inline_elements
294
295                # ブロック要素グループのリスト
296                @group                   = {}
297
298                # エスケープメソッド
299                @escape_proc             = Format.escape_proc
300
301                # 出力先ファイルのデフォルト拡張子
302                @suffix                  = Format.suffix
303
304                # デフォルトのルート要素
305                @default_root            = Format.default_root
306
307                # ルート要素のデフォルトテキスト
308                @default_root_text       = Format.default_root_text
309
310                # 初期化メソッド群
311                @init_procs              = Format.init_procs
312
313                resolve  # 要素名から要素クラスを解決
314
315                # デフォルト文法とカスタマイズ文法をマージ
316                @syntax = SyntaxLoader.new(self, @syntax_files)
317        end
318
319        # Assembleの前とProcessの前に呼ばれる
320        # Assembleの前はprocess_scope = nil
321        def set_scope(assemble_scope, process_scope = nil)
322                ::Parser._set(assemble_scope.params,
323                                                                        self,
324                                                                        assemble_scope.block_parser,
325                                                                        assemble_scope.inline_parser,
326                                                                        @block_base,
327                                                                        @inline_base )
328                if process_scope
329                        @block_base.format_type = process_scope.format_type
330                        @inline_base.format_type = process_scope.format_type
331                        assemble_scope.inline_parser.format_type = process_scope.format_type
332                else
333                        # assemble時にinit_procsを呼ぶ
334                        @init_procs.each{|proc| proc.call}
335                end
336        end
337
338        def supported_format
339                supported = {}
340                basemethods = Object.public_instance_methods
341                @block.each_value {|e|
342                        (e.public_instance_methods - basemethods).each {|m|
343                                if m =~ /process_(.*)/
344                                        supported[$~[1]] = true
345                                end
346                        }
347                }
348                @inline.each_value {|e|
349                        (e.public_instance_methods - basemethods).each {|m|
350                                if m =~ /(?:process_|expand_)(.*)/
351                                        supported[$~[1]] = true
352                                end
353                        }
354                }
355                supported.keys.map {|m| m.to_sym}
356        end
357
358private
359        # .4meディレクトリを再帰的に潜る
360        def load_format(dir, depth)
361                if depth > 6
362                        # FIXME エラーログ
363                        $stderr.puts "format directory depth limits", $@
364                        return
365                end
366                next_dirs = []
367                entries = Dir.entries(dir)
368                entries.delete('.')
369                entries.delete('..')
370                entries.sort!
371                entries.each {|fname|
372                        rpath = "#{dir}/#{fname}"
373                        ftype = File.stat(rpath).ftype
374                        if ftype == 'directory' && fname =~ FORMAT_BUNDLE_REGEXP
375                                next_dirs << rpath
376                        elsif ftype == 'file' && fname =~ FORMAT_FILE_REGEXP
377                                begin
378                                        in_dir(dir) { load(fname) }
379                                rescue
380                                        raise FormatLoadError, "#{$!} on file #{rpath}:#{$!.backtrace[1].split(':').last}"
381                                end
382                        elsif ftype == "file" && fname =~ SYNTAX_FILE_REGEXP
383                                @syntax_files.push(rpath)
384                        end
385                }
386                next_dirs.each {|ndir|
387                        load_format(ndir, depth+1)
388                }
389        end
390        # dirで実行
391        def in_dir(dir)
392                savedir = Dir.pwd
393                Dir.chdir(dir)
394                begin
395                        yield
396                ensure
397                        Dir.chdir(savedir)
398                end
399        end
400private
401        # CONTAIN, UNCONTAIN, GROUP, PARENT_COMPLETIONを解決する
402        def resolve
403                # GROUPを解決する
404                resolver = {}
405                group = @group
406                @block.each_value {|element|
407                        element::CONTAIN_NAMES.each {|contain|
408                                if contain.to_s =~ GROUP_NAME_REGEXP
409                                        element::CONTAIN.push( create_group(contain) )
410                                end
411                        }
412                        element::UNCONTAIN_NAMES.each {|contain|
413                                if contain.to_s =~ GROUP_NAME_REGEXP
414                                        element::UNCONTAIN.push( create_group(contain) )
415                                end
416                        }
417                        element::GROUP_NAMES.each {|contain|
418                                if contain.to_s =~ GROUP_NAME_REGEXP
419                                        create_group(contain)
420                                end
421                        }
422                }
423                @block.each_pair {|name, element|
424                        element::GROUP_NAMES.each {|s|
425                                if !group.include?(s)
426                                        raise FormatLoadError, "While resolving block element :#{name}, No such group :#{s}"
427                                end
428                                element.module_eval {
429                                        include group[s]
430                                }
431                        }
432                        resolver[name] = ElementResolver.new(element, resolver)
433                }
434                # 他の要素を解決する
435                resolver.each_value {|res|
436                        res.resolve1
437                }
438                resolver.each_value {|res|
439                        res.resolve2
440                }
441                @block
442        end
443        def create_group(name)
444                if !@group.include?(name)
445                        @group[name] = Module.new {
446                                const_set(:BLOCK_ELEMENT, false)
447                                const_set(:BLOCK_GROUP, true)
448                                const_set(:INLINE_ELEMENT, false)
449                                # 名前: Symbol
450                                const_set(:NAME, name)
451                        }
452                        @group[name]
453                else
454                        @group[name]
455                end
456        end
457        class ElementResolver
458                attr_reader :element
459                def initialize(element, resolver)
460                        @element = element
461                        @resolver = resolver
462                        @finish = false
463                        @working = false
464                end
465                def resolve1
466                        return @element if @finish
467                        # FIXME 以下のエラーメッセージ
468                        raise FormatLoadError, "Looped dependency is detected" if @working
469                        @working = true
470                        resolver = @resolver
471                        @element.module_eval {
472                                const_get(:CONTAIN_NAMES).each {|s|
473                                        next if s.to_s =~ GROUP_NAME_REGEXP
474                                        raise FormatLoadError, "While resolving block element :#{const_get(:NAME)}, no such element: :#{s}" if !resolver.include?(s)
475                                        const_get(:CONTAIN).push( resolver[s].element )
476                                }
477                                const_get(:UNCONTAIN_NAMES).each {|s|
478                                        next if s.to_s =~ GROUP_NAME_REGEXP
479                                        raise FormatLoadError, "While resolving block element :#{const_get(:NAME)}, no such element: :#{s}" if !resolver.include?(s)
480                                        const_get(:UNCONTAIN).push( resolver[s].element )
481                                }
482                        }
483                        @finish = true
484                        @element
485                end
486                def resolve2
487                        resolver = @resolver
488                        @element.module_eval {
489                                parent = const_get(:PARENT_COMPLETION_NAME)
490                                if parent.set?
491                                        raise FormatLoadError, "While resolving block element :#{const_get(:NAME)}, no such element: :#{parent.obj}" if !resolver.include?(parent.obj)
492                                        const_get(:PARENT_COMPLETION).replace( resolver[parent.obj].element )
493                                end
494                        }
495                        @element
496                end
497        end
498end; end
499
Note: See TracBrowser for help on using the browser.