rubyexts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/CHANGELOG +2 -0
  2. data/LICENSE +20 -0
  3. data/README.textile +0 -0
  4. data/Rakefile +79 -0
  5. data/TODO.txt +6 -0
  6. data/lib/rubyexts.rb +102 -0
  7. data/lib/rubyexts/array.rb +20 -0
  8. data/lib/rubyexts/attribute.rb +151 -0
  9. data/lib/rubyexts/capture_io.rb +32 -0
  10. data/lib/rubyexts/class.rb +177 -0
  11. data/lib/rubyexts/colored_string.rb +103 -0
  12. data/lib/rubyexts/enumerable.rb +30 -0
  13. data/lib/rubyexts/file.rb +84 -0
  14. data/lib/rubyexts/hash.rb +180 -0
  15. data/lib/rubyexts/kernel.rb +91 -0
  16. data/lib/rubyexts/module.rb +53 -0
  17. data/lib/rubyexts/object.rb +56 -0
  18. data/lib/rubyexts/object_space.rb +16 -0
  19. data/lib/rubyexts/os.rb +89 -0
  20. data/lib/rubyexts/platform.rb +27 -0
  21. data/lib/rubyexts/random.rb +25 -0
  22. data/lib/rubyexts/string.rb +92 -0
  23. data/lib/rubyexts/time.rb +15 -0
  24. data/lib/rubyexts/time_dsl.rb +62 -0
  25. data/lib/rubyexts/try_dup.rb +43 -0
  26. data/lib/rubyexts/unique_array.rb +16 -0
  27. data/rubyexts.gemspec +36 -0
  28. data/script/spec +12 -0
  29. data/spec/rubyexts/array_spec.rb +19 -0
  30. data/spec/rubyexts/attribute_spec.rb +37 -0
  31. data/spec/rubyexts/class_spec.rb +1 -0
  32. data/spec/rubyexts/cli_spec.rb +0 -0
  33. data/spec/rubyexts/colored_string_spec.rb +9 -0
  34. data/spec/rubyexts/enumerable_spec.rb +52 -0
  35. data/spec/rubyexts/file_spec.rb +78 -0
  36. data/spec/rubyexts/hash_spec.rb +91 -0
  37. data/spec/rubyexts/kernel_spec.rb +9 -0
  38. data/spec/rubyexts/module_spec.rb +0 -0
  39. data/spec/rubyexts/object_space_spec.rb +17 -0
  40. data/spec/rubyexts/object_spec.rb +57 -0
  41. data/spec/rubyexts/os_spec.rb +121 -0
  42. data/spec/rubyexts/platform_spec.rb +0 -0
  43. data/spec/rubyexts/random_spec.rb +31 -0
  44. data/spec/rubyexts/string_spec.rb +23 -0
  45. data/spec/rubyexts/time_dsl_spec.rb +88 -0
  46. data/spec/rubyexts/time_spec.rb +11 -0
  47. data/spec/rubyexts/unique_array_spec.rb +0 -0
  48. data/spec/rubyexts_spec.rb +182 -0
  49. data/spec/spec.opts +5 -0
  50. data/spec/spec_helper.rb +16 -0
  51. metadata +104 -0
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ = Version 0.0.1
2
+ * Imported from Rango
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jakub Šťastný aka Botanicus
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env rake1.9
2
+ # encoding: utf-8
3
+
4
+ # http://support.runcoderun.com/faqs/builds/how-do-i-run-rake-with-trace-enabled
5
+ Rake.application.options.trace = true
6
+
7
+ task :setup => ["submodules:init"]
8
+
9
+ namespace :submodules do
10
+ desc "Init submodules"
11
+ task :init do
12
+ sh "git submodule init"
13
+ end
14
+
15
+ desc "Update submodules"
16
+ task :update do
17
+ Dir["vendor/*"].each do |path|
18
+ if File.directory?(path) && File.directory?(File.join(path, ".git"))
19
+ Dir.chdir(path) do
20
+ puts "=> #{path}"
21
+ puts %x[git reset --hard]
22
+ puts %x[git fetch]
23
+ puts %x[git reset origin/master --hard]
24
+ puts
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ task :gem do
32
+ sh "gem build rubyexts.gemspec"
33
+ end
34
+
35
+ namespace :gem do
36
+ task :prerelease do
37
+ require_relative "lib/rubyexts"
38
+ gemspec = Dir["*.gemspec"].first
39
+ content = File.read(gemspec)
40
+ prename = "#{gemspec.split(".").first}.pre.gemspec"
41
+ version = RubyExts::VERSION.sub(/^(\d+)\.(\d+)\.\d+$/) { "#$1.#{$1.to_i + 1}" }
42
+ File.open(prename, "w") do |file|
43
+ file.puts(content.gsub(/(\w+::VERSION)/, "'#{version}.pre'"))
44
+ end
45
+ sh "gem build #{prename}"
46
+ rm prename
47
+ end
48
+ end
49
+
50
+ desc "Release new version of rubyexts"
51
+ task release: ["release:tag", "release:gemcutter"]
52
+
53
+ namespace :release do
54
+ desc "Create Git tag"
55
+ task :tag do
56
+ require_relative "lib/rubyexts"
57
+ puts "Creating new git tag #{RubyExts::VERSION} and pushing it online ..."
58
+ sh "git tag -a -m 'Version #{RubyExts::VERSION}' #{RubyExts::VERSION}"
59
+ sh "git push --tags"
60
+ puts "Tag #{RubyExts::VERSION} was created and pushed to GitHub."
61
+ end
62
+
63
+ desc "Push gem to Gemcutter"
64
+ task :gemcutter do
65
+ puts "Pushing to Gemcutter ..."
66
+ sh "gem push #{Dir["*.gem"].last}"
67
+ end
68
+
69
+ desc "Create and push prerelease gem"
70
+ task :pre => ["gem:prerelease", :gemcutter]
71
+ end
72
+
73
+ desc "Run specs"
74
+ task :default => :setup do
75
+ rubylib = (ENV["RUBYLIB"] || String.new).split(":")
76
+ libdirs = Dir["vendor/*/lib"]
77
+ ENV["RUBYLIB"] = (libdirs + rubylib).join(":")
78
+ exec "./script/spec --options spec/spec.opts spec"
79
+ end
data/TODO.txt ADDED
@@ -0,0 +1,6 @@
1
+ missing specs:
2
+ - attribute
3
+ - class (copy from extlib)
4
+
5
+ rewrite/refactor:
6
+ - colored string
data/lib/rubyexts.rb ADDED
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+
3
+ module RubyExts
4
+ VERSION ||= "0.0.1"
5
+ end
6
+
7
+ module Kernel
8
+ # Require all files matching given glob relative to $:
9
+ #
10
+ # @author Botanicus
11
+ # @since 0.0.3
12
+ # @param [String] library Glob of files to require
13
+ # @param [Hash] params Optional parameters.
14
+ # @option params [String, Array<String>] :exclude File or list of files or globs relative to base directory
15
+ # @raise [LoadError] If base directory doesn't exist
16
+ # @raise [ArgumentError] If first argument isn't a glob
17
+ # @return [Array<String>] List of successfully loaded files
18
+ # @example
19
+ # acquire "lib/*"
20
+ # acquire "lib/**/*", exclude: "**/*_spec.rb"
21
+ # acquire "lib/**/*", exclude: ["**/*_spec.rb", "lib/init.rb"]
22
+ def acquire(glob, params = Hash.new)
23
+ base, glob = get_base_and_glob(glob)
24
+ $:.compact.find do |path|
25
+ fullpath = File.expand_path(File.join(path, base))
26
+ if File.directory?(fullpath)
27
+ return __acquire__(fullpath, glob, params.merge(soft: true))
28
+ end
29
+ end
30
+ raise LoadError, "Directory #{base} doesn't exist in $:"
31
+ end
32
+
33
+ def acquire!(glob, params = Hash.new)
34
+ self.acquire(glob, params.merge(soft: false))
35
+ end
36
+
37
+ # Require all files matching given glob relative to current file
38
+ #
39
+ # @author Botanicus
40
+ # @since 0.0.3
41
+ # @param [String] library Glob of files to require
42
+ # @param [Hash] params Optional parameters.
43
+ # @option params [String, Array<String>] :exclude File or list of files or globs relative to base directory
44
+ # @raise [LoadError] If base directory doesn't exist
45
+ # @raise [ArgumentError] If first argument isn't a glob
46
+ # @return [Array<String>] List of successfully loaded files
47
+ # @example
48
+ # acquire "lib/*"
49
+ # acquire "lib/**/*", exclude: "**/*_spec.rb"
50
+ # acquire "lib/**/*", exclude: ["**/*_spec.rb", "lib/init.rb"]
51
+ def acquire_relative(glob, params = Hash.new)
52
+ base, glob = get_base_and_glob(glob)
53
+ path = File.dirname(caller[0].split(":").first)
54
+ full = File.expand_path(File.join(path, base))
55
+ raise LoadError, "Directory #{base} doesn't exist in #{path}" unless File.directory?(full)
56
+ return __acquire__(full, glob, params.merge(soft: true))
57
+ end
58
+
59
+ def acquire_relative!(glob, params = Hash.new)
60
+ self.acquire_relative(glob, params.merge(soft: false))
61
+ end
62
+
63
+ def load_relative(file)
64
+ path = File.dirname(caller[0].split(":").first)
65
+ load File.expand_path(File.join(path, file))
66
+ end
67
+
68
+ private
69
+ def __acquire__(path, glob, params)
70
+ glob.replace("#{glob}.rb") if glob.eql?("*") || glob.end_with?("/*")
71
+ files = Dir[File.join(path, glob)]
72
+ excludes = [params[:exclude]].flatten.compact
73
+ excludes.map! do |glob|
74
+ fullpath = File.join(path, glob)
75
+ File.file?(fullpath) ? fullpath : Dir[fullpath]
76
+ end
77
+ excludes = excludes.flatten.compact
78
+ files.select do |path|
79
+ if File.file?(path) && !excludes.include?(path)
80
+ soft_not_loaded = params[:soft] && ! $LOADED_FEATURES.include?(path)
81
+ load(path) if soft_not_loaded || ! params[:soft]
82
+ $LOADED_FEATURES.push(path) if soft_not_loaded
83
+ end
84
+ end
85
+ end
86
+
87
+ def get_base_and_glob(glob)
88
+ base, glob = glob.match(/^([^*]+)(.*)$/)[1..2]
89
+ raise ArgumentError, "You have to provide glob" if glob.empty?
90
+ return [base, glob]
91
+ end
92
+ end
93
+
94
+ acquire_relative "rubyexts/*"
95
+
96
+ # run console if executed directly
97
+ if $0 == __FILE__
98
+ require "irb"
99
+ require "irb/completion"
100
+ include RubyExts
101
+ IRB.start(__FILE__)
102
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ class Array
4
+ # Returns the _only_ element in the array or raise
5
+ # +IndexError+ if array hasn't exactly one element.
6
+ #
7
+ # @author Botanicus
8
+ # @from Extensions
9
+ # @since 0.0.3
10
+ # @raise [IndexError] If array hasn't exactly one element
11
+ # @return [Object] First (and only) item of the array
12
+ # @example
13
+ # [5].only # => 5
14
+ # [1, 2, 3].only # => IndexError
15
+ # [].only # => IndexError
16
+ def only
17
+ raise IndexError, "Array#only called on non-single-element array" unless self.size == 1
18
+ self.first
19
+ end
20
+ end
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+
3
+ # TODO: refactor similar as attr_accessor_with_default from active support
4
+ # see http://noobkit.com/show/ruby/rails/rails-stable/activesupport/module/attr_accessor_with_default.html
5
+ require "rubyexts/try_dup"
6
+
7
+ # default value for attr
8
+ module AttributeMixin
9
+ # class Array
10
+ # private_alias :join
11
+ # def join(char)
12
+ # puts "New join!"
13
+ # __join__(char)
14
+ # end
15
+ # end
16
+ def private_alias(method)
17
+ alias_method "__#{method}__", method
18
+ private "__#{method}__"
19
+ end
20
+
21
+ # @since 0.0.1
22
+ # @example
23
+ # class Post
24
+ # attribute :title, "Rango rulez!"
25
+ # end
26
+ # Post.new.title
27
+ # # => "Rango rulez!"
28
+ # @param [Symbol] name Name of object variable which will be set. If you have <tt>attribute :title</tt>, then the +@title+ variable will be defined. It also create +#title+ and +#title=+ accessors.
29
+ # @param [Object, @optional] default_value Default value of the variable.
30
+ # @return [name] Returns given default value or if default value.
31
+ # @see #hattribute
32
+ def attribute(name, default_value = nil)
33
+ # define reader method
34
+ define_method(name) do
35
+ if instance_variable_get("@#{name}").nil?
36
+ # lazy loading
37
+ # TODO: why is it lazy loaded?
38
+ default_value = default_value.call if default_value.is_a?(Proc)
39
+ instance_variable_set("@#{name}", default_value.try_dup) # dup is terribly important, otherwise all the objects will points to one object. If it is for example array, all of the objects will push into one array.
40
+ end
41
+ instance_variable_get("@#{name}")
42
+ end
43
+
44
+ # define writer method
45
+ define_method("#{name}=") do |value|
46
+ instance_variable_set("@#{name}", value)
47
+ # TODO: here should be rewritten the reader for cases when user want to do foo.bar = nil because now it will still returns the default value
48
+ end
49
+
50
+ return default_value
51
+ end
52
+
53
+ # This will also define title and title= methods, but it doesn't define @title variable,
54
+ # but @__hattributes__ hash with all the attributes
55
+
56
+ # @since 0.0.1
57
+ # @example
58
+ # class Post
59
+ # hattribute :title, "Rango rulez!"
60
+ # end
61
+ # Post.new.title
62
+ # # => "Rango rulez!"
63
+ # @param [Symbol] name Name of attribute his accessor methods will be defined. It's similar as +#attribute+, but it doesn't define +@name+ variable, but it will be key in +@__hattributes__+ hash. It's useful when you don't like to mess the object namespace with many variables or if you like to separate the attributes from the instance variables.
64
+ # @param [Object, @optional] default_value Default value of the variable.
65
+ # @return [name] Returns given default value or if default value.
66
+ # @see #attribute
67
+ def default_hattribute_names
68
+ # this can't be hash because in case the value will be lambda, we can't call it,
69
+ # because lambda is evaluated in scope of instance
70
+ @default_hattribute_names ||= Array.new
71
+ end
72
+
73
+ def hattribute(name, default_value = nil)
74
+ self.default_hattribute_names.push(name)
75
+
76
+ define_method(:hattributes) do
77
+ @__hattributes__ ||= Hash.new
78
+ # this is importat for initialization @__hattributes__
79
+ self.class.default_hattribute_names.each do |hattribute|
80
+ self.send(hattribute)
81
+ end
82
+ return @__hattributes__
83
+ end
84
+
85
+ # define reader method
86
+ if default_value.is_a?(Proc)
87
+ define_method(name) do
88
+ properties = instance_variable_get("@__hattributes__") || Hash.new
89
+ if properties[name].nil?
90
+ # instance_variable_set("@#{name}", default_value)
91
+ # lazy loading
92
+ properties[name] = self.instance_eval(&default_value)
93
+ end
94
+ # instance_variable_get("@#{name}")
95
+ properties[name]
96
+ end
97
+ else
98
+ define_method(name) do
99
+ properties = instance_variable_get("@__hattributes__") || Hash.new
100
+ # properties = @__hattributes__
101
+ if properties[name].nil?
102
+ # instance_variable_set("@#{name}", default_value)
103
+ # lazy loading
104
+ properties[name] = default_value.try_dup
105
+ end
106
+ # instance_variable_get("@#{name}")
107
+ properties[name]
108
+ end
109
+ end
110
+
111
+ # define writer method
112
+ define_method("#{name}=") do |value|
113
+ instance_variable_set("@__hattributes__", Hash.new) unless instance_variable_get("@__hattributes__")
114
+ instance_variable_get("@__hattributes__")[name] = value
115
+ end
116
+
117
+ return default_value
118
+ end
119
+
120
+ # class << self.class
121
+ # def hattributes
122
+ # raise "Y"
123
+ # end
124
+ # end
125
+
126
+ # class Post
127
+ # questionable :updated, true
128
+ # end
129
+ # Post.new.updated?
130
+ # # => true
131
+ # @since 0.0.2
132
+ def questionable(name, default_value)
133
+ define_method("#{name}?") do
134
+ unless self.instance_variables.include?(name.to_sym)
135
+ self.instance_variable_set("@#{name}", default_value)
136
+ end
137
+ self.instance_variable_get("@#{name}")
138
+ end
139
+ end
140
+ end
141
+
142
+ Class.send(:include, AttributeMixin)
143
+ Module.send(:include, AttributeMixin)
144
+
145
+ # class Test
146
+ # hattribute :bar, -> { "value" }
147
+ # end
148
+ #
149
+ # t = Test.new
150
+ # p t.bar
151
+ # p t.hattributes
@@ -0,0 +1,32 @@
1
+ require "stringio"
2
+
3
+ def STDOUT.capture(&block)
4
+ before = self
5
+ $stdout = StringIO.new
6
+ block.call
7
+ $stdout.rewind
8
+ output = $stdout.read
9
+ $stdout = before
10
+ output
11
+ end
12
+
13
+ # @example STDOUT.capture { puts "hi" }
14
+ # # => "hi"
15
+ def STDERR.capture(&block)
16
+ before = self
17
+ $stderr = StringIO.new
18
+ block.call
19
+ $stderr.rewind
20
+ output = $stderr.read
21
+ $stderr = before
22
+ output
23
+ end
24
+
25
+ # @example STDIN.capture("yes") { ask("Do you hear me?") }
26
+ # # => "yes"
27
+ def STDIN.capture(default, &block)
28
+ STDIN.reopen "/dev/null" # so we don't get the fucking prompt
29
+ STDIN.ungetbyte(default) # so the default value can be get by STDIN.getc & similar methods
30
+ block.call
31
+ # TODO: how I can get back the original STDIN?
32
+ end
@@ -0,0 +1,177 @@
1
+ # Copyright (c) 2004-2008 David Heinemeier Hansson
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ # Allows attributes to be shared within an inheritance hierarchy, but where
23
+ # each descendant gets a copy of their parents' attributes, instead of just a
24
+ # pointer to the same. This means that the child can add elements to, for
25
+ # example, an array without those additions being shared with either their
26
+ # parent, siblings, or children, which is unlike the regular class-level
27
+ # attributes that are shared across the entire hierarchy.
28
+ class Class
29
+ # Defines class-level and instance-level attribute reader.
30
+ #
31
+ # @param *syms<Array> Array of attributes to define reader for.
32
+ # @return <Array[#to_s]> List of attributes that were made into cattr_readers
33
+ #
34
+ # @api public
35
+ #
36
+ # @todo Is this inconsistent in that it does not allow you to prevent
37
+ # an instance_reader via :instance_reader => false
38
+ def cattr_reader(*syms)
39
+ syms.flatten.each do |sym|
40
+ next if sym.is_a?(Hash)
41
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
42
+ unless defined? @@#{sym}
43
+ @@#{sym} = nil
44
+ end
45
+
46
+ def self.#{sym}
47
+ @@#{sym}
48
+ end
49
+
50
+ def #{sym}
51
+ @@#{sym}
52
+ end
53
+ RUBY
54
+ end
55
+ end
56
+
57
+ # Defines class-level (and optionally instance-level) attribute writer.
58
+ #
59
+ # @param <Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define writer for.
60
+ # @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
61
+ # @return <Array[#to_s]> List of attributes that were made into cattr_writers
62
+ #
63
+ # @api public
64
+ def cattr_writer(*syms)
65
+ options = syms.last.is_a?(Hash) ? syms.pop : {}
66
+ syms.flatten.each do |sym|
67
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
68
+ unless defined? @@#{sym}
69
+ @@#{sym} = nil
70
+ end
71
+
72
+ def self.#{sym}=(obj)
73
+ @@#{sym} = obj
74
+ end
75
+ RUBY
76
+
77
+ unless options[:instance_writer] == false
78
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
79
+ def #{sym}=(obj)
80
+ @@#{sym} = obj
81
+ end
82
+ RUBY
83
+ end
84
+ end
85
+ end
86
+
87
+ # Defines class-level (and optionally instance-level) attribute accessor.
88
+ #
89
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define accessor for.
90
+ # @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
91
+ # @return <Array[#to_s]> List of attributes that were made into accessors
92
+ #
93
+ # @api public
94
+ def cattr_accessor(*syms)
95
+ cattr_reader(*syms)
96
+ cattr_writer(*syms)
97
+ end
98
+
99
+ # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
100
+ # each subclass has a copy of parent's attribute.
101
+ #
102
+ # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
103
+ # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
104
+ #
105
+ # @api public
106
+ #
107
+ # @todo Do we want to block instance_reader via :instance_reader => false
108
+ # @todo It would be preferable that we do something with a Hash passed in
109
+ # (error out or do the same as other methods above) instead of silently
110
+ # moving on). In particular, this makes the return value of this function
111
+ # less useful.
112
+ def class_inheritable_reader(*ivars)
113
+ instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
114
+
115
+ ivars.each do |ivar|
116
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
117
+ def self.#{ivar}
118
+ return @#{ivar} if defined?(@#{ivar})
119
+ return nil if self.object_id == #{self.object_id}
120
+ ivar = superclass.#{ivar}
121
+ return nil if ivar.nil?
122
+ @#{ivar} = ivar.try_dup
123
+ end
124
+ RUBY
125
+
126
+ unless instance_reader == false
127
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
128
+ def #{ivar}
129
+ self.class.#{ivar}
130
+ end
131
+ RUBY
132
+ end
133
+ end
134
+ end
135
+
136
+ # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
137
+ # each subclass has a copy of parent's attribute.
138
+ #
139
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
140
+ # define inheritable writer for.
141
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
142
+ # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
143
+ #
144
+ # @api public
145
+ #
146
+ # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
147
+ # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
148
+ def class_inheritable_writer(*ivars)
149
+ instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash)
150
+ ivars.each do |ivar|
151
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
152
+ def self.#{ivar}=(obj)
153
+ @#{ivar} = obj
154
+ end
155
+ RUBY
156
+ unless instance_writer == false
157
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
158
+ def #{ivar}=(obj) self.class.#{ivar} = obj end
159
+ RUBY
160
+ end
161
+ end
162
+ end
163
+
164
+ # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
165
+ # each subclass has a copy of parent's attribute.
166
+ #
167
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
168
+ # define inheritable accessor for.
169
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
170
+ # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
171
+ #
172
+ # @api public
173
+ def class_inheritable_accessor(*syms)
174
+ class_inheritable_reader(*syms)
175
+ class_inheritable_writer(*syms)
176
+ end
177
+ end