├── alt
└── hashery
│ ├── keyhash.rb
│ ├── lruhash.rb
│ ├── fuzzyhash.rb
│ ├── openhash.rb
│ ├── queryhash.rb
│ ├── castinghash.rb
│ ├── linkedlist.rb
│ ├── opencascade.rb
│ ├── orderedhash.rb
│ ├── propertyhash.rb
│ └── statichash.rb
├── demo
├── applique
│ ├── ae.rb
│ └── hashery.rb
├── 00_introduction.rdoc
├── 03_casting_hash.rdoc
├── 05_key_hash.rdoc
├── 02_query_hash.rdoc
├── 04_static_hash.rdoc
├── 08_propery_hash.rdoc
├── 10_association.rdoc
├── 06_open_cascade.rdoc
├── 01_open_hash.rdoc
└── 07_fuzzy_hash.rdoc
├── test
├── fixture
│ └── example.ini
├── helper.rb
├── case_property_hash.rb
├── case_crud_hash.rb
├── case_ini_hash.rb
├── case_casting_hash.rb
├── case_open_hash.rb
├── case_core_ext.rb
├── case_association.rb
├── case_open_cascade.rb
├── case_lru_hash.rb
├── case_linked_list.rb
├── case_key_hash.rb
├── case_query_hash.rb
└── case_dictionary.rb
├── work
├── deprecated
│ ├── webme
│ │ ├── logo.jpg
│ │ ├── config.yml
│ │ └── advert.html
│ ├── bench_harray.rb
│ ├── memoizer.rb
│ ├── basic_object.rb
│ ├── case_basic_cascade.rb
│ ├── basic_cascade.rb
│ ├── ostructable.rb
│ ├── case_basicstruct.rb
│ └── basic_struct.rb
├── consider
│ ├── superhash.rb
│ ├── safeopenstructable.rb
│ ├── autoarray.rb
│ ├── ini.rb
│ ├── having.rb
│ └── openobject.rb
├── trash
│ └── test
│ │ ├── test_autoarray.rb
│ │ ├── test_association.rb
│ │ ├── test_opencascade.rb
│ │ ├── test_openobject.rb
│ │ └── test_dictionary.rb
├── benchmarks
│ └── proc_vs_call.rb
├── reference
│ ├── hash_magic
│ │ ├── LICENSE
│ │ ├── Rakefile
│ │ └── README
│ └── orderedhash.rb
└── NOTES.rdoc
├── .yardopts
├── .gitignore
├── etc
├── qed.rb
└── test.rb
├── Gemfile
├── lib
├── hashery
│ ├── stash.rb
│ ├── static_hash.rb
│ ├── key_hash.rb
│ ├── query_hash.rb
│ ├── core_ext.rb
│ ├── ordered_hash.rb
│ ├── casting_hash.rb
│ ├── open_hash.rb
│ ├── property_hash.rb
│ ├── open_cascade.rb
│ ├── fuzzy_hash.rb
│ ├── association.rb
│ ├── linked_list.rb
│ ├── lru_hash.rb
│ └── path_hash.rb
├── hashery.rb
└── hashery.yml
├── .travis.yml
├── Rakefile
├── Assembly
├── NOTICE.txt
├── LICENSE.txt
├── Index.yml
├── MANIFEST
├── .index
├── README.md
├── HISTORY.md
└── .gemspec
/alt/hashery/keyhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/key_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/lruhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/lru_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/fuzzyhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/fuzzy_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/openhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/open_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/queryhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/query_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/castinghash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/casting_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/linkedlist.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/linked_list'
2 |
--------------------------------------------------------------------------------
/alt/hashery/opencascade.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/open_cascade'
2 |
--------------------------------------------------------------------------------
/alt/hashery/orderedhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/ordered_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/propertyhash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/property_hash'
2 |
--------------------------------------------------------------------------------
/alt/hashery/statichash.rb:
--------------------------------------------------------------------------------
1 | require 'hashery/static_hash'
2 |
--------------------------------------------------------------------------------
/demo/applique/ae.rb:
--------------------------------------------------------------------------------
1 | require 'ae'
2 | require 'ae/should'
3 |
--------------------------------------------------------------------------------
/demo/applique/hashery.rb:
--------------------------------------------------------------------------------
1 | require 'hashery'
2 |
3 | include Hashery
4 |
--------------------------------------------------------------------------------
/test/fixture/example.ini:
--------------------------------------------------------------------------------
1 | # top comment
2 |
3 | a = 1
4 |
5 | [section]
6 | x = 2
7 | y = 3
8 |
9 |
--------------------------------------------------------------------------------
/work/deprecated/webme/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubyworks/hashery/HEAD/work/deprecated/webme/logo.jpg
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --plugin tomdoc
2 | --output-dir doc
3 | --title Hashery
4 | --protected
5 | --private
6 | lib/
7 | -
8 | [A-Z]*.*
9 |
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/work/deprecated/webme/advert.html:
--------------------------------------------------------------------------------
1 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Hashery
2 |
3 | [](http://rubygems.org/gem/hashery)
4 | [](http://travis-ci.org/rubyworks/hashery)
5 | [](http://github.com/rubyworks/hashery)
6 | [](http://github.com/rubyworks/hashery/issues)
7 | [](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 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------