├── .gemspec ├── .gitignore ├── .index ├── .travis.yml ├── Gemfile ├── HISTORY.md ├── LICENSE.txt ├── MANIFEST ├── README.md ├── demo ├── applique │ └── env.rb ├── instance.md └── module.md ├── etc └── qed.rb ├── index.yml └── lib ├── instance.rb └── instance.yml /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rbx/ 2 | .rdoc/ 3 | .yardoc/ 4 | log/ 5 | doc/ 6 | pkg/ 7 | tmp/ 8 | web/ 9 | work/sandbox 10 | work/trash 11 | *.lock 12 | *.gem 13 | 14 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - index.yml 6 | authors: 7 | - name: Thomas Sawyer 8 | email: transfire@gmail.com 9 | organizations: 10 | - name: Rubyworks 11 | requirements: 12 | - groups: 13 | - test 14 | development: true 15 | name: qed 16 | - groups: 17 | - test 18 | development: true 19 | name: ae 20 | - groups: 21 | - test 22 | development: true 23 | name: simplecov 24 | conflicts: [] 25 | alternatives: [] 26 | resources: 27 | - type: home 28 | uri: http://rubyworks.github.com/instance 29 | label: Homepage 30 | - type: code 31 | uri: http://github.com/rubyworks/instance 32 | label: Source Code 33 | - type: bugs 34 | uri: http://github.com/rubyworks/instance/issues 35 | label: Issue Tracker 36 | - type: docs 37 | uri: http://rubydoc.info/gems/instance 38 | label: Documentation 39 | - type: mail 40 | uri: http://groups.google.com/group/facets-universal 41 | label: Mailing List 42 | repositories: 43 | - name: upstream 44 | scm: git 45 | uri: git@github.com:rubyworks/instance.git 46 | categories: [] 47 | copyrights: 48 | - holder: Rubyworks 49 | year: '2014' 50 | license: BSD-2-Clause 51 | customs: [] 52 | paths: 53 | lib: 54 | - lib 55 | name: instance 56 | title: Instance 57 | version: 0.2.0 58 | summary: Object Instance API 59 | description: Instance provides a simple and convenient API for accessing an object's 60 | internal state. In this author's humble opinion it is the interface Ruby would do 61 | well to adopt as offical. (Instance is a spin-off of Ruby Facets.) 62 | slogan: Know Your Objects! 63 | created: '2014-01-31' 64 | scm_uri: https://github.com/rubyworks/instance/tree/master 65 | date: '2015-05-28' 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec qed" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | - 2.0.0 8 | - rbx 9 | - jruby 10 | 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | 4 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 0.2.0 | 2014-02-02 4 | 5 | This release adds an extra set of methods for modules and classes. 6 | 7 | Changes: 8 | 9 | * Add `method_definition` if object is a module or class. 10 | * Add `method_definitions` if object is a module or class. 11 | 12 | 13 | ## 0.1.0 | 2014-02-01 14 | 15 | This is the initial release of Instance, a class spun-off from 16 | Ruby Facets. 17 | 18 | Changes: 19 | 20 | * Happy Release Day! 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD-2-Clause License (http://spdx.org/licenses/BSD-2-Clause) 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are 4 | permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of 7 | conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 10 | of conditions and the following disclaimer in the documentation and/or other materials 11 | provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS OR IMPLIED 14 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 16 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 17 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 18 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 21 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | 24 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .index .yardopts etc lib demo test *.md *.rdoc *.txt 2 | .index 3 | etc/qed.rb 4 | lib/instance.rb 5 | lib/instance.yml 6 | demo/applique/env.rb 7 | demo/instance.md 8 | demo/module.md 9 | README.md 10 | HISTORY.md 11 | LICENSE.txt 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Instance 2 | 3 | ## What Is It? 4 | 5 | Instance is a *convenient* and *safe* API for accessing and manipulating 6 | an object's state. 7 | 8 | ## How Does It Work 9 | 10 | Instance adds a method to all objects called `#instance`. It returns 11 | an `Instance` delegator that provides the full interface to the 12 | object's state. 13 | 14 | Of course, without implementing this in C, directly in the Ruby source, 15 | we are left to depend on the current provisions Ruby has for accessing 16 | the state of an object. So there are some limitations here. However, 17 | we implement the Ruby code in such a way as to minimize the downsides 18 | by caching all the method definitions the Instance class will utilize. 19 | 20 | ## Usage 21 | 22 | Let's use a very simple example class with which to demonstrate usage. 23 | 24 | ```ruby 25 | class Song 26 | attr_accessor :title 27 | attr_accessor :artist 28 | attr_accessor :year 29 | 30 | def initialize(title, artist, year) 31 | @title = title 32 | @artist = artist 33 | @year = year 34 | end 35 | end 36 | ``` 37 | 38 | Now we can create an instance of Song and work with it's state. 39 | 40 | ```ruby 41 | song = Song.new("Paranoid", "Black Sabbath", 1970) 42 | 43 | song.instance.variables 44 | # => [:@title, :@artist, :@year] 45 | 46 | song.instance.get(:title) 47 | # => "Paranoid" 48 | 49 | song.instance[:artist] 50 | # => "Black Sabbath" 51 | 52 | song.instance.to_h 53 | # => {:name => "Paranoid", :author => "Black Sabbath", :year => 1970) 54 | ``` 55 | 56 | For a more complete set of usage examples see the [QED](http://rubyworks.github.com/instance/qed.html) documentation. 57 | 58 | 59 | ## Copyrights 60 | 61 | Copyright © 2014 [Rubyworks](http://rubyworks.github.io) 62 | 63 | BSD-2-Clause License 64 | 65 | See [LICENSE.txt](LICENSE.txt) file for license details. 66 | 67 | -------------------------------------------------------------------------------- /demo/applique/env.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | -------------------------------------------------------------------------------- /demo/instance.md: -------------------------------------------------------------------------------- 1 | # Instance 2 | 3 | First thing we need to do, of course, is load the library. 4 | 5 | require 'instance' 6 | 7 | Now we can create an example class with which to work. 8 | 9 | class ::Friend 10 | attr_accessor :name, :age, :phone 11 | 12 | def initialize(name, age, phone) 13 | @name, @age, @phone = name, age, phone 14 | end 15 | end 16 | 17 | And now demonstrate the available API. 18 | 19 | ## Instance#variables 20 | 21 | A list of instance variables can be had via the `#variables` method. 22 | 23 | f = Friend.new("John", 30, "555-1212") 24 | f.instance.variables.assert == [:@name, :@age, :@phone] 25 | 26 | ## Instance#names 27 | 28 | To get a list of variables names as strings and without the `@` prefix, 29 | use the `#names` method. 30 | 31 | f = Friend.new("John", 30, "555-1212") 32 | f.instance.names.assert == ["name", "age", "phone"] 33 | 34 | ## Instance#keys 35 | 36 | Likewise, to get symbols instead of strings, use the `#keys` method. 37 | 38 | f = Friend.new("John", 30, "555-1212") 39 | f.instance.keys.assert == [:name, :age, :phone] 40 | 41 | ## Instance#values 42 | 43 | The values of the instance variables alone can be had via the `#values` method. 44 | 45 | f = Friend.new("John", 30, "555-1212") 46 | f.instance.values.assert == ["John", 30, "555-1212"] 47 | 48 | ## Instance#size 49 | 50 | The `#size` method reports how many instance variables are defined. 51 | 52 | f = Friend.new("John", 30, "555-1212") 53 | f.instance.size.assert == 3 54 | 55 | This method is primarily of use to the Enumerable mixin. 56 | 57 | ## Instance#variable_defined? 58 | 59 | f = Friend.new("John", 30, "555-1212") 60 | f.instance.assert.variable_defined?(:@name) 61 | f.instance.assert.variable_defined?(:name) 62 | 63 | ## Instance#update 64 | 65 | The `#update` method can be used to change instance variables in mass via 66 | method options. 67 | 68 | f = Friend.new("John", 30, "555-1212") 69 | f.name.assert == 'John' 70 | f.instance.update(:name=>'Jerry') 71 | f.name.assert == 'Jerry' 72 | 73 | ## Instance#assign 74 | 75 | The `#assign` method is simply an alias for `#update`. 76 | 77 | f = Friend.new("John", 30, "555-1212") 78 | f.instance.assign(:name=>'Joe') 79 | f.name.assert == 'Joe' 80 | 81 | ## Instance#to_h 82 | 83 | We can convert the object's state, i.e. it's instance variable names and values 84 | into a Hash very easily with the `#to_h` method. 85 | 86 | f1 = Friend.new("John", 30, "555-1212") 87 | f1.instance.to_h.assert == {:name=>"John", :age=>30, :phone=>"555-1212"} 88 | 89 | Notice that the `@` has beenn removed from the instance variable names. If we 90 | want the `@` to stay simply pass `true` to the `#to_h` method. 91 | 92 | f1 = Friend.new("John", 30, "555-1212") 93 | f1.instance.to_h(true).assert == {:@name=>"John", :@age=>30, :@phone=>"555-1212"} 94 | 95 | ## Instance#class 96 | 97 | To know the class of an object use the `#class` method. 98 | 99 | f = Friend.new("John", 30, "555-1212") 100 | f.instance.class.assert == ::Friend 101 | 102 | Note that to get the class of Instance itself, you must use `#object_class`. 103 | 104 | ## Instance#id 105 | 106 | To know the id of an object use the `#id` method. 107 | 108 | f = Friend.new("John", 30, "555-1212") 109 | f.instance.id == f.object_id 110 | 111 | ## Instance#of? 112 | 113 | f = Friend.new("John", 30, "555-1212") 114 | f.instance.assert.of?(::Friend) 115 | 116 | ## Instance#get 117 | 118 | To get the value of a specific instance variable use the `#get` or `#[]` 119 | methods. 120 | 121 | f = Friend.new("John", 30, "555-1212") 122 | f.instance.get(:name).assert == "John" 123 | f.instance[:name].assert == "John" 124 | 125 | ## Instance#set 126 | 127 | To set the value of a specific instance variable use the `#set` or `#[]=` 128 | methods. 129 | 130 | f = Friend.new("John", 30, "555-1212") 131 | 132 | f.instance.set(:name, "Bill") 133 | f.name.assert == "Bill" 134 | 135 | f.instance[:name] = "Billy" 136 | f.name.assert == "Billy" 137 | 138 | ## Instance#remove 139 | 140 | To remove an instance variable from an object use the `#remove` method. 141 | 142 | f = Friend.new("John", 30, "555-1212") 143 | f.instance.remove(:name) 144 | f.instance.refute.variable_defined?(:@name) 145 | 146 | ## Instance#method 147 | 148 | The Instance class provides a `method` method for getting a Method object for 149 | any of the target object's methods by name. 150 | 151 | f1 = Friend.new("John", 30, "555-1212") 152 | m = f1.instance.method(:name) 153 | m.assert.is_a?(::Method) 154 | 155 | This can seem a little confusing because Ruby's `instance_method` method actually 156 | returns an `UnboundMethod` object. Unfortunately Ruby's use of the term `instance_method` 157 | is a complete misnomer. It should be something like `method_definition` instead. 158 | The actual *instance method* is the object's method. 159 | 160 | ## Instance#methods 161 | 162 | We can also get a whole list of an object's methods via the `#methods` method. 163 | 164 | f = Friend.new("John", 30, "555-1212") 165 | f.instance.methods 166 | f.instance.methods(:private) 167 | f.instance.methods(:protected) 168 | f.instance.methods(:public) 169 | f.instance.methods(:public, :private) 170 | 171 | ## Instance#eval 172 | 173 | To evaluate code in the context to the object's instance use the `#eval` method. 174 | 175 | f = Friend.new("John", 30, "555-1212") 176 | f.instance.eval("@name").assert == "John" 177 | 178 | ## Instance#exec 179 | 180 | Likewise the `#exec` method can also be used. 181 | 182 | f = Friend.new("John", 30, "555-1212") 183 | f.instance.exec{ @name }.assert == "John" 184 | 185 | ## Instance#send 186 | 187 | Sending a message to an object as if within the object itself and thus by-passing 188 | method visibility is somehting that should only be done as an act of *metaprogramming*. 189 | Hence it makes sense for it to be done via the `instance` interface. 190 | 191 | f = Friend.new("John", 30, "555-1212") 192 | f.instance.send(:name).assert == "John" 193 | 194 | ## Instance#delegate 195 | 196 | To get at the delegated object of an Instance, use the `#delegate` method. 197 | 198 | f = Friend.new("John", 30, "555-1212") 199 | f.instance.delegate.assert == f 200 | 201 | 202 | -------------------------------------------------------------------------------- /demo/module.md: -------------------------------------------------------------------------------- 1 | # Instance for Modules/Classes 2 | 3 | Modules and Classes have additional features not shared by other 4 | types of objects. 5 | 6 | ## Instance#method_definition 7 | 8 | um = String.instance.method_definition(:to_s) 9 | um.class.assert == UnboundMethod 10 | 11 | ## Instance#definition 12 | 13 | The `#definition` method is just an alias for `#method_definition`. 14 | 15 | um = String.instance.definition(:to_s) 16 | um.class.assert == UnboundMethod 17 | 18 | ## Instance#method_definitions 19 | 20 | list = String.instance.method_definitions 21 | 22 | Method definitions can be selected use support symbol selectors. 23 | 24 | list = String.instance.method_definitions(:public) 25 | list = String.instance.method_definitions(:protected) 26 | list = String.instance.method_definitions(:private) 27 | list = String.instance.method_definitions(:private, :protected) 28 | 29 | ## Instance#definitions 30 | 31 | The `#definitions` method is likewise an alias for `#method_definitions`. 32 | 33 | list = String.instance.definitions 34 | 35 | -------------------------------------------------------------------------------- /etc/qed.rb: -------------------------------------------------------------------------------- 1 | QED.configure 'coverage' do 2 | require 'simplecov' 3 | SimpleCov.command_name 'QED' 4 | SimpleCov.start do 5 | coverage_dir 'log/coverage' 6 | end 7 | end 8 | 9 | -------------------------------------------------------------------------------- /index.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | instance 4 | 5 | version: 6 | 0.2.0 7 | 8 | title: 9 | Instance 10 | 11 | summary: 12 | Object Instance API 13 | 14 | description: 15 | Instance provides a simple and convenient API for accessing 16 | an object's internal state. In this author's humble opinion 17 | it is the interface Ruby would do well to adopt as offical. 18 | (Instance is a spin-off of Ruby Facets.) 19 | 20 | slogan: 21 | Know Your Objects! 22 | 23 | requirements: 24 | - qed (test) 25 | - ae (test) 26 | - simplecov (test) 27 | #- detroit (build) 28 | 29 | resources: 30 | home: http://rubyworks.github.com/instance 31 | code: http://github.com/rubyworks/instance 32 | bugs: http://github.com/rubyworks/instance/issues 33 | docs: http://rubydoc.info/gems/instance 34 | mail: http://groups.google.com/group/facets-universal 35 | 36 | repositories: 37 | upstream: git@github.com:rubyworks/instance.git 38 | 39 | created: 40 | 2014-01-31 41 | 42 | authors: 43 | - Thomas Sawyer 44 | 45 | organizations: 46 | - Rubyworks 47 | 48 | copyrights: 49 | - (c) 2014 Rubyworks (BSD-2-Clause) 50 | 51 | scm_uri: 52 | https://github.com/rubyworks/instance/tree/master 53 | 54 | -------------------------------------------------------------------------------- /lib/instance.rb: -------------------------------------------------------------------------------- 1 | # Instance class is a delgator for any object which provides an elegant 2 | # and protected interface to an object's state, i.e. its *instance*. 3 | # Elgence is achieved by providing a single interface, the `instance` 4 | # method. Protection is made possible by caching all the built-in 5 | # Ruby methods used to interface with an object's internal state. 6 | # This way they can not be overriden by some errant code or third 7 | # party library. 8 | # 9 | # Examples 10 | # 11 | # class Friend 12 | # attr_accessor :name, :age, :phone 13 | # def initialize(name, age, phone) 14 | # @name, @age, @phone = name, age, phone 15 | # end 16 | # end 17 | # 18 | # f1 = Friend.new("John", 30, "555-1212") 19 | # f1.instance.get(:name) #=> "John" 20 | # f1.instance.update(:name=>'Jerry') 21 | # f1.instance.get(:name) #=> "Jerry" 22 | # 23 | class Instance 24 | include Enumerable 25 | 26 | # Store Object methods so they cannot be overriden by the delegate class. 27 | METHODS = {} 28 | 29 | def self.freeze_method(name) 30 | METHODS[name.to_sym] = Object.instance_method(name) 31 | end 32 | 33 | freeze_method :object_id 34 | freeze_method :class 35 | freeze_method :instance_of? 36 | freeze_method :method 37 | freeze_method :methods 38 | freeze_method :public_methods 39 | freeze_method :protected_methods 40 | freeze_method :private_methods 41 | freeze_method :instance_eval 42 | freeze_method :instance_exec 43 | freeze_method :instance_variables 44 | freeze_method :instance_variable_get 45 | freeze_method :instance_variable_set 46 | freeze_method :instance_variable_defined? 47 | freeze_method :remove_instance_variable 48 | freeze_method :send 49 | freeze_method :is_a? 50 | freeze_method :kind_of? 51 | 52 | # Instance cache acts as a global cache for instances of Instance. 53 | @cache = {} 54 | 55 | # Instance is multiton. Use this method instead of #new to get a 56 | # cached instance. 57 | def self.instance(delegate) 58 | @cache[delegate] ||= Instance.new(delegate) 59 | end 60 | 61 | # Initialize new Instance instance. If the delegate is a type of 62 | # Module or Class then the instance will be extended with the 63 | # {ModuleExtensions} mixin. 64 | # 65 | def initialize(delegate) 66 | @delegate = delegate 67 | extend ModuleExtensions if Module === delegate 68 | end 69 | 70 | # The delegated object. 71 | def delegate 72 | @delegate 73 | end 74 | 75 | # Iterate over instance variables. 76 | def each 77 | variables.each do |name| 78 | yield(name[1..-1].to_sym, get(name)) 79 | end 80 | end 81 | 82 | # Number of instance variables. 83 | def size 84 | variables.size 85 | end 86 | 87 | # Get instance variables with values as a hash. 88 | # 89 | # Examples 90 | # 91 | # class X 92 | # def initialize(a,b) 93 | # @a, @b = a, b 94 | # end 95 | # end 96 | # 97 | # x = X.new(1,2) 98 | # 99 | # x.instance.to_h #=> { :a=>1, :b=>2 } 100 | # 101 | # Returns [Hash]. 102 | def to_h(at=false) 103 | h = {} 104 | if at 105 | variables.each do |name| 106 | h[name] = get(name) 107 | end 108 | else 109 | each do |key, value| 110 | h[key] = value 111 | end 112 | end 113 | h 114 | end 115 | 116 | # Get instance variable's value. Will return `nil` if the 117 | # variable does not exist. 118 | # 119 | # Returns the value of the instance variable. 120 | def get(name) 121 | name = atize(name) 122 | bind_call(:instance_variable_get, name) 123 | end 124 | alias :[] :get 125 | 126 | # Set instance variable. 127 | # 128 | # Returns the set value. 129 | def set(name, value) 130 | name = atize(name) 131 | bind_call(:instance_variable_set, name, value) 132 | end 133 | alias :[]= :set 134 | 135 | # Set an instance variable given a name and a value in an array pair. 136 | # 137 | # Example 138 | # 139 | # f = Friend.new 140 | # f.instance << [:name, "John"] 141 | # f.name #=> "John" 142 | # 143 | # Returns the set value. 144 | def <<(pair) 145 | name, value = *pair 146 | name = atize(name) 147 | set(name, value) 148 | end 149 | 150 | # Remove instance variable. 151 | def remove(name) 152 | name = atize(name) 153 | bind_call(:remove_instance_variable, name) 154 | end 155 | 156 | # Set instance variables given a +hash+. 157 | # 158 | # instance.update('@a'=>1, '@b'=>2) 159 | # @a #=> 1 160 | # @b #=> 2 161 | # 162 | # Also, +@+ sign is not neccessary. 163 | # 164 | # instance.update(:a=>1, :b=>2) 165 | # @a #=> 1 166 | # @b #=> 2 167 | # 168 | # Returns nothing. 169 | def update(hash) 170 | hash.each do |pair| 171 | self << pair 172 | end 173 | end 174 | 175 | # A hold-over from the the old #instance_assign method. 176 | alias_method :assign, :update 177 | 178 | # Same as #instance_variables. 179 | def variables 180 | bind_call(:instance_variables) 181 | end 182 | 183 | # Instance vairable names as symbols. 184 | # 185 | # Returns [Array]. 186 | def keys 187 | variables.collect do |name| 188 | name[1..-1].to_sym 189 | end 190 | end 191 | 192 | # Instance variable names as strings. 193 | # 194 | # Returns [Array]. 195 | def names 196 | variables.collect do |name| 197 | name[1..-1] 198 | end 199 | end 200 | 201 | # Instance variable values. 202 | # 203 | # Returns [Array]. 204 | def values 205 | variables.collect do |name| 206 | get(name) 207 | end 208 | end 209 | 210 | # Instance evaluation. 211 | def eval(*a,&b) 212 | bind_call(:instance_eval, *a, &b) 213 | end 214 | 215 | # Instance execution. 216 | def exec(*a,&b) 217 | bind_call(:instance_exec, *a, &b) 218 | end 219 | 220 | # Get method. Usage of this might seem strange because Ruby's own 221 | # `instance_method` method is a misnomer. It should be something 222 | # like `definition` or `method_definition`. In Ruby the acutal 223 | # "instance" method is accessed via the unadorned `method` method. 224 | # 225 | # Returns [Method]. 226 | def method(name) 227 | bind_call(:method, name) 228 | end 229 | 230 | # Returns list of method names. 231 | # 232 | # Returns [Array]. 233 | def methods(*selection) 234 | list = [] 235 | 236 | if selection.empty? 237 | list.concat bind_call(:methods) 238 | end 239 | 240 | selection.each do |s| 241 | case s 242 | when :public, :all 243 | list.concat bind_call(:public_methods) 244 | when :protected, :all 245 | list.concat bind_call(:protected_methods) 246 | when :private, :all 247 | list.concat bind_call(:private_methods) 248 | end 249 | end 250 | 251 | return list 252 | end 253 | 254 | # Is the object an instance of a given class? 255 | # 256 | # Returns [Boolean] 257 | def of?(a_class) 258 | bind_call(:instance_of?, a_class) 259 | end 260 | 261 | # Is the object an instance of a given class or subclass? 262 | # 263 | # Returns [Boolean] 264 | def is_a?(a_class) 265 | bind_call(:is_a?, a_class) 266 | end 267 | 268 | # Is the object an instance of a given class or subclass? 269 | # 270 | # Returns [Boolean] 271 | def kind_of?(a_class) 272 | bind_call(:kind_of?, a_class) 273 | end 274 | 275 | # Is an instaance variable defined? 276 | # 277 | # Returns [Boolean] 278 | def variable_defined?(name) 279 | name = atize(name) 280 | bind_call(:instance_variable_defined?, name) 281 | end 282 | 283 | # Get object's instance id. 284 | # 285 | # Returns [Integer] 286 | def id 287 | bind_call(:object_id) 288 | end 289 | 290 | # Fallback to get the real class of the Instance delegate itself. 291 | alias :object_class :class 292 | 293 | # Get object's instance id. 294 | # 295 | # Returns [Class] 296 | def class 297 | bind_call(:class) 298 | end 299 | 300 | # Send message to instance. 301 | def send(*a, &b) 302 | bind_call(:send, *a, &b) 303 | end 304 | 305 | private 306 | 307 | def atize(name) 308 | name.to_s !~ /^@/ ? "@#{name}" : name 309 | end 310 | 311 | ## 312 | # ModuleExtensions provides some additional methods for Module and Class 313 | # objects. 314 | # 315 | # TODO: Are there any other module/class methods that need to be provided? 316 | # 317 | module ModuleExtensions 318 | # Store Object methods so they cannot be overriden by the delegate class. 319 | METHODS = {} 320 | 321 | def self.freeze_method(name) 322 | METHODS[name.to_sym] = Module.instance_method(name) 323 | end 324 | 325 | freeze_method :instance_method 326 | freeze_method :instance_methods 327 | freeze_method :public_instance_methods 328 | freeze_method :protected_instance_methods 329 | freeze_method :private_instance_methods 330 | 331 | # List of method definitions in a module or class. 332 | # 333 | # selection - Any of `:public`, `:protected` or `:private` which 334 | # is used to select specific subsets of methods. 335 | # 336 | # Returns [Array] 337 | def method_definitions(*selection) 338 | list = [] 339 | 340 | if selection.empty? 341 | list.concat bind_call(:instance_methods) 342 | end 343 | 344 | selection.each do |s| 345 | case s 346 | when :public, :all 347 | list.concat bind_call(:public_instance_methods) 348 | when :protected, :all 349 | list.concat bind_call(:protected_instance_methods) 350 | when :private, :all 351 | list.concat bind_call(:private_instance_methods) 352 | end 353 | end 354 | 355 | return list 356 | end 357 | 358 | # Shorter alias for #method_definitions. 359 | alias :definitions :method_definitions 360 | 361 | # Get a first-class method definition object. 362 | # 363 | # Returns an unbound method object. [UnboundMethod] 364 | def method_definition(name) 365 | bind_call(:instance_method, name) 366 | end 367 | 368 | alias :definition :method_definition 369 | end 370 | 371 | # TODO: Are there any method we need specific to a Class vs a Module? 372 | #module ClassExtensions 373 | # 374 | #end 375 | 376 | private 377 | 378 | # Helper method for binding the method to the delegate object and calling it. 379 | def bind_call(method_name, *args, &block) 380 | METHODS[mathod_name].bind(@delegate).call(&args, &block) 381 | end 382 | end 383 | 384 | 385 | class BasicObject 386 | # Returns an instance of Instance for `self`, which allows convenient 387 | # access to an object's internals. 388 | def instance 389 | ::Instance.instance(self) 390 | end 391 | end 392 | 393 | -------------------------------------------------------------------------------- /lib/instance.yml: -------------------------------------------------------------------------------- 1 | ../.index --------------------------------------------------------------------------------