merb 0.3.4 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/README +206 -197
  2. data/Rakefile +12 -21
  3. data/bin/merb +1 -1
  4. data/examples/skeleton/Rakefile +6 -20
  5. data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
  6. data/examples/skeleton/dist/conf/database.yml +23 -0
  7. data/examples/skeleton/dist/conf/environments/development.rb +1 -0
  8. data/examples/skeleton/dist/conf/environments/production.rb +1 -0
  9. data/examples/skeleton/dist/conf/environments/test.rb +1 -0
  10. data/examples/skeleton/dist/conf/merb.yml +32 -28
  11. data/examples/skeleton/dist/conf/merb_init.rb +16 -13
  12. data/examples/skeleton/dist/conf/router.rb +9 -9
  13. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
  14. data/lib/merb.rb +23 -18
  15. data/lib/merb/caching/fragment_cache.rb +3 -7
  16. data/lib/merb/caching/store/memcache.rb +20 -0
  17. data/lib/merb/core_ext/merb_array.rb +0 -0
  18. data/lib/merb/core_ext/merb_class.rb +44 -4
  19. data/lib/merb/core_ext/merb_enumerable.rb +43 -1
  20. data/lib/merb/core_ext/merb_hash.rb +200 -122
  21. data/lib/merb/core_ext/merb_kernel.rb +2 -0
  22. data/lib/merb/core_ext/merb_module.rb +41 -0
  23. data/lib/merb/core_ext/merb_numeric.rb +57 -5
  24. data/lib/merb/core_ext/merb_object.rb +172 -6
  25. data/lib/merb/generators/merb_app/merb_app.rb +15 -9
  26. data/lib/merb/merb_abstract_controller.rb +193 -0
  27. data/lib/merb/merb_constants.rb +26 -1
  28. data/lib/merb/merb_controller.rb +143 -234
  29. data/lib/merb/merb_dispatcher.rb +28 -20
  30. data/lib/merb/merb_drb_server.rb +2 -3
  31. data/lib/merb/merb_exceptions.rb +194 -49
  32. data/lib/merb/merb_handler.rb +34 -26
  33. data/lib/merb/merb_mail_controller.rb +200 -0
  34. data/lib/merb/merb_mailer.rb +33 -13
  35. data/lib/merb/merb_part_controller.rb +42 -0
  36. data/lib/merb/merb_plugins.rb +293 -0
  37. data/lib/merb/merb_request.rb +6 -4
  38. data/lib/merb/merb_router.rb +99 -65
  39. data/lib/merb/merb_server.rb +65 -21
  40. data/lib/merb/merb_upload_handler.rb +2 -1
  41. data/lib/merb/merb_view_context.rb +36 -15
  42. data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
  43. data/lib/merb/mixins/controller_mixin.rb +67 -28
  44. data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
  45. data/lib/merb/mixins/form_control_mixin.rb +280 -42
  46. data/lib/merb/mixins/render_mixin.rb +127 -45
  47. data/lib/merb/mixins/responder_mixin.rb +5 -7
  48. data/lib/merb/mixins/view_context_mixin.rb +260 -94
  49. data/lib/merb/session.rb +23 -0
  50. data/lib/merb/session/merb_ar_session.rb +28 -16
  51. data/lib/merb/session/merb_mem_cache_session.rb +108 -0
  52. data/lib/merb/session/merb_memory_session.rb +65 -20
  53. data/lib/merb/template/erubis.rb +22 -13
  54. data/lib/merb/template/haml.rb +5 -16
  55. data/lib/merb/template/markaby.rb +5 -3
  56. data/lib/merb/template/xml_builder.rb +17 -5
  57. data/lib/merb/test/merb_fake_request.rb +63 -0
  58. data/lib/merb/test/merb_multipart.rb +58 -0
  59. data/lib/tasks/db.rake +2 -0
  60. data/lib/tasks/merb.rake +20 -8
  61. metadata +24 -25
  62. data/examples/skeleton.tar +0 -0
data/Rakefile CHANGED
@@ -15,11 +15,11 @@ include FileUtils
15
15
 
16
16
 
17
17
  NAME = "merb"
18
- VERS = "0.3.4"
18
+ VERS = "0.3.7"
19
19
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
20
20
 
21
21
  setup_clean [ "pkg", "lib/*.bundle", "*.gem",
22
- "doc", ".config", "examples/sample_app/dist/public/files/**/*", 'examples/sample_app/log/*']
22
+ "doc", ".config", "examples/sample_app/dist/public/files/**/*", 'examples/sample_app/log/*', 'coverage']
23
23
 
24
24
 
25
25
  desc "Packages up Merb."
@@ -60,8 +60,6 @@ spec = Gem::Specification.new do |s|
60
60
  s.add_dependency('erubis')
61
61
  s.add_dependency('json')
62
62
  s.add_dependency('mime-types')
63
- s.add_dependency('xml-simple')
64
- s.add_dependency('archive-tar-minitar')
65
63
  s.required_ruby_version = '>= 1.8.4'
66
64
 
67
65
  s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{bin,test,lib,examples}/**/*")
@@ -91,20 +89,6 @@ task :doc_rforge do
91
89
  sh %{scp -r -p doc/* ezmobius@rubyforge.org:/var/www/gforge-projects/merb}
92
90
  end
93
91
 
94
-
95
- desc "Pack skeleton app as a tar.gz file"
96
- task :skeleton do
97
- require 'archive/tar/minitar'
98
- include Archive::Tar
99
- Find.find(File.join(__DIR__,'examples/skeleton')) do |f|
100
- FileUtils.rm_rf(f) if /\.svn$/ =~ f
101
- end
102
- File.open(File.join(__DIR__,'examples/skeleton.tar'), 'wb') do |tar|
103
- Dir.chdir 'examples/skeleton'
104
- Minitar.pack('.', tar, true)
105
- end
106
- end
107
-
108
92
  desc 'Run unit tests'
109
93
  Rake::TestTask.new('test_unit') do |t|
110
94
  t.libs << 'test'
@@ -135,13 +119,20 @@ desc "Run all specs"
135
119
  Spec::Rake::SpecTask.new('specs') do |t|
136
120
  t.spec_opts = ["--format", "specdoc"]
137
121
  t.libs = ['lib', 'server/lib' ]
138
- t.spec_files = FileList['specs/**/*_spec.rb']
122
+ t.spec_files = Dir['specs/**/*_spec.rb'].sort
123
+ end
124
+
125
+ desc "Run all specs output html"
126
+ Spec::Rake::SpecTask.new('specs_html') do |t|
127
+ t.spec_opts = ["--format", "html"]
128
+ t.libs = ['lib', 'server/lib' ]
129
+ t.spec_files = Dir['specs/**/*_spec.rb'].sort
139
130
  end
140
131
 
141
132
  desc "RCov"
142
133
  Spec::Rake::SpecTask.new('rcov') do |t|
143
134
  t.spec_opts = ["--format", "specdoc"]
144
- t.spec_files = FileList['specs/**/*_spec.rb']
135
+ t.spec_files = Dir['specs/**/*_spec.rb'].sort
145
136
  t.libs = ['lib', 'server/lib' ]
146
137
  t.rcov = true
147
138
  end
@@ -166,4 +157,4 @@ end
166
157
  desc "Add new files to subversion"
167
158
  task :svn_add do
168
159
  system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
169
- end
160
+ end
data/bin/merb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/local/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
3
  require 'rubygems'
4
4
  require 'merb/merb_server'
@@ -1,10 +1,13 @@
1
1
  require 'rake'
2
2
  require 'rake/rdoctask'
3
3
  require 'rake/testtask'
4
- require 'code_statistics'
5
4
  require 'fileutils'
6
5
  require 'rubygems'
7
- require 'merb'
6
+ if File.directory?( File.join(File.dirname(__FILE__), "dist/framework"))
7
+ require File.join(File.dirname(__FILE__), "dist/framework/merb")
8
+ else
9
+ require 'merb'
10
+ end
8
11
  require MERB_FRAMEWORK_ROOT+'/merb_tasks'
9
12
  MERB_ROOT = File.dirname(__FILE__)
10
13
  include FileUtils
@@ -55,23 +58,6 @@ task :aok do
55
58
  sh %{rake spec}
56
59
  end
57
60
 
58
- ##############################################################################
59
- # Statistics
60
- ##############################################################################
61
-
62
- STATS_DIRECTORIES = [
63
- %w(Code lib/),
64
- %w(Unit\ tests test/unit),
65
- %w(Functional\ tests test/functional)
66
- ].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
67
-
68
- desc "Report code statistics (KLOCs, etc) from the application"
69
- task :stats do
70
- #require 'extra/stats'
71
- verbose = true
72
- CodeStatistics.new(*STATS_DIRECTORIES).to_s
73
- end
74
-
75
61
  ##############################################################################
76
62
  # SVN
77
63
  ##############################################################################
@@ -79,4 +65,4 @@ end
79
65
  desc "Add new files to subversion"
80
66
  task :svn_add do
81
67
  system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
82
- end
68
+ end
@@ -0,0 +1 @@
1
+ <%= catch_content :layout %>
@@ -0,0 +1,23 @@
1
+ development:
2
+ adapter: mysql
3
+ database: sample_development
4
+ username: teh_user
5
+ password: secrets
6
+ host: localhost
7
+ socket: /tmp/mysql.sock
8
+
9
+ test:
10
+ adapter: mysql
11
+ database: sample_test
12
+ username: teh_user
13
+ password: secrets
14
+ host: localhost
15
+ socket: /tmp/mysql.sock
16
+
17
+ production:
18
+ adapter: mysql
19
+ database: sample_production
20
+ username: teh_user
21
+ password: secrets
22
+ host: /tmp/mysql.sock
23
+
@@ -0,0 +1 @@
1
+ puts "Loaded DEVELOPMENT Environment..."
@@ -0,0 +1 @@
1
+ puts "Loaded PRODUCTION Environment..."
@@ -0,0 +1 @@
1
+ puts "Loaded TEST Environment..."
@@ -1,53 +1,57 @@
1
1
  ---
2
- # hostname or IP to bind to.
2
+ # Hostname or IP address to bind to.
3
3
  :host: 127.0.0.1
4
4
 
5
- # port merb runs on or starting port for merb cluster.
5
+ # Port merb runs on or starting port for merb cluster.
6
6
  :port: "4000"
7
7
 
8
- # in development mode your controler classes get reloaded every request
9
- # and templates are parsed each time and not cached
10
- # in production mode templates are cached, as well as all your classes
8
+ # In development mode your controller classes get reloaded on every request,
9
+ # and templates are parsed each time and not cached. In production mode
10
+ # templates are cached, as well as all your classes
11
11
  :environment: development
12
12
 
13
- # uncomment for memory sessions. This only works when
14
- # you are running 1 merb at a time. ANd sessions do not persist
15
- # between restarts.
13
+ # Uncomment for memory sessions. This only works when you are running 1 merb
14
+ # at a time. And sessions do not persist between restarts.
16
15
  # :memory_session: true
17
16
 
18
- # This turns on the ActiveRecord sessions with rails parasite
19
- # mode if active_support gem is installed. Skeleton app comes with a
20
- # migration to create the sessions table. Or you can point merb to
21
- # the same sessions table that your rails app uses to share sessions
22
- # between merb and rails.
17
+ # Turn on memcached sessions.
18
+ # Requires these lines in merb_init.rb (and a running memcached server):
19
+ # require 'memcache_util'
20
+ # CACHE = MemCache.new('127.0.0.1:11211', { :namespace => 'my_app' })
21
+ # :mem_cache_session: true
22
+
23
+ # This turns on the ActiveRecord sessions with rails parasite mode if
24
+ # active_support gem is installed. Skeleton app comes with a migration to
25
+ # create the sessions table. Or you can point merb to the same sessions
26
+ # table that your rails app uses to share sessions between merb and rails.
23
27
  :sql_session: true
28
+ :log_level: debug
24
29
 
25
- # uncomment to use the merb upload progress
30
+ # Uncomment to use the merb upload progress
26
31
  #:config: dist/conf/upload.conf
27
32
 
28
- # uncomment to cache templates in dev mode.
29
- # templates are cached automatically in production mode.
33
+ # Uncomment to cache templates in dev mode. Templates are cached
34
+ # automatically in production mode.
30
35
  #:cache_templates: true
31
36
 
32
- # uncomment and set this is you want to run a drb
33
- # server for upload progress or other drb services.
37
+ # Uncomment and set this if you want to run a drb server for upload progress
38
+ # or other drb services.
34
39
  #:drb_server_port: 32323
35
40
 
36
- # If you want to protect some or all of your app with
37
- # HTTP basic auth then uncomment the folowing and fill
38
- # in your credentials you want it to use. Then you need
39
- # to set a before filter in a controller:
40
- # before :basic_authentication
41
+ # If you want to protect some or all of your app with HTTP basic auth then
42
+ # uncomment the following and fill in your credentials you want it to use.
43
+ # You will then need to set a 'before' filter in a controller. For example:
44
+ # before :basic_authentication
41
45
  #:basic_auth:
42
46
  # :username: ezra
43
47
  # :password: test
44
48
  # :domain: localhost
45
49
 
46
- # uncomment this if you want merb to daemonize when you start it
47
- # you can also just use merb -d for the same effect. Don't uncomment
48
- # this if you use the cluster option
50
+ # Uncomment this if you want merb to daemonize when you start it. You can also
51
+ # just use merb -d for the same effect. Don't uncomment this if you use the
52
+ # cluster option.
49
53
  #:daemonize: true
50
54
 
51
- # uncomment this to set the number of members in your merb cluster
52
- # don't set this and :daemonize: at the same time.
55
+ # Uncomment this to set the number of members in your merb cluster. Don't set
56
+ # this and :daemonize: at the same time.
53
57
  #:cluster: 3
@@ -2,20 +2,23 @@ puts "merb init called"
2
2
  require 'active_record'
3
3
  ActiveRecord::Base.verification_timeout = 14400
4
4
  ActiveRecord::Base.logger = MERB_LOGGER
5
- Dir[DIST_ROOT+"/app/helpers/*.rb"].each { |m| require m }
5
+
6
6
  require DIST_ROOT+"/app/controllers/application.rb"
7
- Dir[DIST_ROOT+"/app/controllers/*.rb"].each { |m| require m }
8
- Dir[DIST_ROOT+"/app/models/*.rb"].each { |m| require m }
9
- Dir[DIST_ROOT+"/lib/**/*.rb"].each { |m| require m }
10
- Dir[DIST_ROOT+"/plugins/*/init.rb"].each { |m| require m }
7
+ Dir[DIST_ROOT+"/app/controllers/*.rb"].each{ |m| require m }
8
+ Dir[DIST_ROOT+"/app/helpers/*.rb"].each { |m| require m }
9
+ Dir[DIST_ROOT+"/app/models/*.rb"].each { |m| require m }
10
+ Dir[DIST_ROOT+"/app/mailers/*.rb"].each { |m| require m }
11
+ Dir[DIST_ROOT+"/lib/*/lib/*.rb"].each { |m| require m }
12
+ Dir[DIST_ROOT+"/lib/*/bin/*.rb"].each { |m| require m }
13
+ Dir[DIST_ROOT+"/plugins/*/init.rb"].each { |m| require m }
14
+
15
+ #Get Database Config
16
+ puts "Connecting to database..."
17
+ conn_options = YAML::load(Erubis::Eruby.new(IO.read("#{DIST_ROOT}/conf/database.yml")).result)
18
+ ActiveRecord::Base.establish_connection conn_options["#{MERB_ENV}"]
11
19
 
12
- # set your db info here
13
- ActiveRecord::Base.establish_connection(
14
- :adapter => 'mysql',
15
- :username => 'root',
16
- :password => 'xxxxx',
17
- :database => 'merb'
18
- )
20
+ #Get Environment File
21
+ require "#{DIST_ROOT}/conf/environments/#{MERB_ENV}"
19
22
 
20
23
  # add your own ruby code here for app specific stuff. This file gets loaded
21
- # after the framework is loaded.
24
+ # after the framework is loaded.
@@ -1,13 +1,13 @@
1
1
  # Merb::RouteMatcher is the request routing mapper for the merb framework.
2
- # You can define placeholder parts of the url with the :smbol notation.
3
- # so r.add '/foo/:bar/baz/:id', :class => 'Bar', :method => 'foo'
4
- # will match against a request to /foo/123/baz/456. It will then
5
- # use the class Bar as your merb controller and call the foo method on it.
6
- # the foo method will recieve a hash with {:bar => '123', :id => '456'}
7
- # as the content. So the :placeholders sections of your routes become
8
- # a hash of arguments to your controller methods.
9
- # The default route is installed
10
-
2
+ # You can define placeholder parts of the url with the :symbol notation. For
3
+ # example:
4
+ #
5
+ # r.add '/admin/:email/users/:id', :controller => 'admin_users', :action => 'foo'
6
+ #
7
+ # will match against a request to /admin/me@gmail.com/users/456. It will then
8
+ # use the class AdminUsers as your merb controller and call the 'foo' method
9
+ # on it. The 'foo' method will be able to access the :email and :id values via
10
+ # the 'params' hash, e.g. 'params[:email]' will return 'me@gmail.com'.
11
11
 
12
12
  puts "Compiling routes.."
13
13
  Merb::Router.prepare do |r|
@@ -1,8 +1,8 @@
1
1
  class AddSessionsTable < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :sessions, :force => true do |t|
4
- t.column :session_id, :string, :limit => 32
5
- t.column :created_at, :datetime
4
+ t.column :session_id, :string, :limit => 32
5
+ t.column :created_at, :datetime
6
6
  t.column :data, :text
7
7
  end
8
8
  add_index "sessions", ["session_id"], :name => "session_id_index"
@@ -15,7 +15,7 @@ require 'json'
15
15
 
16
16
 
17
17
  module Merb
18
- VERSION='0.3.4' unless defined?VERSION
18
+ VERSION='0.3.7' unless defined?VERSION
19
19
  class Server
20
20
  class << self
21
21
  def config
@@ -50,7 +50,7 @@ MERB_FRAMEWORK_ROOT = __DIR__
50
50
 
51
51
  MERB_ROOT = Merb::Server.merb_root || Dir.pwd
52
52
  DIST_ROOT = Merb::Server.dist_root || Dir.pwd+'/dist'
53
- MERB_ENV = Merb::Server.config[:environment]
53
+ MERB_ENV = Merb::Server.config[:environment].nil? ? 'development' : Merb::Server.config[:environment]
54
54
  MERB_VIEW_ROOT = MERB_ROOT / "dist/app/views"
55
55
 
56
56
  logpath = $TESTING ? "/tmp/merb_test.log" : "#{MERB_ROOT}/log/merb.#{Merb::Server.port}.log"
@@ -72,16 +72,6 @@ MERB_LOGGER.level = case (Merb::Server.log_level.downcase rescue '')
72
72
  else
73
73
  Logger::INFO
74
74
  end
75
-
76
- module Mongrel::Const
77
- HTTP_COOKIE = 'HTTP_COOKIE'.freeze
78
- QUERY_STRING = 'QUERY_STRING'.freeze
79
- APPLICATION_JSON = 'application/json'.freeze
80
- TEXT_JSON = 'text/x-json'.freeze
81
- APPLICATION_XML = 'application/xml'.freeze
82
- TEXT_XML = 'text/xml'.freeze
83
- UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
84
- end
85
75
 
86
76
  lib = File.join(__DIR__, 'merb')
87
77
  Dir.entries(lib).sort.select{|f| f !~ /merb\/session\// }.each {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
@@ -89,9 +79,10 @@ Dir.entries(lib).sort.select{|f| f !~ /merb\/session\// }.each {|fn| require Fil
89
79
  require File.join(__DIR__, 'merb/vendor/paginator/paginator')
90
80
 
91
81
  class Merb::Controller
82
+ lib = File.join(__DIR__, 'merb')
92
83
  if Merb::Server.memory_session
93
- require "merb/session/merb_memory_session"
94
- Merb::MemorySession.setup
84
+ require lib + "/session/merb_memory_session"
85
+ Merb::MemorySessionContainer.setup
95
86
  include ::Merb::SessionMixin
96
87
  puts "memory session mixed in"
97
88
  end
@@ -104,14 +95,28 @@ class Merb::Controller
104
95
  rescue LoadError
105
96
  puts "Rails session compatibilty disabled. If you need this then install the actionpack gem"
106
97
  end
107
- require "merb/session/merb_ar_session"
98
+ require lib + "/session/merb_ar_session"
99
+ include ::Merb::SessionMixin
100
+ end
101
+
102
+ if Merb::Server.mem_cache_session
103
+ require lib + "/session/merb_mem_cache_session"
108
104
  include ::Merb::SessionMixin
109
- Thread.new{ loop{ sleep(60*60); ActiveRecord::Base.verify_active_connections! } }.priority = -10
105
+ puts "MemCache session mixed in"
110
106
  end
111
107
 
112
108
  if Merb::Server.basic_auth
113
- require "merb/mixins/basic_authentication_mixin"
109
+ require lib + "/mixins/basic_authentication_mixin"
114
110
  include ::Merb::Authentication
115
111
  puts "Basic Authentication mixed in"
116
112
  end
117
- end
113
+ end
114
+
115
+ if defined? ActiveRecord
116
+ Thread.new{ loop{ sleep(60*60); ActiveRecord::Base.verify_active_connections! } }.priority = -10
117
+ end
118
+
119
+ if $TESTING
120
+ test_files = File.join(lib, 'test', '*.rb')
121
+ Dir[test_files].each { |file| require file }
122
+ end
@@ -20,17 +20,13 @@ module Merb
20
20
  end
21
21
 
22
22
  def determine_cache_store
23
- case ::Merb::Server.cache_store
24
- when "file", :file
23
+ if ::Merb::Server.cache_store.to_s == "file"
25
24
  require 'merb/caching/store/file_cache'
26
25
  ::Merb::Caching::Store::FileCache.new
27
- when "memory", :memory
28
- require 'merb/caching/store/memory_cache'
29
- ::Merb::Caching::Store::MemoryCache.new
30
26
  else
31
27
  require 'merb/caching/store/memory_cache'
32
- ::Merb::Caching::Store::MemoryCache.new
33
- end
28
+ ::Merb::Caching::Store::MemoryCache.new
29
+ end
34
30
  end
35
31
  end
36
32
  end
@@ -0,0 +1,20 @@
1
+ module Merb
2
+ module Caching
3
+ module MemcachedStore
4
+
5
+
6
+ def get(name)
7
+ ::Cache.get("fragment:#{name}")
8
+ end
9
+
10
+ def put(name, content = nil)
11
+ ::Cache.put("fragment:#{name}", content)
12
+ content
13
+ end
14
+
15
+ def expire_fragment(name)
16
+ ::Cache.delete(name)
17
+ end
18
+ end
19
+ end
20
+ end
File without changes
@@ -1,12 +1,52 @@
1
- # Retain for backward compatibility. Methods are now included in Class.
2
- module ClassInheritableAttributes # :nodoc:
3
- end
4
-
5
1
  # Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
6
2
  # their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
7
3
  # to, for example, an array without those additions being shared with either their parent, siblings, or
8
4
  # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
9
5
  class Class # :nodoc:
6
+ def cattr_reader(*syms)
7
+ syms.flatten.each do |sym|
8
+ next if sym.is_a?(Hash)
9
+ class_eval(<<-EOS, __FILE__, __LINE__)
10
+ unless defined? @@#{sym}
11
+ @@#{sym} = nil
12
+ end
13
+
14
+ def self.#{sym}
15
+ @@#{sym}
16
+ end
17
+
18
+ def #{sym}
19
+ @@#{sym}
20
+ end
21
+ EOS
22
+ end
23
+ end
24
+
25
+ def cattr_writer(*syms)
26
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
27
+ syms.flatten.each do |sym|
28
+ class_eval(<<-EOS, __FILE__, __LINE__)
29
+ unless defined? @@#{sym}
30
+ @@#{sym} = nil
31
+ end
32
+
33
+ def self.#{sym}=(obj)
34
+ @@#{sym} = obj
35
+ end
36
+
37
+ #{"
38
+ def #{sym}=(obj)
39
+ @@#{sym} = obj
40
+ end
41
+ " unless options[:instance_writer] == false }
42
+ EOS
43
+ end
44
+ end
45
+
46
+ def cattr_accessor(*syms)
47
+ cattr_reader(*syms)
48
+ cattr_writer(*syms)
49
+ end
10
50
  def class_inheritable_reader(*syms)
11
51
  syms.each do |sym|
12
52
  next if sym.is_a?(Hash)