require 'fileutils' require 'attributes' class Widget #--{{{ VERSION = '0.0.2' unless defined? Widget::VERSION def self.version() VERSION end class << self attribute('libdir'){ File.join RAILS_ROOT, 'lib', 'widgets' } def for_controller controller, name, *a, &b #--{{{ klass = Widget.load name returning( klass.allocate ) do |obj| obj.instance_eval do @controller = controller @defined_at = caller initialize *a, &b end end #--}}} end def load name #--{{{ lib = File.join libdir, "#{ name }.rb" RAILS_ENV == "development" ? Kernel.load(lib) : Kernel.require(lib) begin const_for name rescue raise "wtf? Widget '#{ name }' was not defined in #{ lib }" end #--}}} end def const_for name #--{{{ consts = name.camelize.split %r/::/ klass = Widget consts.each{|const| klass = klass.const_get(const)} klass #--}}} end end FileUtils.mkdir_p libdir rescue nil attribute('name'){ self.class.name.gsub(%r/^.*Widget::/, '').underscore } attribute 'controller' attribute 'template' attribute 'show' => true attribute 'content' => '' attribute 'in_render' => false attribute 'defined_at' def inspect #--{{{ "#{ name }=#{ super }" #--}}} end def template #--{{{ "widgets/#{ name }" #--}}} end def configure options = {} , &block #--{{{ options.to_options! has_attribute = attributes.inject({}){|h,k| h.update k.to_s => true, k.to_sym => true} options.each do |k,v| if has_attribute[k] send k, v else attribute k => v end end instance_eval &block if block self #--}}} end def inherited_attributes #--{{{ ancestors = self.class.ancestors offset = ancestors.index Widget ancestors = ancestors[0, offset + 1].compact if offset ancestors.reverse.map do |ancestor| ancestor.attributes end.flatten.uniq #--}}} end def to_hash #--{{{ inherited_attributes.inject({}){|h,a| h.update a.to_sym => send(a)} #--}}} end def render options = {} #--{{{ raise "recursive render of #{ name }" if in_render? in_render true begin return '' unless show? widget = self template = widget.template locals = widget.to_hash.update options.to_options! locals[:widget] = locals['widget'] = locals[:w] = locals['w'] = widget controller.instance_eval do render_to_string :file => template, :use_full_path => true, :locals => locals end ensure in_render false end #--}}} end alias_method "to_s", "render" def with_content #--{{{ content yield render #--}}} end alias_method "for_content", "with_content" def [] k #--{{{ send k #--}}} end def []= k, v #--{{{ send k, v #--}}} end def widget name, *a, &b #--{{{ controller.widget name, *a, &b #--}}} end def self.class_factory path, &block #--{{{ classes = path.camelize.split %r/::/ klass = Widget classes.each do |const| subklass = Class.new Widget klass.module_eval{ remove_const const if const_defined? const const_set const, subklass } klass = subklass end klass.module_eval &block klass #--}}} end #--}}} end if defined?(Rails) class ActionController::Base def widget name = '', *a, &b Widget.for_controller self, name, *a, &b end helper_method 'widget' end end def Widget(*a, &b) Widget.class_factory(*a, &b) end Widgetz = Widget unless defined? Widgetz def Widgetz(*a, &b) Widgetz.class_factory(*a, &b) end