├── .gemspec ├── .gitignore ├── .index ├── .travis.yml ├── .yardopts ├── Assembly ├── Gemfile ├── HISTORY.md ├── Index.yml ├── LICENSE.txt ├── MANIFEST ├── NOTICE.txt ├── README.md ├── Rakefile ├── alt └── hashery │ ├── castinghash.rb │ ├── fuzzyhash.rb │ ├── keyhash.rb │ ├── linkedlist.rb │ ├── lruhash.rb │ ├── opencascade.rb │ ├── openhash.rb │ ├── orderedhash.rb │ ├── propertyhash.rb │ ├── queryhash.rb │ └── statichash.rb ├── demo ├── 00_introduction.rdoc ├── 01_open_hash.rdoc ├── 02_query_hash.rdoc ├── 03_casting_hash.rdoc ├── 04_static_hash.rdoc ├── 05_key_hash.rdoc ├── 06_open_cascade.rdoc ├── 07_fuzzy_hash.rdoc ├── 08_propery_hash.rdoc ├── 10_association.rdoc └── applique │ ├── ae.rb │ └── hashery.rb ├── etc ├── qed.rb └── test.rb ├── lib ├── hashery.rb ├── hashery.yml └── hashery │ ├── association.rb │ ├── casting_hash.rb │ ├── core_ext.rb │ ├── crud_hash.rb │ ├── dictionary.rb │ ├── fuzzy_hash.rb │ ├── ini_hash.rb │ ├── key_hash.rb │ ├── linked_list.rb │ ├── lru_hash.rb │ ├── open_cascade.rb │ ├── open_hash.rb │ ├── ordered_hash.rb │ ├── path_hash.rb │ ├── property_hash.rb │ ├── query_hash.rb │ ├── stash.rb │ └── static_hash.rb ├── test ├── case_association.rb ├── case_casting_hash.rb ├── case_core_ext.rb ├── case_crud_hash.rb ├── case_dictionary.rb ├── case_ini_hash.rb ├── case_key_hash.rb ├── case_linked_list.rb ├── case_lru_hash.rb ├── case_open_cascade.rb ├── case_open_hash.rb ├── case_property_hash.rb ├── case_query_hash.rb ├── fixture │ └── example.ini └── helper.rb └── work ├── NOTES.rdoc ├── benchmarks └── proc_vs_call.rb ├── consider ├── autoarray.rb ├── having.rb ├── ini.rb ├── openobject.rb ├── safeopenstructable.rb └── superhash.rb ├── deprecated ├── basic_cascade.rb ├── basic_object.rb ├── basic_struct.rb ├── bench_harray.rb ├── case_basic_cascade.rb ├── case_basicstruct.rb ├── memoizer.rb ├── ostructable.rb ├── test_sparray.rb └── webme │ ├── advert.html │ ├── config.yml │ └── logo.jpg ├── reference ├── hash_magic.rb ├── hash_magic │ ├── LICENSE │ ├── README │ ├── Rakefile │ └── lib │ │ └── hash_magic.rb └── orderedhash.rb └── trash └── test ├── test_association.rb ├── test_autoarray.rb ├── test_dictionary.rb ├── test_opencascade.rb └── test_openobject.rb /.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 | *.gz 2 | *.gem 3 | *.lock 4 | .rbx 5 | .digest 6 | .yardoc 7 | doc/ 8 | log/ 9 | tmp/ 10 | web/ 11 | DEMO.rdoc 12 | DEMO.md 13 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Index.yml 6 | - Gemfile 7 | authors: 8 | - name: Trans 9 | email: transfire@gmail.com 10 | - name: Kirk Haines 11 | - name: Robert Klemme 12 | - name: Jan Molic 13 | - name: George Moschovitis 14 | - name: Jeena Paradies 15 | - name: Erik Veenstra 16 | organizations: 17 | - name: RubyWorks (http://rubyworks.github.com/) 18 | requirements: 19 | - groups: 20 | - development 21 | - test 22 | version: ">= 0" 23 | name: qed 24 | - groups: 25 | - development 26 | - test 27 | version: ">= 0" 28 | name: lemon 29 | - groups: 30 | - development 31 | - test 32 | version: ">= 0" 33 | name: rubytest-cli 34 | conflicts: [] 35 | alternatives: [] 36 | resources: 37 | - type: home 38 | uri: http://rubyworks.github.com/hashery 39 | label: Homepage 40 | - type: code 41 | uri: http://github.com/rubyworks/hashery 42 | label: Source Code 43 | - type: mail 44 | uri: http://groups.google.com/group/rubyworks-mailinglist 45 | label: Mailing List 46 | - type: docs 47 | uri: http://rubydoc.info/github/rubyworks/hashery/master/frames 48 | label: Documentation 49 | - type: wiki 50 | uri: http://wiki.github.com/rubyworks/hashery 51 | label: User Guide 52 | - type: gems 53 | uri: http://rubygems.org/gems/hashery 54 | repositories: 55 | - name: upstream 56 | scm: git 57 | uri: git://github.com/rubyworks/hashery.git 58 | categories: [] 59 | copyrights: 60 | - holder: Rubyworks 61 | year: '2010' 62 | license: BSD-2-Clause 63 | customs: [] 64 | paths: 65 | lib: 66 | - lib 67 | - alt 68 | name: hashery 69 | title: Hashery 70 | version: 2.1.2 71 | summary: Facets-bread collection of Hash-like classes. 72 | description: The Hashery is a tight collection of Hash-like classes. Included among 73 | its many offerings are the auto-sorting Dictionary class, the efficient LRUHash, 74 | the flexible OpenHash and the convenient KeyHash. Nearly every class is a subclass 75 | of the CRUDHash which defines a CRUD model on top of Ruby's standard Hash making 76 | it a snap to subclass and augment to fit any specific use case. 77 | created: '2010-04-21' 78 | date: '2016-05-01' 79 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec $TESTCOMMAND" 4 | rvm: 5 | - 1.9.3 6 | - 2.1.1 7 | - rbx-2 8 | - rbx 9 | - jruby 10 | env: 11 | - TESTCOMMAND="qed" 12 | - TESTCOMMAND="rubytest -Ilib -Itest test/" 13 | matrix: 14 | allow_failures: 15 | - rvm: rbx 16 | - rvm: rbx-2 17 | cache: bundler 18 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --plugin tomdoc 2 | --output-dir doc 3 | --title Hashery 4 | --protected 5 | --private 6 | lib/ 7 | - 8 | [A-Z]*.* 9 | 10 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | mailto: 4 | - ruby-talk@ruby-lang.org 5 | - rubyworks-mailinglist@googlegroups.com 6 | 7 | github: 8 | gh_pages: web 9 | 10 | gem: 11 | gemspec: pkg/hashery.gemspec 12 | 13 | yard: 14 | yardopts: true 15 | priority: 2 16 | 17 | qedoc: 18 | title: Hashery Demonstrandum 19 | output: 20 | - DEMO.rdoc 21 | - web/demo.html 22 | 23 | #lemon: 24 | # service: Custom 25 | # test: | 26 | # abort unless system "lemon -Ilib test/" 27 | 28 | dnote: 29 | labels: ~ 30 | output: work/NOTES.rdoc 31 | 32 | vclog: 33 | output: 34 | - log/History.rdoc 35 | - log/Changes.rdoc 36 | active: false 37 | 38 | 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | source "http://rubygems.org" 4 | 5 | group "development", "test" do 6 | #gem "detroit", :group => "build" 7 | gem "qed" 8 | gem "lemon" 9 | gem "rubytest-cli" 10 | end 11 | 12 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # RELEASE HISTORY 2 | 3 | ## 2.1.2 / 2016-05-02 4 | 5 | Minor release fixes a test for KeyHash. And that's it. 6 | 7 | Changes: 8 | 9 | * Fix initialize test for KeyHash class. 10 | 11 | 12 | ## 2.1.1 / 2013-08-21 13 | 14 | This minor release clarifies the licensing. The entire library is now 15 | distributed under the BSD-2-Clause license, also known as the FreeBSD 16 | license. In addition this release provides a bug fix for flattening 17 | arrays that contain an OpenCascade object. 18 | 19 | Changes: 20 | 21 | * Clarify licensing. 22 | * Fix #flatten on Arrays that contain an OpenCascade. 23 | 24 | 25 | ## 2.1.0 / 2012-11-24 (Golden Retriever) 26 | 27 | The major change of the 2.1 release is to switch over to `Hash#fetch` 28 | as the fundamental CRUD read method in-place of the previous `#read` core 29 | extension (an alias of `#[]`). This is a pretty fundamental change which 30 | required modification of a number of classes. So please do extra-diligence 31 | and file an issue if you experience any problems. 32 | 33 | In addition, the Hash#read core extension has been renamed to Hash#retrieve 34 | to avoid any possible confusion with IO objects. This release also fixes 35 | a couple of issues with 1.8 compatibility and makes a few other small 36 | enhancements. 37 | 38 | Changes: 39 | 40 | * Rename Hash#read to Hash#retrieve. 41 | * Deprecate `Dictionary.alpha` in favor of `Dictionary.alphabetic`. 42 | * Add support for block argument in Dictionary#order_by_key and #order_by_value. 43 | * Fix OpenHash issues with Ruby 1.8.x compatibility. 44 | * OpenHash methods are opened up via `protected` instead of `private`. 45 | * Change OpenCascade to auto-create the subclass when inherited. 46 | 47 | 48 | ## 2.0.1 / 2012-07-06 49 | 50 | This minor release fixes an issue with OpenCascade (#13). 51 | The key_proc procedure wasn't being passed along to sub-cascades. 52 | 53 | Changes: 54 | 55 | * OpenCascade passes along key_proc to children. 56 | 57 | 58 | ## 2.0.0 / 2012-05-11 (Crud Space) 59 | 60 | This is a big release for Hashery which both culls some of it's 61 | less fitting classes and modules while greatly improving the rest. 62 | The first and most immediate change is use of a proper namespace. 63 | All classes and modules are now appropriately kept in the `Hashery` 64 | namespace. To get the old behavior you can `include Hashery` as the 65 | toplevel. For the other changes and improvements dive into the 66 | API documentation. 67 | 68 | Changes: 69 | 70 | * Use proper Hashery namespace. 71 | * Add CRUDHash, which also serves a good base class. 72 | * Improved OpenHash to be nearly 100% open. 73 | * Deprecate BasicStruct, as it would be better to improve OpenStruct. 74 | * Deprecate BasicCascade, though it never really came to be. 75 | * Deprecate BasicObject emulator, as it is no longer needed. 76 | * Deprecate Memoizer, not sure how that got in here anyway. 77 | * Deprecate Ostructable, which can be paired up with better OpenStruct. 78 | * Removed open_object.rb, which has long been deprecated. 79 | 80 | 81 | ## 1.5.1 / 2012-05-09 82 | 83 | This release adds transformative #each method to OpenCascade, to 84 | ensure #each returns an OpenCascade. Also, BasicCascade has been 85 | added that is like OpenCascade by fully open by use of BasicObject 86 | as a base class. 87 | 88 | Changes: 89 | 90 | * Fix OpenCascade#each (porecreat). 91 | * Introduce BasicCascade class. 92 | * Renamed `Ini` class to `IniHash` class. 93 | 94 | 95 | ## 1.5.0 / 2011-11-10 (Devil's Core) 96 | 97 | In this release, CoreExt module has been added to encapsulate 98 | methods that extend Ruby's core Hash class (there are only a few). 99 | Presently these are only loaded when using `require 'hashery'`. 100 | If you are cherry-picking from Hashery but still want the core 101 | extensions, you need to use `require 'hasery/core_ext'` first. 102 | In addition, BasicStruct class now has a #key method. And finally 103 | this release switches licensing to BSD 2-Clause. 104 | 105 | Changes: 106 | 107 | * Use CoreExt mixin for core Hash extensions. 108 | * Add BasicStruct#key method (b/c #index is deprecated in Ruby 1.9). 109 | * Deprecate SparseArray class. 110 | * Switch license to BSD-2-Clause license. 111 | 112 | 113 | ## 1.4.0 / 2011-01-19 (Back To Basics) 114 | 115 | This release includes a copy of Ruby Facets' BasicObject class, which 116 | fixes the loading bug of the previous version. This release also renames 117 | OpenObject to BasicStruct, which is a much better description of what the 118 | class actually provides. 119 | 120 | Changes: 121 | 122 | * Rename OpenObject to BasicStruct. 123 | * Fix basicobject.rb loading issue. 124 | 125 | 126 | ## 1.3.0 / 2010-10-01 (Private Property) 127 | 128 | This release fixes a minor bug in CastingHash and adds a new 129 | PropertyHash class. 130 | 131 | Changes: 132 | 133 | * 1 New Library 134 | 135 | * Added PropertyHash 136 | 137 | * 1 Bug Fix 138 | 139 | * Fixed CastingHash#new where #to_proc is called against NilClass 140 | 141 | 142 | ## 1.2.0 / 2010-06-04 (Fuzzy Wuzzy) 143 | 144 | This release makes two significant changes to the Hashery. 145 | First, we have a new shiny library called FuzzyHash by 146 | Joshua Hull. It's a cool idea that allows hash keys to be 147 | regular expressions. Secondly, OpenCascade is now a subclass 148 | of OpenHash rather than OpenObject (to go along with the 149 | changes of the last release), and it now support cascading 150 | within Arrays. 151 | 152 | Changes: 153 | 154 | * 1 New Library 155 | 156 | * FuzzyHash by Joshua Hull 157 | 158 | * 1 Major Enhancement 159 | 160 | * OpenCascade subclasses OpenHash and handles Array cascading. 161 | 162 | 163 | ## 1.1.0 / 2010-04-28 (Ugly Ducklings) 164 | 165 | A follow-up release of Hashery that adds two new libraries: 166 | Association and SparseArray. Both of these may seem like odd 167 | entries, but they each belong in a unique way. An Association 168 | is akin to a single entry Hash --it represents a pairing. 169 | While a SpareArray, though compatible with the Array class, 170 | is completely under-pinned by a Hash in order to make it 171 | efficient when no entries are given for a set of indexes, 172 | hence "sparse". 173 | 174 | Changes: 175 | 176 | * 2 New Libraries 177 | 178 | * Added association.rb 179 | * Added sparsearray.rb 180 | 181 | 182 | ## 1.0.0 / 2010-04-21 (Hatching Hashery) 183 | 184 | This is the first release of the Facets Hashery. 185 | Most of included classes come directly from Ruby 186 | Facets, so they have been around a while and are 187 | in good working condition. 188 | 189 | Some improvements are planned for the next release. 190 | In particular the OrderHash and Dictionary, which 191 | presently have essentially the same coding, will 192 | diverge to target slightly different use cases. 193 | 194 | Changes: 195 | 196 | * Happy Birthday! 197 | 198 | -------------------------------------------------------------------------------- /Index.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | hashery 4 | 5 | version: 6 | 2.1.2 7 | 8 | title: 9 | Hashery 10 | 11 | summary: 12 | Facets-bread collection of Hash-like classes. 13 | 14 | description: 15 | The Hashery is a tight collection of Hash-like classes. Included among its many 16 | offerings are the auto-sorting Dictionary class, the efficient LRUHash, the 17 | flexible OpenHash and the convenient KeyHash. Nearly every class is a subclass 18 | of the CRUDHash which defines a CRUD model on top of Ruby's standard Hash 19 | making it a snap to subclass and augment to fit any specific use case. 20 | 21 | authors: 22 | - Trans 23 | - Kirk Haines 24 | - Robert Klemme 25 | - Jan Molic 26 | - George Moschovitis 27 | - Jeena Paradies 28 | - Erik Veenstra 29 | 30 | resources: 31 | home: http://rubyworks.github.com/hashery 32 | code: http://github.com/rubyworks/hashery 33 | mail: http://groups.google.com/group/rubyworks-mailinglist 34 | docs: http://rubydoc.info/github/rubyworks/hashery/master/frames 35 | wiki: http://wiki.github.com/rubyworks/hashery 36 | gems: http://rubygems.org/gems/hashery 37 | 38 | repositories: 39 | upstream: "git://github.com/rubyworks/hashery.git" 40 | 41 | organizations: 42 | - RubyWorks (http://rubyworks.github.com/) 43 | 44 | copyrights: 45 | - 2010 Rubyworks (BSD-2-Clause) 46 | 47 | load_path: 48 | - lib 49 | - alt 50 | 51 | created: '2010-04-21' 52 | 53 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD-2-Clause License 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 15 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast -x etc .index .yardopts bin demo lib man spec test *.md *.txt *.rdoc 2 | .index 3 | .yardopts 4 | demo/00_introduction.rdoc 5 | demo/01_open_hash.rdoc 6 | demo/02_query_hash.rdoc 7 | demo/03_casting_hash.rdoc 8 | demo/04_static_hash.rdoc 9 | demo/05_key_hash.rdoc 10 | demo/06_open_cascade.rdoc 11 | demo/07_fuzzy_hash.rdoc 12 | demo/08_propery_hash.rdoc 13 | demo/10_association.rdoc 14 | demo/applique/ae.rb 15 | demo/applique/hashery.rb 16 | lib/hashery/association.rb 17 | lib/hashery/casting_hash.rb 18 | lib/hashery/core_ext.rb 19 | lib/hashery/crud_hash.rb 20 | lib/hashery/dictionary.rb 21 | lib/hashery/fuzzy_hash.rb 22 | lib/hashery/ini_hash.rb 23 | lib/hashery/key_hash.rb 24 | lib/hashery/linked_list.rb 25 | lib/hashery/lru_hash.rb 26 | lib/hashery/open_cascade.rb 27 | lib/hashery/open_hash.rb 28 | lib/hashery/ordered_hash.rb 29 | lib/hashery/path_hash.rb 30 | lib/hashery/property_hash.rb 31 | lib/hashery/query_hash.rb 32 | lib/hashery/stash.rb 33 | lib/hashery/static_hash.rb 34 | lib/hashery.rb 35 | lib/hashery.yml 36 | test/case_association.rb 37 | test/case_casting_hash.rb 38 | test/case_core_ext.rb 39 | test/case_crud_hash.rb 40 | test/case_dictionary.rb 41 | test/case_ini_hash.rb 42 | test/case_key_hash.rb 43 | test/case_linked_list.rb 44 | test/case_lru_hash.rb 45 | test/case_open_cascade.rb 46 | test/case_open_hash.rb 47 | test/case_property_hash.rb 48 | test/case_query_hash.rb 49 | test/fixture/example.ini 50 | test/helper.rb 51 | README.md 52 | HISTORY.md 53 | NOTICE.txt 54 | LICENSE.txt 55 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | All copyright holders have released their wares with a compatible 2 | license or have given permission for their work to be distributed 3 | under the same license as Hashery. 4 | 5 | Dictionary is based on OrderedHash by Jan Molic. 6 | 7 | OrderHash 2.0 Copyright (c) 2005 Jan Molic. 8 | 9 | 10 | LRUHash is based on same by Robert Klemme 11 | 12 | Copyright (c) 2010 Robert Klemme 13 | 14 | 15 | Memoizer is based on same by Erik Veenstra 16 | 17 | Copyright (c) 2006 Erik Veenstra 18 | 19 | 20 | IniHash is based on Ini library by Jeena Paradies 21 | 22 | Copyright (c) 2007 Jeena Paradies 23 | 24 | 25 | LinkedList is based on same by Kirk Haines 26 | 27 | Copyright (C) 2006 Kirk Haines 28 | 29 | 30 | FuzzyHash is based on the same by Joshua Hull. 31 | 32 | Copyright (c) 2009 Joshua Hull 33 | 34 | 35 | PathHash is based on SlashedHash by Daniel Parker 36 | 37 | HashMagic by Daniel Parker 38 | Copyright (c) 2006 BehindLogic 39 | http://hash_magic.rubyforge.org 40 | 41 | BasicObject is based on Jim Weirich's BlankSlate class. 42 | 43 | BlankSlate 44 | Copyright 2004 by Jim Weirich (jim@weirichhouse.org). 45 | All rights reserved. 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hashery 2 | 3 | [![Gem Version](http://img.shields.io/gem/v/hashery.svg?style=flat)](http://rubygems.org/gem/hashery) 4 | [![Build Status](http://img.shields.io/travis/rubyworks/hashery.svg?style=flat)](http://travis-ci.org/rubyworks/hashery) 5 | [![Fork Me](http://img.shields.io/badge/scm-github-blue.svg?style=flat)](http://github.com/rubyworks/hashery) 6 | [![Report Issue](http://img.shields.io/github/issues/rubyworks/hashery.svg?style=flat)](http://github.com/rubyworks/hashery/issues) 7 | [![Gittip](http://img.shields.io/badge/gittip-$1/wk-green.svg?style=flat)](https://www.gittip.com/rubyworksorg/) 8 | 9 | [Homepage](http://rubyworks.github.com/hashery) · 10 | [Development](http://github.com/rubyworks/hashery) · 11 | [Report Issue](http://github.com/rubyworks/hashery/issues) · 12 | [Chat](https://kiwiirc.com/client/irc.freenode.net/?nick=user1|?#rubyworks) 13 | 14 | 15 | ## Description 16 | 17 | Among Ruby Facets most common additions were an assortment 18 | of Hash-like classes. To better support this collection 19 | of libraries it was deemed prudent to create a new project 20 | specifically for them. Hence the *Facets Hashery*. 21 | 22 | Included in this collection are the widely used OrderedHash, 23 | the related but more featured Dictionary class, a number 24 | of _open_ classes similar to the standard OpenStruct, 25 | some variations of the standard Hash class and a few 26 | other yummy morsels. 27 | 28 | 29 | ## Usage 30 | 31 | For instruction on usage, please see the individual library files 32 | included in this collection and read the demo documents which give 33 | examples of almost all features. 34 | 35 | 36 | ### Core Extensions 37 | 38 | Hashery adds four core extensions of Ruby's Hash class: `#retrieve`, 39 | `#rekey`, `#rekey!` and `Hash.create`. The first is simply an alias 40 | for `#[]`. The later two have proven too useful over the years to 41 | omit. And the last is a convenience class method for populating 42 | a new hash with another hash upon initialization. All of these are 43 | sorely missing for Ruby itself, which is why they are provided here. 44 | 45 | 46 | ## Installation 47 | 48 | To install with RubyGems simply open a console and type: 49 | 50 | $ sudo gem install hashery 51 | 52 | Or add it as a dependency to your project's Gemfile: 53 | 54 | gem "hashery" 55 | 56 | Tarball packages are available for manual site installations 57 | via [Ruby Setup](http://rubyworks.github.com/setup). 58 | 59 | 60 | ## Authors 61 | 62 | Developers who have contributed code to the project include: 63 | 64 | * Kirk Haines 65 | * Joshua Hull 66 | * Robert Klemme 67 | * Jan Molic 68 | * George Moschovitis 69 | * Jeena Paradies 70 | * Erik Veenstra 71 | 72 | 73 | ## Contributing 74 | 75 | Don't be a lump on a log. See an issue? Have a suggestion? Want to help? 76 | Well *git* in there! 77 | 78 | ### Communicating 79 | 80 | The project is hosted on Github so the easiest means of communication 81 | is via the [Issues](http://github.com/rubyworks/hashery/issues) page. 82 | For more direct communication you can try the **#rubyworks** IRC channel 83 | on **irc.freenode.net**. 84 | 85 |     [![IRC](https://kiwiirc.com/buttons/irc.freenode.net/rubyworks.png)](https://kiwiirc.com/client/irc.freenode.net/?nick=user1|?#rubyworks) 86 | 87 | ### Testing 88 | 89 | Hashery uses [QED](http://rubyworks.github.com/qed) and 90 | [Lemon](http://rubyworks.github.com/lemon) test frameworks. 91 | The QED framework to create well tested high-level documentation. 92 | Run the QED specs via: 93 | 94 | $ qed -Ilib demo/ 95 | 96 | Lemon is used to create low-level unit tests. Run these via the Rubytest: 97 | 98 | $ rubytest -Ilib -Itest test/ 99 | 100 | ### Patches 101 | 102 | Hashery's repository is hosted on [GitHub](http://github.com/rubyworks/hashery). 103 | If you'd like to offer up a fix or feature, fork the repo and submit a pull 104 | request (preferably in a topic branch). I assume you have heard 105 | all the talk about proper [practices](https://github.com/rubyworks/rubyworks.github.com/wiki/GitHub-Best-Pactices), 106 | so I won't bug you with it yet again. 107 | 108 | ### Donations 109 | 110 | Yes, we FOSS programmers need to eat too! ;-) No seriously, any help you can 111 | offer goes a long way toward continued development of Rubyworks projects, 112 | including Hashery. Hop over to our [Gittips](https://www.gittip.com/rubyworksorg/) 113 | page, or see the upper right-hand corner on the [Rubyworks](http://rubyworks.github.com) homepage. 114 | Thanks! 115 | 116 | 117 | ## Copyrights 118 | 119 | Copyright (c) 2010 Rubyworks 120 | 121 | Licensed under the *BSD-2-Clause* license. 122 | 123 | See LICENSE.txt file for further details. 124 | 125 | Some libraries included in the Hashery have special copyrights 126 | attributing specific authors. Please see each library script for 127 | specifics and the NOTICE.txt file for an overview. 128 | 129 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | #ignore 'web', 'log', 'doc', 'tmp', 'work' 4 | 5 | desc 'run tests' 6 | task 'test' do 7 | sh 'rubytest test/' 8 | end 9 | 10 | desc 'run demos' 11 | task 'demo' do 12 | sh 'qed demo/' 13 | end 14 | 15 | desc 'coverage report' 16 | task 'coverage' do 17 | system({'p'=>'coverage'}, 'qed demo/') 18 | system({'p'=>'coverage'}, 'rubytest test/') 19 | end 20 | 21 | -------------------------------------------------------------------------------- /alt/hashery/castinghash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/casting_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/fuzzyhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/fuzzy_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/keyhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/key_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/linkedlist.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/linked_list' 2 | -------------------------------------------------------------------------------- /alt/hashery/lruhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/lru_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/opencascade.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/open_cascade' 2 | -------------------------------------------------------------------------------- /alt/hashery/openhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/open_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/orderedhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/ordered_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/propertyhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/property_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/queryhash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/query_hash' 2 | -------------------------------------------------------------------------------- /alt/hashery/statichash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/static_hash' 2 | -------------------------------------------------------------------------------- /demo/00_introduction.rdoc: -------------------------------------------------------------------------------- 1 | = Hashery 2 | 3 | For the following demos the `hashery` script has 4 | been preloaded and the Hashery namespace has been 5 | included into the demo context for convenience. 6 | 7 | -------------------------------------------------------------------------------- /demo/01_open_hash.rdoc: -------------------------------------------------------------------------------- 1 | = OpenHash 2 | 3 | An OpenHash is a Hash that provides +open+ access to its entries via method 4 | calls. Writers (methods ending in =-marks) assign entries. Methods without 5 | special puncuation will retrieve entries. 6 | 7 | o = OpenHash.new 8 | o.a = 1 9 | o.b = 2 10 | o.a.assert == 1 11 | o.b.assert == 2 12 | 13 | Writers always use a Symbol for keys in the underlying Hash. 14 | 15 | o.to_h.assert == { :a=>1, :b=>2 } 16 | 17 | All the usual Hash methods are still available in an OpenHash. 18 | 19 | c = o.map{ |k,v| [k,v] } 20 | c.assert.include?([:a,1]) 21 | c.assert.include?([:b,2]) 22 | 23 | And they are protected from being overridden by writers. 24 | 25 | o.map = 3 26 | o.map.refute == 3 27 | 28 | Even so, the underlying Hash object does contain the entry even 29 | when it cannot be accessed via a reader method. 30 | 31 | o.to_h.assert == { :a=>1, :b=>2, :map=>3 } 32 | 33 | We can see if a method is open or not via the `#open?` method. 34 | 35 | o.open?(:a).assert == true 36 | o.open?(:map).assert == false 37 | 38 | For some usecases it may be necessary to give up access to one or 39 | more Hash methods in favor of access to the hash entries. This can 40 | be done using the `#open!` method. 41 | 42 | o.open!(:map, :merge) 43 | o.map.assert == 3 44 | o.merge = 4 45 | o.merge.assert == 4 46 | 47 | Becuase of nature of a writer, a certain set of Hash methods are always 48 | protected, in particluar all methods buffered by underscore (e.g. `__id__`). 49 | So these cannot be opened. 50 | 51 | expect ArgumentError do 52 | o.open!(:__id__) 53 | end 54 | 55 | Even though writers alwasy use Symbols as keys, because an OpenHash 56 | is a true Hash object, any object can be used as a key internally. 57 | 58 | o = OpenHash.new 59 | o[nil] = "Nothing" 60 | o.to_h.assert == { nil=>"Nothing" } 61 | 62 | It simply cannot be accessible via a reader method. 63 | 64 | -------------------------------------------------------------------------------- /demo/02_query_hash.rdoc: -------------------------------------------------------------------------------- 1 | = QueryHash 2 | 3 | A QueryHash is a Hash that provides open access much like an OpenHash, but it limits readers 4 | to bang and query methods (i.e. method ending in `!` or `?`). 5 | 6 | q = QueryHash.new 7 | q.a = 1 8 | q.b = 2 9 | q.a?.assert == 1 10 | q.b?.assert == 2 11 | 12 | By default keys are converted to strings. 13 | 14 | q.assert == { "a"=>1, "b"=>2 } 15 | 16 | A QueryHash is compatible with Ruby's standard Hash in every other respect. 17 | 18 | -------------------------------------------------------------------------------- /demo/03_casting_hash.rdoc: -------------------------------------------------------------------------------- 1 | = CastingHash 2 | 3 | A CastingHash is a Hash that allows _casting_ procedures to 4 | defined that the keys and values pass through upon assignment. 5 | 6 | c = CastingHash.new 7 | c.cast_proc = lambda { |k,v| [k.to_s, v.to_s.upcase] } 8 | 9 | c[:a] = 'a' 10 | 11 | c.assert == {'a'=>'A'} 12 | 13 | -------------------------------------------------------------------------------- /demo/04_static_hash.rdoc: -------------------------------------------------------------------------------- 1 | = StaticHash 2 | 3 | A StaticHash is simply a Hash that can only be assigned 4 | once per key. Once assigned a subsequent attempt to assign 5 | a value to the same key will raise an ArgumentError. 6 | 7 | h = StaticHash.new 8 | 9 | h["x"] = 1 10 | 11 | expect ArgumentError do 12 | h["x"] = 2 13 | end 14 | 15 | The same error will be raised when using #update or #merge!. 16 | 17 | expect ArgumentError do 18 | h.update( "x"=>3 ) 19 | end 20 | 21 | -------------------------------------------------------------------------------- /demo/05_key_hash.rdoc: -------------------------------------------------------------------------------- 1 | = KeyHash 2 | 3 | The KeyHash is essentially the same as regular Hash but instead 4 | of a `default_proc` the initializer takes the `key_proc` for 5 | normalizing keys. 6 | 7 | kh = KeyHash.new{ |k| k.to_s.upcase } 8 | kh[:a] = 1 9 | kh.to_h #=> ({'A'=>1}) 10 | 11 | By default, when no `key_proc` is given, it converts all keys to strings. 12 | 13 | kh = KeyHash.new 14 | kh[:a] = 1 15 | kh.to_h #=> ({'a'=>1}) 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/06_open_cascade.rdoc: -------------------------------------------------------------------------------- 1 | = OpenCascade 2 | 3 | The reason this class is labeled "cascade", is that every internal 4 | Hash is transformed into an OpenCascade dynamically upon access. 5 | This makes it easy to create _cascading_ references. 6 | 7 | h = { :x => { :y => { :z => 1 } } } 8 | c = OpenCascade[h] 9 | assert c.x.y.z == 1 10 | 11 | As soon as you access a node it automatically becomes an OpenCascade. 12 | 13 | c = OpenCascade.new 14 | assert(OpenCascade === c.r) 15 | assert(OpenCascade === c.a.b) 16 | 17 | But if you set a node, then that will be it's value. 18 | 19 | c.a.b = 4 20 | assert c.a.b == 4 21 | 22 | To query a node without causing the auto-creation of an OpenCasade 23 | object, use the ?-mark. 24 | 25 | assert c.a.z? == nil 26 | 27 | OpenCascade also transforms Hashes within Arrays. 28 | 29 | h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 } 30 | c = OpenCascade[h] 31 | assert c.x.first.a == 1 32 | assert c.x.last.a == 2 33 | 34 | Like OpenObject, OpenCascade allows you to insert entries as array 35 | pairs. 36 | 37 | c = OpenCascade.new 38 | c << [:x,8] 39 | c << [:y,9] 40 | 41 | assert c.x == 8 42 | assert c.y == 9 43 | 44 | Finally, you can call methods ending in a !-mark to access the 45 | underlying hash (Note that these differ in behavior from the 46 | built-in !-methods). 47 | 48 | bk = c.map!{ |k,v| k.to_s.upcase } 49 | bk.sort.assert == ['X', 'Y'] 50 | 51 | So you can see that for the most an OpenCascade is just like 52 | OpenHash, but it allows us to conveniently build open sub-layers 53 | easily. 54 | 55 | Enumerable still works with OpenCascades too. 56 | 57 | h = {} 58 | c = OpenCascade[:a=>1,:b=>{:c=>3}] 59 | c.each do |k,v| 60 | h[k] = v 61 | end 62 | OpenCascade.assert === h[:b] 63 | 64 | 65 | -------------------------------------------------------------------------------- /demo/07_fuzzy_hash.rdoc: -------------------------------------------------------------------------------- 1 | = FuzzyHash 2 | 3 | Should accept strings and retrieve based on them. 4 | 5 | l = FuzzyHash.new 6 | l['asd'] = 'qwe' 7 | l['asd'].should == 'qwe' 8 | 9 | Should accept strings, but the second time you set the same string, it should overwrite. 10 | 11 | l = FuzzyHash.new 12 | l['asd'] = 'asd' 13 | l['asd'] = 'qwe' 14 | l['asd'].should == 'qwe' 15 | 16 | Should accept regexs too. 17 | 18 | l = FuzzyHash.new 19 | l[/asd.*/] = 'qwe' 20 | l['asdqweasd'].should == 'qwe' 21 | 22 | Should accept regexs too, but the second time you set the same regex, it should overwrite. 23 | 24 | l = FuzzyHash.new 25 | l[/asd/] = 'asd' 26 | l[/asd/] = 'qwe' 27 | l['asdqweasd'].should == 'qwe' 28 | 29 | Should accept regexs too with the match. 30 | 31 | l = FuzzyHash.new 32 | l[/asd.*/] = 'qwe' 33 | l.match_with_result('asdqweasd').should == ['qwe', 'asdqweasd'] 34 | 35 | Should accept regexs that match the whole strong too with the match. 36 | 37 | l = FuzzyHash.new 38 | l[/asd/] = 'qwe' 39 | l.match_with_result('asd').should == ['qwe', 'asd'] 40 | 41 | Should prefer string to regex matches. 42 | 43 | l = FuzzyHash.new 44 | l['asd'] = 'qwe2' 45 | l[/asd.*/] = 'qwe' 46 | l['asd'].should == 'qwe2' 47 | 48 | Should allow nil keys. 49 | 50 | l = FuzzyHash.new 51 | l[nil] = 'qwe2' 52 | l['asd'] = 'qwe' 53 | l['asd'].should == 'qwe' 54 | l[nil].should == 'qwe2' 55 | 56 | Should allow boolean keys. 57 | 58 | l = FuzzyHash.new 59 | l[false] = 'false' 60 | l[true] = 'true' 61 | l[/.*/] = 'everything else' 62 | l[true].should == 'true' 63 | l[false].should == 'false' 64 | l['false'].should == 'everything else' 65 | 66 | Should pick between the correct regex. 67 | 68 | hash = FuzzyHash.new 69 | hash[/^\d+$/] = 'number' 70 | hash[/.*/] = 'something' 71 | hash['123asd'].should == 'something' 72 | 73 | Should be able to delete by value for hash. 74 | 75 | l = FuzzyHash.new 76 | l[nil] = 'qwe2' 77 | l['asd'] = 'qwe' 78 | l['asd'].should == 'qwe' 79 | l[nil].should == 'qwe2' 80 | l.delete_value('qwe2') 81 | l[nil].should == nil 82 | 83 | Should be able to delete by value for regex. 84 | 85 | l = FuzzyHash.new 86 | l[/qwe.*/] = 'qwe2' 87 | l['asd'] = 'qwe' 88 | l['asd'].should == 'qwe' 89 | l['qweasd'].should == 'qwe2' 90 | l.delete_value('qwe2') 91 | l['qweasd'].should == nil 92 | 93 | Should iterate through the keys. 94 | 95 | l = FuzzyHash.new 96 | l[/qwe.*/] = 'qwe2' 97 | l['asd'] = 'qwe' 98 | l['zxc'] = 'qwe' 99 | l.keys.size.should == 3 100 | 101 | Should iterate through the values. 102 | 103 | l = FuzzyHash.new 104 | l[/qwe.*/] = 'qwe2' 105 | l['asd'] = 'qwe' 106 | l['zxc'] = 'qwelkj' 107 | (['qwe2','qwe','qwelkj'] & l.values).size.should == 3 108 | 109 | Should clear. 110 | 111 | l = FuzzyHash.new 112 | l[/qwe.*/] = 'qwe2' 113 | l['asd'] = 'qwe' 114 | l['zxc'] = 'qwelkj' 115 | l.clear 116 | l.empty?.should == true 117 | 118 | Should handle equality. 119 | 120 | l_1 = FuzzyHash.new 121 | l_1[/qwe.*/] = 'qwe2' 122 | l_1['asd'] = 'qwelkj' 123 | l_1['zxc'] = 'qwe' 124 | l_2 = FuzzyHash.new 125 | l_2['zxc'] = 'qwe' 126 | l_2['asd'] = 'qwelkj' 127 | l_2[/qwe.*/] = 'qwe2' 128 | l_1.should == l_2 129 | 130 | Should return the value when adding the value. 131 | 132 | h = FuzzyHash.new 133 | (h[/asd/] = '123').should == '123' 134 | (h['qwe'] = '123').should == '123' 135 | 136 | That's It. 137 | 138 | -------------------------------------------------------------------------------- /demo/08_propery_hash.rdoc: -------------------------------------------------------------------------------- 1 | = PropertyHash 2 | 3 | The Property hash can be used an object in itself. 4 | 5 | h = PropertyHash.new(:a=>1, :b=>2) 6 | h[:a] #=> 1 7 | h[:a] = 3 8 | h[:a] #=> 3 9 | 10 | Becuase the properties are fixed, if we try to set a key that is not present, 11 | then we will get an error. 12 | 13 | expect ArgumentError do 14 | h[:x] = 5 15 | end 16 | 17 | The PropertyHash can also be used as a superclass. 18 | 19 | class MyPropertyHash < PropertyHash 20 | property :a, :default => 1 21 | property :b, :default => 2 22 | end 23 | 24 | h = MyPropertyHash.new 25 | h[:a] #=> 1 26 | h[:a] = 3 27 | h[:a] #=> 3 28 | 29 | Again, if we try to set key that was not fixed, then we will get an error. 30 | 31 | expect ArgumentError do 32 | h[:x] = 5 33 | end 34 | 35 | -------------------------------------------------------------------------------- /demo/10_association.rdoc: -------------------------------------------------------------------------------- 1 | = Association 2 | 3 | An Association is a class for creating simple pairings. 4 | 5 | require 'hashery/association' 6 | 7 | An Association can bew created through the usual means 8 | of instantiation. 9 | 10 | Association.new(:a, :b) 11 | 12 | Or the shortcut method #>> can be used in most cases. 13 | 14 | :x >> :z 15 | 16 | An association provides two methods to access its content, #index and #value. 17 | 18 | a = 'foo' >> 'bar' 19 | 20 | a.index.assert == 'foo' 21 | a.value.assert == 'bar' 22 | 23 | Associations can be used to create ordered-hashes via normal 24 | arrays. 25 | 26 | keys = [] 27 | vals = [] 28 | 29 | ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ] 30 | 31 | ohash.each{ |k,v| keys << k ; vals << v } 32 | 33 | keys.assert == ['A','B','C'] 34 | vals.assert == ['3','2','1'] 35 | 36 | 37 | Becuase Associations are objects in themselves more complex 38 | collections can also be created. 39 | 40 | complex = [ 41 | 'parent' >> 'child', 42 | 'childless', 43 | 'another_parent' >> [ 44 | 'subchildless', 45 | 'subparent' >> 'subchild' 46 | ] 47 | ] 48 | 49 | An experimental feature of Association keeps a cache of all defined associations. 50 | 51 | o = Object.new 52 | o >> :a 53 | o >> :b 54 | o >> :c 55 | 56 | o.associations.assert == [:a, :b, :c] 57 | 58 | However this feature will probably be deprecated. 59 | 60 | -------------------------------------------------------------------------------- /demo/applique/ae.rb: -------------------------------------------------------------------------------- 1 | require 'ae' 2 | require 'ae/should' 3 | -------------------------------------------------------------------------------- /demo/applique/hashery.rb: -------------------------------------------------------------------------------- 1 | require 'hashery' 2 | 3 | include Hashery 4 | -------------------------------------------------------------------------------- /etc/qed.rb: -------------------------------------------------------------------------------- 1 | QED.configure 'cov' do 2 | require 'simplecov' 3 | SimpleCov.command_name 'QED' 4 | SimpleCov.start do 5 | add_filter '/demo/' 6 | coverage_dir 'log/coverage' 7 | end 8 | end 9 | 10 | -------------------------------------------------------------------------------- /etc/test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | #require 'lemon' 4 | #require 'ae' 5 | 6 | Test.configure do |run| 7 | run.load_path 'lib', 'test' 8 | end 9 | 10 | Test.configure 'coverage' do |run| 11 | # run all tests to get complete coverage report 12 | run.test_files << 'test/case_*.rb' 13 | run.load_path 'lib', 'test' 14 | run.before do 15 | require 'simplecov' 16 | SimpleCov.command_name 'RubyTest' 17 | SimpleCov.start do 18 | add_filter '/test/' 19 | add_filter '/lib/hashery/ordered_hash.rb' 20 | coverage_dir 'log/coverage' 21 | end 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /lib/hashery.rb: -------------------------------------------------------------------------------- 1 | module Hashery 2 | VERSION = "2.1.0" 3 | end 4 | 5 | require 'hashery/core_ext' 6 | 7 | #require 'hashery/basic_struct' 8 | require 'hashery/casting_hash' 9 | require 'hashery/crud_hash' 10 | require 'hashery/dictionary' 11 | require 'hashery/fuzzy_hash' 12 | require 'hashery/ini_hash' 13 | require 'hashery/linked_list' 14 | require 'hashery/lru_hash' 15 | require 'hashery/key_hash' 16 | require 'hashery/open_cascade' 17 | require 'hashery/open_hash' 18 | #require 'hashery/ordered_hash' 19 | #require 'hashery/ostructable' 20 | require 'hashery/property_hash' 21 | require 'hashery/query_hash' 22 | require 'hashery/static_hash' 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/hashery.yml: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Index.yml 6 | - Gemfile 7 | authors: 8 | - name: Trans 9 | email: transfire@gmail.com 10 | - name: Kirk Haines 11 | - name: Robert Klemme 12 | - name: Jan Molic 13 | - name: George Moschovitis 14 | - name: Jeena Paradies 15 | - name: Erik Veenstra 16 | organizations: 17 | - name: RubyWorks (http://rubyworks.github.com/) 18 | requirements: 19 | - groups: 20 | - development 21 | - test 22 | version: ">= 0" 23 | name: qed 24 | - groups: 25 | - development 26 | - test 27 | version: ">= 0" 28 | name: lemon 29 | - groups: 30 | - development 31 | - test 32 | version: ">= 0" 33 | name: rubytest-cli 34 | conflicts: [] 35 | alternatives: [] 36 | resources: 37 | - type: home 38 | uri: http://rubyworks.github.com/hashery 39 | label: Homepage 40 | - type: code 41 | uri: http://github.com/rubyworks/hashery 42 | label: Source Code 43 | - type: mail 44 | uri: http://groups.google.com/group/rubyworks-mailinglist 45 | label: Mailing List 46 | - type: docs 47 | uri: http://rubydoc.info/github/rubyworks/hashery/master/frames 48 | label: Documentation 49 | - type: wiki 50 | uri: http://wiki.github.com/rubyworks/hashery 51 | label: User Guide 52 | - type: gems 53 | uri: http://rubygems.org/gems/hashery 54 | repositories: 55 | - name: upstream 56 | scm: git 57 | uri: git://github.com/rubyworks/hashery.git 58 | categories: [] 59 | copyrights: 60 | - holder: Rubyworks 61 | year: '2010' 62 | license: BSD-2-Clause 63 | customs: [] 64 | paths: 65 | lib: 66 | - lib 67 | - alt 68 | name: hashery 69 | title: Hashery 70 | version: 2.1.2 71 | summary: Facets-bread collection of Hash-like classes. 72 | description: The Hashery is a tight collection of Hash-like classes. Included among 73 | its many offerings are the auto-sorting Dictionary class, the efficient LRUHash, 74 | the flexible OpenHash and the convenient KeyHash. Nearly every class is a subclass 75 | of the CRUDHash which defines a CRUD model on top of Ruby's standard Hash making 76 | it a snap to subclass and augment to fit any specific use case. 77 | created: '2010-04-21' 78 | date: '2016-05-01' 79 | -------------------------------------------------------------------------------- /lib/hashery/association.rb: -------------------------------------------------------------------------------- 1 | module Hashery 2 | 3 | # TODO: Should associations be singleton? 4 | # 5 | # TODO: Is it really wise to keep a table of all associations? 6 | 7 | # Association is a general binary association that allows one 8 | # object to be associated with another. It has a variety of uses, 9 | # such as linked-lists, simple ordered maps and mixed collections, 10 | # among them. 11 | # 12 | # NOTE: This class is still fairly experimental. And it is not 13 | # loaded along with the other Hashery libraries when using 14 | # `require 'hashery'`. It must be required independently. 15 | # 16 | # Associations can be used to draw simple relationships. 17 | # 18 | # :Apple >> :Fruit 19 | # :Apple >> :Red 20 | # 21 | # :Apple.associations #=> [ :Fruit, :Red ] 22 | # 23 | # It can also be used for simple lists of ordered pairs. 24 | # 25 | # c = [ :a >> 1, :b >> 2 ] 26 | # c.each { |k,v| puts "#{k} associated with #{v} } 27 | # 28 | # produces 29 | # 30 | # a associated with 1 31 | # b associated with 2 32 | # 33 | # The method :>> is used to construct the association. 34 | # It is a rarely used method so it is generally available. 35 | # But you can't use it for any of the following classes 36 | # becuase they use #>> for other things. 37 | # 38 | # Bignum 39 | # Fixnum 40 | # Date 41 | # IPAddr 42 | # Process::Status 43 | # 44 | class Association 45 | include Comparable 46 | 47 | class << self 48 | # 49 | # Store association references. 50 | # 51 | # Returns `Hash` of all associaitons. 52 | # 53 | def reference 54 | @reference ||= Hash.new{ |h,k,v| h[k]=[] } 55 | end 56 | 57 | # 58 | # Shortcut for #new. 59 | # 60 | # index - The "index key" of the association. 61 | # value - The "value" of the association. 62 | # 63 | # Returns `Association`. 64 | # 65 | def [](index, value) 66 | new(index, value) 67 | end 68 | 69 | #def new(index, value) 70 | # lookup[[index, value]] ||= new(index, value) 71 | #end 72 | 73 | #def lookup 74 | # @lookup ||= {} 75 | #end 76 | end 77 | 78 | # 79 | # The "index key" of the association. 80 | # 81 | attr_accessor :index 82 | 83 | # 84 | # The "value" of the association. 85 | # 86 | attr_accessor :value 87 | 88 | # 89 | # Initialize new Association. 90 | # 91 | # index - The "index key" of the association. 92 | # value - The "value" of the association. 93 | # 94 | def initialize(index, value=nil) 95 | @index = index 96 | @value = value 97 | 98 | unless index.associations.include?(value) 99 | index.associations << value 100 | end 101 | end 102 | 103 | # 104 | # Compare the values of two associations. 105 | # 106 | # TODO: Comparions with non-associations? 107 | # 108 | # assoc - The other `Association`. 109 | # 110 | # Returns [Integer] `1`, `0`, or `-1`. 111 | # 112 | def <=>(assoc) 113 | return -1 if self.value < assoc.value 114 | return 1 if self.value > assoc.value 115 | return 0 if self.value == assoc.value 116 | end 117 | 118 | # 119 | # Invert association, making the index the value and vice-versa. 120 | # 121 | # Returns [Array] with two-elements reversed. 122 | # 123 | def invert! 124 | temp = @index 125 | @index = @value 126 | @value = temp 127 | end 128 | 129 | # 130 | # Produce a string representation. 131 | # 132 | # Returns [String]. 133 | # 134 | def to_s 135 | return "#{index} >> #{value}" 136 | end 137 | 138 | # 139 | # Produce a literal code string for creating an association. 140 | # 141 | # Returns [String]. 142 | # 143 | def inspect 144 | "#{index.inspect} >> #{value.inspect}" 145 | end 146 | 147 | # 148 | # Convert to two-element associative array. 149 | # 150 | # Returns [Array] Two-element Array of index and value pair. 151 | # 152 | def to_ary 153 | [index, value] 154 | end 155 | 156 | # 157 | # Object extensions. 158 | # 159 | module Kernel 160 | 161 | # 162 | # Define an association for +self+. 163 | # 164 | # to - The value of the association. 165 | # 166 | # Returns [Association]. 167 | # 168 | def >>(to) 169 | Association.new(self, to) 170 | end 171 | 172 | # 173 | # List of associations for this object. 174 | # 175 | # Returns an `Array` of `Associations`. 176 | # 177 | def associations 178 | Association.reference[self] 179 | end 180 | 181 | end 182 | 183 | end 184 | 185 | end 186 | 187 | class Object #:nodoc: 188 | include Hashery::Association::Kernel 189 | end 190 | 191 | #-- 192 | # Setup the >> method in classes that use it already. 193 | # 194 | # This is a bad idea b/c it can cause backward compability issues. 195 | # 196 | # class Bignum 197 | # alias_method( :rshift, :>>) if method_defined?(:>>) 198 | # remove_method :>> 199 | # end 200 | # 201 | # class Fixnum 202 | # alias_method( :rshift, :>>) if method_defined?(:>>) 203 | # remove_method :>> 204 | # end 205 | # 206 | # class Date 207 | # alias_method( :months_later, :>>) if method_defined?(:>>) 208 | # remove_method :>> 209 | # end 210 | # 211 | # class IPAddr 212 | # alias_method( :rshift, :>>) if method_defined?(:>>) 213 | # remove_method :>> 214 | # end 215 | # 216 | # class Process::Status 217 | # alias_method( :rshift, :>>) if method_defined?(:>>) 218 | # remove_method :>> 219 | # end 220 | #++ 221 | 222 | # Copyright (c) 2005 Rubyworks, Thomas Sawyer 223 | -------------------------------------------------------------------------------- /lib/hashery/casting_hash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/crud_hash' 2 | 3 | module Hashery 4 | 5 | # CastingHash is just like CRUDHash, except that both keys and values 6 | # can be passed through casting procedures. 7 | # 8 | class CastingHash < CRUDHash 9 | 10 | # 11 | # Like `#new` but can take a priming Hash or Array-pairs. 12 | # 13 | # hash - Hash-like object. 14 | # 15 | # Examples 16 | # 17 | # CastingHash[:a,1,:b,2] 18 | # 19 | # Returns `CastingHash`. 20 | # 21 | def self.[](hash) 22 | s = new 23 | hash.each{ |k,v| s[k] = v } 24 | s 25 | end 26 | 27 | # 28 | # Unlike traditional Hash a CastingHash's block argument 29 | # coerces key/value pairs when #store is called. 30 | # 31 | # default - Default value. 32 | # cast_proc - Casting procedure. 33 | # 34 | def initialize(default=nil, &cast_proc) 35 | @cast_proc = cast_proc 36 | super(default, &nil) 37 | end 38 | 39 | # 40 | # The cast procedure. 41 | # 42 | # proc - Casting procedure. 43 | # 44 | # Returns `Proc` used for casting. 45 | # 46 | def cast_proc(&proc) 47 | @cast_proc = proc if proc 48 | @cast_proc 49 | end 50 | 51 | # 52 | # Set `cast_proc`. This procedure must take two arguments (`key, value`) 53 | # and return the same. 54 | # 55 | # proc - Casting procedure. 56 | # 57 | # Returns +proc+. 58 | # 59 | def cast_proc=(proc) 60 | raise ArgumentError unless Proc === proc or NilClass === proc 61 | @cast_proc = proc 62 | end 63 | 64 | # 65 | # CRUD method for create and update. Unlike the parent class 66 | # the key, value pair are passed threw the cast_proc before 67 | # being set in the underlying hash table. 68 | # 69 | # key - Key of entry. 70 | # value - Value of entry. 71 | # 72 | # Returns the +value+. 73 | # 74 | def store(key, value) 75 | super(*cast_pair(key, value)) 76 | end 77 | 78 | # 79 | # Replace current entries with those from another Hash, 80 | # or Hash-like object. Each entry is run through the 81 | # casting procedure as it is added. 82 | # 83 | # other - Hash-like object. 84 | # 85 | # Returns +self+. 86 | # 87 | def replace(other) 88 | super cast(other) 89 | end 90 | 91 | # 92 | # Convert the CastingHash to a regular Hash. 93 | # 94 | # Returns an ordinary `Hash`. 95 | # 96 | def to_hash 97 | h = {}; each{ |k,v| h[k] = v }; h 98 | end 99 | 100 | # 101 | # Returns an ordinary `Hash`. 102 | # 103 | alias_method :to_h, :to_hash 104 | 105 | # 106 | # Recast all entries via the cast procedure. 107 | # 108 | # TODO: Isn't this the same as `#rehash`? 109 | # 110 | # Returns +self+. 111 | # 112 | def recast! 113 | replace self 114 | end 115 | 116 | private 117 | 118 | # 119 | # If `cast_proc` is defined then use it to process key-value pair, 120 | # otherwise return them as is. 121 | # 122 | # key - Key of entry. 123 | # value - Value of entry. 124 | # 125 | # Returns `Array` of key-value pair. 126 | # 127 | def cast_pair(key, value) 128 | if cast_proc 129 | return cast_proc.call(key, value) 130 | else 131 | return key, value 132 | end 133 | end 134 | 135 | # 136 | # Cast a given +hash+ according to the `#key_proc` and `#value_proc`. 137 | # 138 | # hash - A `Hash` or anything the responds to `#each` like a hash. 139 | # 140 | # Returns a recasted `Hash`. 141 | # 142 | def cast(hash) 143 | h = {} 144 | hash.each do |k,v| 145 | k, v = cast_pair(k, v) 146 | h[k] = v 147 | end 148 | h 149 | end 150 | 151 | end 152 | 153 | end 154 | 155 | # TODO: Should we add #to_casting_hash to Hash classs? 156 | 157 | #class Hash 158 | # 159 | # # Convert a Hash to a CastingHash. 160 | # def to_casting_hash(value_cast=nil, &key_cast) 161 | # CastingHash.new(self, value_cast, &key_cast) 162 | # end 163 | # 164 | #end 165 | -------------------------------------------------------------------------------- /lib/hashery/core_ext.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | 3 | # 4 | # Create a hash given an `initial_hash`. 5 | # 6 | # initial_hash - Hash or hash-like object to use as priming data. 7 | # block - Procedure used by initialize (e.g. default_proc). 8 | # 9 | # Returns a `Hash`. 10 | # 11 | def self.create(initial_hash={}, &block) 12 | o = new &block 13 | o.update(initial_hash) 14 | o 15 | end 16 | 17 | # 18 | # Like #fetch but returns the results of calling `default_proc`, if defined, 19 | # otherwise `default`. 20 | # 21 | # key - Hash key to lookup. 22 | # 23 | # Returns value of Hash entry or `nil`. 24 | # 25 | def retrieve(key) 26 | fetch(key, default_proc ? default_proc[self, key] : default) 27 | end 28 | 29 | # 30 | # Convert to Hash. 31 | # 32 | def to_hash 33 | dup # -or- `h = {}; each{ |k,v| h[k] = v }; h` ? 34 | end \ 35 | unless method_defined?(:to_hash) 36 | 37 | # 38 | # For a Hash, `#to_h` is the same as `#to_hash`. 39 | # 40 | alias :to_h :to_hash \ 41 | unless method_defined?(:to_h) 42 | 43 | # 44 | # Synonym for Hash#rekey, but modifies the receiver in place (and returns it). 45 | # 46 | # key_map - Hash of old key to new key. 47 | # block - Procedure to convert keys, which can take just the key 48 | # or both key and value as arguments. 49 | # 50 | # Examples 51 | # 52 | # foo = { :name=>'Gavin', :wife=>:Lisa } 53 | # foo.rekey!{ |k| k.to_s } #=> { "name"=>"Gavin", "wife"=>:Lisa } 54 | # foo.inspect #=> { "name"=>"Gavin", "wife"=>:Lisa } 55 | # 56 | # Returns `Hash`. 57 | # 58 | def rekey(key_map=nil, &block) 59 | if !(key_map or block) 60 | block = lambda{|k| k.to_sym} 61 | end 62 | 63 | key_map ||= {} 64 | 65 | hash = {} 66 | 67 | (keys - key_map.keys).each do |key| 68 | hash[key] = self[key] 69 | end 70 | 71 | key_map.each do |from, to| 72 | hash[to] = self[from] if key?(from) 73 | end 74 | 75 | hash2 = {} 76 | 77 | if block 78 | case block.arity 79 | when 0 80 | raise ArgumentError, "arity of 0 for #{block.inspect}" 81 | when 2 82 | hash.each do |k,v| 83 | nk = block.call(k,v) 84 | hash2[nk] = v 85 | end 86 | else 87 | hash.each do |k,v| 88 | nk = block[k] 89 | hash2[nk] = v 90 | end 91 | end 92 | else 93 | hash2 = hash 94 | end 95 | 96 | hash2 97 | end 98 | 99 | # 100 | # Synonym for Hash#rekey, but modifies the receiver in place (and returns it). 101 | # 102 | # key_map - Hash of old key to new key. 103 | # block - Procedure to convert keys, which can take just the key 104 | # or both key and value as arguments. 105 | # 106 | # Examples 107 | # 108 | # foo = { :name=>'Gavin', :wife=>:Lisa } 109 | # foo.rekey!{ |k| k.to_s } #=> { "name"=>"Gavin", "wife"=>:Lisa } 110 | # foo #=> { "name"=>"Gavin", "wife"=>:Lisa } 111 | # 112 | # Returns `Hash`. 113 | # 114 | def rekey!(key_map=nil, &block) 115 | replace(rekey(key_map, &block)) 116 | end 117 | 118 | end 119 | 120 | -------------------------------------------------------------------------------- /lib/hashery/fuzzy_hash.rb: -------------------------------------------------------------------------------- 1 | require 'set' 2 | 3 | module Hashery 4 | 5 | # FuzzyHash is a weird hash with special semantics for regex keys. 6 | # 7 | # This is useful when you want to have a lookup table that can either contain strings or regexes. 8 | # For instance, you might want a catch all for certain regexes that perform a certain logic. 9 | # 10 | # >> hash = FuzzyHash.new 11 | # >> hash[/^\d+$/] = 'number' 12 | # >> hash[/.*/] = 'something' 13 | # >> hash['chunky'] = 'bacon' 14 | # >> hash['foo'] = 'vader' 15 | # 16 | # >> hash['foo'] 17 | # << 'vader' 18 | # >> hash['food'] 19 | # << 'something' 20 | # >> hash['123'] 21 | # << 'number' 22 | # 23 | # This class is based on Joshua Hull's original FuzzyHash class. 24 | # 25 | class FuzzyHash 26 | 27 | # 28 | # 29 | # 30 | def initialize(init_hash = nil) 31 | @fuzzies = [] 32 | @hash_reverse = {} 33 | @fuzzies_reverse = {} 34 | @fuzzy_hash = {} 35 | @hash = {} 36 | init_hash.each{ |key,value| self[key] = value } if init_hash 37 | end 38 | 39 | # 40 | # 41 | # 42 | def clear 43 | hash.clear 44 | fuzzies.clear 45 | hash_reverse.clear 46 | fuzzies_reverse.clear 47 | end 48 | 49 | # 50 | # 51 | # 52 | def size 53 | hash.size + fuzzies.size 54 | end 55 | 56 | alias_method :count, :size 57 | 58 | # 59 | # 60 | # 61 | def ==(o) 62 | o.is_a?(FuzzyHash) 63 | o.send(:hash) == hash && 64 | o.send(:fuzzies) == fuzzies 65 | end 66 | 67 | # 68 | # 69 | # 70 | def empty? 71 | hash.empty? && fuzzies.empty? 72 | end 73 | 74 | # 75 | # 76 | # 77 | def keys 78 | hash.keys + fuzzy_hash.keys 79 | end 80 | 81 | # 82 | # 83 | # 84 | def values 85 | hash.values + fuzzies.collect{|r| r.last} 86 | end 87 | 88 | # 89 | # 90 | # 91 | def each 92 | hash.each{|k,v| yield k,v } 93 | fuzzies.each{|v| yield v.first, v.last } 94 | end 95 | 96 | # 97 | # 98 | # 99 | def delete_value(value) 100 | hash.delete(hash_reverse[value]) || ((rr = fuzzies_reverse[value]) && fuzzies.delete_at(rr[0])) 101 | end 102 | 103 | # 104 | # 105 | # 106 | def []=(key, value) 107 | if Regexp === key 108 | fuzzies.delete_if{|f| f.first.inspect.hash == key.inspect.hash} 109 | fuzzies_reverse.delete_if{|k, v| v[1].inspect.hash == key.inspect.hash} 110 | hash_reverse.delete_if{|k,v| v.inspect.hash == key.inspect.hash} 111 | 112 | fuzzy_hash[key] = value 113 | fuzzies << [key, value] 114 | reset_fuzz_test! 115 | fuzzies_reverse[value] = [fuzzies.size - 1, key, value] 116 | else 117 | hash[key] = value 118 | hash_reverse.delete_if{|k,v| v.hash == key.hash} 119 | hash_reverse[value] = key 120 | end 121 | value 122 | end 123 | 124 | # 125 | # 126 | # 127 | def replace(src, dest) 128 | if hash_reverse.key?(src) 129 | key = hash_reverse[src] 130 | hash[key] = dest 131 | hash_reverse.delete(src) 132 | hash_reverse[dest] = key 133 | elsif fuzzies_reverse.key?(src) 134 | key = fuzzies_reverse[src] 135 | fuzzies[rkey[0]] = [rkey[1], dest] 136 | fuzzies_reverse.delete(src) 137 | fuzzies_reverse[dest] = [rkey[0], rkey[1], dest] 138 | end 139 | end 140 | 141 | # 142 | # 143 | # 144 | def [](key) 145 | (hash.key?(key) && hash[key]) || 146 | ((lookup = fuzzy_lookup(key)) && lookup && lookup.first) || 147 | fuzzy_hash[key] 148 | end 149 | 150 | # 151 | # 152 | # 153 | def match_with_result(key) 154 | if hash.key?(key) 155 | [hash[key], key] 156 | else 157 | fuzzy_lookup(key) 158 | end 159 | end 160 | 161 | private 162 | 163 | attr_reader :fuzzies, :hash_reverse, :fuzzies_reverse, :hash, :fuzzy_hash 164 | attr_writer :fuzz_test 165 | 166 | # 167 | # 168 | # 169 | def reset_fuzz_test! 170 | self.fuzz_test = nil 171 | end 172 | 173 | # 174 | # 175 | # 176 | def fuzz_test 177 | unless @fuzz_test 178 | @fuzz_test = Object.new 179 | @fuzz_test.instance_variable_set(:'@fuzzies', fuzzies) 180 | method = " 181 | def match(str) 182 | case str\n 183 | " 184 | fuzzies.each_with_index do |reg, index| 185 | method << "when #{reg.first.inspect}; [@fuzzies[#{index}][1], Regexp.last_match(0)];" 186 | end 187 | method << "end\nend\n" 188 | @fuzz_test.instance_eval method 189 | end 190 | @fuzz_test 191 | end 192 | 193 | # 194 | # 195 | # 196 | def fuzzy_lookup(key) 197 | if !fuzzies.empty? && (value = fuzz_test.match(key)) 198 | value 199 | end 200 | end 201 | 202 | end 203 | 204 | end 205 | 206 | # Copyright (c) 2009 Joshua Hull 207 | -------------------------------------------------------------------------------- /lib/hashery/key_hash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/crud_hash' 2 | 3 | module Hashery 4 | 5 | # The KeyHash class is a Hash class which accepts a block for 6 | # normalizing keys. 7 | # 8 | # The KeyHash class is essentially the same as a normal Hash. 9 | # But notice the significant distinction of indifferent key 10 | # access. 11 | # 12 | # s = KeyHash.new 13 | # s[:x] = 1 14 | # s[:x] #=> 1 15 | # s['x'] #=> 1 16 | # 17 | # We can see that internally the key has indeed been converted 18 | # to a String. 19 | # 20 | # s.to_h #=> {'x'=>1 } 21 | # 22 | # By default all keys are converted to strings. This has two advantages 23 | # over a regular Hash is many usecases. First it means hash entries have 24 | # indifferent access. 1, "1" and :1 are all 25 | # equivalent --any object that defines #to_s can be used as a key. 26 | # Secondly, since strings are garbage collected so will default KeyHash 27 | # objects. 28 | # 29 | # But keys can be normalized by any function. Theses functions can be quite 30 | # unique. 31 | # 32 | # h = KeyHash.new(0){ |k| k.to_i } 33 | # h[1.34] += 1 34 | # h[1.20] += 1 35 | # h[1.00] += 1 36 | # h #=> { 1 => 3 } 37 | # 38 | class KeyHash < CRUDHash 39 | 40 | # 41 | # Unlike a regular Hash, a KeyHash's block sets the `key_proc` rather 42 | # than the `default_proc`. 43 | # 44 | def initialize(*default, &block) 45 | super(*default) 46 | @key_proc = block || Proc.new{ |k| k.to_s } 47 | end 48 | 49 | end 50 | 51 | end 52 | 53 | #class Hash 54 | # # 55 | # # Convert a Hash to a KeyHash object. 56 | # # 57 | # def to_keyhash 58 | # Hashery::KeyHash[self] 59 | # end 60 | #end 61 | 62 | -------------------------------------------------------------------------------- /lib/hashery/linked_list.rb: -------------------------------------------------------------------------------- 1 | require 'enumerator' 2 | 3 | module Hashery 4 | 5 | # LinkedList implements a simple doubly linked list with efficient 6 | # hash-like element access. 7 | # 8 | # This is a simple linked-list implementation with efficient random 9 | # access of data elements. It was inspired by George Moscovitis' 10 | # LRUCache implementation found in Facets 1.7.30, but unlike the 11 | # linked-list in that cache, this one does not require the use of a 12 | # mixin on any class to be stored. The linked-list provides the 13 | # push, pop, shift, unshift, first, last, delete and length methods 14 | # which work just like their namesakes in the Array class, but it 15 | # also supports setting and retrieving values by key, just like a 16 | # hash. 17 | # 18 | # LinkedList was ported from the original in Kirk Hanes IOWA web framework. 19 | # 20 | # == Acknowledgements 21 | # 22 | # LinkedList is based on the LinkedList library by Kirk Haines. 23 | # 24 | # Copyright (C) 2006 Kirk Haines . 25 | # 26 | class LinkedList 27 | 28 | include Enumerable 29 | 30 | # Represents a single node of the linked list. 31 | # 32 | class Node 33 | attr_accessor :key, :value, :prev_node, :next_node 34 | 35 | def initialize(key=nil,value=nil,prev_node=nil,next_node=nil) 36 | @key = key 37 | @value = value 38 | @prev_node = prev_node 39 | @next_node = next_node 40 | end 41 | end 42 | 43 | # 44 | # Initialize new LinkedList instance. 45 | # 46 | def initialize 47 | @head = Node.new 48 | @tail = Node.new 49 | @lookup = Hash.new 50 | 51 | node_join(@head,@tail) 52 | end 53 | 54 | # 55 | # Lookup entry by key. 56 | # 57 | def [](key) 58 | @lookup[key].value 59 | end 60 | 61 | # 62 | # Add node to linked list. 63 | # 64 | def []=(k,v) 65 | if @lookup.has_key?(k) 66 | @lookup[k].value = v 67 | else 68 | n = Node.new(k,v,@head,@head.next_node) 69 | node_join(n,@head.next_node) 70 | node_join(@head,n) 71 | @lookup[k] = n 72 | end 73 | v 74 | end 75 | 76 | # 77 | # Is linked list empty? 78 | # 79 | def empty? 80 | @lookup.empty? 81 | end 82 | 83 | # 84 | # Remove node idenified by key. 85 | # 86 | def delete(key) 87 | n = @lookup.delete(key) 88 | v = n ? node_purge(n) : nil 89 | v 90 | end 91 | 92 | # 93 | # Get value of first node. 94 | # 95 | def first 96 | @head.next_node.value 97 | end 98 | 99 | # 100 | # Get value of last node. 101 | # 102 | def last 103 | @tail.prev_node.value 104 | end 105 | 106 | # 107 | # 108 | # 109 | def shift 110 | k = @head.next_node.key 111 | n = @lookup.delete(k) 112 | node_delete(n) if n 113 | end 114 | 115 | # 116 | # 117 | # 118 | def unshift(v) 119 | if @lookup.has_key?(v) 120 | n = @lookup[v] 121 | node_delete(n) 122 | node_join(n,@head.next_node) 123 | node_join(@head,n) 124 | else 125 | n = Node.new(v,v,@head,@head.next_node) 126 | node_join(n,@head.next_node) 127 | node_join(@head,n) 128 | @lookup[v] = n 129 | end 130 | v 131 | end 132 | 133 | # 134 | # 135 | # 136 | def pop 137 | k = @tail.prev_node.key 138 | n = @lookup.delete(k) 139 | node_delete(n) if n 140 | end 141 | 142 | # 143 | # 144 | # 145 | def push(v) 146 | if @lookup.has_key?(v) 147 | n = @lookup[v] 148 | node_delete(n) 149 | node_join(@tail.prev_node,n) 150 | node_join(n,@tail) 151 | else 152 | n = Node.new(v,v,@tail.prev_node,@tail) 153 | node_join(@tail.prev_node,n) 154 | node_join(n,@tail) 155 | @lookup[v] = n 156 | end 157 | v 158 | end 159 | 160 | alias :<< :push 161 | 162 | # 163 | # Produces an Array of key values. 164 | # 165 | # Returns [Array]. 166 | # 167 | def queue 168 | r = [] 169 | n = @head 170 | while (n = n.next_node) and n != @tail 171 | r << n.key 172 | end 173 | r 174 | end 175 | 176 | # 177 | # Converts to an Array of node values. 178 | # 179 | # Returns [Array]. 180 | # 181 | def to_a 182 | r = [] 183 | n = @head 184 | while (n = n.next_node) and n != @tail 185 | r << n.value 186 | end 187 | r 188 | end 189 | 190 | # 191 | # Number of nodes. 192 | # 193 | def length 194 | @lookup.length 195 | end 196 | 197 | alias size length 198 | 199 | # 200 | # Iterate over nodes, starting with the head node 201 | # and ending with the tail node. 202 | # 203 | def each 204 | n = @head 205 | while (n = n.next_node) and n != @tail 206 | yield(n.key,n.value) 207 | end 208 | end 209 | 210 | private 211 | 212 | # 213 | # Delete a node. 214 | # 215 | # n - A node. 216 | # 217 | def node_delete(n) 218 | node_join(n.prev_node,n.next_node) 219 | v = n.value 220 | end 221 | 222 | # 223 | # Purge a node. 224 | # 225 | # n - A node. 226 | # 227 | def node_purge(n) 228 | node_join(n.prev_node,n.next_node) 229 | v = n.value 230 | n.value = nil 231 | n.key = nil 232 | n.next_node = nil 233 | n.prev_node = nil 234 | v 235 | end 236 | 237 | # Join two nodes. 238 | # 239 | # a - A node. 240 | # b - A node. 241 | # 242 | def node_join(a,b) 243 | a.next_node = b 244 | b.prev_node = a 245 | end 246 | 247 | end 248 | 249 | end 250 | -------------------------------------------------------------------------------- /lib/hashery/lru_hash.rb: -------------------------------------------------------------------------------- 1 | require 'enumerator' 2 | 3 | module Hashery 4 | 5 | # Hash with LRU expiry policy. There are at most max_size elements in a 6 | # LRUHash. When adding more elements old elements are removed according 7 | # to LRU policy. 8 | # 9 | # Based on Robert Klemme's LRUHash class. 10 | # 11 | # LRUHash, Copyright (c) 2010 Robert Klemme. 12 | # 13 | class LRUHash 14 | 15 | include Enumerable 16 | 17 | attr_reader :max_size 18 | 19 | attr_accessor :default 20 | attr_accessor :default_proc 21 | attr_accessor :release_proc 22 | 23 | # 24 | # Initialize new LRUHash instance. 25 | # 26 | # max_size - 27 | # default_value - 28 | # block - 29 | # 30 | def initialize(max_size, default_value=nil, &block) 31 | @max_size = normalize_max(max_size) 32 | @default = default_value 33 | @default_proc = block 34 | 35 | @h = {} 36 | @head = Node.new 37 | @tail = front(Node.new) 38 | end 39 | 40 | # 41 | # Iterate over each pair. 42 | # 43 | def each_pair 44 | if block_given? 45 | each_node do |n| 46 | yield [n.key, n.value] 47 | end 48 | else 49 | enum_for :each_pair 50 | end 51 | end 52 | 53 | # 54 | # Same as each pair. 55 | # 56 | alias each each_pair 57 | 58 | # 59 | # Iterate over each key. 60 | # 61 | def each_key 62 | if block_given? 63 | each_node do |n| 64 | yield n.key 65 | end 66 | else 67 | enum_for :each_key 68 | end 69 | end 70 | 71 | # 72 | # Iterate over each value. 73 | # 74 | def each_value 75 | if block_given? 76 | each_node do |n| 77 | yield n.value 78 | end 79 | else 80 | enum_for :each_value 81 | end 82 | end 83 | 84 | # 85 | # Size of the hash. 86 | # 87 | def size 88 | @h.size 89 | end 90 | 91 | # 92 | # 93 | # 94 | def empty? 95 | @head.succ.equal? @tail 96 | end 97 | 98 | # 99 | # 100 | # 101 | def fetch(key, &b) 102 | n = @h[key] 103 | 104 | if n 105 | front(n).value 106 | else 107 | (b || FETCH)[key] 108 | end 109 | end 110 | 111 | # 112 | # 113 | # 114 | def [](key) 115 | fetch(key) do |k| 116 | @default_proc ? @default_proc[self, k] : default 117 | end 118 | end 119 | 120 | # 121 | # 122 | # 123 | def keys 124 | @h.keys 125 | end 126 | 127 | # 128 | # 129 | # 130 | def values 131 | @h.map {|k,n| n.value} 132 | end 133 | 134 | # 135 | # 136 | # 137 | def has_key?(key) 138 | @h.has_key? key 139 | end 140 | 141 | alias key? has_key? 142 | alias member? has_key? 143 | alias include? has_key? 144 | 145 | # 146 | # 147 | # 148 | def has_value?(value) 149 | each_pair do |k, v| 150 | return true if value.eql? v 151 | end 152 | 153 | false 154 | end 155 | 156 | alias value? has_value? 157 | 158 | def values_at(*key_list) 159 | key_list.map {|k| self[k]} 160 | end 161 | 162 | # 163 | # 164 | # 165 | def assoc(key) 166 | n = @h[key] 167 | 168 | if n 169 | front(n) 170 | [n.key, n.value] 171 | end 172 | end 173 | 174 | # 175 | # 176 | # 177 | def rassoc(value) 178 | each_node do |n| 179 | if value.eql? n.value 180 | front(n) 181 | return [n.key, n.value] 182 | end 183 | end 184 | nil 185 | end 186 | 187 | # 188 | # 189 | # 190 | def key(value) 191 | pair = rassoc(value) and pair.first 192 | end 193 | 194 | # 195 | # 196 | # 197 | def store(key, value) 198 | # same optimization as in Hash 199 | key = key.dup.freeze if String === key && !key.frozen? 200 | 201 | n = @h[key] 202 | 203 | unless n 204 | if size == max_size 205 | # reuse node to optimize memory usage 206 | n = delete_oldest 207 | n.key = key 208 | n.value = value 209 | else 210 | n = Node.new key, value 211 | end 212 | 213 | @h[key] = n 214 | end 215 | 216 | front(n).value = value 217 | end 218 | 219 | alias []= store 220 | 221 | # 222 | # 223 | # 224 | def delete(key) 225 | n = @h[key] and remove_node(n).value 226 | end 227 | 228 | # 229 | # 230 | # 231 | def delete_if 232 | each_node do |n| 233 | remove_node n if yield n.key, n.value 234 | end 235 | end 236 | 237 | # 238 | # 239 | # 240 | def max_size=(limit) 241 | limit = normalize_max(limit) 242 | 243 | while size > limit 244 | delete_oldest 245 | end 246 | 247 | @max_size = limit 248 | end 249 | 250 | # 251 | # 252 | # 253 | def clear 254 | until empty? 255 | delete_oldest 256 | end 257 | 258 | self 259 | end 260 | 261 | # 262 | # 263 | # 264 | def to_s 265 | s = nil 266 | each_pair {|k, v| (s ? (s << ', ') : s = '{') << k.to_s << '=>' << v.to_s} 267 | s ? (s << '}') : '{}' 268 | end 269 | 270 | alias inspect to_s 271 | 272 | private 273 | 274 | # 275 | # Iterate nodes. 276 | # 277 | def each_node 278 | n = @head.succ 279 | 280 | until n.equal? @tail 281 | succ = n.succ 282 | yield n 283 | n = succ 284 | end 285 | 286 | self 287 | end 288 | 289 | # 290 | # Move node to front. 291 | # 292 | # node - [Node] 293 | # 294 | def front(node) 295 | node.insert_after(@head) 296 | end 297 | 298 | # 299 | # Remove the node and invoke release_proc 300 | # if set 301 | # 302 | # node - [Node] 303 | # 304 | def remove_node(node) 305 | n = @h.delete(node.key) 306 | n.unlink 307 | release_proc and release_proc[n.key, n.value] 308 | n 309 | end 310 | 311 | # 312 | # Remove the oldest node returning the node 313 | # 314 | def delete_oldest 315 | n = @tail.pred 316 | raise "Cannot delete from empty hash" if @head.equal? n 317 | remove_node n 318 | end 319 | 320 | # 321 | # Normalize the argument in order to be usable as max_size 322 | # criterion is that n.to_i must be an Integer and it must 323 | # be larger than zero. 324 | # 325 | # n - [#to_i] max size 326 | # 327 | def normalize_max(n) 328 | n = n.to_i 329 | raise ArgumentError, 'Invalid max_size: %p' % n unless Integer === n && n > 0 330 | n 331 | end 332 | 333 | # 334 | FETCH = Proc.new {|k| raise KeyError, 'key not found'} 335 | 336 | # A single node in the doubly linked LRU list of nodes. 337 | Node = Struct.new :key, :value, :pred, :succ do 338 | def unlink 339 | pred.succ = succ if pred 340 | succ.pred = pred if succ 341 | self.succ = self.pred = nil 342 | self 343 | end 344 | 345 | def insert_after(node) 346 | raise 'Cannot insert after self' if equal? node 347 | return self if node.succ.equal? self 348 | 349 | unlink 350 | 351 | self.succ = node.succ 352 | self.pred = node 353 | 354 | node.succ.pred = self if node.succ 355 | node.succ = self 356 | 357 | self 358 | end 359 | end 360 | 361 | end 362 | 363 | end 364 | -------------------------------------------------------------------------------- /lib/hashery/open_cascade.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/open_hash' 2 | 3 | module Hashery 4 | 5 | # OpenCascade is subclass of OpenHash. It differs in a few 6 | # significant ways. The reason this class is called "cascade" is that 7 | # every internal Hash is transformed into an OpenCascade dynamically 8 | # upon access. This makes it easy to create "cascading" references. 9 | # 10 | # h = { :x => { :y => { :z => 1 } } } 11 | # c = OpenCascade[h] 12 | # c.x.y.z #=> 1 13 | # 14 | # As soon as you access a node it automatically becomes an OpenCascade. 15 | # 16 | # c = OpenCascade.new #=> # 17 | # c.r #=> # 18 | # c.a.b #=> # 19 | # 20 | # But if you set a node, then that will be that value. 21 | # 22 | # c.a.b = 4 #=> 4 23 | # 24 | # To query a node without causing the auto-creation of an OpenCasade 25 | # instance, use the `?`-mark. 26 | # 27 | # c.a.z? #=> nil 28 | # 29 | # OpenCascade also transforms Hashes within Arrays. 30 | # 31 | # h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 } 32 | # c = OpenCascade[h] 33 | # c.x.first.a.assert == 1 34 | # c.x.last.a.assert == 2 35 | # 36 | # Finally, you can set call a private method via bang methods using the `!`-mark. 37 | # 38 | # c = OpenCascade.new #=> # 39 | # c.each = 4 40 | # c.each! do |k,v| 41 | # ... 42 | # end 43 | # 44 | # c.x!(4).y!(3) #=> #4, :y=>3}> 45 | # 46 | # Subclassing OpenCascade with cause the new subclass to become the class that 47 | # is auto-created. If this is not the behavior desired, consider using delegation 48 | # instead of subclassing. 49 | # 50 | class OpenCascade < OpenHash 51 | 52 | # 53 | #def self.[](hash) 54 | # oc = new 55 | # hash.each{ |(k,v)| oc.store(k,v) } 56 | # oc 57 | #end 58 | 59 | # 60 | # Initialize new OpenCascade instance. 61 | # 62 | # default - The usual default object. 63 | # 64 | def initialize(*default) 65 | @read = {} 66 | 67 | leet = lambda { |h,k| h[k] = self.class.new(&leet) } 68 | super(*default, &leet) 69 | end 70 | 71 | # 72 | # Alias for original read method. 73 | # 74 | alias :retrieve! :retrieve 75 | 76 | # 77 | # Read value given a +key+. 78 | # 79 | # key - Index key to lookup. 80 | # 81 | # Returns value. 82 | # 83 | def retrieve(key) 84 | ckey = cast_key(key) 85 | if @read[ckey] 86 | super(key) 87 | else 88 | @read[ckey] = store(key, cast_value(super(key))) 89 | end 90 | end 91 | 92 | # 93 | # 94 | # 95 | def method_missing(sym, *args, &blk) 96 | type = sym.to_s[-1,1] 97 | name = sym.to_s.gsub(/[=!?]$/, '').to_sym 98 | 99 | case type 100 | when '=' 101 | store(name, args.first) 102 | when '?' 103 | key?(name) ? retrieve!(name) : nil # key?(name) 104 | when '!' 105 | __send__(name, *args, &blk) 106 | else 107 | #if key?(name) 108 | retrieve(name) 109 | #else 110 | # #default = OpenCascade.new #self.class.new 111 | # #default = default_proc ? default_proc.call(self, name) : default 112 | # store(name, read(name)) 113 | #end 114 | end 115 | end 116 | 117 | def respond_to?(sym, include_private = false) 118 | sym != :to_ary && super 119 | end 120 | 121 | #def each 122 | # super do |key, entry| 123 | # yield([key, transform_entry(entry)]) 124 | # end 125 | #end 126 | 127 | private 128 | 129 | # 130 | # Cast value, such that Hashes are converted to OpenCascades. 131 | # And Hashes in Arrays are converted to OpenCascades as well. 132 | # 133 | def cast_value(entry) 134 | case entry 135 | when Hash 136 | e = OpenCascade.new 137 | e.key_proc = key_proc if key_proc 138 | e.merge!(entry) 139 | e 140 | when Array 141 | entry.map{ |e| cast_value(e) } 142 | else 143 | entry 144 | end 145 | end 146 | 147 | end 148 | 149 | end 150 | 151 | 152 | #-- 153 | # Last, when an entry is not found, 'null' is returned rather then 'nil'. 154 | # This allows for run-on entries withuot error. Eg. 155 | # 156 | # o = OpenCascade.new 157 | # o.a.b.c #=> null 158 | # 159 | # Unfortuately this requires an explict test for null? in 'if' conditions. 160 | # 161 | # if o.a.b.c.null? # true if null 162 | # if o.a.b.c.nil? # true if nil or null 163 | # if o.a.b.c.not? # true if nil or null or false 164 | # 165 | # So be sure to take that into account. 166 | #++ 167 | 168 | -------------------------------------------------------------------------------- /lib/hashery/open_hash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/crud_hash' 2 | 3 | module Hashery 4 | 5 | # OpenHash is a Hash, but also supports open properties much like 6 | # OpenStruct. 7 | # 8 | # Only names that are name methods of Hash can be used as open slots. 9 | # To open a slot for a name that would otherwise be a method, the 10 | # method needs to be made private. The `#open!` method can be used 11 | # to handle this. 12 | # 13 | # Examples 14 | # 15 | # o = OpenHash.new 16 | # o.open!(:send) 17 | # o.send = 4 18 | # 19 | class OpenHash < CRUDHash 20 | 21 | alias :object_class :class 22 | 23 | #FILTER = /(^__|^\W|^instance_|^object_|^to_)/ 24 | #methods = Hash.instance_methods(true).select{ |m| m !~ FILTER } 25 | #methods = methods - [:each, :inspect, :send] # :class, :as] 26 | #private *methods 27 | 28 | # 29 | # Initialize new OpenHash instance. 30 | # 31 | # TODO: Maybe `safe` should be the first argument? 32 | # 33 | def initialize(default=nil, safe=false, &block) 34 | @safe = safe 35 | super(*[default].compact, &block) 36 | end 37 | 38 | # 39 | # If safe is set to true, then public methods cannot be overriden 40 | # by hash keys. 41 | # 42 | attr_accessor :safe 43 | 44 | # 45 | # Alias to original store method. 46 | # 47 | #alias :store! :store 48 | 49 | # 50 | # Index `value` to `key`. Unless safe mode, will also open up the 51 | # key if it is not already open. 52 | # 53 | # key - Index key to associate with value. 54 | # value - Value to be associate with key. 55 | # 56 | # Returns +value+. 57 | # 58 | def store(key, value) 59 | #open!(key) 60 | super(key, value) 61 | end 62 | 63 | # 64 | # Open up a slot that that would normally be a Hash method. 65 | # 66 | # The only methods that can't be opened are ones starting with `__`. 67 | # 68 | # methods - [Array] method names 69 | # 70 | # Returns Array of slot names that were opened. 71 | # 72 | def open!(*methods) 73 | # Only select string and symbols, any other type of key is allowed, 74 | # it just won't be accessible via dynamic methods. 75 | methods = methods.select{ |x| String === x || Symbol === x } 76 | if methods.any?{ |m| m.to_s.start_with?('__') } 77 | raise ArgumentError, "cannot open shadow methods" 78 | end 79 | # only public methods need be made protected 80 | methods = methods.map{ |x| x.to_sym } 81 | methods = methods & public_methods(true).map{ |x| x.to_sym } 82 | if @safe 83 | raise ArgumentError, "cannot set public method" unless methods.empty? 84 | else 85 | (class << self; self; end).class_eval{ protected *methods } 86 | end 87 | methods 88 | end 89 | 90 | # @deprecated 91 | alias :omit! :open! 92 | 93 | # 94 | # Is a slot open? 95 | # 96 | # method - [String,Symbol] method name 97 | # 98 | # Returns `true` or `false`. 99 | # 100 | def open?(method) 101 | methods = public_methods(true).map{ |m| m.to_sym } 102 | ! methods.include?(method.to_sym) 103 | end 104 | 105 | # 106 | # Make specific Hash methods available for use that have previously opened. 107 | # 108 | # methods - [Array] method names 109 | # 110 | # Returns +methods+. 111 | # 112 | def close!(*methods) 113 | (class << self; self; end).class_eval{ public *methods } 114 | methods 115 | end 116 | 117 | # 118 | # 119 | # 120 | def method_missing(s,*a, &b) 121 | type = s.to_s[-1,1] 122 | name = s.to_s.sub(/[!?=]$/, '') 123 | key = name.to_sym 124 | 125 | case type 126 | when '=' 127 | store(key, a.first) 128 | when '?' 129 | key?(key) 130 | when '!' 131 | # call an underlying private method 132 | # TODO: limit this to omitted methods (from included) ? 133 | __send__(name, *a, &b) 134 | else 135 | #if key?(key) 136 | retrieve(key) 137 | #else 138 | # super(s,*a,&b) 139 | #end 140 | end 141 | end 142 | 143 | end 144 | 145 | end 146 | -------------------------------------------------------------------------------- /lib/hashery/ordered_hash.rb: -------------------------------------------------------------------------------- 1 | module Hashery 2 | 3 | # OrderedHash is a simple ordered hash implmentation, for users of 4 | # Ruby 1.8.7 or less. 5 | # 6 | # NOTE: As of Ruby 1.9+ this class is not needed, since 7 | # Ruby 1.9's standard Hash tracks inseration order. 8 | # 9 | # This implementation derives from the same class in 10 | # ActiveSupport library. 11 | # 12 | class OrderedHash < ::Hash 13 | 14 | def to_yaml_type 15 | "!tag:yaml.org,2002:omap" 16 | end 17 | 18 | def to_yaml(opts = {}) 19 | YAML.quick_emit(self, opts) do |out| 20 | out.seq(taguri, to_yaml_style) do |seq| 21 | each do |k, v| 22 | seq.add(k => v) 23 | end 24 | end 25 | end 26 | end 27 | 28 | # Hash is ordered in Ruby 1.9! 29 | if RUBY_VERSION < '1.9' 30 | 31 | def initialize(*args, &block) 32 | super 33 | @keys = [] 34 | end 35 | 36 | def self.[](*args) 37 | ordered_hash = new 38 | 39 | if (args.length == 1 && args.first.is_a?(Array)) 40 | args.first.each do |key_value_pair| 41 | next unless (key_value_pair.is_a?(Array)) 42 | ordered_hash[key_value_pair[0]] = key_value_pair[1] 43 | end 44 | 45 | return ordered_hash 46 | end 47 | 48 | unless (args.size % 2 == 0) 49 | raise ArgumentError.new("odd number of arguments for Hash") 50 | end 51 | 52 | args.each_with_index do |val, ind| 53 | next if (ind % 2 != 0) 54 | ordered_hash[val] = args[ind + 1] 55 | end 56 | 57 | ordered_hash 58 | end 59 | 60 | def initialize_copy(other) 61 | super(other) 62 | @keys = other.keys 63 | end 64 | 65 | def []=(key, value) 66 | @keys << key unless key?(key) 67 | super(key, value) 68 | end 69 | 70 | def delete(key) 71 | if has_key? key 72 | index = @keys.index(key) 73 | @keys.delete_at(index) 74 | end 75 | super(key) 76 | end 77 | 78 | def delete_if 79 | super 80 | sync_keys! 81 | self 82 | end 83 | 84 | def reject! 85 | super 86 | sync_keys! 87 | self 88 | end 89 | 90 | def reject(&block) 91 | dup.reject!(&block) 92 | end 93 | 94 | def keys 95 | @keys.dup 96 | end 97 | 98 | def values 99 | @keys.collect{ |key| self[key] } 100 | end 101 | 102 | def to_hash 103 | self 104 | end 105 | 106 | def to_a 107 | @keys.map{ |key| [ key, self[key] ] } 108 | end 109 | 110 | def each_key 111 | @keys.each{ |key| yield(key) } 112 | end 113 | 114 | def each_value 115 | @keys.each{ |key| yield(self[key]) } 116 | end 117 | 118 | def each 119 | @keys.each{ |key| yield(key, self[key]) } 120 | end 121 | 122 | alias_method :each_pair, :each 123 | 124 | def clear 125 | super 126 | @keys.clear 127 | self 128 | end 129 | 130 | def shift 131 | k = @keys.first 132 | v = delete(k) 133 | [k, v] 134 | end 135 | 136 | def merge!(other_hash) 137 | other_hash.each{ |k,v| self[k] = v } 138 | self 139 | end 140 | 141 | def merge(other_hash) 142 | dup.merge!(other_hash) 143 | end 144 | 145 | # When replacing with another hash, the initial order of our 146 | # keys must come from the other hash, ordered or not. 147 | def replace(other) 148 | super 149 | @keys = other.keys 150 | self 151 | end 152 | 153 | def inspect 154 | "#" 155 | end 156 | 157 | private 158 | 159 | def sync_keys! 160 | @keys.delete_if{ |k| !key?(k) } 161 | end 162 | 163 | end 164 | 165 | end 166 | 167 | end 168 | 169 | require 'yaml' 170 | 171 | YAML.add_builtin_type("omap") do |type, val| 172 | OrderedHash[val.map(&:to_a).map(&:first)] 173 | end 174 | 175 | -------------------------------------------------------------------------------- /lib/hashery/path_hash.rb: -------------------------------------------------------------------------------- 1 | module Hashery 2 | 3 | # A PathHash is a hash whose values can be accessed in the normal manner, 4 | # or with keys that are slash (`/`) separated strings. To get the whole hash 5 | # as a single flattened level, call `#flat`. All keys are converted to strings. 6 | # All end-of-the-chain values are kept in whatever value they are. 7 | # 8 | # s = PathHash['a' => 'b', 'c' => {'d' => :e}] 9 | # s['a'] #=> 'b' 10 | # s['c'] #=> {slashed: 'd'=>:e} 11 | # s['c']['d'] #=> :e 12 | # s['c/d'] #=> :e 13 | # 14 | # PathHash is derived from the SlashedHash class in the HashMagic project 15 | # by Daniel Parker . 16 | # 17 | # Copyright (c) 2006 BehindLogic (http://hash_magic.rubyforge.org) 18 | # 19 | # Authors: Daniel Parker 20 | # 21 | # TODO: This class is very much a work in progess and will be substantially rewritten 22 | # for future versions. 23 | # 24 | class PathHash < Hash 25 | 26 | # 27 | # Initialize PathHash. 28 | # 29 | # hsh - Priming Hash. 30 | # 31 | def initialize(hsh={}) 32 | raise ArgumentError, "must be a hash or array of slashed values" unless hsh.is_a?(Hash) || hsh.is_a?(Array) 33 | @constructor = hsh.is_a?(Hash) ? hsh.class : Hash 34 | @flat = flatten_to_hash(hsh) 35 | end 36 | 37 | # Standard Hash methods, plus the overwritten ones 38 | #include StandardHashMethodsInRuby 39 | 40 | # Behaves like the usual Hash#[] method, but you can access nested hash 41 | # values by composing a single key of the traversing keys joined by '/': 42 | # 43 | # hash['c']['d'] # is the same as: 44 | # hash['c/d'] 45 | # 46 | def [](key) 47 | rg = Regexp.new("^#{key}/?") 48 | start_obj = if @constructor == OrderedHash 49 | @constructor.new((@flat.instance_variable_get(:@keys_in_order) || []).collect {|e| e.gsub(rg,'')}) 50 | else 51 | @constructor.new 52 | end 53 | v = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ rg)}.inject(start_obj) {|h,(k,v)| h[k.gsub(rg,'')] = v; h}) 54 | v.is_a?(self.class) && v.empty? ? nil : v 55 | end 56 | 57 | # 58 | # Same as above, except sets value rather than retrieving it. 59 | # 60 | def []=(key,value) 61 | @flat.reject! {|k,v| k == key || k =~ Regexp.new("^#{key}/")} 62 | if value.is_a?(Hash) 63 | flatten_to_hash(value).each do |hk,hv| 64 | @flat[key.to_s+'/'+hk.to_s] = hv 65 | end 66 | else 67 | @flat[key.to_s] = value 68 | end 69 | end 70 | 71 | def clear # :nodoc: 72 | @flat.clear 73 | end 74 | 75 | # 76 | # 77 | # 78 | def fetch(key,default=:ehisehoah0928309q98y30,&block) # :nodoc: 79 | value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h}) 80 | if value.is_a?(self.class) && value.empty? 81 | if default == :ehisehoah0928309q98y30 82 | if block_given? 83 | block.call(key) 84 | else 85 | raise IndexError 86 | end 87 | value 88 | else 89 | default 90 | end 91 | else 92 | value 93 | end 94 | end 95 | 96 | # 97 | # Delete entry from Hash. Slashed keys can be used here, too. 98 | # 99 | # key - The key to delete. 100 | # block - Produces the return value if key not found. 101 | # 102 | # Returns delete value. 103 | # 104 | def delete(key,&block) 105 | value = @flat.has_key?(key) ? @flat[key] : self.class.new(@flat.reject {|k,v| !(k == key || k =~ Regexp.new("^#{key}/"))}.inject({}) {|h,(k,v)| h[k.split('/',2)[1]] = v; h}) 106 | return block.call(key) if value.is_a?(self.class) && value.empty? && block_given? 107 | @flat.keys.reject {|k| !(k == key || k =~ Regexp.new("^#{key}/"))}.each {|k| @flat.delete(k)} 108 | return value 109 | end 110 | 111 | # 112 | def empty? 113 | @flat.empty? 114 | end 115 | 116 | # This gives you the slashed key of the value, no matter where the value is in the tree. 117 | def index(value) 118 | @flat.index(value) 119 | end 120 | 121 | # 122 | def inspect 123 | @flat.inspect.insert(1,'slashed: ') 124 | end 125 | 126 | # This gives you only the top-level keys, no slashes. To get the list of slashed keys, do hash.flat.keys 127 | def keys 128 | @flat.inject([]) {|a,(k,v)| a << [k.split('/',2)].flatten[0]; a}.uniq 129 | end 130 | 131 | # This is rewritten to mean something slightly different than usual: Use this to restructure the hash, for cases when you 132 | # end up with an array holding several hashes. 133 | def rehash # :nodoc: 134 | @flat.rehash 135 | end 136 | 137 | # Gives a list of all keys in all levels in the multi-level hash, joined by slashes. 138 | # 139 | # {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed.flat.keys 140 | # #=> ['a/b', 'a/c', 'b'] 141 | # 142 | def flat 143 | @flat 144 | end 145 | 146 | # Expands the whole hash to Hash objects ... not useful very often, it seems. 147 | def expand 148 | inject({}) {|h,(k,v)| h[k] = v.is_a?(SlashedHash) ? v.expand : v; h} 149 | end 150 | 151 | def to_string_array 152 | flatten_to_array(flat,[]) 153 | end 154 | 155 | def slashed # :nodoc: 156 | self 157 | end 158 | 159 | # Same as ordered! but returns a new SlashedHash object instead of modifying the same. 160 | def ordered(*keys_in_order) 161 | dup.ordered!(*keys_in_order) 162 | end 163 | 164 | # Sets the SlashedArray as ordered. The *keys_in_order must be a flat array 165 | # of slashed keys that specify the order for each level: 166 | # 167 | # s = {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed 168 | # s.ordered!('b', 'a/c', 'a/b') 169 | # s.expand # => {'b'=>'c', 'a'=>{'c'=>'d', 'b'=>'c'}} 170 | # # Note that the expanded hashes will *still* be ordered! 171 | # 172 | def ordered!(*keys_in_order) 173 | return self if @constructor == OrderedHash 174 | @constructor = OrderedHash 175 | @flat = @flat.ordered(*keys_in_order) 176 | self 177 | end 178 | 179 | # 180 | def ==(other) 181 | case other 182 | when SlashedHash 183 | @slashed == other.instance_variable_get(:@slashed) 184 | when Hash 185 | self == SlashedHash.new(other) 186 | else 187 | raise TypeError, "Cannot compare #{other.class.name} with SlashedHash" 188 | end 189 | end 190 | 191 | private 192 | 193 | def flatten_to_hash(hsh) 194 | flat = @constructor.new 195 | if hsh.is_a?(Array) 196 | hsh.each do |e| 197 | flat.merge!(flatten_to_hash(e)) 198 | end 199 | elsif hsh.is_a?(Hash) 200 | hsh.each do |k,v| 201 | if v.is_a?(Hash) 202 | flatten_to_hash(v).each do |hk,hv| 203 | flat[k.to_s+'/'+hk.to_s] = hv 204 | end 205 | else 206 | flat[k.to_s] = v 207 | end 208 | end 209 | else 210 | ks = hsh.split('/',-1) 211 | v = ks.pop 212 | ks = ks.join('/') 213 | if !flat[ks].nil? 214 | if flat[ks].is_a?(Array) 215 | flat[ks] << v 216 | else 217 | flat[ks] = [flat[ks], v] 218 | end 219 | else 220 | flat[ks] = v 221 | end 222 | end 223 | flat 224 | end 225 | 226 | def flatten_to_array(value,a) 227 | if value.is_a?(Array) 228 | value.each {|e| flatten_to_array(e,a)} 229 | elsif value.is_a?(Hash) 230 | value.inject([]) {|aa,(k,v)| flatten_to_array(v,[]).each {|vv| aa << k+'/'+vv.to_s}; aa}.each {|e| a << e} 231 | else 232 | a << value.to_s 233 | end 234 | a 235 | end 236 | end 237 | 238 | end 239 | -------------------------------------------------------------------------------- /lib/hashery/property_hash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/crud_hash' 2 | 3 | module Hashery 4 | 5 | # A PropertyHash is the same as a regular Hash except it strictly limits the 6 | # allowed keys. 7 | # 8 | # There are two ways to use it. 9 | # 10 | # 1) As an object in itself. 11 | # 12 | # h = PropertyHash.new(:a=>1, :b=>2) 13 | # h[:a] #=> 1 14 | # h[:a] = 3 15 | # h[:a] #=> 3 16 | # 17 | # But if we try to set key that was not fixed, then we will get an error. 18 | # 19 | # h[:x] = 5 #=> ArgumentError 20 | # 21 | # 2) As a superclass. 22 | # 23 | # class MyPropertyHash < PropertyHash 24 | # property :a, :default => 1 25 | # property :b, :default => 2 26 | # end 27 | # 28 | # h = MyPropertyHash.new 29 | # h[:a] #=> 1 30 | # h[:a] = 3 31 | # h[:a] #=> 3 32 | # 33 | # Again, if we try to set key that was not fixed, then we will get an error. 34 | # 35 | # h[:x] = 5 #=> ArgumentError 36 | # 37 | class PropertyHash < CRUDHash 38 | 39 | # 40 | # Get a list of properties with default values. 41 | # 42 | # Returns [Hash] of properties and their default values. 43 | # 44 | def self.properties 45 | @properties ||= ( 46 | parent = ancestors[1] 47 | if parent.respond_to?(:properties) 48 | parent.properties 49 | else 50 | {} 51 | end 52 | ) 53 | end 54 | 55 | # 56 | # Define a property. 57 | # 58 | # key - Name of property. 59 | # opts - Property options. 60 | # :default - Default value of property. 61 | # 62 | # Returns default value. 63 | # 64 | def self.property(key, opts={}) 65 | properties[key] = opts[:default] 66 | end 67 | 68 | # 69 | # Initialize new instance of PropertyHash. 70 | # 71 | # properties - [Hash] Priming properties with default values, or 72 | # if it doesn't respond to #each_pair, a default object. 73 | # default_proc - [Proc] Procedure for default value of properties 74 | # for properties without specific defaults. 75 | # 76 | def initialize(properties={}, &default_proc) 77 | if properties.respond_to?(:each_pair) 78 | super(&default_proc) 79 | fixed = self.class.properties.merge(properties) 80 | fixed.each_pair do |key, value| 81 | store!(key, value) 82 | end 83 | else 84 | super(*[properties].compact, &default_proc) 85 | end 86 | end 87 | 88 | # Alias original #store method and make private. 89 | alias :store! :store 90 | private :store! 91 | 92 | # 93 | # Create a new property, on-the-fly. 94 | # 95 | # key - Name of property. 96 | # opts - Property options. 97 | # :default - Default value of property. 98 | # 99 | # Returns default value. 100 | # 101 | def property(key, opts={}) 102 | if opts[:default] 103 | store!(key, opts[:default]) 104 | else 105 | store!(key, retrieve(key)) 106 | end 107 | end 108 | 109 | # 110 | # Store key value pair, ensuring the key is a valid property first. 111 | # 112 | # key - The `Object` to act as indexing key. 113 | # value - The `Object` to associate with key. 114 | # 115 | # Raises ArgumentError if key is not a valid property. 116 | # 117 | # Returns +value+. 118 | # 119 | def store(key, value) 120 | assert_key!(key) 121 | super(key, value) 122 | end 123 | 124 | # 125 | #def update(h) 126 | # h.keys.each{ |k| assert_key!(k) } 127 | # super(h) 128 | #end 129 | 130 | # 131 | #def merge!(h) 132 | # h.keys.each{ |k| assert_key!(k) } 133 | # super(h) 134 | #end 135 | 136 | # 137 | # Like #store but takes a two-element Array of `[key, value]`. 138 | # 139 | # Returns value. 140 | # 141 | #def <<(a) 142 | # k,v = *a 143 | # store(k,v) 144 | #end 145 | 146 | private 147 | 148 | # 149 | # Asserta that a key is a defined property. 150 | # 151 | # Raises ArgumentError if key is not a property. 152 | # 153 | def assert_key!(key) 154 | unless key?(key) 155 | raise ArgumentError, "property is not defined -- #{key.inspect}" 156 | end 157 | end 158 | 159 | end 160 | 161 | end 162 | -------------------------------------------------------------------------------- /lib/hashery/query_hash.rb: -------------------------------------------------------------------------------- 1 | module Hashery 2 | 3 | require 'hashery/key_hash' 4 | 5 | # QueryHash is essentially a Hash class, but with some OpenStruct-like features. 6 | # 7 | # q = QueryHash.new 8 | # 9 | # Entries can be added to the Hash via a setter method. 10 | # 11 | # q.a = 1 12 | # 13 | # Then looked up via a query method. 14 | # 15 | # q.a? #=> 1 16 | # 17 | # The can also be looked up via a bang method. 18 | # 19 | # q.a! #=> 1 20 | # 21 | # The difference between query methods and bang methods is that the bang method 22 | # will auto-instantiate the entry if not present, where as a query method will not. 23 | # 24 | # A QueryHash might not be quite as elegant as an OpenHash in that reader 25 | # methods must end in `?` or `!`, but it remains fully compatible with Hash 26 | # regardless of it's settings. 27 | # 28 | class QueryHash < CRUDHash 29 | 30 | # 31 | # By default the `key_proc` is set to convert all keys to strings via `#to_s`. 32 | # 33 | # default - Default object, or 34 | # default_proc - Default procedure. 35 | # 36 | def initialize(*default, &default_proc) 37 | @key_proc = Proc.new{ |k| k.to_s } 38 | super(*default, &default_proc) 39 | end 40 | 41 | # 42 | # Route get and set calls. 43 | # 44 | # s - [Symbol] Name of method. 45 | # a - [Array] Method arguments. 46 | # b - [Proc] Block argument. 47 | # 48 | # Examples 49 | # 50 | # o = QueryHash.new 51 | # o.a = 1 52 | # o.a? #=> 1 53 | # o.b? #=> nil 54 | # 55 | def method_missing(s,*a, &b) 56 | type = s.to_s[-1,1] 57 | name = s.to_s.sub(/[!?=]$/, '') 58 | key = name #key = cast_key(name) 59 | 60 | case type 61 | when '=' 62 | store(key, a.first) 63 | when '!' 64 | default = (default_proc ? default_proc.call(self, key) : default) 65 | key?(key) ? fetch(key) : store(key, default) 66 | when '?' 67 | key?(key) ? fetch(key) : nil 68 | else 69 | # return self[key] if key?(key) 70 | super(s,*a,&b) 71 | end 72 | end 73 | 74 | # 75 | # Custom #respond_to to account for #method_missing. 76 | # 77 | # name - The method name to check. 78 | # 79 | # Returns `true` or `false`. 80 | # 81 | def respond_to?(name) 82 | return true if name.to_s.end_with?('=') 83 | return true if name.to_s.end_with?('?') 84 | return true if name.to_s.end_with?('!') 85 | #key?(name.to_sym) || super(name) 86 | super(name) 87 | end 88 | 89 | end 90 | 91 | end 92 | -------------------------------------------------------------------------------- /lib/hashery/stash.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/key_hash' 2 | 3 | module Hashery 4 | # Stash is the original name for the KeyHash. 5 | Stash = KeyHash 6 | end 7 | 8 | class Hash 9 | # Convert Hash to Stash. 10 | def to_stash 11 | Hashery::Stash[self] 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/hashery/static_hash.rb: -------------------------------------------------------------------------------- 1 | module Hashery 2 | 3 | # StaticHash ia a Hash object which raises an error if any 4 | # previously-defined key attempts to be set again. 5 | # 6 | # foo = StaticHash.new 7 | # foo['name'] = 'Tom' #=> 'Tom' 8 | # foo['age'] = 30 #=> 30 9 | # foo['name'] = 'Bob' 10 | # 11 | # produces 12 | # 13 | # ArgumentError: Duplicate key for StaticHash -- 'name' 14 | # 15 | # StaticHash has it's orgins in Gavin Kistner's WriteOnceHash 16 | # class found in his +basiclibrary.rb+ script. 17 | # 18 | # TODO: Maybe StaticHash isn't bets name for this class? 19 | # 20 | class StaticHash < CRUDHash 21 | 22 | # 23 | # Set a value for a key. Raises an error if that key already 24 | # exists with a different value. 25 | # 26 | # key - Index key to associate with value. 27 | # value - Value to associate with key. 28 | # 29 | # Retruns value. 30 | # 31 | def store(key, value) 32 | if key?(key) && fetch(key) != value 33 | raise ArgumentError, "Duplicate key for StaticHash -- #{key.inspect}" 34 | end 35 | super(key, value) 36 | end 37 | 38 | # 39 | #def update(hash) 40 | # dups = (keys | hash.keys) 41 | # if dups.empty? 42 | # super(hash) 43 | # else 44 | # raise ArgumentError, "Duplicate key for StaticHash -- #{dups.inspect}" 45 | # end 46 | #end 47 | 48 | # 49 | #alias_method :merge!, :update 50 | 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /test/case_association.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | # must be required independently 4 | require 'hashery/association' 5 | 6 | testcase Association do 7 | 8 | class_method :new do 9 | test do 10 | Association.new(:A, :B) 11 | end 12 | end 13 | 14 | class_method :[] do 15 | test do 16 | a = Association[:a, 1] 17 | a.assert.index == :a 18 | a.assert.value == 1 19 | end 20 | end 21 | 22 | method :index do 23 | test do 24 | a = Association.new(:a,1) 25 | a.index.assert == :a 26 | end 27 | end 28 | 29 | method :value do 30 | test do 31 | a = Association.new(:a,1) 32 | a.value.assert == 1 33 | end 34 | end 35 | 36 | method :to_ary do 37 | test do 38 | k,v = [],[] 39 | ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ] 40 | ohash.each { |e1,e2| k << e1 ; v << e2 } 41 | k.assert == ['A','B','C'] 42 | v.assert == ['3','2','1'] 43 | end 44 | end 45 | 46 | method :index do 47 | test do 48 | complex = [ 'Drop Menu' >> [ 'Button 1', 'Button 2', 'Button 3' ], 'Help' ] 49 | complex[0].index.assert == 'Drop Menu' 50 | end 51 | end 52 | 53 | method :<=> do 54 | test 'when differnt in value' do 55 | a = Association.new(:a,1) 56 | b = Association.new(:b,2) 57 | (a <=> b).assert == -1 58 | (b <=> a).assert == 1 59 | end 60 | 61 | test 'when equal value' do 62 | a = Association.new(:a,1) 63 | b = Association.new(:b,1) 64 | (a <=> b).assert == 0 65 | end 66 | end 67 | 68 | method :invert! do 69 | test do 70 | a = Association.new(:a,1) 71 | a.invert! 72 | a.index.assert == 1 73 | a.value.assert == :a 74 | end 75 | end 76 | 77 | method :inspect do 78 | test do 79 | a = Association.new(:a,1) 80 | a.inspect.assert == ":a >> 1" 81 | end 82 | end 83 | 84 | method :to_s do 85 | test do 86 | a = Association.new(:a,1) 87 | a.to_s.assert == "a >> 1" 88 | end 89 | end 90 | 91 | end 92 | 93 | testcase Object do 94 | method :associations do 95 | test do 96 | s = 'a' 97 | complex = [ s >> :b, s >> :c ] 98 | s.associations.assert == [:b, :c] 99 | end 100 | end 101 | 102 | end 103 | 104 | -------------------------------------------------------------------------------- /test/case_casting_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case CastingHash do 4 | 5 | class_method :[] do 6 | test do 7 | h = CastingHash[:a=>1, :b=>2] 8 | end 9 | end 10 | 11 | class_method :new do 12 | test do 13 | h = CastingHash.new 14 | end 15 | 16 | test 'with default' do 17 | h = CastingHash.new(0) 18 | h['a'].assert == 0 19 | end 20 | 21 | test 'with casting procedure' do 22 | h = CastingHash.new{ |k,v| [k.to_sym, v] } 23 | h['a'] = 1 24 | h.assert == {:a=>1} 25 | end 26 | 27 | test 'with default and casting procedure' do 28 | h = CastingHash.new(0){ |k,v| [k.to_sym, v] } 29 | h['a'].assert == 0 30 | h['b'] = 2 31 | h.assert == {:b=>2} 32 | end 33 | end 34 | 35 | method :recast! do 36 | test do 37 | h = CastingHash[:a=>1, :b=>2] 38 | h.cast_proc{ |k,v| [k.to_s, v] } 39 | h.recast! 40 | h.assert == {'a'=>1, 'b'=>2} 41 | end 42 | end 43 | 44 | method :cast_proc= do 45 | test do 46 | h = CastingHash[:a=>1, :b=>2] 47 | h.cast_proc = Proc.new{ |k,v| [k.to_s, v] } 48 | h.recast! 49 | h.assert == {'a'=>1, 'b'=>2} 50 | end 51 | end 52 | 53 | method :to_hash do 54 | test do 55 | h = CastingHash[:a=>1, :b=>2] 56 | h.to_hash 57 | ::Hash.assert === h 58 | h.assert == {:a=>1, :b=>2} 59 | end 60 | end 61 | 62 | method :to_h do 63 | test do 64 | h = CastingHash[:a=>1, :b=>2] 65 | h.to_h 66 | ::Hash.assert === h 67 | h.assert == {:a=>1, :b=>2} 68 | end 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /test/case_core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case Hash do 4 | 5 | method :rekey do 6 | 7 | test "default" do 8 | foo = { "a"=>1, "b"=>2 } 9 | foo.rekey.assert == { :a=>1, :b=>2 } 10 | foo.assert == { "a"=>1, "b"=>2 } 11 | end 12 | 13 | test "specific key" do 14 | bar = { :a=>1, :b=>2 } 15 | foo = bar.rekey(:a=>:c) 16 | foo[:c].assert == 1 17 | foo[:b].assert == 2 18 | foo[:a].assert == nil 19 | end 20 | 21 | test "with block" do 22 | bar = { :a=>1, :b=>2 } 23 | foo = bar.rekey{ |k| k.to_s } 24 | foo['a'].assert == 1 25 | foo['b'].assert == 2 26 | foo[:a].assert == nil 27 | foo[:b].assert == nil 28 | foo.assert == { 'a'=>1, 'b'=>2 } 29 | end 30 | 31 | test "symbol proc" do 32 | foo = { :a=>1, :b=>2 } 33 | foo.rekey(&:to_s).assert == { "a"=>1, "b"=>2 } 34 | foo.assert == { :a =>1, :b=>2 } 35 | end 36 | 37 | end 38 | 39 | method :rekey! do 40 | 41 | test "default" do 42 | foo = { "a"=>1, "b"=>2 } 43 | foo.rekey!.assert == { :a=>1, :b=>2 } 44 | foo.assert == { :a=>1, :b=>2 } 45 | end 46 | 47 | test "specific key" do 48 | foo = { :a=>1, :b=>2 } 49 | foo.rekey!(:a=>:c) 50 | foo[:c].assert == 1 51 | foo[:b].assert == 2 52 | foo[:a].assert == nil 53 | end 54 | 55 | test "with block" do 56 | foo = { :a=>1, :b=>2 } 57 | foo.rekey!{ |k| k.to_s } 58 | foo['a'].assert == 1 59 | foo['b'].assert == 2 60 | foo[:a].assert == nil 61 | foo[:b].assert == nil 62 | foo.assert == { 'a'=>1, 'b'=>2 } 63 | end 64 | 65 | test "symbol proc" do 66 | foo = { :a=>1, :b=>2 } 67 | foo.rekey!(&:to_s).assert == { "a"=>1, "b"=>2 } 68 | foo.assert == { "a"=>1, "b"=>2 } 69 | end 70 | 71 | test "no conflict between keys" do 72 | r = {1 => :a, 2 => :b}.rekey!{ |k| k + 1 } 73 | r.refute = {3 => :a} 74 | r.assert = {2 => :a, 3 => :b} 75 | end 76 | 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /test/case_crud_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case CRUDHash do 4 | 5 | class_method :create do 6 | test do 7 | h = CRUDHash.create(:a=>1,:b=>2) 8 | h.assert == {:a=>1,:b=>2} 9 | end 10 | end 11 | 12 | class_method :auto do 13 | test 'without a block' do 14 | h = CRUDHash.auto 15 | h[:a].assert == {} 16 | end 17 | 18 | test 'with a block' do 19 | h = CRUDHash.auto{ [] } 20 | h[:a].assert == [] 21 | end 22 | end 23 | 24 | end 25 | 26 | 27 | # 28 | # OT: Why not make `:a=>1` a Pair object? 29 | # 30 | -------------------------------------------------------------------------------- /test/case_dictionary.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | testcase Dictionary do 4 | include AE::Legacy::Assertions 5 | 6 | class_method :[] do 7 | test do 8 | d = Dictionary['z', 1, 'a', 2, 'c', 3] 9 | assert_equal( ['z','a','c'], d.keys ) 10 | end 11 | end 12 | 13 | class_method :new do 14 | test "with default" do 15 | d = Dictionary.new{ |hash,key| hash[key] = 0 } 16 | d[:a] = 0 17 | d[:b] += 1 18 | assert_equal [0, 1], d.values 19 | assert_equal [:a,:b], d.keys 20 | end 21 | end 22 | 23 | method :[] do 24 | test do 25 | d = Dictionary['a', 1] 26 | d['a'].assert == 1 27 | end 28 | end 29 | 30 | method :[]= do 31 | test do 32 | d = Dictionary.new 33 | d['z'] = 1 34 | d['a'] = 2 35 | d['c'] = 3 36 | assert_equal( ['z','a','c'], d.keys ) 37 | end 38 | end 39 | 40 | method :[]= do 41 | test do 42 | d = Dictionary[] 43 | d[:a] = 1 44 | d[:c] = 3 45 | assert_equal( [1,3], d.values ) 46 | d[:b,1] = 2 47 | assert_equal( [1,2,3], d.values ) 48 | assert_equal( [:a,:b,:c], d.keys ) 49 | end 50 | end 51 | 52 | method :push do 53 | test do 54 | d = Dictionary['a', 1, 'c', 2, 'z', 3] 55 | assert( d.push('end', 15) ) 56 | assert_equal( 15, d['end'] ) 57 | assert( ! d.push('end', 30) ) 58 | assert( d.unshift('begin', 50) ) 59 | assert_equal( 50, d['begin'] ) 60 | assert( ! d.unshift('begin', 60) ) 61 | assert_equal( ["begin", "a", "c", "z", "end"], d.keys ) 62 | assert_equal( ["end", 15], d.pop ) 63 | assert_equal( ["begin", "a", "c", "z"], d.keys ) 64 | assert_equal( ["begin", 50], d.shift ) 65 | end 66 | end 67 | 68 | method :insert do 69 | test "front" do 70 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 71 | r = Dictionary['d', 4, 'a', 1, 'b', 2, 'c', 3] 72 | assert_equal( 4, d.insert(0,'d',4) ) 73 | assert_equal( r, d ) 74 | end 75 | test "back" do 76 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 77 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 78 | assert_equal( 4, d.insert(-1,'d',4) ) 79 | assert_equal( r, d ) 80 | end 81 | end 82 | 83 | method :update do 84 | test "with other orderred hash" do 85 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 86 | c = Dictionary['d', 4] 87 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 88 | assert_equal( r, d.update(c) ) 89 | assert_equal( r, d ) 90 | end 91 | test "with other hash" do 92 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 93 | c = { 'd' => 4 } 94 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 95 | assert_equal( r, d.update(c) ) 96 | assert_equal( r, d ) 97 | end 98 | end 99 | 100 | method :merge do 101 | test "with other orderred hash" do 102 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 103 | c = Dictionary['d', 4] 104 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 105 | assert_equal( r, d.merge(c) ) 106 | end 107 | test "with other hash" do 108 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 109 | c = { 'd' => 4 } 110 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 111 | assert_equal( r, d.merge(c) ) 112 | end 113 | end 114 | 115 | method :order_by do 116 | test do 117 | d = Dictionary['a', 3, 'b', 2, 'c', 1] 118 | d.order_by{ |k,v| v } 119 | assert_equal( [1,2,3], d.values ) 120 | assert_equal( ['c','b','a'], d.keys ) 121 | end 122 | end 123 | 124 | method :reverse! do 125 | test do 126 | d = Dictionary['z', 1, 'a', 2, 'c', 3] 127 | d.reverse! 128 | assert_equal( ['c','a','z'], d.keys ) 129 | end 130 | end 131 | 132 | method :collect do 133 | test "enumerable method" do 134 | d = Dictionary[] 135 | d[:a] = "a" 136 | d[:c] = "b" 137 | r = d.collect{|k,v| v.capitalize} 138 | r.assert == ["A","B"] 139 | end 140 | end 141 | 142 | method :dup do 143 | test "with array values" do 144 | d = Dictionary.new 145 | d.dup 146 | d[:a]=['t',5] 147 | assert_equal(d, d.dup) 148 | end 149 | end 150 | 151 | method :first do 152 | test do 153 | d = Dictionary[] 154 | d[:a] = "a" 155 | d[:b] = "b" 156 | d[:c] = "c" 157 | d.first.assert == "a" 158 | d.first(0).assert == [] 159 | assert_equal ["a"] , d.first(1) 160 | assert_equal ["a", "b"] , d.first(2) 161 | end 162 | end 163 | 164 | method :last do 165 | test do 166 | d = Dictionary[] 167 | d[:a] = "a" 168 | d[:b] = "b" 169 | d[:c] = "c" 170 | d.last.assert == "c" 171 | d.last(0).assert == [] 172 | d.last(1).assert == ["c"] 173 | d.last(2).assert == ["b", "c"] 174 | end 175 | end 176 | 177 | method :select do 178 | test do 179 | d = Dictionary[:a=>1, :b=>2, :c=>3] 180 | r = d.select{ |k,v| v % 2 == 1 } 181 | r.assert == [[:a, 1], [:c, 3]] 182 | end 183 | end 184 | 185 | method :to_h do 186 | test do 187 | d = Dictionary[:a=>1, :b=>2] 188 | h = d.to_h 189 | h.assert == {:a=>1, :b=>2} 190 | end 191 | end 192 | 193 | method :replace do 194 | test do 195 | d1 = Dictionary[:a=>1, :b=>2] 196 | d2 = Dictionary[:c=>3, :d=>4] 197 | d1.replace(d2) 198 | d1.to_h.assert == {:c=>3, :d=>4} 199 | end 200 | end 201 | 202 | method :reverse do 203 | test do 204 | d = Dictionary[:a=>1, :b=>2, :c=>3] 205 | r = d.reverse 206 | r.first.assert == 3 207 | end 208 | end 209 | 210 | method :invert do 211 | test do 212 | d = Dictionary[:a=>1, :b=>2, :c=>3] 213 | r = d.invert 214 | Dictionary.assert === r 215 | r.to_h.assert == {1=>:a, 2=>:b, 3=>:c} 216 | end 217 | end 218 | 219 | method :each_key do 220 | d = Dictionary[:a=>1, :b=>2, :c=>3] 221 | d.order_by_key 222 | a = [] 223 | d.each_key{ |k| a << k } 224 | a.assert == [:a, :b, :c] 225 | end 226 | 227 | method :each_value do 228 | d = Dictionary[:a=>1, :b=>2, :c=>3] 229 | d.order_by_value 230 | a = [] 231 | d.each_value{ |v| a << v } 232 | a.assert == [1, 2, 3] 233 | end 234 | 235 | method :clear do 236 | d = Dictionary[:a=>1, :b=>2, :c=>3] 237 | d.clear 238 | d.to_a.assert == [] 239 | end 240 | 241 | method :fetch do 242 | d = Dictionary[:a=>1, :b=>2, :c=>3] 243 | d.fetch(:a).assert == 1 244 | end 245 | 246 | method :key? do 247 | test do 248 | d = Dictionary[:a=>1, :b=>2, :c=>3] 249 | d.assert.key?(:a) 250 | d.refute.key?(:d) 251 | end 252 | end 253 | 254 | method :has_key? do 255 | test do 256 | d = Dictionary[:a=>1, :b=>2, :c=>3] 257 | d.assert.has_key?(:a) 258 | d.refute.has_key?(:d) 259 | end 260 | end 261 | 262 | method :length do 263 | test do 264 | d = Dictionary[:a=>1, :b=>2, :c=>3] 265 | d.length.assert == 3 266 | end 267 | end 268 | 269 | method :to_a do 270 | test do 271 | d = Dictionary[:a=>1, :b=>2, :c=>3] 272 | d.to_a.assert == [[:a,1], [:b,2], [:c,3]] 273 | end 274 | end 275 | 276 | method :to_hash do 277 | test do 278 | d = Dictionary[:a=>1, :b=>2, :c=>3] 279 | d.to_hash.assert == {:a=>1, :b=>2, :c=>3} 280 | end 281 | end 282 | 283 | method :empty? do 284 | test "is emtpy" do 285 | d = Dictionary[] 286 | d.assert.empty? 287 | end 288 | 289 | test 'is not emtpy' do 290 | d = Dictionary[:a=>1, :b=>2, :c=>3] 291 | d.refute.empty? 292 | end 293 | end 294 | 295 | method :order_by_key do 296 | test do 297 | d = Dictionary[:b=>1, :c=>2, :a=>4] 298 | d.order_by_key 299 | d.order.assert == [:a, :b, :c] 300 | end 301 | end 302 | 303 | method :order_by_value do 304 | test do 305 | d = Dictionary[:b=>1, :c=>2, :a=>4] 306 | d.order_by_value 307 | d.order.assert == [:b, :c, :a] 308 | end 309 | end 310 | 311 | class_method :alpha do 312 | test do 313 | d = Dictionary.alpha 314 | d.update(:b=>1, :c=>2, :a=>4) 315 | d.order.assert == [:a, :b, :c] 316 | end 317 | end 318 | 319 | class_method :auto do 320 | test do 321 | d = Dictionary.auto 322 | s = d[:foo] 323 | s.class.assert == Dictionary 324 | end 325 | end 326 | 327 | end 328 | 329 | -------------------------------------------------------------------------------- /test/case_ini_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case IniHash do 4 | 5 | class_method :new do 6 | test do 7 | h = IniHash.new('foo.ini', false) 8 | end 9 | end 10 | 11 | method :[]= do 12 | test do 13 | h = IniHash.new('foo.ini', false) 14 | h['a'] = '1' 15 | h['a'].assert = '1' 16 | end 17 | end 18 | 19 | method :[] do 20 | test do 21 | h = IniHash.new('foo.ini', false) 22 | h['a'] = '1' 23 | h['a'].assert = '1' 24 | end 25 | end 26 | 27 | method :to_h do 28 | test do 29 | h = IniHash.new('foo.ini', false) 30 | h['a'] = '1' 31 | h.to_h.assert = {'a'=>'1'} 32 | end 33 | end 34 | 35 | method :to_s do 36 | test do 37 | h = IniHash.new('foo.ini', false) 38 | h['a'] = '1' 39 | h.to_s.assert == "a=1\n" 40 | end 41 | 42 | test do 43 | h = IniHash.new('foo.ini', false) 44 | h['a'] = '1' 45 | h['b'] = {'c'=>3} 46 | h.to_s.assert == "a=1\n[b]\nc=3\n" 47 | end 48 | end 49 | 50 | class_method :load do 51 | h = IniHash.load('test/fixture/example.ini') 52 | h['a'].assert == '1' 53 | end 54 | 55 | end 56 | 57 | -------------------------------------------------------------------------------- /test/case_key_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | testcase KeyHash do 4 | 5 | class_method :[] do 6 | test 'creates new KeyHash' do 7 | s = KeyHash[] 8 | KeyHash.assert === s 9 | end 10 | 11 | test 'pre-assigns values' do 12 | s = KeyHash[:a=>1, :b=>2] 13 | s[:a].assert == 1 14 | s[:b].assert == 2 15 | end 16 | end 17 | 18 | method :[] do 19 | test 'instance level fetch' do 20 | s = KeyHash[:a=>1, :b=>2] 21 | s[:a].assert == 1 22 | s[:b].assert == 2 23 | end 24 | 25 | test 'by default keys are converted to strings' do 26 | s = KeyHash[:a=>1, :b=>2] 27 | s['a'].assert == 1 28 | s['b'].assert == 2 29 | end 30 | end 31 | 32 | method :[]= do 33 | test do 34 | s = KeyHash.new 35 | s[:a] = 1 36 | s[:b] = 2 37 | s[:a].assert == 1 38 | s[:b].assert == 2 39 | s['a'].assert == 1 40 | s['b'].assert == 2 41 | end 42 | end 43 | 44 | method :initialize do 45 | test do 46 | StandardError.refute.raised? do 47 | s = KeyHash.new 48 | end 49 | end 50 | end 51 | 52 | method :to_hash do 53 | test do 54 | s = KeyHash[:a=>1, :b=>2] 55 | s.to_hash.assert == {'a'=>1, 'b'=>2} 56 | end 57 | end 58 | 59 | method :to_h do 60 | test do 61 | s = KeyHash[:a=>1, :b=>2] 62 | s.to_h.assert == {'a'=>1, 'b'=>2} 63 | end 64 | end 65 | 66 | method :replace do 67 | test do 68 | s = KeyHash.new 69 | s.replace(:a=>1, :b=>2) 70 | s.to_h.assert == {'a'=>1, 'b'=>2} 71 | end 72 | end 73 | 74 | method :delete do 75 | test do 76 | s = KeyHash[:a=>1, :b=>2] 77 | s.delete(:a) 78 | s.to_h.assert == {'b'=>2} 79 | end 80 | end 81 | 82 | method :each do 83 | test do 84 | s = KeyHash[:a=>1, :b=>2] 85 | s.each do |k,v| 86 | String.assert === k 87 | end 88 | end 89 | end 90 | 91 | method :store do 92 | test do 93 | s = KeyHash.new 94 | s.store(:a, 1) 95 | s.to_h.assert == {'a'=>1} 96 | end 97 | end 98 | 99 | method :update do 100 | test do 101 | s1 = KeyHash[:a=>1,:b=>2] 102 | s2 = KeyHash[:c=>3,:d=>4] 103 | s1.update(s2) 104 | s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4} 105 | end 106 | end 107 | 108 | method :rekey do 109 | test do 110 | s = KeyHash[:a=>1,:b=>2,:c=>3] 111 | x = s.rekey{ |k| k.upcase } 112 | x.to_h.assert == {'A'=>1,'B'=>2,'C'=>3} 113 | end 114 | end 115 | 116 | method :rekey! do 117 | test do 118 | s = KeyHash[:a=>1,:b=>2,:c=>3] 119 | s.rekey!{ |k| k.upcase } 120 | s.to_h.assert == {'A'=>1,'B'=>2,'C'=>3} 121 | end 122 | end 123 | 124 | method :key? do 125 | test do 126 | s = KeyHash[:a=>1] 127 | s.assert.key?(:a) 128 | s.assert.key?('a') 129 | end 130 | end 131 | 132 | method :has_key? do 133 | test do 134 | s = KeyHash[:a=>1] 135 | s.assert.has_key?(:a) 136 | s.assert.has_key?('a') 137 | end 138 | end 139 | 140 | method :<< do 141 | test do 142 | s = KeyHash.new 143 | s << [:a, 1] 144 | s << [:b, 2] 145 | s.to_h.assert == {'a'=>1, 'b'=>2} 146 | end 147 | end 148 | 149 | method :merge! do 150 | test do 151 | s1 = KeyHash[:a=>1,:b=>2] 152 | s2 = KeyHash[:c=>3,:d=>4] 153 | s1.merge!(s2) 154 | s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4} 155 | end 156 | end 157 | 158 | method :values_at do 159 | test do 160 | s = KeyHash[:a=>1,:b=>2,:c=>3] 161 | s.values_at(:a, :b).assert == [1,2] 162 | s.values_at('a','b').assert == [1,2] 163 | end 164 | end 165 | 166 | method :fetch do 167 | test do 168 | s = KeyHash[:a=>1,:b=>2,:c=>3] 169 | s.fetch(:a).assert == 1 170 | s.fetch('a').assert == 1 171 | end 172 | end 173 | 174 | #method :cast_key do 175 | # test do 176 | # s = KeyHash.new 177 | # s.send(:cast_key, :a).assert == 'a' 178 | # end 179 | #end 180 | 181 | end 182 | 183 | -------------------------------------------------------------------------------- /test/case_linked_list.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case LinkedList do 4 | 5 | class_method :new do 6 | ll = LinkedList.new 7 | LinkedList.assert === ll 8 | end 9 | 10 | method :to_a do 11 | test 'empty' do 12 | ll = LinkedList.new 13 | ll.to_a.assert == [] 14 | end 15 | test 'not empty' do 16 | ll = LinkedList.new 17 | ll.push :a 18 | ll.to_a.assert == [:a] 19 | end 20 | end 21 | 22 | method :empty? do 23 | test do 24 | ll = LinkedList.new 25 | ll.assert.empty? 26 | end 27 | end 28 | 29 | method :delete do 30 | test do 31 | ll = LinkedList.new 32 | ll.push :a 33 | ll.to_a.assert == [:a] 34 | ll.delete(:a) 35 | ll.to_a.assert == [] 36 | end 37 | 38 | test do 39 | ll = LinkedList.new 40 | ll.push :a 41 | ll.push :b 42 | ll.push :c 43 | ll.to_a.assert == [:a, :b, :c] 44 | ll.delete(:b) 45 | ll.to_a.assert == [:a, :c] 46 | end 47 | end 48 | 49 | method :each do 50 | test do 51 | a = [] 52 | ll = LinkedList.new 53 | ll.push :a 54 | ll.each do |e| 55 | a << e 56 | end 57 | a.assert == [:a] 58 | end 59 | end 60 | 61 | method :length do 62 | test do 63 | ll = LinkedList.new 64 | ll.push :a 65 | ll.length.assert == 1 66 | end 67 | end 68 | 69 | method :push do 70 | test do 71 | ll = LinkedList.new 72 | ll.push :a 73 | ll.to_a.assert == [:a] 74 | end 75 | end 76 | 77 | method :unshift do 78 | test do 79 | ll = LinkedList.new 80 | ll.unshift :a 81 | ll.to_a.assert == [:a] 82 | end 83 | test do 84 | ll = LinkedList.new 85 | ll.push :a 86 | ll.unshift :b 87 | ll.to_a.assert == [:b, :a] 88 | end 89 | end 90 | 91 | method :pop do 92 | test do 93 | ll = LinkedList.new 94 | ll.push :a 95 | ll.push :b 96 | ll.to_a.assert == [:a, :b] 97 | ll.pop 98 | ll.to_a.assert == [:a] 99 | end 100 | end 101 | 102 | method :shift do 103 | test do 104 | ll = LinkedList.new 105 | ll.push :a 106 | ll.push :b 107 | ll.to_a.assert == [:a, :b] 108 | ll.shift 109 | ll.to_a.assert == [:b] 110 | end 111 | end 112 | 113 | method :first do 114 | test do 115 | ll = LinkedList.new 116 | ll.push :a 117 | ll.push :b 118 | ll.to_a.assert == [:a, :b] 119 | ll.first.assert == :a 120 | end 121 | end 122 | 123 | method :last do 124 | test do 125 | ll = LinkedList.new 126 | ll.push :a 127 | ll.push :b 128 | ll.to_a.assert == [:a, :b] 129 | ll.last.assert == :b 130 | end 131 | end 132 | 133 | method :queue do 134 | test do 135 | ll = LinkedList.new 136 | ll.push :a 137 | ll.push :b 138 | ll.queue.assert == [:a, :b] 139 | end 140 | end 141 | 142 | method :[]= do 143 | test do 144 | ll = LinkedList.new 145 | ll[:a] = :b 146 | ll.to_a.assert == [:b] 147 | ll[:a].assert == :b 148 | end 149 | end 150 | 151 | method :[] do 152 | test do 153 | ll = LinkedList.new 154 | ll.push :a 155 | ll[:a].assert == :a 156 | end 157 | 158 | test do 159 | ll = LinkedList.new 160 | ll.push :a 161 | ll.push :b 162 | ll[:a].assert == :a 163 | ll[:b].assert == :b 164 | end 165 | end 166 | 167 | end 168 | -------------------------------------------------------------------------------- /test/case_lru_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case LRUHash do 4 | 5 | class_method :new do 6 | h = LRUHash.new(10) 7 | LRUHash.assert === h 8 | end 9 | 10 | method :max_size= do 11 | test do 12 | h = LRUHash.new(10) 13 | h.max_size = 100 14 | end 15 | end 16 | 17 | method :store do 18 | test do 19 | h = LRUHash.new(10) 20 | h.store(:a, 1) 21 | h[:a].assert == 1 22 | end 23 | end 24 | 25 | method :[] do 26 | test do 27 | h = LRUHash.new(10) 28 | h.store(:a, 1) 29 | h[:a].assert == 1 30 | end 31 | 32 | test do 33 | h = LRUHash.new(10){ |h,k| h[k] = 0 } 34 | h[:a].assert == 0 35 | end 36 | end 37 | 38 | method :empty? do 39 | test do 40 | h = LRUHash.new(10) 41 | h.assert.empty? 42 | end 43 | end 44 | 45 | method :key do 46 | test do 47 | h = LRUHash.new(10) 48 | h[:a] = 1 49 | h.key(1).assert == :a 50 | end 51 | end 52 | 53 | method :keys do 54 | test do 55 | h = LRUHash.new(10) 56 | h[:a] = 1 57 | h.keys.assert == [:a] 58 | end 59 | end 60 | 61 | method :values do 62 | test do 63 | h = LRUHash.new(10) 64 | h[:a] = 1 65 | h.values.assert == [1] 66 | end 67 | end 68 | 69 | method :values_at do 70 | test do 71 | h = LRUHash.new(10) 72 | h[:a] = 1 73 | h[:b] = 2 74 | h.values_at(:a).assert == [1] 75 | end 76 | end 77 | 78 | method :has_key? do 79 | test do 80 | h = LRUHash.new(10) 81 | h[:a] = 1 82 | h.assert.has_key?(:a) 83 | end 84 | end 85 | 86 | method :has_value? do 87 | test do 88 | h = LRUHash.new(10) 89 | h[:a] = 1 90 | h.assert.has_value?(1) 91 | end 92 | end 93 | 94 | method :assoc do 95 | test do 96 | h = LRUHash.new(10) 97 | h[:a] = 1 98 | h[:b] = 2 99 | h.assoc(:a).assert == [:a,1] 100 | end 101 | end 102 | 103 | method :rassoc do 104 | test do 105 | h = LRUHash.new(10) 106 | h[:a] = 1 107 | h[:b] = 2 108 | h.rassoc(1).assert == [:a,1] 109 | end 110 | end 111 | 112 | method :each_key do 113 | test do 114 | h = LRUHash.new(10) 115 | h[:a] = 1 116 | h[:b] = 2 117 | h.each_key do |k| 118 | [:a,:b].assert.include?(k) 119 | end 120 | end 121 | end 122 | 123 | method :each_value do 124 | test do 125 | h = LRUHash.new(10) 126 | h[:a] = 1 127 | h[:b] = 2 128 | h.each_value do |v| 129 | [1,2].assert.include?(v) 130 | end 131 | end 132 | end 133 | 134 | method :clear do 135 | test do 136 | h = LRUHash.new(10) 137 | h[:a] = 1 138 | h[:b] = 2 139 | h.clear 140 | h.assert.empty? 141 | end 142 | end 143 | 144 | method :delete do 145 | test do 146 | h = LRUHash.new(10) 147 | h[:a] = 1 148 | h.delete(:a) 149 | h.assert.empty? 150 | end 151 | end 152 | 153 | method :delete_if do 154 | test do 155 | h = LRUHash.new(10) 156 | h[:a] = 1 157 | h.delete_if{ |k,v| k == :a } 158 | h.assert.empty? 159 | end 160 | end 161 | 162 | end 163 | -------------------------------------------------------------------------------- /test/case_open_cascade.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | testcase OpenCascade do 4 | include AE::Legacy::Assertions 5 | 6 | class_method :new do 7 | OpenCascade[:a=>1] 8 | end 9 | 10 | class_method :[] do 11 | test "hash" do 12 | o = OpenCascade[:a=>1,:b=>2] 13 | assert_equal(1, o.a) 14 | assert_equal(2, o.b) 15 | end 16 | test "hash in hash" do 17 | o = OpenCascade[:a=>1,:b=>2,:c=>{:x=>9}] 18 | assert_equal(9, o.c.x) 19 | end 20 | test "hash in hash in hash" do 21 | h = {:a=>1,:x=>{:y=>{:z=>1}}} 22 | c = OpenCascade[h] 23 | assert_equal(1, c.x.y.z) 24 | end 25 | end 26 | 27 | method :[] do 28 | test "basic assignment" do 29 | o = OpenCascade.new 30 | o[:a] = 1 31 | assert_equal(1, o[:a]) 32 | end 33 | end 34 | 35 | method :[]= do 36 | test "basic assignment with primed OpenCascade" do 37 | o = OpenCascade[:a=>1] 38 | o[:b] = 2 39 | o.to_h.assert == {:a=>1,:b=>2} 40 | end 41 | end 42 | 43 | method :to_a do 44 | test do 45 | o = OpenCascade[:a=>1,:b=>2] 46 | a = o.to_a 47 | a.assert.include?([:a,1]) 48 | a.assert.include?([:b,2]) 49 | a.size.assert == 2 50 | end 51 | end 52 | 53 | method :to_h do 54 | test do 55 | o = OpenCascade[:a=>1] 56 | assert_equal({:a=>1}, o.to_h) 57 | end 58 | end 59 | 60 | method :merge! do 61 | test do 62 | o = OpenCascade[:f0=>"f0"] 63 | r = OpenCascade[:f0=>"f0", :h0=>"h0"] 64 | h = { :h0=>"h0" } 65 | assert_equal(r, o.merge!(h)) 66 | assert_equal({:f0=>"f0", :h0=>"h0"}, h.merge(o)) 67 | end 68 | end 69 | 70 | method :update do 71 | test do 72 | o = OpenCascade[:f1=>"f1"] 73 | o.update(:h1=>"h1") 74 | o.assert == OpenCascade[:f1=>"f1", :h1=>"h1"] 75 | end 76 | end 77 | 78 | method :method_missing do 79 | test "writer and reader" do 80 | o = OpenCascade.new 81 | 10.times{ |i| o.__send__("n#{i}=", 1 ) } 82 | 10.times{ |i| assert_equal(1, o.__send__("n#{i}")) } 83 | end 84 | end 85 | 86 | method :<< do 87 | test do 88 | c = OpenCascade.new 89 | c << [:x,8] 90 | c << [:y,9] 91 | 92 | assert_equal(8, c.x) 93 | assert_equal(9, c.y) 94 | end 95 | end 96 | 97 | end 98 | 99 | testcase Hash do 100 | 101 | method :update do 102 | test "hash can be updated by OpenCascade" do 103 | o = OpenCascade[:f1=>"f1"] 104 | h = {:h1=>"h1"} 105 | h.update(o) 106 | h.assert == {:f1=>"f1", :h1=>"h1"} 107 | end 108 | end 109 | 110 | end 111 | 112 | testcase Array do 113 | 114 | method :flatten do 115 | test "array can be flattened if contains OpenCascade" do 116 | cascade = OpenCascade[:foo=>"bar"] 117 | array = [cascade] 118 | array.flatten # should not raise error 119 | end 120 | end 121 | 122 | end 123 | -------------------------------------------------------------------------------- /test/case_open_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | testcase OpenHash do 4 | 5 | class_method :[] do 6 | test do 7 | o = OpenHash[:a=>1, :b=>2] 8 | o.a.assert == 1 9 | o.b.assert == 2 10 | end 11 | 12 | test do 13 | o = OpenHash[:a=>1, :b=>2] 14 | o.a.assert == 1 15 | o.b.assert == 2 16 | end 17 | end 18 | 19 | method :open? do 20 | test do 21 | o = OpenHash[:a=>1, :b=>2] 22 | o.assert.open?(:foo) 23 | o.refute.open?(:each) 24 | end 25 | end 26 | 27 | method :open! do 28 | test do 29 | o = OpenHash[:a=>1, :b=>2] 30 | o.open!(:each) 31 | o.assert.open?(:each) 32 | o.each = 10 33 | o.each.assert == 10 34 | end 35 | end 36 | 37 | method :close! do 38 | test do 39 | o = OpenHash[:a=>1, :b=>2] 40 | o.open!(:each) 41 | o.assert.open?(:each) 42 | o.each = 10 43 | o.each.assert == 10 44 | o.close!(:each) 45 | o.each.refute == 10 46 | end 47 | end 48 | 49 | method :method_missing do 50 | test 'bang method' do 51 | o = OpenHash[] 52 | o.open!(:each) 53 | o.each = 10 54 | o.each.assert == 10 55 | 56 | a = [] 57 | o.each! do |k,v| 58 | a << [k,v] 59 | end 60 | a.assert == [[:each,10]] 61 | end 62 | 63 | test 'query method' do 64 | o = OpenHash[] 65 | o.a = 1 66 | o.assert.a? 67 | o.refute.b? 68 | end 69 | end 70 | 71 | method :send do 72 | test do 73 | o = OpenHash[] 74 | o.open!(:each) 75 | o.each = 10 76 | o.each.assert == 10 77 | 78 | a = [] 79 | o.send(:each) do |k,v| 80 | a << [k,v] 81 | end 82 | a.assert == [[:each,10]] 83 | end 84 | end 85 | 86 | end 87 | 88 | -------------------------------------------------------------------------------- /test/case_property_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | test_case PropertyHash do 4 | 5 | class_method :new do 6 | test do 7 | PropertyHash.new 8 | end 9 | end 10 | 11 | method :update do 12 | test do 13 | h = PropertyHash.new 14 | h.property :a 15 | h.property :b 16 | h.update(:a=>1, :b=>2) 17 | h.assert == {:a=>1, :b=>2} 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/case_query_hash.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | testcase QueryHash do 4 | 5 | class_method :new do 6 | test 'new QueryHash with default proc' do 7 | q = QueryHash.new{ |h,k| h[k] = 1 } 8 | q[:a].assert == 1 9 | end 10 | end 11 | 12 | class_method :[] do 13 | test 'creates new QueryHash' do 14 | s = QueryHash[] 15 | QueryHash.assert === s 16 | end 17 | 18 | test 'pre-assigns values' do 19 | s = QueryHash[:a=>1, :b=>2] 20 | s[:a].assert == 1 21 | s[:b].assert == 2 22 | end 23 | end 24 | 25 | method :[] do 26 | test 'instance level fetch' do 27 | s = QueryHash[:a=>1, :b=>2] 28 | s[:a].assert == 1 29 | s[:b].assert == 2 30 | #s['a'].assert == 1 31 | #s['b'].assert == 2 32 | end 33 | end 34 | 35 | method :[]= do 36 | test do 37 | s = QueryHash.new 38 | s[:a] = 1 39 | s[:b] = 2 40 | s[:a].assert == 1 41 | s[:b].assert == 2 42 | #s['a'].assert == 1 43 | #s['b'].assert == 2 44 | end 45 | end 46 | 47 | method :respond_to? do 48 | test 'responds to all query methods' do 49 | q = QueryHash.new 50 | q.assert.respond_to?(:anything?) 51 | end 52 | 53 | test 'responds to all bang methods' do 54 | q = QueryHash.new 55 | q.assert.respond_to?(:anything!) 56 | end 57 | 58 | test 'responds to all setter methods' do 59 | q = QueryHash.new 60 | q.assert.respond_to?(:anything=) 61 | end 62 | 63 | test 'responds to usual methods' do 64 | q = QueryHash.new 65 | q.assert.respond_to?(:each) 66 | end 67 | end 68 | 69 | method :to_hash do 70 | test do 71 | s = QueryHash[:a=>1, :b=>2] 72 | s.to_hash.assert == {'a'=>1, 'b'=>2} 73 | end 74 | end 75 | 76 | method :to_h do 77 | test do 78 | s = QueryHash[:a=>1, :b=>2] 79 | s.to_h.assert == {'a'=>1, 'b'=>2} 80 | end 81 | end 82 | 83 | method :replace do 84 | test do 85 | s = QueryHash.new 86 | s.replace(:a=>1, :b=>2) 87 | s.to_h.assert == {'a'=>1, 'b'=>2} 88 | end 89 | end 90 | 91 | method :delete do 92 | test do 93 | s = QueryHash[:a=>1, :b=>2] 94 | s.delete(:a) 95 | s.to_h.assert == {'b'=>2} 96 | end 97 | end 98 | 99 | method :each do 100 | test do 101 | s = QueryHash[:a=>1, :b=>2] 102 | s.each do |k,v| 103 | String.assert === k 104 | end 105 | end 106 | end 107 | 108 | method :store do 109 | test do 110 | s = QueryHash.new 111 | s.store(:a, 1) 112 | s.to_h.assert == {'a'=>1} 113 | end 114 | end 115 | 116 | method :update do 117 | test do 118 | s1 = QueryHash[:a=>1,:b=>2] 119 | s2 = QueryHash[:c=>3,:d=>4] 120 | s1.update(s2) 121 | s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4} 122 | end 123 | end 124 | 125 | method :rekey do 126 | test do 127 | s = QueryHash[:a=>1,:b=>2,:c=>3] 128 | x = s.rekey{ |k| k.upcase } 129 | x.to_h.assert == {'A'=>1,'B'=>2,'C'=>3} 130 | end 131 | end 132 | 133 | method :rekey! do 134 | test do 135 | s = QueryHash[:a=>1,:b=>2,:c=>3] 136 | s.rekey!{ |k| k.upcase } 137 | s.to_h.assert == {'A'=>1,'B'=>2,'C'=>3} 138 | end 139 | end 140 | 141 | method :key? do 142 | test do 143 | s = QueryHash[:a=>1] 144 | s.assert.key?(:a) 145 | s.assert.key?('a') 146 | end 147 | end 148 | 149 | method :has_key? do 150 | test do 151 | s = QueryHash[:a=>1] 152 | s.assert.has_key?(:a) 153 | s.assert.has_key?('a') 154 | end 155 | end 156 | 157 | method :<< do 158 | test do 159 | s = QueryHash.new 160 | s << [:a, 1] 161 | s << [:b, 2] 162 | s.to_h.assert == {'a'=>1, 'b'=>2} 163 | end 164 | end 165 | 166 | method :merge! do 167 | test do 168 | s1 = QueryHash[:a=>1,:b=>2] 169 | s2 = QueryHash[:c=>3,:d=>4] 170 | s1.merge!(s2) 171 | s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4} 172 | end 173 | end 174 | 175 | method :values_at do 176 | test do 177 | s = QueryHash[:a=>1,:b=>2,:c=>3] 178 | s.values_at(:a, :b).assert == [1,2] 179 | s.values_at('a','b').assert == [1,2] 180 | end 181 | end 182 | 183 | method :fetch do 184 | test do 185 | s = QueryHash[:a=>1,:b=>2,:c=>3] 186 | s.fetch(:a).assert == 1 187 | s.fetch('a').assert == 1 188 | end 189 | end 190 | 191 | method :cast_key do 192 | test do 193 | s = QueryHash.new 194 | s.pry.cast_key(:a).assert == 'a' 195 | end 196 | end 197 | 198 | method :method_missing do 199 | test 'dynamic query methods can look-up values' do 200 | q = QueryHash[:a=>1,:b=>2,:c=>3] 201 | q.a?.assert == 1 202 | q.b?.assert == 2 203 | q.c?.assert == 3 204 | end 205 | 206 | test 'dynamic bang methods can looks up values too' do 207 | q = QueryHash[:a=>1,:b=>2,:c=>3] 208 | q.a!.assert == 1 209 | q.b!.assert == 2 210 | q.c!.assert == 3 211 | end 212 | 213 | test 'dynamic bang methods will auto-instantiate' do 214 | q = QueryHash.new 215 | q.default_proc{ |h,k| h[k] = 'default' } 216 | q.foo!.assert == 'default' 217 | end 218 | 219 | test 'dynamic query methods will NOT auto-instantiate' do 220 | q = QueryHash.new 221 | q.default_proc{ |h,k| h[k] = 'default' } 222 | q.foo?.assert == nil 223 | end 224 | end 225 | 226 | end 227 | -------------------------------------------------------------------------------- /test/fixture/example.ini: -------------------------------------------------------------------------------- 1 | # top comment 2 | 3 | a = 1 4 | 5 | [section] 6 | x = 2 7 | y = 3 8 | 9 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'lemon' 2 | require 'ae' 3 | require 'ae/legacy' # bacause imitation BasicObject sucks 4 | require 'ae/pry' 5 | 6 | require 'hashery' 7 | 8 | include Hashery 9 | -------------------------------------------------------------------------------- /work/NOTES.rdoc: -------------------------------------------------------------------------------- 1 | = 2 | 3 | == TODO 4 | 5 | === file://lib/hashery/association.rb 6 | 7 | * TODO: Should associations be singleton? (3) 8 | * TODO: Is it really wise to keep a table of all associations? (5) 9 | * TODO: Comparions with non-associations? (106) 10 | 11 | === file://lib/hashery/casting_hash.rb 12 | 13 | * TODO: Isn't this the same as `#rehash`? (108) 14 | * TODO: Should we add #to_casting_hash to Hash classs? (155) 15 | 16 | === file://lib/hashery/crud_hash.rb 17 | 18 | * TODO: Since a CRUDHash is a subclass of Hash should #to_hash just `#dup` 19 | insted of converting to traditional Hash? (302) 20 | * TODO: Since a CRUDHash is a subclass of Hash should #to_h just `#dup` 21 | insted of converting to traditional Hash? (312) 22 | * TODO: Consider value callback procs for future version of CRUDHash. (347) 23 | 24 | === file://lib/hashery/ini_hash.rb 25 | 26 | * TODO: Use class method for loading from file, not initializer. (86) 27 | * TODO: Sublcass Hash instead of delegating. (205) 28 | * TODO: Rename `IniHash.text` method to something else ? (308) 29 | 30 | === file://lib/hashery/open_hash.rb 31 | 32 | * TODO: Maybe `safe` should be the first argument? (31) 33 | * TODO: limit this to omitted methods (from included) ? (130) 34 | 35 | === file://lib/hashery/path_hash.rb 36 | 37 | * TODO: This class is very much a work in progess and will be substantially rewritten 38 | for future versions. (21) 39 | 40 | === file://lib/hashery/static_hash.rb 41 | 42 | * TODO: Maybe StaticHash isn't bets name for this class? (18) 43 | 44 | === file://work/deprecated/basic_cascade.rb 45 | 46 | * TODO: can't get `self.class` ? (60) 47 | 48 | === file://work/deprecated/basic_struct.rb 49 | 50 | * TODO: Need to get __class__ and __id__ in hex form. (54) 51 | * TODO: Is this wise? How to fake a subclass? (112) 52 | * TODO: Should this work like #merge or #update ? (176) 53 | 54 | === file://work/deprecated/ostructable.rb 55 | 56 | * TODO: Update to matchh current OpenStruct class. (25) 57 | * TODO: Keep this uptodate with ostruct.rb. (27) 58 | * TODO: See if Matz will accept it into core so we don't have to anymore. (29) 59 | * TODO: As with OpenStruct, marshalling is problematic at the moment. (31) 60 | * TODO: OpenStruct could be compared too, but only if it is loaded. How? (139) 61 | 62 | 63 | == FIXME 64 | 65 | === file://lib/hashery/dictionary.rb 66 | 67 | * FIXME: This looks like it is implemented wrong!!! (375) 68 | 69 | -------------------------------------------------------------------------------- /work/benchmarks/proc_vs_call.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark' 2 | 3 | class ProcVsCall 4 | 5 | def initialize(&proc) 6 | @the_proc = proc 7 | end 8 | 9 | def the_proc 10 | @the_proc ||= Proc.new{ |*x| x } 11 | end 12 | 13 | def call(*x) 14 | @the_proc ? @the_proc.call(*x) : x 15 | end 16 | 17 | end 18 | 19 | c = ProcVsCall.new 20 | 21 | n = 50000 22 | 23 | Benchmark.bmbm do |x| 24 | x.report('proc') { n.times { c.the_proc[:foo] } } 25 | x.report('call') { n.times { c.call(:foo) } } 26 | end 27 | 28 | c = ProcVsCall.new{ |x| x.class } 29 | 30 | Benchmark.bmbm do |x| 31 | x.report('proc') { n.times{ c.the_proc[:foo] } } 32 | x.report('call') { n.times{ c.call(:foo) } } 33 | end 34 | 35 | # The results would indicate that it is better to define a default proc rather then 36 | # using a call to check if it exists. 37 | 38 | -------------------------------------------------------------------------------- /work/consider/autoarray.rb: -------------------------------------------------------------------------------- 1 | # AutoArray 2 | # Copyright (c) 2005 Brian Schröder 3 | 4 | # An Array that automatically expands dimensions as needed. 5 | # 6 | # a = Autoarray.new 7 | # a[1][2][3] = 12 8 | # a #=> [nil, [nil, nil, [nil, nil, nil, 12]]] 9 | # a[2][3][4] #=> [] 10 | # a #=> [nil, [nil, nil, [nil, nil, nil, 12]]] 11 | # a[1][-2][1] = "Negative" 12 | # a #=> [nil, [nil, [nil, "Negative"], [nil, nil, nil, 12]]] 13 | # 14 | class AutoArray < Array 15 | 16 | def initialize(size=0, default=nil, update = nil, update_index = nil) 17 | super(size, default) 18 | @update, @update_index = update, update_index 19 | end 20 | 21 | def [](k) 22 | if -self.length+1 < k and k < self.length 23 | super(k) 24 | else 25 | Autoarray.new(0, nil, self, k) 26 | end 27 | end 28 | 29 | def []=(k, v) 30 | @update[@update_index] = self if @update and @update_index 31 | super 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /work/consider/having.rb: -------------------------------------------------------------------------------- 1 | ## THIS IS NOT THUROUGH ENOUGH 2 | ## PROBABY SHOULD MAKE SPECIAL SUBCLASSES 3 | ## OF HASH TO HANDLE THIS CONCEPT. 4 | # 5 | # IMPORTANT TODO!!!! 6 | 7 | # 8 | 9 | class Hash 10 | 11 | attr_accessor :fallback 12 | 13 | # Define a fallback object for #fetch and #[]. 14 | # 15 | # f = Hash[:b=>2] 16 | # h = Hash[:a=>1].having_aquisition(f) 17 | # h[:b] => 2 18 | 19 | def having_aquisition(fallback) 20 | 21 | @fallback = fallback 22 | 23 | unless @fallsback 24 | 25 | def self.[](key) 26 | begin 27 | return @fallback[key] if @fallback and not key?(key) 28 | rescue 29 | end 30 | val = super 31 | p val 32 | val.fallback(self) if Hash == val 33 | val 34 | end 35 | 36 | def self.fetch(key, *args, &blk) 37 | begin 38 | return @fallback.fetch(key, *args, &blk) if @fallback and not key?(key) 39 | rescue 40 | end 41 | val = super 42 | val.fallback(self) if Hash == val 43 | val 44 | end 45 | 46 | @fallsback = true 47 | end 48 | 49 | self 50 | end 51 | 52 | # Define a fallback object for #fetch and #[]. 53 | # 54 | # f = Hash[:b=>2] 55 | # h = Hash[:a=>1].having_fallback(f) 56 | # h[:b] => 2 57 | 58 | def having_fallback(parent=nil) 59 | @fallback = parent 60 | unless @fallsback 61 | def self.[](key) 62 | return @fallback[key] if @fallback and not key?(key) rescue super 63 | super 64 | end 65 | 66 | def self.fetch(key, *args, &blk) 67 | return @fallback.fetch(key, *args, &blk) if @fallback and not key?(key) rescue super 68 | super 69 | end 70 | @fallsback = true 71 | end 72 | self 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /work/consider/ini.rb: -------------------------------------------------------------------------------- 1 | # encoding: us-ascii 2 | # 3 | #----------------------------------------------------------- 4 | # INI File Parser 5 | # 6 | # Version: 0.1 7 | # License: Public Domain 8 | # Author: Dice 9 | #----------------------------------------------------------- 10 | 11 | module Ini 12 | VERSION = '0.1' 13 | VERSION_NUMBER = 0.1 14 | 15 | def self.parse(text, filename = self.to_s) 16 | re = {} 17 | current_section = nil 18 | 19 | text.each_with_index do |line, index| 20 | case line.chomp 21 | when /^\;/, /^\s*$/ 22 | # skip (primary) 23 | when /^\[(.+)\]/ 24 | # section start 25 | current_section = $1 26 | if re[current_section] then 27 | self.warn(filename, index+1, "section '#{$1}' overlap") 28 | else 29 | re[current_section] = {} 30 | end 31 | 32 | when /^(.+?)\s*=\s*(.*)$/ 33 | # key and value 34 | if current_section then 35 | if re[current_section][$1] then 36 | self.warn(filename, index+1, "key '#{$1}' overlap") 37 | end 38 | 39 | re[current_section][$1] = $2 40 | else 41 | self.warn(filename, index+1, "'#{$1}' is not in section") 42 | end 43 | else 44 | self.warn(filename, index+1, "warning: unknown statement") 45 | end 46 | end 47 | 48 | re 49 | end 50 | 51 | def self.parse_file(path) 52 | self.parse(File.read(path), path.to_s) 53 | end 54 | 55 | def self.warn(filename, lineno, msg) 56 | if $VERBOSE or $DEBUG then 57 | $stderr.puts "#{filename}:#{lineno}: warning: #{msg}" 58 | end 59 | end 60 | end 61 | 62 | 63 | INI = Ini -------------------------------------------------------------------------------- /work/consider/openobject.rb: -------------------------------------------------------------------------------- 1 | # = OpenObject 2 | # 3 | # OpenObject is very similar to Ruby's own OpenStruct, but it offers some 4 | # useful advantages. With OpenStruct slots with the same name's as predefined 5 | # Object methods can not be used. With OpenObject any slot can be defined, 6 | # OpendObject is also a bit faster becuase it is based on a Hash, not 7 | # method signitures. 8 | # 9 | # Because OpenObject is a subclass of Hash, it can do just about 10 | # everything a Hash can do, except that most public methods have been 11 | # made protected and thus available only internally or via #send. 12 | # 13 | # OpenObject will also clobber any method for which a slot is defined. 14 | # Even generally very important methods can be clobbered, like 15 | # instance_eval. So be aware of this. OpenObject should be used in well 16 | # controlled scenarios. 17 | # 18 | # If you wish to pass an OpenObject to a routine that normal takes a Hash, 19 | # but are uncertain it can handle the distictions properly you can convert 20 | # easily to a Hash using #to_hash! and the result will automatically be 21 | # converted back to an OpenObject on return. 22 | # 23 | # o = OpenObject.new(:a=>1,:b=>2) 24 | # o.as_hash!{ |h| h.update(:a=>6) } 25 | # o #=> #6,:b=>2}> 26 | # 27 | # Finally, unlike a regular Hash, all OpenObject's keys are symbols and 28 | # all keys are converted to such using #to_sym on the fly. 29 | # 30 | # == Authors 31 | # 32 | # * Thomas Sawyer 33 | # * George Moschovitis 34 | # 35 | # == Copying 36 | # 37 | # Copyright (c) 2005 Thomas Sawyer, George Moschovitis 38 | # 39 | # Ruby License 40 | # 41 | # This module is free software. You may use, modify, and/or redistribute this 42 | # software under the same terms as Ruby. 43 | # 44 | # This program is distributed in the hope that it will be useful, but WITHOUT 45 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 46 | # FOR A PARTICULAR PURPOSE. 47 | 48 | require 'facets/hash/to_h' 49 | require 'facets/hash/to_proc' 50 | require 'facets/kernel/object_class' 51 | require 'facets/kernel/object_hexid' 52 | 53 | # = OpenObject 54 | # 55 | # OpenObject is very similar to Ruby's own OpenStruct, but it offers some 56 | # useful advantages. With OpenStruct slots with the same name's as predefined 57 | # Object methods can not be used. With OpenObject any slot can be defined, 58 | # OpendObject is also a bit faster becuase it is based on a Hash, not 59 | # method signitures. 60 | # 61 | # Because OpenObject is a subclass of Hash, it can do just about 62 | # everything a Hash can do, except that most public methods have been 63 | # made protected and thus available only internally or via #send. 64 | # 65 | # OpenObject will also clobber any method for which a slot is defined. 66 | # Even generally very important methods can be clobbered, like 67 | # instance_eval. So be aware of this. OpenObject should be used in well 68 | # controlled scenarios. 69 | # 70 | # If you wish to pass an OpenObject to a routine that normal takes a Hash, 71 | # but are uncertain it can handle the distictions properly you can convert 72 | # easily to a Hash using #to_hash! and the result will automatically be 73 | # converted back to an OpenObject on return. 74 | # 75 | # o = OpenObject.new(:a=>1,:b=>2) 76 | # o.as_hash!{ |h| h.update(:a=>6) } 77 | # o #=> #6,:b=>2}> 78 | # 79 | # Finally, unlike a regular Hash, all OpenObject's keys are symbols and 80 | # all keys are converted to such using #to_sym on the fly. 81 | 82 | class OpenObject < Hash 83 | 84 | PUBLIC_METHODS = /(^__|^instance_|^object_|^\W|^as$|^send$|^class$|\?$)/ 85 | 86 | protected(*public_instance_methods.select{ |m| m !~ PUBLIC_METHODS }) 87 | 88 | def self.[](hash=nil) 89 | new(hash) 90 | end 91 | 92 | old_verbose = $VERBOSE 93 | $VERBOSE = false # shut warnings up while redefining these methods. 94 | 95 | # Inititalizer for OpenObject is slightly differnt than that of Hash. 96 | # It does not take a default parameter, but an initial priming Hash 97 | # as with OpenStruct. The initializer can still take a default block 98 | # however. To set the degault value use ++#default!(value)++. 99 | # 100 | # OpenObject(:a=>1).default!(0) 101 | 102 | def initialize( hash=nil, &yld ) 103 | super( &yld ) 104 | hash.each { |k,v| define_slot(k,v) } if hash 105 | end 106 | 107 | def initialize_copy( orig ) 108 | orig.each { |k,v| define_slot(k,v) } 109 | end 110 | 111 | # Object inspection. (Careful, this can be clobbered!) 112 | 113 | def inspect 114 | "#<#{object_class}:#{object_hexid} #{super}>" 115 | end 116 | 117 | # Conversion methods. (Careful, these can be clobbered!) 118 | 119 | def to_a() super end 120 | 121 | def to_h() {}.update(self) end 122 | def to_hash() {}.update(self) end 123 | 124 | def to_proc() super end 125 | 126 | def to_openobject() self end 127 | 128 | # Iterate over each key-value pair. (Careful, this can be clobbered!) 129 | 130 | def each(&yld) super(&yld) end 131 | 132 | # Merge one OpenObject with another creating a new OpenObject. 133 | 134 | def merge( other ) 135 | d = dup 136 | d.send(:update, other) 137 | d 138 | end 139 | 140 | # Update this OpenObject with another. 141 | 142 | def update( other ) 143 | begin 144 | other.each { |k,v| define_slot(k,v) } 145 | rescue 146 | other = other.to_h 147 | retry 148 | end 149 | end 150 | 151 | # 152 | 153 | def delete(key) 154 | super(key.to_sym) 155 | end 156 | 157 | # Set the default value. 158 | 159 | def default!(default) 160 | self.default = default 161 | end 162 | 163 | # Preform inplace action on OpenObject as if it were a regular Hash. 164 | #-- 165 | # TODO Not so sure about #as_hash!. For starters if it doesn't return a hash it will fail. 166 | # TODO Replace by using #as(Hash). Perhaps as_hash and as_object shortcuts? Why? 167 | #++ 168 | 169 | def as_hash!(&yld) 170 | replace(yld.call(to_hash)) 171 | end 172 | 173 | # Check equality. (Should equal be true for Hash too?) 174 | 175 | def ==( other ) 176 | return false unless OpenObject === other 177 | super(other) #(other.send(:table)) 178 | end 179 | 180 | def []=(k,v) 181 | protect_slot(k) 182 | super(k.to_sym,v) 183 | end 184 | 185 | def [](k) 186 | super(k.to_sym) 187 | end 188 | 189 | protected 190 | 191 | def store(k,v) 192 | super(k.to_sym,v) 193 | define_slot(k) 194 | end 195 | 196 | def fetch(k,*d,&b) 197 | super(k.to_sym,*d,&b) 198 | end 199 | 200 | def define_slot( key, value=nil ) 201 | protect_slot( key ) 202 | self[key.to_sym] = value 203 | end 204 | 205 | def protect_slot( key ) 206 | (class << self; self; end).class_eval { 207 | protected key rescue nil 208 | } 209 | end 210 | 211 | def method_missing( sym, arg=nil, &blk) 212 | type = sym.to_s[-1,1] 213 | key = sym.to_s.sub(/[=?!]$/,'').to_sym 214 | if type == '=' 215 | define_slot(key,arg) 216 | elsif type == '!' 217 | define_slot(key,arg) 218 | self 219 | else 220 | self[key] 221 | end 222 | end 223 | 224 | $VERBOSE = old_verbose 225 | 226 | end 227 | 228 | # Core Extensions 229 | 230 | class NilClass 231 | # Nil converts to an empty OpenObject. 232 | 233 | def to_openobject 234 | OpenObject.new 235 | end 236 | end 237 | 238 | class Hash 239 | # Convert a Hash into an OpenObject. 240 | 241 | def to_openobject 242 | OpenObject[self] 243 | end 244 | end 245 | 246 | class Proc 247 | # Translates a Proc into an OpenObject. By droping an OpenObject into 248 | # the Proc, the resulting assignments incured as the procedure is 249 | # evaluated produce the OpenObject. This technique is simlar to that 250 | # of MethodProbe. 251 | # 252 | # p = lambda { |x| 253 | # x.word = "Hello" 254 | # } 255 | # o = p.to_openobject 256 | # o.word #=> "Hello" 257 | # 258 | # NOTE The Proc must have an arity of one --no more and no less. 259 | 260 | def to_openobject 261 | raise ArgumentError, 'bad arity for converting Proc to openobject' if arity != 1 262 | o = OpenObject.new 263 | self.call( o ) 264 | o 265 | end 266 | end 267 | 268 | -------------------------------------------------------------------------------- /work/consider/safeopenstructable.rb: -------------------------------------------------------------------------------- 1 | # Same as +OpenStructable+ but if attribute was not set before then 2 | # reading it will fail. 3 | # 4 | # Example: 5 | # 6 | # class X; is OpenStructable 7 | # end 8 | # x = X.new 9 | # x.a = 10 10 | # x.a => 10 11 | # x.b => "Method missing" error. 12 | # 13 | module SafeOpenStructable 14 | 15 | def method_missing(method_id, *args, &block) 16 | if block then return super(method_id, *args, &block); end 17 | var = method_id.to_s 18 | @safeopenstructable_table ||= {} 19 | table = @safeopenstructable_table 20 | if var[-1].chr == "=" and args.size == 1 then return table[var.chop] = args[0]; end 21 | if not var[-1].chr == "=" and args.empty? and table.has_key?(var) then return table[var]; end 22 | super(method_id, *args, &block) 23 | end 24 | 25 | end 26 | 27 | -------------------------------------------------------------------------------- /work/consider/superhash.rb: -------------------------------------------------------------------------------- 1 | class SuperHash < Hash 2 | def initialize 3 | super { |h, k| h[k] = SuperHash.new } 4 | end 5 | end 6 | 7 | -------------------------------------------------------------------------------- /work/deprecated/basic_cascade.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/basic_struct' 2 | 3 | # BasicCascade is subclass of BasicStruct. It differs in a few 4 | # significant ways. 5 | # 6 | # The main reason this class is labeled "cascade", every internal 7 | # Hash is transformed into an BasicCascade dynamically upon access. 8 | # This makes it easy to create "cascading" references. 9 | # 10 | # h = { :x => { :y => { :z => 1 } } } 11 | # c = BasicCascade[h] 12 | # c.x.y.z #=> 1 13 | # 14 | # As soon as you access a node it automatically becomes an BasicCascade. 15 | # 16 | # c = BasicCascade.new #=> # 17 | # c.r #=> # 18 | # c.a.b #=> # 19 | # 20 | # But if you set a node, then that will be it's value. 21 | # 22 | # c.a.b = 4 #=> 4 23 | # 24 | # To query a node without causing the auto-creation of an OpenCasade 25 | # object, use the ?-mark. 26 | # 27 | # c.a.z? #=> nil 28 | # 29 | # BasicCascade also transforms Hashes within Arrays. 30 | # 31 | # h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 } 32 | # c = BasicCascade[h] 33 | # c.x.first.a.assert == 1 34 | # c.x.last.a.assert == 2 35 | # 36 | # Finally, you can set a node and get the reciever back using 37 | # the !-mark. 38 | # 39 | # c = BasicCascade.new #=> # 40 | # c.x!(4).y!(3) #=> #4, :y=>3}> 41 | # 42 | class BasicCascade < BasicStruct 43 | 44 | # 45 | def method_missing(sym, *args, &blk) 46 | type = sym.to_s[-1,1] 47 | name = sym.to_s.gsub(/[=!?]$/, '').to_sym 48 | case type 49 | when '=' 50 | self[name] = args.first 51 | when '!' 52 | #@hash.__send__(name, *args, &blk) 53 | __send__(name, *args, &blk) 54 | when '?' 55 | self[name] 56 | else 57 | if key?(name) 58 | self[name] = transform_entry(self[name]) 59 | else 60 | self[name] = ::BasicCascade.new # TODO: can't get `self.class` ? 61 | end 62 | end 63 | end 64 | 65 | def each 66 | super do |key, entry| 67 | yield([key, transform_entry(entry)]) 68 | end 69 | end 70 | 71 | private 72 | 73 | # 74 | def transform_entry(entry) 75 | case entry 76 | when ::Hash 77 | ::BasicCascade.new(entry) #self.class.new(entry) 78 | when ::Array 79 | entry.map{ |e| transform_entry(e) } 80 | else 81 | entry 82 | end 83 | end 84 | 85 | end 86 | 87 | #-- 88 | # Last, when an entry is not found, 'null' is returned rather then 'nil'. 89 | # This allows for run-on entries withuot error. Eg. 90 | # 91 | # o = BasicCascade.new 92 | # o.a.b.c #=> null 93 | # 94 | # Unfortuately this requires an explict test for null? in 'if' conditions. 95 | # 96 | # if o.a.b.c.null? # true if null 97 | # if o.a.b.c.nil? # true if nil or null 98 | # if o.a.b.c.not? # true if nil or null or false 99 | # 100 | # So be sure to take that into account. 101 | #++ 102 | 103 | -------------------------------------------------------------------------------- /work/deprecated/basic_object.rb: -------------------------------------------------------------------------------- 1 | # Facets' BasicObject is an implementation of Jim Weirich's BlankSlate. 2 | # 3 | # BlankSlate 4 | # Copyright 2004 by Jim Weirich (jim@weirichhouse.org). 5 | # All rights reserved. 6 | # 7 | # Since Ruby 1.9 has a BasicObject class this will of course be 8 | # deprecated as 1.9 goes mainstream. 9 | 10 | unless defined? BasicObject # in case it already exists! 11 | 12 | # BasicObject provides an abstract base class with no predefined 13 | # methods (except for \_\_send__ and \_\_id__). 14 | # BlankSlate is useful as a base class when writing classes that 15 | # depend upon method_missing (e.g. dynamic proxies). 16 | class BasicObject 17 | class << self 18 | # Hide the method named +name+ in the BlankSlate class. Don't 19 | # hide +instance_eval+ or any method beginning with "__". 20 | # 21 | # According to 1.9.1 it should have only these methods: 22 | # 23 | # * #__send__ 24 | # * #instance_eval 25 | # * #instance_exec 26 | # * #equal? 27 | # * #== 28 | # * #! 29 | # * #!= 30 | # * respond_to? 31 | # 32 | # Seems to me it should have #__id__ too. 33 | def hide(name) 34 | undef_method name if 35 | instance_methods.include?(name.to_s) and 36 | name !~ /^(__|respond_to\?|instance_eval$|instance_exec$|equal\?$|\=\=$)/ 37 | end 38 | end 39 | instance_methods.each{ |m| hide(m) } 40 | end 41 | 42 | # Since Ruby is very dynamic, methods added to the ancestors of 43 | # BlankSlate after BlankSlate is defined will show up in the 44 | # list of available BlankSlate methods. We handle this by defining a 45 | # hook in the Object and Kernel classes that will hide any defined 46 | module Kernel #:nodoc: 47 | class << self 48 | alias_method :basic_object_method_added, :method_added 49 | 50 | # Detect method additions to Kernel and remove them in the 51 | # BlankSlate class. 52 | def method_added(name) 53 | basic_object_method_added(name) 54 | return if self != Kernel 55 | BasicObject.hide(name) 56 | end 57 | end 58 | end 59 | 60 | class Object #:nodoc: 61 | class << self 62 | alias_method :basic_object_method_added, :method_added 63 | 64 | # Detect method additions to Object and remove them in the 65 | # BlankSlate class. 66 | def method_added(name) 67 | basic_object_method_added(name) 68 | return if self != Object 69 | BasicObject.hide(name) 70 | end 71 | end 72 | end 73 | 74 | end 75 | -------------------------------------------------------------------------------- /work/deprecated/basic_struct.rb: -------------------------------------------------------------------------------- 1 | unless defined?(BasicObject) 2 | require 'blankslate' 3 | BasicObject = BlankSlate 4 | end 5 | 6 | # = BasicStruct 7 | # 8 | # BasicStruct is very similar to Ruby's own OpenStruct, but it offers some 9 | # advantages. With OpenStruct, slots with the same name as predefined 10 | # Object methods cannot be used. With BasicStruct, almost any slot can be 11 | # defined. BasicStruct is a subclass of BasicObject to ensure all method 12 | # slots, except those that are absolutely essential, are open for use. 13 | # 14 | #-- 15 | # If you wish to pass a BasicStruct to a routine that normal takes a Hash, 16 | # but are uncertain it can handle the distictions properly you can convert 17 | # easily to a Hash using #as_hash! and the result will automatically be 18 | # converted back to an BasicStruct on return. 19 | # 20 | # o = BasicStruct.new(:a=>1,:b=>2) 21 | # o.as_hash!{ |h| h.update(:a=>6) } 22 | # o #=> #6,:b=>2}> 23 | #++ 24 | # 25 | # Unlike a Hash, all BasicStruct's keys are symbols and all keys are converted 26 | # to such using #to_sym on the fly. 27 | 28 | class BasicStruct < BasicObject 29 | 30 | def self.[](hash=nil) 31 | new(hash) 32 | end 33 | 34 | # Inititalizer for BasicStruct is slightly different than that of Hash. 35 | # It does not take a default parameter, but an initial priming Hash, 36 | # like OpenStruct. The initializer can still take a default block 37 | # however. To set the default value use #default!(value). 38 | # 39 | # BasicStruct.new(:a=>1).default!(0) 40 | # 41 | def initialize(hash=nil, &yld) 42 | super(&yld) 43 | if hash 44 | hash.each{ |k,v| store(k,v) } 45 | end 46 | end 47 | 48 | # 49 | def initialize_copy(orig) 50 | orig.each{ |k,v| store(k,v) } 51 | end 52 | 53 | # Object inspection. 54 | # TODO: Need to get __class__ and __id__ in hex form. 55 | def inspect 56 | #@table.inspect 57 | hexid = __id__ 58 | klass = "BasicStruct" # __class__ 59 | "#<#{klass}:#{hexid} #{@table.inspect}>" 60 | end 61 | 62 | # Convert to an associative array. 63 | def to_a 64 | super 65 | end 66 | 67 | # 68 | def to_hash 69 | h = {} 70 | each do |k,v| 71 | h[k] = v 72 | end 73 | h 74 | end 75 | 76 | # 77 | alias_method :to_h, :to_hash 78 | 79 | # 80 | def to_basicstruct 81 | self 82 | end 83 | 84 | # Convert to an assignment procedure. 85 | def to_proc(response=false) 86 | hash = self #@table 87 | if response 88 | ::Proc.new do |o| 89 | hash.each do |k,v| 90 | o.__send__("#{k}=", v) rescue nil 91 | end 92 | end 93 | else 94 | ::Proc.new do |o| 95 | hash.each{ |k,v| o.__send__("#{k}=", v) } 96 | end 97 | end 98 | end 99 | 100 | # NOT SURE ABOUT THIS 101 | #def as_hash 102 | # @table 103 | #end 104 | 105 | # Is a given +key+ defined? 106 | def key?(key) 107 | super(key.to_sym) 108 | end 109 | 110 | # 111 | def is_a?(klass) 112 | return true if klass == ::Hash # TODO: Is this wise? How to fake a subclass? 113 | return true if klass == ::BasicStruct 114 | false 115 | end 116 | 117 | # Iterate over each key-value pair. 118 | def each(&yld) 119 | super(&yld) 120 | end 121 | 122 | # Set the default value. 123 | def default=(default) 124 | #@table.default = default 125 | super(default) 126 | end 127 | 128 | # Check equality. 129 | def ==( other ) 130 | case other 131 | when ::BasicStruct 132 | to_hash == other.to_hash # as_hash 133 | when ::Hash 134 | to_hash == other 135 | else 136 | if other.respond_to?(:to_hash) 137 | to_hash == other.to_hash 138 | else 139 | false 140 | end 141 | end 142 | end 143 | 144 | # 145 | def eql?( other ) 146 | case other 147 | when ::BasicStruct 148 | super(other.to_hash) # other.as_hash 149 | else 150 | false 151 | end 152 | end 153 | 154 | # 155 | def <<(x) 156 | case x 157 | when ::Hash 158 | self.update(x) 159 | when ::Array 160 | x.each_slice(2) do |(k,v)| 161 | self[k] = v 162 | end 163 | end 164 | end 165 | 166 | # 167 | def []=(key, value) 168 | super(key.to_sym, value) 169 | end 170 | 171 | # 172 | def [](key) 173 | super(key.to_sym) 174 | end 175 | 176 | # TODO: Should this work like #merge or #update ? 177 | def merge!(other) 178 | ::BasicStruct.new(to_hash.merge(other)) 179 | end 180 | 181 | # 182 | def update!(other) 183 | self.update(other) 184 | self 185 | end 186 | 187 | # 188 | def respond_to?(key) 189 | key?(key) 190 | end 191 | 192 | # NOTE: These were protected, why? 193 | 194 | # 195 | def store(k, v) 196 | super(k.to_sym, v) 197 | end 198 | 199 | # 200 | def fetch(k, *d, &b) 201 | super(k.to_sym, *d, &b) 202 | end 203 | 204 | protected 205 | 206 | #def as_hash! 207 | # Functor.new do |op,*a,&b| 208 | # result = @table.__send__(op,*a,&b) 209 | # case result 210 | # when Hash 211 | # BasicObject.new(result) 212 | # else 213 | # result 214 | # end 215 | # end 216 | #end 217 | 218 | #def define_slot(key, value=nil) 219 | # @table[key.to_sym] = value 220 | #end 221 | 222 | #def protect_slot( key ) 223 | # (class << self; self; end).class_eval { 224 | # protected key rescue nil 225 | # } 226 | #end 227 | 228 | def method_missing(sym, *args, &blk) 229 | type = sym.to_s[-1,1] 230 | key = sym.to_s.sub(/[=?!]$/,'').to_sym 231 | case type 232 | when '=' 233 | store(key, args[0]) 234 | when '!' 235 | __send__(key, *args, &blk) 236 | # if key?(key) 237 | # fetch(key) 238 | # else 239 | # store(key, BasicObject.new) 240 | # end 241 | when '?' 242 | fetch(key) 243 | else 244 | fetch(key) 245 | end 246 | end 247 | 248 | end 249 | 250 | # Core Extensions 251 | 252 | class Hash 253 | # Convert a Hash into a BasicStruct. 254 | def to_basicstruct 255 | BasicStruct[self] 256 | end 257 | end 258 | 259 | =begin 260 | class NilClass 261 | # Nil converts to an empty BasicObject. 262 | def to_basicstruct 263 | BasicObject.new 264 | end 265 | end 266 | 267 | class Proc 268 | # Translates a Proc into an BasicObject. By droping an BasicObject into 269 | # the Proc, the resulting assignments incured as the procedure is 270 | # evaluated produce the BasicObject. This technique is simlar to that 271 | # of MethodProbe. 272 | # 273 | # p = lambda { |x| 274 | # x.word = "Hello" 275 | # } 276 | # o = p.to_basicstruct 277 | # o.word #=> "Hello" 278 | # 279 | # NOTE The Proc must have an arity of one --no more and no less. 280 | def to_basicstruct 281 | raise ArgumentError, 'bad arity for converting Proc to basicstruct' if arity != 1 282 | o = BasicObject.new 283 | self.call( o ) 284 | o 285 | end 286 | end 287 | =end 288 | 289 | -------------------------------------------------------------------------------- /work/deprecated/bench_harray.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | 3 | Some speed comparisions between standard Array and HArray. 4 | 5 | =end 6 | 7 | require 'trix/harray' 8 | require 'benchmark' 9 | 10 | $n = 50000 11 | 12 | # standard array 13 | 14 | def sarray_make 15 | $n.times do 16 | $sa = [1,'a',nil] 17 | end 18 | end 19 | 20 | def sarray_slice 21 | $n.times do 22 | $sa[1..2] 23 | end 24 | end 25 | 26 | # hash array 27 | 28 | def harray_make 29 | $n.times do 30 | $ha = HArray.new_h({0=>1,1=>'a',2=>nil}) 31 | end 32 | end 33 | 34 | def harray_slice 35 | $n.times do 36 | $ha[1..2] 37 | end 38 | end 39 | 40 | 41 | ### --- bench --- 42 | 43 | puts "\nCURRENT" 44 | Benchmark.bm(15) do |b| 45 | b.report("HAarry#new:") { harray_make } 46 | b.report("HArray#slice:") { harray_slice } 47 | b.report("Array#new:") { sarray_make } 48 | b.report("Array#slice:") { sarray_slice } 49 | end 50 | -------------------------------------------------------------------------------- /work/deprecated/case_basic_cascade.rb: -------------------------------------------------------------------------------- 1 | require 'lemon' 2 | require 'ae' 3 | require 'ae/legacy' # bacause imitation BasicObject sucks 4 | 5 | require 'hashery/basic_cascade' 6 | 7 | testcase BasicCascade do 8 | include AE::Legacy::Assertions 9 | 10 | class_method :new do 11 | BasicCascade.new(:a=>1) 12 | end 13 | 14 | class_method :[] do 15 | test "hash" do 16 | o = BasicCascade[:a=>1,:b=>2] 17 | assert_equal(1, o.a) 18 | assert_equal(2, o.b) 19 | end 20 | test "hash in hash" do 21 | o = BasicCascade[:a=>1,:b=>2,:c=>{:x=>9}] 22 | assert_equal(9, o.c.x) 23 | end 24 | test "hash in hash in hash" do 25 | h = {:a=>1,:x=>{:y=>{:z=>1}}} 26 | c = BasicCascade[h] 27 | assert_equal(1, c.x.y.z) 28 | end 29 | end 30 | 31 | method :[] do 32 | test "basic assignment" do 33 | o = BasicCascade.new 34 | o[:a] = 1 35 | assert_equal(1, o.a) 36 | end 37 | end 38 | 39 | method :[]= do 40 | test "basic assignment with primed BasicCascade" do 41 | o = BasicCascade[:a=>1] 42 | o[:b] = 2 43 | o.to_h.assert == {:a=>1,:b=>2} 44 | end 45 | end 46 | 47 | method :to_a do 48 | test do 49 | o = BasicCascade[:a=>1,:b=>2] 50 | a = o.to_a 51 | a.assert.include?([:a,1]) 52 | a.assert.include?([:b,2]) 53 | a.size.assert == 2 54 | end 55 | end 56 | 57 | method :to_h do 58 | test do 59 | o = BasicCascade[:a=>1] 60 | assert_equal({:a=>1}, o.to_h) 61 | end 62 | end 63 | 64 | method :merge! do 65 | test 'can merge from hash' do 66 | o = BasicCascade[:f0=>"f0"] 67 | h = { :h0=>"h0" } 68 | r = BasicCascade[:f0=>"f0", :h0=>"h0"] 69 | assert_equal(r, o.merge!(h)) 70 | #assert_equal({:f0=>"f0", :h0=>"h0"}, h.merge(o)) 71 | end 72 | end 73 | 74 | method :update do 75 | test 'can update from hash' do 76 | o = BasicCascade[:f1=>"f1"] 77 | o.update(:h1=>"h1") 78 | assert_equal(o, BasicCascade[:f1=>"f1", :h1=>"h1"]) 79 | end 80 | end 81 | 82 | method :method_missing do 83 | test "writer and reader" do 84 | o = BasicCascade.new 85 | 10.times{ |i| o.__send__("n#{i}=", 1 ) } 86 | 10.times{ |i| assert_equal(1, o.__send__("n#{i}")) } 87 | end 88 | end 89 | 90 | method :<< do 91 | test do 92 | c = BasicCascade.new 93 | c << [:x,8] 94 | c << [:y,9] 95 | 96 | assert_equal(8, c.x) 97 | assert_equal(9, c.y) 98 | end 99 | end 100 | 101 | end 102 | 103 | testcase Hash do 104 | 105 | method :update do 106 | test "hash can be updated by BasicCascade" do 107 | o = BasicCascade[:f1=>"f1"] 108 | h = {:h1=>"h1"} 109 | h.update(o) 110 | h.assert == {:f1=>"f1", :h1=>"h1"} 111 | end 112 | end 113 | 114 | end 115 | -------------------------------------------------------------------------------- /work/deprecated/case_basicstruct.rb: -------------------------------------------------------------------------------- 1 | require 'lemon' 2 | require 'ae' 3 | require 'ae/legacy' 4 | 5 | require 'hashery/basic_struct' 6 | 7 | testcase BasicStruct do 8 | include AE::Legacy::Assertions 9 | 10 | method :respond_to? do 11 | test do 12 | o = BasicStruct.new 13 | t = o.respond_to?(:key?) 14 | assert t 15 | end 16 | end 17 | 18 | method :is_a? do 19 | test do 20 | assert BasicStruct[{}].is_a?(Hash) 21 | assert BasicStruct[{}].is_a?(BasicStruct) 22 | end 23 | end 24 | 25 | method :[] do 26 | test "subhash access" do 27 | o = BasicStruct[:a=>1,:b=>{:x=>9}] 28 | assert( o[:b][:x] == 9 ) 29 | assert( o.b[:x] == 9 ) 30 | end 31 | 32 | test "indifferent key access" do 33 | o = BasicStruct["a"=>1,"b"=>{:x=>9}] 34 | assert( o["a"] == 1 ) 35 | assert( o[:a] == 1 ) 36 | assert( o["b"] == {:x=>9} ) 37 | assert( o[:b] == {:x=>9} ) 38 | assert( o["b"][:x] == 9 ) 39 | assert( o[:b]["x"] == nil ) 40 | end 41 | end 42 | 43 | method :[]= do 44 | test "setting first entry" do 45 | f0 = BasicStruct.new 46 | f0[:a] = 1 47 | assert( f0.to_h == {:a=>1} ) 48 | end 49 | 50 | test "setting an additional entry" do 51 | f0 = BasicStruct[:a=>1] 52 | f0[:b] = 2 53 | assert( f0.to_h == {:a=>1,:b=>2} ) 54 | end 55 | end 56 | 57 | method :method_missing do 58 | test "reading entries" do 59 | f0 = BasicStruct[:class=>1] 60 | assert( f0.class == 1 ) 61 | end 62 | 63 | test "setting entries" do 64 | fo = BasicStruct.new 65 | 9.times{ |i| fo.__send__("n#{i}=", 1) } 66 | 9.times{ |i| 67 | assert( fo.__send__("n#{i}") == 1 ) 68 | } 69 | end 70 | 71 | test "using bang" do 72 | o = BasicStruct.new 73 | o.a = 10 74 | o.b = 20 75 | h = {} 76 | o.each!{ |k,v| h[k] = v + 10 } 77 | assert( h == {:a=>20, :b=>30} ) 78 | end 79 | end 80 | 81 | #method :as_hash do 82 | # test do 83 | # f0 = BasicStruct[:f0=>"f0"] 84 | # h0 = { :h0=>"h0" } 85 | # assert( BasicStruct[:f0=>"f0", :h0=>"h0"] == f0.as_hash.merge(h0) ) 86 | # assert( {:f0=>"f0", :h0=>"h0"} == h0.merge(f0) ) 87 | # end 88 | #end 89 | 90 | method :as_hash do 91 | test do 92 | f1 = BasicStruct[:f1=>"f1"] 93 | h1 = { :h1=>"h1" } 94 | f1.as_hash.update(h1) 95 | assert( f1 == BasicStruct[:f1=>"f1", :h1=>"h1"] ) 96 | end 97 | end 98 | 99 | #method :as_hash do 100 | # test do 101 | # f1 = BasicStruct[:f1=>"f1"] 102 | # h1 = { :h1=>"h1" } 103 | # f1.as_hash.update(h1) 104 | # h1.update(f1) 105 | # assert( f1 == BasicStruct[:f1=>"f1", :h1=>"h1"] ) 106 | # assert( h1 == {:f1=>"f1", :h1=>"h1"} ) 107 | # end 108 | #end 109 | 110 | method :<< do 111 | test "passing a hash" do 112 | fo = BasicStruct.new 113 | fo << {:a=>1,:b=>2} 114 | assert( fo.to_h == {:a=>1, :b=>2} ) 115 | end 116 | 117 | test "passing a pair" do 118 | fo = BasicStruct.new 119 | fo << [:a, 1] 120 | fo << [:b, 2] 121 | assert( fo.to_h == {:a=>1, :b=>2} ) 122 | end 123 | end 124 | 125 | method :to_h do 126 | test do 127 | ho = {} 128 | fo = BasicStruct.new 129 | 5.times{ |i| ho["n#{i}".to_sym] = 1 } 130 | 5.times{ |i| fo.__send__("n#{i}=", 1) } 131 | assert( fo.to_h == ho ) 132 | end 133 | test "BasicStruct within BasicStruct" do 134 | o = BasicStruct.new 135 | o.a = 10 136 | o.b = 20 137 | o.x = BasicStruct.new 138 | o.x.a = 100 139 | o.x.b = 200 140 | o.x.c = 300 141 | assert( o.to_h == {:a=>10, :b=>20, :x=>{:a=>100, :b=>200, :c=>300}} ) 142 | end 143 | end 144 | 145 | method :to_proc do 146 | test do 147 | #p = Proc.new{ |x| x.word = "Hello" } 148 | o = BasicStruct[:a=>1,:b=>2] 149 | assert( Proc === o.to_proc ) 150 | end 151 | end 152 | 153 | end 154 | 155 | TestCase Hash do 156 | 157 | method :to_basicstruct do 158 | test do 159 | h = {'a'=>1, 'b'=>2} 160 | o = h.to_basicstruct 161 | assert( o.a == 1 ) 162 | assert( o.b == 2 ) 163 | end 164 | end 165 | 166 | method :update do 167 | test "by BasicStruct" do 168 | raise NotImplementedError, "Ruby 1.8 does not know #to_hash." 169 | 170 | h1 = { :h1=>"h1" } 171 | f1 = BasicStruct[:f1=>"f1"] 172 | h1.update(f1) 173 | assert( h1 == {:f1=>"f1", :h1=>"h1"} ) 174 | end 175 | end 176 | 177 | end 178 | 179 | =begin 180 | TestCase Proc do 181 | 182 | method :to_basicstruct do 183 | test do 184 | p = lambda { |x| x.word = "Hello" } 185 | o = p.to_basicstruct 186 | assert( o.word == "Hello" ) 187 | end 188 | end 189 | 190 | end 191 | =end 192 | 193 | -------------------------------------------------------------------------------- /work/deprecated/memoizer.rb: -------------------------------------------------------------------------------- 1 | # Memoizer wraps objects to provide cached method calls. 2 | # 3 | # class X 4 | # def initialize ; @tick = 0 ; end 5 | # def tick; @tick + 1; end 6 | # def memo; @memo ||= Memoizer.new(self) ; end 7 | # end 8 | # 9 | # x = X.new 10 | # x.tick #=> 1 11 | # x.memo.tick #=> 2 12 | # x.tick #=> 3 13 | # x.memo.tick #=> 2 14 | # x.tick #=> 4 15 | # x.memo.tick #=> 2 16 | # 17 | # You can also use to cache collections of objects to gain code speed ups. 18 | # 19 | # points = points.collect{|point| Memoizer.cache(point)} 20 | # 21 | # After our algorithm has finished using points, we want to get rid of 22 | # these Memoizer objects. That's easy: 23 | # 24 | # points = points.collect{|point| point.__self__ } 25 | # 26 | # Or if you prefer (it is ever so slightly safer): 27 | # 28 | # points = points.collect{|point| Memoizer.uncache(point)} 29 | # 30 | # Memoizer is the work of Erik Veenstra 31 | # 32 | # Copyright (c) 2006 Erik Veenstra 33 | # 34 | # See http://javathink.blogspot.com/2008/09/what-is-memoizer-and-why-should-you.html 35 | 36 | class Memoizer 37 | 38 | #private :class, :clone, :display, :type, :method, :to_a, :to_s 39 | private *instance_methods(true).select{ |m| m.to_s !~ /^__/ } 40 | 41 | def initialize(object) 42 | @self = object 43 | @cache = {} 44 | end 45 | 46 | def __self__ ; @self ; end 47 | 48 | # Not thread-safe! Speed is important in caches... ;] 49 | def method_missing(method_name, *args, &block) 50 | @cache[[method_name, args, block]] ||= @self.__send__(method_name, *args, &block) 51 | end 52 | 53 | #def self; @self; end 54 | 55 | def self.cache(object) 56 | new(object) 57 | end 58 | 59 | def self.uncache(cached_object) 60 | cached_object.instance_variable_get('@self') 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /work/deprecated/ostructable.rb: -------------------------------------------------------------------------------- 1 | # OpensStructable is a mixin module which can provide OpenStruct behavior to 2 | # any class or object. OpenStructable allows extention of data objects 3 | # with arbitrary attributes. 4 | # 5 | # require 'ostructable' 6 | # 7 | # class Record 8 | # include OpenStructable 9 | # end 10 | # 11 | # record = Record.new 12 | # record.name = "John Smith" 13 | # record.age = 70 14 | # record.pension = 300 15 | # 16 | # puts record.name # -> "John Smith" 17 | # puts record.address # -> nil 18 | # 19 | # @author 7rans 20 | # @author Yukihiro Matsumoto 21 | # @author Gavin Sinclair 22 | # 23 | module OpenStructable 24 | 25 | # TODO: Update to matchh current OpenStruct class. 26 | 27 | # TODO: Keep this uptodate with ostruct.rb. 28 | 29 | # TODO: See if Matz will accept it into core so we don't have to anymore. 30 | 31 | # TODO: As with OpenStruct, marshalling is problematic at the moment. 32 | 33 | def self.include(base) 34 | if Hash > base 35 | base.module_eval do 36 | define_method(:__table__) do 37 | self 38 | end 39 | end 40 | protected :__table__ 41 | end 42 | end 43 | 44 | def initialize(hash=nil) 45 | @__table__ = {} 46 | if hash 47 | for k,v in hash 48 | __table__[k.to_sym] = v 49 | new_ostruct_member(k) 50 | end 51 | end 52 | end 53 | 54 | # 55 | def __table__ 56 | @__table__ ||= {} 57 | end 58 | protected :__table__ 59 | 60 | # duplicate an OpenStruct object members. 61 | def initialize_copy(orig) 62 | super 63 | __table__.replace(__table__.dup) 64 | end 65 | 66 | def marshal_dump 67 | __table__ 68 | end 69 | 70 | def marshal_load(hash) 71 | __table__.replace(hash) 72 | __table__.each_key{|key| new_ostruct_member(key)} 73 | end 74 | 75 | def new_ostruct_member(name) 76 | unless self.respond_to?(name) 77 | self.instance_eval %{ 78 | def #{name}; __table__[:#{name}]; end 79 | def #{name}=(x); __table__[:#{name}] = x; end 80 | } 81 | end 82 | end 83 | 84 | # 85 | # Generate additional attributes and values. 86 | # 87 | def update(hash) 88 | #__table__ ||= {} 89 | if hash 90 | for k,v in hash 91 | __table__[k.to_sym] = v 92 | new_ostruct_member(k) 93 | end 94 | end 95 | end 96 | 97 | # 98 | def method_missing(mid, *args) # :nodoc: 99 | mname = mid.to_s 100 | len = args.length 101 | if mname =~ /=$/ 102 | if len != 1 103 | raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) 104 | end 105 | if self.frozen? 106 | raise TypeError, "can't modify frozen #{self.class}", caller(1) 107 | end 108 | mname.chop! 109 | #@__table__ ||= {} 110 | __table__[mname.intern] = args[0] 111 | self.new_ostruct_member(mname) 112 | elsif len == 0 113 | #@__table__ ||= {} 114 | __table__[mid] 115 | else 116 | raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1) 117 | end 118 | end 119 | 120 | # 121 | # Remove the named field from the object. 122 | # 123 | def delete_field(name) 124 | #@__table__ ||= {} 125 | __table__.delete(name.to_sym) 126 | end 127 | 128 | # 129 | # Returns a string containing a detailed summary of the keys and values. 130 | # 131 | def inspect 132 | str = "<#{self.class}" 133 | for k,v in (@__table__ ||= {}) 134 | str << " #{k}=#{v.inspect}" 135 | end 136 | str << ">" 137 | end 138 | 139 | # TODO: OpenStruct could be compared too, but only if it is loaded. How? 140 | 141 | # 142 | # Compare this object and +other+ for equality. 143 | # 144 | def ==(other) 145 | case other 146 | when OpenStructable 147 | __table__ == other.__table__ 148 | #when OpenStruct 149 | # __table__ == other.__table__ 150 | when Hash 151 | __table__ == other 152 | else 153 | false 154 | end 155 | end 156 | 157 | end 158 | 159 | =begin 160 | # 161 | # It is possibe to implement OpenStruct itself with 162 | # this OpenStructable module as follows: 163 | # 164 | class OpenStruct 165 | include OpenStructable 166 | end 167 | =end 168 | -------------------------------------------------------------------------------- /work/deprecated/webme/advert.html: -------------------------------------------------------------------------------- 1 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /work/deprecated/webme/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | font : 'sans serif, helvetica' 3 | 4 | size : '16px' 5 | 6 | color: '#EE6600' 7 | 8 | menu: 9 | host: Rubygem 10 | mail: Mailing List 11 | wiki: User Guide 12 | api : API Guide 13 | code: Development 14 | 15 | -------------------------------------------------------------------------------- /work/deprecated/webme/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyworks/hashery/da224493905c51e4045b385799dd57637187112d/work/deprecated/webme/logo.jpg -------------------------------------------------------------------------------- /work/reference/hash_magic/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 BehindLogic 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | and associated documentation files (the "Software"), to deal in the Software without restriction, 5 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 6 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 7 | subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 13 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 14 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 16 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /work/reference/hash_magic/README: -------------------------------------------------------------------------------- 1 | HashMagic simply contains a couple of spin-off data structures based on the basic Ruby Hash: 2 | 1. SlashedHash 3 | 2. OrderedHash 4 | 5 | You can use either of those separately or in combination. Note that ruby-1.9 Hashes are made to keep their order, but I'm not sure about setting an order explicitly for them, and definitely not setting an order ahead of time. 6 | 7 | ==SlashedHash 8 | The concept of a SlashedHash is to flatten down a multi-level Hash structure into a single-level Hash whose keys represent the pathway to the data in the original multi-level structure. For example, 9 | >> sh = {'a' => 'b', 'c' => {'d' => :e, 'f' => ['a', 'b']}, 'd' => ['e', 'f']}.slashed 10 | => {slashed: "a"=>"b", "c/d"=>:e, "d"=>["e", "f"], "c/f"=>["a", "b"]} 11 | 12 | Once you transform a Hash into a SlashedHash, the only negative differences are: 13 | 1. All but the end-point (or 'leaf node') data are turned into strings. Restated: Any data at the "end" of a Hash multi-level structure is left alone, but all keys in between, whether string, symbol, or anything else, is turned into a string in order to pack them together into a "slashed" key. 14 | 2. Speed. A Ruby Hash is built into the core and written mostly in C so it is very fast; but a SlashedHash clones the same C functionality on all the Hash's methods so that it can act as a normal Hash but with more sugar. 15 | 16 | Like I said, the above mentioned things are the only negative differences. You can still access items by their original keys -- to continue the example above, 17 | >> sh['c']['d'] 18 | => :e 19 | BUT you can also access the same values by their "slashed" keys: 20 | >> sh['c/d'] 21 | => :e 22 | >> sh['c/f'] 23 | => ["a", "b"] 24 | And you can access the keys either normally or flattened: 25 | >> sh.keys 26 | => ["a", "c", "d"] 27 | >> sh.flat.keys 28 | => ["a", "c/d", "d", "c/f"] 29 | Accessing one level into the SlashedHash gives you another SlashedHash, if applicable: 30 | >> sh['c'] 31 | => {slashed: "d"=>:e, "f"=>["a", "b"]} 32 | 33 | ==What good is SlashedHash? What can it do for me? 34 | 35 | 1) It comes in VERY handy when doing special transformations or multi-level key mappings. Think of reading an XML structure into specific property names (example uses the formattedstring gem syntax): 36 | >> joe = "Joe Schmoe25Joseph Schmoe56".formatted(:xml).to_hash.slashed 37 | => {slashed: "person"=>{"name"=>"Joe Schmoe", "age"=>"25", "parent"=>{"name"=>"Joseph Schmoe", "age"=>"56"}}} 38 | >> mapping = {'name' => 'person/name', 'age' => 'person/age', 'parent_name' => 'person/parent/name', 'parent_age' => 'person/parent/age'} 39 | >> joe.transform_keys_with_mapping(mapping) 40 | => {"name"=>"Joe Schmoe", "parent_name"=>"Joseph Schmoe", "age"=>"25", "parent_age"=>"56"} 41 | 42 | 2) It is useful for sorting multi-level hashes by integration with OrderedHash. 43 | 44 | / / / Still writing, will come back to this sometime... :) / / / -------------------------------------------------------------------------------- /work/reference/hash_magic/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'rake/testtask' 4 | require 'rake/rdoctask' 5 | require 'rake/gempackagetask' 6 | require 'rake/contrib/rubyforgepublisher' 7 | 8 | PKG_NAME = 'hash_magic' 9 | PKG_VERSION = "0.1.0" 10 | 11 | PKG_FILES = FileList[ 12 | "lib/**/*", "rspec/**/*", "[A-Z]*", "Rakefile", "doc/**/*" 13 | ] 14 | 15 | desc "Default Task" 16 | task :default => [ :test ] do 17 | # Run the unit tests 18 | desc "Run all rspec tests (rake task not yet implemented!)" 19 | end 20 | 21 | # Make a console, useful when working on tests 22 | desc "Generate a test console" 23 | task :console do 24 | verbose( false ) { sh "irb -I lib/ -r '#{PKG_NAME}'" } 25 | end 26 | 27 | # Genereate the RDoc documentation 28 | desc "Create documentation" 29 | Rake::RDocTask.new("doc") { |rdoc| 30 | rdoc.title = "Hash Magic" 31 | rdoc.rdoc_dir = 'doc' 32 | rdoc.rdoc_files.include('README') 33 | rdoc.rdoc_files.include('LICENSE') 34 | rdoc.rdoc_files.include('lib/**/*.rb') 35 | } 36 | 37 | # Generate the package 38 | spec = Gem::Specification.new do |s| 39 | #### Basic information. 40 | s.name = PKG_NAME 41 | s.version = PKG_VERSION 42 | s.platform = Gem::Platform::RUBY 43 | s.description = s.summary = "Adds Hash#ordered and Hash#slashed to Hash, which flavor a hash to behave in certain more humanly manners." 44 | 45 | #### Which files are to be included in this gem? Everything! (Except CVS directories.) 46 | s.files = PKG_FILES 47 | 48 | #### Load-time details: library and application (you will need one or both). 49 | s.require_path = 'lib' 50 | 51 | #### Documentation and testing. 52 | s.has_rdoc = true 53 | # s.extra_rdoc_files = ["README", "LICENSE"] 54 | 55 | #### Author and project details. 56 | s.author = "Daniel Parker" 57 | s.email = "gems@behindlogic.com" 58 | s.homepage = "http://hash_magic.rubyforge.org" 59 | s.rubyforge_project = 'hash-magic' 60 | end 61 | 62 | Rake::GemPackageTask.new(spec) do |pkg| 63 | pkg.need_zip = false 64 | pkg.need_tar = false 65 | end 66 | 67 | desc "Report code statistics (KLOCs, etc) from the application" 68 | task :stats do 69 | require 'code_statistics' 70 | CodeStatistics.new( 71 | ["Library", "lib"] 72 | ).to_s 73 | end 74 | 75 | desc "Publish new documentation" 76 | task :publish => [:doc] do 77 | `scp -r doc/* dcparker@rubyforge.org:/var/www/gforge-projects/hash-magic` 78 | end 79 | -------------------------------------------------------------------------------- /work/reference/orderedhash.rb: -------------------------------------------------------------------------------- 1 | 2 | # AUTHOR 3 | # jan molic /mig/at/1984/dot/cz/ 4 | # 5 | # DESCRIPTION 6 | # Hash with preserved order and some array-like extensions 7 | # Public domain. 8 | # 9 | # THANKS 10 | # Andrew Johnson for his suggestions and fixes of Hash[], 11 | # merge, to_a, inspect and shift 12 | class OrderedHash < ::Hash 13 | attr_accessor :order 14 | 15 | class << self 16 | def [] *args 17 | hsh = OrderedHash.new 18 | if Hash === args[0] 19 | hsh.replace args[0] 20 | elsif (args.size % 2) != 0 21 | raise ArgumentError, "odd number of elements for Hash" 22 | else 23 | 0.step(args.size - 1, 2) do |a| 24 | b = a + 1 25 | hsh[args[a]] = args[b] 26 | end 27 | end 28 | hsh 29 | end 30 | end 31 | def initialize(*a, &b) 32 | super 33 | @order = [] 34 | end 35 | def store_only a,b 36 | store a,b 37 | end 38 | alias orig_store store 39 | def store a,b 40 | @order.push a unless has_key? a 41 | super a,b 42 | end 43 | alias []= store 44 | def == hsh2 45 | return false if @order != hsh2.order 46 | super hsh2 47 | end 48 | def clear 49 | @order = [] 50 | super 51 | end 52 | def delete key 53 | @order.delete key 54 | super 55 | end 56 | def each_key 57 | @order.each { |k| yield k } 58 | self 59 | end 60 | def each_value 61 | @order.each { |k| yield self[k] } 62 | self 63 | end 64 | def each 65 | @order.each { |k| yield k,self[k] } 66 | self 67 | end 68 | alias each_pair each 69 | def delete_if 70 | @order.clone.each { |k| 71 | delete k if yield(k) 72 | } 73 | self 74 | end 75 | def values 76 | ary = [] 77 | @order.each { |k| ary.push self[k] } 78 | ary 79 | end 80 | def keys 81 | @order 82 | end 83 | def first 84 | {@order.first => self[@order.first]} 85 | end 86 | def last 87 | {@order.last => self[@order.last]} 88 | end 89 | def invert 90 | hsh2 = Hash.new 91 | @order.each { |k| hsh2[self[k]] = k } 92 | hsh2 93 | end 94 | def reject &block 95 | self.dup.delete_if &block 96 | end 97 | def reject! &block 98 | hsh2 = reject &block 99 | self == hsh2 ? nil : hsh2 100 | end 101 | def replace hsh2 102 | @order = hsh2.keys 103 | super hsh2 104 | end 105 | def shift 106 | key = @order.first 107 | key ? [key,delete(key)] : super 108 | end 109 | def unshift k,v 110 | unless self.include? k 111 | @order.unshift k 112 | orig_store(k,v) 113 | true 114 | else 115 | false 116 | end 117 | end 118 | def push k,v 119 | unless self.include? k 120 | @order.push k 121 | orig_store(k,v) 122 | true 123 | else 124 | false 125 | end 126 | end 127 | def pop 128 | key = @order.last 129 | key ? [key,delete(key)] : nil 130 | end 131 | def to_a 132 | ary = [] 133 | each { |k,v| ary << [k,v] } 134 | ary 135 | end 136 | def to_s 137 | self.to_a.to_s 138 | end 139 | def inspect 140 | ary = [] 141 | each {|k,v| ary << k.inspect + "=>" + v.inspect} 142 | '{' + ary.join(", ") + '}' 143 | end 144 | def update hsh2 145 | hsh2.each { |k,v| self[k] = v } 146 | self 147 | end 148 | alias :merge! update 149 | def merge hsh2 150 | self.dup update(hsh2) 151 | end 152 | def select 153 | ary = [] 154 | each { |k,v| ary << [k,v] if yield k,v } 155 | ary 156 | end 157 | def class 158 | Hash 159 | end 160 | def __class__ 161 | OrderedHash 162 | end 163 | 164 | attr_accessor "to_yaml_style" 165 | def yaml_inline= bool 166 | if respond_to?("to_yaml_style") 167 | self.to_yaml_style = :inline 168 | else 169 | unless defined? @__yaml_inline_meth 170 | @__yaml_inline_meth = 171 | lambda {|opts| 172 | YAML::quick_emit(object_id, opts) {|emitter| 173 | emitter << '{ ' << map{|kv| kv.join ': '}.join(', ') << ' }' 174 | } 175 | } 176 | class << self 177 | def to_yaml opts = {} 178 | begin 179 | @__yaml_inline ? @__yaml_inline_meth[ opts ] : super 180 | rescue 181 | @to_yaml_style = :inline 182 | super 183 | end 184 | end 185 | end 186 | end 187 | end 188 | @__yaml_inline = bool 189 | end 190 | def yaml_inline!() self.yaml_inline = true end 191 | 192 | def each_with_index 193 | @order.each_with_index { |k, index| yield k, self[k], index } 194 | self 195 | end 196 | end # class OrderedHash 197 | 198 | def OrderedHash(*a, &b) 199 | OrderedHash.new(*a, &b) 200 | end 201 | -------------------------------------------------------------------------------- /work/trash/test/test_association.rb: -------------------------------------------------------------------------------- 1 | require 'association.rb' 2 | require 'test/unit' 3 | 4 | class TC_Associations < Test::Unit::TestCase 5 | 6 | def setup 7 | @complex_hierarchy = [ 8 | 'parent' >> 'child', 9 | 'childless', 10 | 'another_parent' >> [ 11 | 'subchildless', 12 | 'subparent' >> 'subchild' 13 | ] 14 | ] 15 | end 16 | 17 | def test_ohash 18 | k,v = [],[] 19 | ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ] 20 | ohash.each { |e1,e2| k << e1 ; v << e2 } 21 | assert_equal( ['A','B','C'], k ) 22 | assert_equal( ['3','2','1'], v ) 23 | end 24 | 25 | def test_complex 26 | complex = [ 'Drop Menu' >> [ 'Button 1', 'Button 2', 'Button 3' ], 'Help' ] 27 | assert_equal( 'Drop Menu', complex[0].index ) 28 | end 29 | 30 | def test_associations 31 | complex = [ :a >> :b, :a >> :c ] 32 | assert_equal( [ :b, :c ], :a.associations ) 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /work/trash/test/test_autoarray.rb: -------------------------------------------------------------------------------- 1 | # Test for facets/autoarray.rb 2 | 3 | require 'facets/autoarray.rb' 4 | require 'test/unit' 5 | 6 | class TC_Autoarray 7 | 8 | def test_001 9 | a = Autoarray.new 10 | assert_equal( 12, a[1][2][3] = 12 ) 11 | assert_equal( [nil, [nil, nil, [nil, nil, nil, 12]]], a ) 12 | assert_equal( [], a[2][3][4] ) 13 | assert_equal( [nil, [nil, nil, [nil, nil, nil, 12]]], a ) 14 | assert_equal( "Negative", a[1][-2][1] = "Negative" ) 15 | assert_equal( [nil, [nil, [nil, "Negative"], [nil, nil, nil, 12]]], a ) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /work/trash/test/test_dictionary.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/dictionary.rb' 2 | 3 | class TC_Dictionary < Test::Unit::TestCase 4 | 5 | def test_create 6 | d = Dictionary['z', 1, 'a', 2, 'c', 3] 7 | assert_equal( ['z','a','c'], d.keys ) 8 | end 9 | 10 | def test_op_store 11 | d = Dictionary.new 12 | d['z'] = 1 13 | d['a'] = 2 14 | d['c'] = 3 15 | assert_equal( ['z','a','c'], d.keys ) 16 | end 17 | 18 | def test_push 19 | d = Dictionary['a', 1, 'c', 2, 'z', 3] 20 | assert( d.push('end', 15) ) 21 | assert_equal( 15, d['end'] ) 22 | assert( ! d.push('end', 30) ) 23 | assert( d.unshift('begin', 50) ) 24 | assert_equal( 50, d['begin'] ) 25 | assert( ! d.unshift('begin', 60) ) 26 | assert_equal( ["begin", "a", "c", "z", "end"], d.keys ) 27 | assert_equal( ["end", 15], d.pop ) 28 | assert_equal( ["begin", "a", "c", "z"], d.keys ) 29 | assert_equal( ["begin", 50], d.shift ) 30 | end 31 | 32 | def test_insert 33 | # front 34 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 35 | r = Dictionary['d', 4, 'a', 1, 'b', 2, 'c', 3] 36 | assert_equal( 4, d.insert(0,'d',4) ) 37 | assert_equal( r, d ) 38 | # back 39 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 40 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 41 | assert_equal( 4, d.insert(-1,'d',4) ) 42 | assert_equal( r, d ) 43 | end 44 | 45 | def test_update 46 | # with other orderred hash 47 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 48 | c = Dictionary['d', 4] 49 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 50 | assert_equal( r, d.update(c) ) 51 | assert_equal( r, d ) 52 | # with other hash 53 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 54 | c = { 'd' => 4 } 55 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 56 | assert_equal( r, d.update(c) ) 57 | assert_equal( r, d ) 58 | end 59 | 60 | def test_merge 61 | # with other orderred hash 62 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 63 | c = Dictionary['d', 4] 64 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 65 | assert_equal( r, d.merge(c) ) 66 | # with other hash 67 | d = Dictionary['a', 1, 'b', 2, 'c', 3] 68 | c = { 'd' => 4 } 69 | r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] 70 | assert_equal( r, d.merge(c) ) 71 | end 72 | 73 | def test_order_by 74 | d = Dictionary['a', 3, 'b', 2, 'c', 1] 75 | d.order_by{ |k,v| v } 76 | assert_equal( [1,2,3], d.values ) 77 | assert_equal( ['c','b','a'], d.keys ) 78 | end 79 | 80 | def test_op_store_again 81 | d = Dictionary[] 82 | d[:a] = 1 83 | d[:c] = 3 84 | assert_equal( [1,3], d.values ) 85 | d[:b,1] = 2 86 | assert_equal( [1,2,3], d.values ) 87 | assert_equal( [:a,:b,:c], d.keys ) 88 | end 89 | 90 | def test_reverse! 91 | d = Dictionary['z', 1, 'a', 2, 'c', 3] 92 | d.reverse! 93 | assert_equal( ['c','a','z'], d.keys ) 94 | end 95 | 96 | def test_enumerable 97 | d = Dictionary[] 98 | d[:a] = "a" 99 | d[:c] = "b" 100 | assert_equal( ["A","B"], d.collect{|k,v| v.capitalize} ) 101 | end 102 | 103 | def test_autohash 104 | d = Dictionary.new{ |hash,key| hash[key] = 0 } 105 | d[:a] = 0 106 | d[:b] += 1 107 | assert_equal([0, 1], d.values) 108 | assert_equal([:a,:b], d.keys) 109 | end 110 | 111 | def test_dup_with_array_values 112 | d = Dictionary.new 113 | d.dup 114 | d[:a]=['t',5] 115 | assert_equal(d, d.dup) 116 | end 117 | 118 | def test_first 119 | d = Dictionary[] 120 | d[:a] = "a" 121 | d[:b] = "b" 122 | d[:c] = "c" 123 | assert_equal( "a" , d.first ) 124 | assert_equal( [] , d.first(0) ) 125 | assert_equal( ["a"] , d.first(1) ) 126 | assert_equal( ["a", "b"] , d.first(2) ) 127 | end 128 | 129 | def test_last 130 | d = Dictionary[] 131 | d[:a] = "a" 132 | d[:b] = "b" 133 | d[:c] = "c" 134 | assert_equal( "c" , d.last ) 135 | assert_equal( [] , d.last(0) ) 136 | assert_equal( ["c"] , d.last(1) ) 137 | assert_equal( ["b", "c"] , d.last(2) ) 138 | end 139 | 140 | end 141 | 142 | -------------------------------------------------------------------------------- /work/trash/test/test_opencascade.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/opencascade.rb' 2 | 3 | class TestOpenCascade1 < Test::Unit::TestCase 4 | 5 | def test_1_01 6 | o = OpenCascade[:a=>1,:b=>2] 7 | assert_equal( 1, o.a ) 8 | assert_equal( 2, o.b ) 9 | end 10 | 11 | def test_1_02 12 | o = OpenCascade[:a=>1,:b=>2,:c=>{:x=>9}] 13 | assert_equal( 9, o.c.x ) 14 | end 15 | 16 | def test_1_03 17 | f0 = OpenCascade.new 18 | f0[:a] = 1 19 | assert_equal( [[:a,1]], f0.to_a ) 20 | assert_equal( {:a=>1}, f0.to_h ) 21 | end 22 | 23 | def test_1_04 24 | f0 = OpenCascade[:a=>1] 25 | f0[:b] = 2 26 | assert_equal( {:a=>1,:b=>2}, f0.to_h ) 27 | end 28 | end 29 | 30 | class TestOpenCascade2 < Test::Unit::TestCase 31 | 32 | def test_02_001 33 | f0 = OpenCascade[:f0=>"f0"] 34 | h0 = { :h0=>"h0" } 35 | assert_equal( OpenCascade[:f0=>"f0", :h0=>"h0"], f0.as_hash.merge(h0) ) 36 | assert_equal( {:f0=>"f0", :h0=>"h0"}, h0.merge( f0 ) ) 37 | end 38 | 39 | def test_02_002 40 | f1 = OpenCascade[:f1=>"f1"] 41 | h1 = { :h1=>"h1" } 42 | f1.as_hash.update(h1) 43 | h1.update( f1 ) 44 | assert_equal( OpenCascade[:f1=>"f1", :h1=>"h1"], f1 ) 45 | assert_equal( {:f1=>"f1", :h1=>"h1"}, h1 ) 46 | end 47 | end 48 | 49 | class TestOpenCascade3 < Test::Unit::TestCase 50 | 51 | def test_01_001 52 | fo = OpenCascade.new 53 | 99.times{ |i| fo.__send__( "n#{i}=", 1 ) } 54 | 99.times{ |i| 55 | assert_equal( 1, fo.__send__( "n#{i}" ) ) 56 | } 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /work/trash/test/test_openobject.rb: -------------------------------------------------------------------------------- 1 | require 'hashery/openobject' 2 | 3 | class TestOpenObject1 < Test::Unit::TestCase 4 | 5 | def test_1_01 6 | o = OpenObject.new 7 | assert( o.respond_to?(:key?) ) 8 | end 9 | 10 | def test_1_02 11 | assert OpenObject[{}].is_a?(Hash) 12 | assert OpenObject[{}].is_a?(OpenObject) 13 | end 14 | 15 | def test_1_03 16 | f0 = OpenObject.new 17 | f0[:a] = 1 18 | #assert_equal( [1], f0.to_a ) 19 | assert_equal( {:a=>1}, f0.to_h ) 20 | end 21 | 22 | def test_1_04 23 | f0 = OpenObject[:a=>1] 24 | f0[:b] = 2 25 | assert_equal( {:a=>1,:b=>2}, f0.to_h ) 26 | end 27 | 28 | def test_1_05 29 | f0 = OpenObject[:class=>1] 30 | assert_equal( 1, f0.class ) 31 | end 32 | end 33 | 34 | class TestOpenObject2 < Test::Unit::TestCase 35 | 36 | def test_merge 37 | f0 = OpenObject[:f0=>"f0"] 38 | h0 = { :h0=>"h0" } 39 | assert_equal( OpenObject[:f0=>"f0", :h0=>"h0"], f0.as_hash.merge(h0) ) 40 | assert_equal( {:f0=>"f0", :h0=>"h0"}, h0.merge(f0) ) 41 | end 42 | 43 | def test_update 44 | f1 = OpenObject[:f1=>"f1"] 45 | h1 = { :h1=>"h1" } 46 | f1.as_hash.update(h1) 47 | h1.update(f1) 48 | assert_equal( OpenObject[:f1=>"f1", :h1=>"h1"], f1 ) 49 | assert_equal( {:f1=>"f1", :h1=>"h1"}, h1 ) 50 | end 51 | 52 | def test_2_03 53 | o = OpenObject[:a=>1,:b=>{:x=>9}] 54 | assert_equal( 9, o[:b][:x] ) 55 | assert_equal( 9, o.b[:x] ) 56 | end 57 | 58 | def test_2_04 59 | o = OpenObject["a"=>1,"b"=>{:x=>9}] 60 | assert_equal( 1, o["a"] ) 61 | assert_equal( 1, o[:a] ) 62 | assert_equal( {:x=>9}, o["b"] ) 63 | assert_equal( {:x=>9}, o[:b] ) 64 | assert_equal( 9, o["b"][:x] ) 65 | assert_equal( nil, o[:b]["x"] ) 66 | end 67 | 68 | end 69 | 70 | class TestOpenObject3 < Test::Unit::TestCase 71 | def test_3_01 72 | fo = OpenObject.new 73 | 9.times{ |i| fo.__send__( "n#{i}=", 1 ) } 74 | 9.times{ |i| 75 | assert_equal( 1, fo.__send__( "n#{i}" ) ) 76 | } 77 | end 78 | end 79 | 80 | class TestOpenObject4 < Test::Unit::TestCase 81 | 82 | def test_4_01 83 | ho = {} 84 | fo = OpenObject.new 85 | 5.times{ |i| ho["n#{i}".to_sym]=1 } 86 | 5.times{ |i| fo.__send__( "n#{i}=", 1 ) } 87 | assert_equal(ho, fo.to_h) 88 | end 89 | 90 | end 91 | 92 | class TestOpenObject5 < Test::Unit::TestCase 93 | 94 | def test_5_01 95 | p = lambda { |x| 96 | x.word = "Hello" 97 | } 98 | o = p.to_openobject 99 | assert_equal( "Hello", o.word ) 100 | end 101 | 102 | def test_5_02 103 | p = lambda { |x| 104 | x.word = "Hello" 105 | } 106 | o = OpenObject[:a=>1,:b=>2] 107 | assert_instance_of( Proc, o.to_proc ) 108 | end 109 | 110 | end 111 | --------------------------------------------------------------------------------