Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+78
View File
@@ -0,0 +1,78 @@
---
revision: 2013
type: ruby
sources:
- Index.yml
- Gemfile
authors:
- name: Trans
email: transfire@gmail.com
- name: Kirk Haines
- name: Robert Klemme
- name: Jan Molic
- name: George Moschovitis
- name: Jeena Paradies
- name: Erik Veenstra
organizations:
- name: RubyWorks (http://rubyworks.github.com/)
requirements:
- groups:
- development
- test
version: ">= 0"
name: qed
- groups:
- development
- test
version: ">= 0"
name: lemon
- groups:
- development
- test
version: ">= 0"
name: rubytest-cli
conflicts: []
alternatives: []
resources:
- type: home
uri: http://rubyworks.github.com/hashery
label: Homepage
- type: code
uri: http://github.com/rubyworks/hashery
label: Source Code
- type: mail
uri: http://groups.google.com/group/rubyworks-mailinglist
label: Mailing List
- type: docs
uri: http://rubydoc.info/github/rubyworks/hashery/master/frames
label: Documentation
- type: wiki
uri: http://wiki.github.com/rubyworks/hashery
label: User Guide
- type: gems
uri: http://rubygems.org/gems/hashery
repositories:
- name: upstream
scm: git
uri: git://github.com/rubyworks/hashery.git
categories: []
copyrights:
- holder: Rubyworks
year: '2010'
license: BSD-2-Clause
customs: []
paths:
lib:
- lib
- alt
name: hashery
title: Hashery
version: 2.1.2
summary: Facets-bread collection of Hash-like classes.
description: The Hashery is a tight collection of Hash-like classes. Included among
its many offerings are the auto-sorting Dictionary class, the efficient LRUHash,
the flexible OpenHash and the convenient KeyHash. Nearly every class is a subclass
of the CRUDHash which defines a CRUD model on top of Ruby's standard Hash making
it a snap to subclass and augment to fit any specific use case.
created: '2010-04-21'
date: '2016-05-01'
+9
View File
@@ -0,0 +1,9 @@
--plugin tomdoc
--output-dir doc
--title Hashery
--protected
--private
lib/
-
[A-Z]*.*
+188
View File
@@ -0,0 +1,188 @@
# RELEASE HISTORY
## 2.1.1 / 2013-08-21
This minor release clarifies the licensing. The entire library is now
distributed under the BSD-2-Clause license, also known as the FreeBSD
license. In addition this release provides a bug fix for flattening
arrays that contain an OpenCascade object.
Changes:
* Clarify licensing.
* Fix #flatten on Arrays that contain an OpenCascade.
## 2.1.0 / 2012-11-24 (Golden Retriever)
The major change of the 2.1 release is to switch over to `Hash#fetch`
as the fundamental CRUD read method in-place of the previous `#read` core
extension (an alias of `#[]`). This is a pretty fundamental change which
required modification of a number of classes. So please do extra-diligence
and file an issue if you experience any problems.
In addition, the Hash#read core extension has been renamed to Hash#retrieve
to avoid any possible confusion with IO objects. This release also fixes
a couple of issues with 1.8 compatibility and makes a few other small
enhancements.
Changes:
* Rename Hash#read to Hash#retrieve.
* Deprecate `Dictionary.alpha` in favor of `Dictionary.alphabetic`.
* Add support for block argument in Dictionary#order_by_key and #order_by_value.
* Fix OpenHash issues with Ruby 1.8.x compatibility.
* OpenHash methods are opened up via `protected` instead of `private`.
* Change OpenCascade to auto-create the subclass when inherited.
## 2.0.1 / 2012-07-06
This minor release fixes an issue with OpenCascade (#13).
The key_proc procedure wasn't being passed along to sub-cascades.
Changes:
* OpenCascade passes along key_proc to children.
## 2.0.0 / 2012-05-11 (Crud Space)
This is a big release for Hashery which both culls some of it's
less fitting classes and modules while greatly improving the rest.
The first and most immediate change is use of a proper namespace.
All classes and modules are now appropriately kept in the `Hashery`
namespace. To get the old behavior you can `include Hashery` as the
toplevel. For the other changes and improvements dive into the
API documentation.
Changes:
* Use proper Hashery namespace.
* Add CRUDHash, which also serves a good base class.
* Improved OpenHash to be nearly 100% open.
* Deprecate BasicStruct, as it would be better to improve OpenStruct.
* Deprecate BasicCascade, though it never really came to be.
* Deprecate BasicObject emulator, as it is no longer needed.
* Deprecate Memoizer, not sure how that got in here anyway.
* Deprecate Ostructable, which can be paired up with better OpenStruct.
* Removed open_object.rb, which has long been deprecated.
## 1.5.1 / 2012-05-09
This release adds transformative #each method to OpenCascade, to
ensure #each returns an OpenCascade. Also, BasicCascade has been
added that is like OpenCascade by fully open by use of BasicObject
as a base class.
Changes:
* Fix OpenCascade#each (porecreat).
* Introduce BasicCascade class.
* Renamed `Ini` class to `IniHash` class.
## 1.5.0 / 2011-11-10 (Devil's Core)
In this release, CoreExt module has been added to encapsulate
methods that extend Ruby's core Hash class (there are only a few).
Presently these are only loaded when using `require 'hashery'`.
If you are cherry-picking from Hashery but still want the core
extensions, you need to use `require 'hasery/core_ext'` first.
In addition, BasicStruct class now has a #key method. And finally
this release switches licensing to BSD 2-Clause.
Changes:
* Use CoreExt mixin for core Hash extensions.
* Add BasicStruct#key method (b/c #index is deprecated in Ruby 1.9).
* Deprecate SparseArray class.
* Switch license to BSD-2-Clause license.
## 1.4.0 / 2011-01-19 (Back To Basics)
This release includes a copy of Ruby Facets' BasicObject class, which
fixes the loading bug of the previous version. This release also renames
OpenObject to BasicStruct, which is a much better description of what the
class actually provides.
Changes:
* Rename OpenObject to BasicStruct.
* Fix basicobject.rb loading issue.
## 1.3.0 / 2010-10-01 (Private Property)
This release fixes a minor bug in CastingHash and adds a new
PropertyHash class.
Changes:
* 1 New Library
* Added PropertyHash
* 1 Bug Fix
* Fixed CastingHash#new where #to_proc is called against NilClass
## 1.2.0 / 2010-06-04 (Fuzzy Wuzzy)
This release makes two significant changes to the Hashery.
First, we have a new shiny library called FuzzyHash by
Joshua Hull. It's a cool idea that allows hash keys to be
regular expressions. Secondly, OpenCascade is now a subclass
of OpenHash rather than OpenObject (to go along with the
changes of the last release), and it now support cascading
within Arrays.
Changes:
* 1 New Library
* FuzzyHash by Joshua Hull
* 1 Major Enhancement
* OpenCascade subclasses OpenHash and handles Array cascading.
## 1.1.0 / 2010-04-28 (Ugly Ducklings)
A follow-up release of Hashery that adds two new libraries:
Association and SparseArray. Both of these may seem like odd
entries, but they each belong in a unique way. An Association
is akin to a single entry Hash --it represents a pairing.
While a SpareArray, though compatible with the Array class,
is completely under-pinned by a Hash in order to make it
efficient when no entries are given for a set of indexes,
hence "sparse".
Changes:
* 2 New Libraries
* Added association.rb
* Added sparsearray.rb
## 1.0.0 / 2010-04-21 (Hatching Hashery)
This is the first release of the Facets Hashery.
Most of included classes come directly from Ruby
Facets, so they have been around a while and are
in good working condition.
Some improvements are planned for the next release.
In particular the OrderHash and Dictionary, which
presently have essentially the same coding, will
diverge to target slightly different use cases.
Changes:
* Happy Birthday!
+52
View File
@@ -0,0 +1,52 @@
---
name:
hashery
version:
2.1.2
title:
Hashery
summary:
Facets-bread collection of Hash-like classes.
description:
The Hashery is a tight collection of Hash-like classes. Included among its many
offerings are the auto-sorting Dictionary class, the efficient LRUHash, the
flexible OpenHash and the convenient KeyHash. Nearly every class is a subclass
of the CRUDHash which defines a CRUD model on top of Ruby's standard Hash
making it a snap to subclass and augment to fit any specific use case.
authors:
- Trans <transfire@gmail.com>
- Kirk Haines
- Robert Klemme
- Jan Molic
- George Moschovitis
- Jeena Paradies
- Erik Veenstra
resources:
home: http://rubyworks.github.com/hashery
code: http://github.com/rubyworks/hashery
mail: http://groups.google.com/group/rubyworks-mailinglist
docs: http://rubydoc.info/github/rubyworks/hashery/master/frames
wiki: http://wiki.github.com/rubyworks/hashery
gems: http://rubygems.org/gems/hashery
repositories:
upstream: "git://github.com/rubyworks/hashery.git"
organizations:
- RubyWorks (http://rubyworks.github.com/)
copyrights:
- 2010 Rubyworks (BSD-2-Clause)
load_path:
- lib
- alt
created: '2010-04-21'
+23
View File
@@ -0,0 +1,23 @@
BSD-2-Clause License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+46
View File
@@ -0,0 +1,46 @@
All copyright holders have released their wares with a compatible
license or have given permission for their work to be distributed
under the same license as Hashery.
Dictionary is based on OrderedHash by Jan Molic.
OrderHash 2.0 Copyright (c) 2005 Jan Molic.
LRUHash is based on same by Robert Klemme
Copyright (c) 2010 Robert Klemme
Memoizer is based on same by Erik Veenstra
Copyright (c) 2006 Erik Veenstra
IniHash is based on Ini library by Jeena Paradies
Copyright (c) 2007 Jeena Paradies <info@jeenaparadies.net>
LinkedList is based on same by Kirk Haines
Copyright (C) 2006 Kirk Haines <khaines@enigo.com>
FuzzyHash is based on the same by Joshua Hull.
Copyright (c) 2009 Joshua Hull
PathHash is based on SlashedHash by Daniel Parker
HashMagic by Daniel Parker <gems@behindlogic.com>
Copyright (c) 2006 BehindLogic
http://hash_magic.rubyforge.org
BasicObject is based on Jim Weirich's BlankSlate class.
BlankSlate
Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
All rights reserved.
+128
View File
@@ -0,0 +1,128 @@
# Hashery
[![Gem Version](http://img.shields.io/gem/v/hashery.svg?style=flat)](http://rubygems.org/gem/hashery)
[![Build Status](http://img.shields.io/travis/rubyworks/hashery.svg?style=flat)](http://travis-ci.org/rubyworks/hashery)
[![Fork Me](http://img.shields.io/badge/scm-github-blue.svg?style=flat)](http://github.com/rubyworks/hashery)
[![Report Issue](http://img.shields.io/github/issues/rubyworks/hashery.svg?style=flat)](http://github.com/rubyworks/hashery/issues)
[![Gittip](http://img.shields.io/badge/gittip-$1/wk-green.svg?style=flat)](https://www.gittip.com/rubyworksorg/)
[Homepage](http://rubyworks.github.com/hashery) &middot;
[Development](http://github.com/rubyworks/hashery) &middot;
[Report Issue](http://github.com/rubyworks/hashery/issues) &middot;
[Chat](https://kiwiirc.com/client/irc.freenode.net/?nick=user1|?#rubyworks)
## Description
Among Ruby Facets most common additions were an assortment
of Hash-like classes. To better support this collection
of libraries it was deemed prudent to create a new project
specifically for them. Hence the *Facets Hashery*.
Included in this collection are the widely used OrderedHash,
the related but more featured Dictionary class, a number
of _open_ classes similar to the standard OpenStruct,
some variations of the standard Hash class and a few
other yummy morsels.
## Usage
For instruction on usage, please see the individual library files
included in this collection and read the demo documents which give
examples of almost all features.
### Core Extensions
Hashery adds four core extensions of Ruby's Hash class: `#retrieve`,
`#rekey`, `#rekey!` and `Hash.create`. The first is simply an alias
for `#[]`. The later two have proven too useful over the years to
omit. And the last is a convenience class method for populating
a new hash with another hash upon initialization. All of these are
sorely missing for Ruby itself, which is why they are provided here.
## Installation
To install with RubyGems simply open a console and type:
$ sudo gem install hashery
Or add it as a dependency to your project's Gemfile:
gem "hashery"
Tarball packages are available for manual site installations
via [Ruby Setup](http://rubyworks.github.com/setup).
## Authors
Developers who have contributed code to the project include:
* Kirk Haines
* Joshua Hull
* Robert Klemme
* Jan Molic
* George Moschovitis
* Jeena Paradies
* Erik Veenstra
## Contributing
Don't be a lump on a log. See an issue? Have a suggestion? Want to help?
Well *git* in there!
### Communicating
The project is hosted on Github so the easiest means of communication
is via the [Issues](http://github.com/rubyworks/hashery/issues) page.
For more direct communication you can try the **#rubyworks** IRC channel
on **irc.freenode.net**.
&nbsp; &nbsp; [![IRC](https://kiwiirc.com/buttons/irc.freenode.net/rubyworks.png)](https://kiwiirc.com/client/irc.freenode.net/?nick=user1|?#rubyworks)
### Testing
Hashery uses [QED](http://rubyworks.github.com/qed) and
[Lemon](http://rubyworks.github.com/lemon) test frameworks.
The QED framework to create well tested high-level documentation.
Run the QED specs via:
$ qed -Ilib demo/
Lemon is used to create low-level unit tests. Run these via the Rubytest:
$ rubytest -Ilib -Itest test/
### Patches
Hashery's repository is hosted on [GitHub](http://github.com/rubyworks/hashery).
If you'd like to offer up a fix or feature, fork the repo and submit a pull
request (preferably in a topic branch). I assume you have heard
all the talk about proper [practices](https://github.com/rubyworks/rubyworks.github.com/wiki/GitHub-Best-Pactices),
so I won't bug you with it yet again.
### Donations
Yes, we FOSS programmers need to eat too! ;-) No seriously, any help you can
offer goes a long way toward continued development of Rubyworks projects,
including Hashery. Hop over to our [Gittips](https://www.gittip.com/rubyworksorg/)
page, or see the upper right-hand corner on the [Rubyworks](http://rubyworks.github.com) homepage.
Thanks!
## Copyrights
Copyright (c) 2010 Rubyworks
Licensed under the *BSD-2-Clause* license.
See LICENSE.txt file for further details.
Some libraries included in the Hashery have special copyrights
attributing specific authors. Please see each library script for
specifics and the NOTICE.txt file for an overview.
@@ -0,0 +1 @@
require 'hashery/casting_hash'
@@ -0,0 +1 @@
require 'hashery/fuzzy_hash'
@@ -0,0 +1 @@
require 'hashery/key_hash'
@@ -0,0 +1 @@
require 'hashery/linked_list'
@@ -0,0 +1 @@
require 'hashery/lru_hash'
@@ -0,0 +1 @@
require 'hashery/open_cascade'
@@ -0,0 +1 @@
require 'hashery/open_hash'
@@ -0,0 +1 @@
require 'hashery/ordered_hash'
@@ -0,0 +1 @@
require 'hashery/property_hash'
@@ -0,0 +1 @@
require 'hashery/query_hash'
@@ -0,0 +1 @@
require 'hashery/static_hash'
@@ -0,0 +1,6 @@
= Hashery
For the following demos the `hashery` script has
been preloaded and the Hashery namespace has been
included into the demo context for convenience.
@@ -0,0 +1,63 @@
= OpenHash
An OpenHash is a Hash that provides +open+ access to its entries via method
calls. Writers (methods ending in =-marks) assign entries. Methods without
special puncuation will retrieve entries.
o = OpenHash.new
o.a = 1
o.b = 2
o.a.assert == 1
o.b.assert == 2
Writers always use a Symbol for keys in the underlying Hash.
o.to_h.assert == { :a=>1, :b=>2 }
All the usual Hash methods are still available in an OpenHash.
c = o.map{ |k,v| [k,v] }
c.assert.include?([:a,1])
c.assert.include?([:b,2])
And they are protected from being overridden by writers.
o.map = 3
o.map.refute == 3
Even so, the underlying Hash object does contain the entry even
when it cannot be accessed via a reader method.
o.to_h.assert == { :a=>1, :b=>2, :map=>3 }
We can see if a method is open or not via the `#open?` method.
o.open?(:a).assert == true
o.open?(:map).assert == false
For some usecases it may be necessary to give up access to one or
more Hash methods in favor of access to the hash entries. This can
be done using the `#open!` method.
o.open!(:map, :merge)
o.map.assert == 3
o.merge = 4
o.merge.assert == 4
Becuase of nature of a writer, a certain set of Hash methods are always
protected, in particluar all methods buffered by underscore (e.g. `__id__`).
So these cannot be opened.
expect ArgumentError do
o.open!(:__id__)
end
Even though writers alwasy use Symbols as keys, because an OpenHash
is a true Hash object, any object can be used as a key internally.
o = OpenHash.new
o[nil] = "Nothing"
o.to_h.assert == { nil=>"Nothing" }
It simply cannot be accessible via a reader method.
@@ -0,0 +1,17 @@
= QueryHash
A QueryHash is a Hash that provides open access much like an OpenHash, but it limits readers
to bang and query methods (i.e. method ending in `!` or `?`).
q = QueryHash.new
q.a = 1
q.b = 2
q.a?.assert == 1
q.b?.assert == 2
By default keys are converted to strings.
q.assert == { "a"=>1, "b"=>2 }
A QueryHash is compatible with Ruby's standard Hash in every other respect.
@@ -0,0 +1,12 @@
= CastingHash
A CastingHash is a Hash that allows _casting_ procedures to
defined that the keys and values pass through upon assignment.
c = CastingHash.new
c.cast_proc = lambda { |k,v| [k.to_s, v.to_s.upcase] }
c[:a] = 'a'
c.assert == {'a'=>'A'}
@@ -0,0 +1,20 @@
= StaticHash
A StaticHash is simply a Hash that can only be assigned
once per key. Once assigned a subsequent attempt to assign
a value to the same key will raise an ArgumentError.
h = StaticHash.new
h["x"] = 1
expect ArgumentError do
h["x"] = 2
end
The same error will be raised when using #update or #merge!.
expect ArgumentError do
h.update( "x"=>3 )
end
@@ -0,0 +1,17 @@
= KeyHash
The KeyHash is essentially the same as regular Hash but instead
of a `default_proc` the initializer takes the `key_proc` for
normalizing keys.
kh = KeyHash.new{ |k| k.to_s.upcase }
kh[:a] = 1
kh.to_h #=> ({'A'=>1})
By default, when no `key_proc` is given, it converts all keys to strings.
kh = KeyHash.new
kh[:a] = 1
kh.to_h #=> ({'a'=>1})
@@ -0,0 +1,64 @@
= OpenCascade
The reason this class is labeled "cascade", is that every internal
Hash is transformed into an OpenCascade dynamically upon access.
This makes it easy to create _cascading_ references.
h = { :x => { :y => { :z => 1 } } }
c = OpenCascade[h]
assert c.x.y.z == 1
As soon as you access a node it automatically becomes an OpenCascade.
c = OpenCascade.new
assert(OpenCascade === c.r)
assert(OpenCascade === c.a.b)
But if you set a node, then that will be it's value.
c.a.b = 4
assert c.a.b == 4
To query a node without causing the auto-creation of an OpenCasade
object, use the ?-mark.
assert c.a.z? == nil
OpenCascade also transforms Hashes within Arrays.
h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 }
c = OpenCascade[h]
assert c.x.first.a == 1
assert c.x.last.a == 2
Like OpenObject, OpenCascade allows you to insert entries as array
pairs.
c = OpenCascade.new
c << [:x,8]
c << [:y,9]
assert c.x == 8
assert c.y == 9
Finally, you can call methods ending in a !-mark to access the
underlying hash (Note that these differ in behavior from the
built-in !-methods).
bk = c.map!{ |k,v| k.to_s.upcase }
bk.sort.assert == ['X', 'Y']
So you can see that for the most an OpenCascade is just like
OpenHash, but it allows us to conveniently build open sub-layers
easily.
Enumerable still works with OpenCascades too.
h = {}
c = OpenCascade[:a=>1,:b=>{:c=>3}]
c.each do |k,v|
h[k] = v
end
OpenCascade.assert === h[:b]
@@ -0,0 +1,137 @@
= FuzzyHash
Should accept strings and retrieve based on them.
l = FuzzyHash.new
l['asd'] = 'qwe'
l['asd'].should == 'qwe'
Should accept strings, but the second time you set the same string, it should overwrite.
l = FuzzyHash.new
l['asd'] = 'asd'
l['asd'] = 'qwe'
l['asd'].should == 'qwe'
Should accept regexs too.
l = FuzzyHash.new
l[/asd.*/] = 'qwe'
l['asdqweasd'].should == 'qwe'
Should accept regexs too, but the second time you set the same regex, it should overwrite.
l = FuzzyHash.new
l[/asd/] = 'asd'
l[/asd/] = 'qwe'
l['asdqweasd'].should == 'qwe'
Should accept regexs too with the match.
l = FuzzyHash.new
l[/asd.*/] = 'qwe'
l.match_with_result('asdqweasd').should == ['qwe', 'asdqweasd']
Should accept regexs that match the whole strong too with the match.
l = FuzzyHash.new
l[/asd/] = 'qwe'
l.match_with_result('asd').should == ['qwe', 'asd']
Should prefer string to regex matches.
l = FuzzyHash.new
l['asd'] = 'qwe2'
l[/asd.*/] = 'qwe'
l['asd'].should == 'qwe2'
Should allow nil keys.
l = FuzzyHash.new
l[nil] = 'qwe2'
l['asd'] = 'qwe'
l['asd'].should == 'qwe'
l[nil].should == 'qwe2'
Should allow boolean keys.
l = FuzzyHash.new
l[false] = 'false'
l[true] = 'true'
l[/.*/] = 'everything else'
l[true].should == 'true'
l[false].should == 'false'
l['false'].should == 'everything else'
Should pick between the correct regex.
hash = FuzzyHash.new
hash[/^\d+$/] = 'number'
hash[/.*/] = 'something'
hash['123asd'].should == 'something'
Should be able to delete by value for hash.
l = FuzzyHash.new
l[nil] = 'qwe2'
l['asd'] = 'qwe'
l['asd'].should == 'qwe'
l[nil].should == 'qwe2'
l.delete_value('qwe2')
l[nil].should == nil
Should be able to delete by value for regex.
l = FuzzyHash.new
l[/qwe.*/] = 'qwe2'
l['asd'] = 'qwe'
l['asd'].should == 'qwe'
l['qweasd'].should == 'qwe2'
l.delete_value('qwe2')
l['qweasd'].should == nil
Should iterate through the keys.
l = FuzzyHash.new
l[/qwe.*/] = 'qwe2'
l['asd'] = 'qwe'
l['zxc'] = 'qwe'
l.keys.size.should == 3
Should iterate through the values.
l = FuzzyHash.new
l[/qwe.*/] = 'qwe2'
l['asd'] = 'qwe'
l['zxc'] = 'qwelkj'
(['qwe2','qwe','qwelkj'] & l.values).size.should == 3
Should clear.
l = FuzzyHash.new
l[/qwe.*/] = 'qwe2'
l['asd'] = 'qwe'
l['zxc'] = 'qwelkj'
l.clear
l.empty?.should == true
Should handle equality.
l_1 = FuzzyHash.new
l_1[/qwe.*/] = 'qwe2'
l_1['asd'] = 'qwelkj'
l_1['zxc'] = 'qwe'
l_2 = FuzzyHash.new
l_2['zxc'] = 'qwe'
l_2['asd'] = 'qwelkj'
l_2[/qwe.*/] = 'qwe2'
l_1.should == l_2
Should return the value when adding the value.
h = FuzzyHash.new
(h[/asd/] = '123').should == '123'
(h['qwe'] = '123').should == '123'
That's It.
@@ -0,0 +1,34 @@
= PropertyHash
The Property hash can be used an object in itself.
h = PropertyHash.new(:a=>1, :b=>2)
h[:a] #=> 1
h[:a] = 3
h[:a] #=> 3
Becuase the properties are fixed, if we try to set a key that is not present,
then we will get an error.
expect ArgumentError do
h[:x] = 5
end
The PropertyHash can also be used as a superclass.
class MyPropertyHash < PropertyHash
property :a, :default => 1
property :b, :default => 2
end
h = MyPropertyHash.new
h[:a] #=> 1
h[:a] = 3
h[:a] #=> 3
Again, if we try to set key that was not fixed, then we will get an error.
expect ArgumentError do
h[:x] = 5
end
@@ -0,0 +1,59 @@
= Association
An Association is a class for creating simple pairings.
require 'hashery/association'
An Association can bew created through the usual means
of instantiation.
Association.new(:a, :b)
Or the shortcut method #>> can be used in most cases.
:x >> :z
An association provides two methods to access its content, #index and #value.
a = 'foo' >> 'bar'
a.index.assert == 'foo'
a.value.assert == 'bar'
Associations can be used to create ordered-hashes via normal
arrays.
keys = []
vals = []
ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ]
ohash.each{ |k,v| keys << k ; vals << v }
keys.assert == ['A','B','C']
vals.assert == ['3','2','1']
Becuase Associations are objects in themselves more complex
collections can also be created.
complex = [
'parent' >> 'child',
'childless',
'another_parent' >> [
'subchildless',
'subparent' >> 'subchild'
]
]
An experimental feature of Association keeps a cache of all defined associations.
o = Object.new
o >> :a
o >> :b
o >> :c
o.associations.assert == [:a, :b, :c]
However this feature will probably be deprecated.
@@ -0,0 +1,2 @@
require 'ae'
require 'ae/should'
@@ -0,0 +1,3 @@
require 'hashery'
include Hashery
+24
View File
@@ -0,0 +1,24 @@
module Hashery
VERSION = "2.1.0"
end
require 'hashery/core_ext'
#require 'hashery/basic_struct'
require 'hashery/casting_hash'
require 'hashery/crud_hash'
require 'hashery/dictionary'
require 'hashery/fuzzy_hash'
require 'hashery/ini_hash'
require 'hashery/linked_list'
require 'hashery/lru_hash'
require 'hashery/key_hash'
require 'hashery/open_cascade'
require 'hashery/open_hash'
#require 'hashery/ordered_hash'
#require 'hashery/ostructable'
require 'hashery/property_hash'
require 'hashery/query_hash'
require 'hashery/static_hash'
+78
View File
@@ -0,0 +1,78 @@
---
revision: 2013
type: ruby
sources:
- Index.yml
- Gemfile
authors:
- name: Trans
email: transfire@gmail.com
- name: Kirk Haines
- name: Robert Klemme
- name: Jan Molic
- name: George Moschovitis
- name: Jeena Paradies
- name: Erik Veenstra
organizations:
- name: RubyWorks (http://rubyworks.github.com/)
requirements:
- groups:
- development
- test
version: ">= 0"
name: qed
- groups:
- development
- test
version: ">= 0"
name: lemon
- groups:
- development
- test
version: ">= 0"
name: rubytest-cli
conflicts: []
alternatives: []
resources:
- type: home
uri: http://rubyworks.github.com/hashery
label: Homepage
- type: code
uri: http://github.com/rubyworks/hashery
label: Source Code
- type: mail
uri: http://groups.google.com/group/rubyworks-mailinglist
label: Mailing List
- type: docs
uri: http://rubydoc.info/github/rubyworks/hashery/master/frames
label: Documentation
- type: wiki
uri: http://wiki.github.com/rubyworks/hashery
label: User Guide
- type: gems
uri: http://rubygems.org/gems/hashery
repositories:
- name: upstream
scm: git
uri: git://github.com/rubyworks/hashery.git
categories: []
copyrights:
- holder: Rubyworks
year: '2010'
license: BSD-2-Clause
customs: []
paths:
lib:
- lib
- alt
name: hashery
title: Hashery
version: 2.1.2
summary: Facets-bread collection of Hash-like classes.
description: The Hashery is a tight collection of Hash-like classes. Included among
its many offerings are the auto-sorting Dictionary class, the efficient LRUHash,
the flexible OpenHash and the convenient KeyHash. Nearly every class is a subclass
of the CRUDHash which defines a CRUD model on top of Ruby's standard Hash making
it a snap to subclass and augment to fit any specific use case.
created: '2010-04-21'
date: '2016-05-01'
@@ -0,0 +1,222 @@
module Hashery
# TODO: Should associations be singleton?
#
# TODO: Is it really wise to keep a table of all associations?
# Association is a general binary association that allows one
# object to be associated with another. It has a variety of uses,
# such as linked-lists, simple ordered maps and mixed collections,
# among them.
#
# NOTE: This class is still fairly experimental. And it is not
# loaded along with the other Hashery libraries when using
# `require 'hashery'`. It must be required independently.
#
# Associations can be used to draw simple relationships.
#
# :Apple >> :Fruit
# :Apple >> :Red
#
# :Apple.associations #=> [ :Fruit, :Red ]
#
# It can also be used for simple lists of ordered pairs.
#
# c = [ :a >> 1, :b >> 2 ]
# c.each { |k,v| puts "#{k} associated with #{v} }
#
# produces
#
# a associated with 1
# b associated with 2
#
# The method :>> is used to construct the association.
# It is a rarely used method so it is generally available.
# But you can't use it for any of the following classes
# becuase they use #>> for other things.
#
# Bignum
# Fixnum
# Date
# IPAddr
# Process::Status
#
class Association
include Comparable
class << self
#
# Store association references.
#
# Returns `Hash` of all associaitons.
#
def reference
@reference ||= Hash.new{ |h,k,v| h[k]=[] }
end
#
# Shortcut for #new.
#
# index - The "index key" of the association.
# value - The "value" of the association.
#
# Returns `Association`.
#
def [](index, value)
new(index, value)
end
#def new(index, value)
# lookup[[index, value]] ||= new(index, value)
#end
#def lookup
# @lookup ||= {}
#end
end
#
# The "index key" of the association.
#
attr_accessor :index
#
# The "value" of the association.
#
attr_accessor :value
#
# Initialize new Association.
#
# index - The "index key" of the association.
# value - The "value" of the association.
#
def initialize(index, value=nil)
@index = index
@value = value
unless index.associations.include?(value)
index.associations << value
end
end
#
# Compare the values of two associations.
#
# TODO: Comparions with non-associations?
#
# assoc - The other `Association`.
#
# Returns [Integer] `1`, `0`, or `-1`.
#
def <=>(assoc)
return -1 if self.value < assoc.value
return 1 if self.value > assoc.value
return 0 if self.value == assoc.value
end
#
# Invert association, making the index the value and vice-versa.
#
# Returns [Array] with two-elements reversed.
#
def invert!
temp = @index
@index = @value
@value = temp
end
#
# Produce a string representation.
#
# Returns [String].
#
def to_s
return "#{index} >> #{value}"
end
#
# Produce a literal code string for creating an association.
#
# Returns [String].
#
def inspect
"#{index.inspect} >> #{value.inspect}"
end
#
# Convert to two-element associative array.
#
# Returns [Array] Two-element Array of index and value pair.
#
def to_ary
[index, value]
end
#
# Object extensions.
#
module Kernel
#
# Define an association for +self+.
#
# to - The value of the association.
#
# Returns [Association].
#
def >>(to)
Association.new(self, to)
end
#
# List of associations for this object.
#
# Returns an `Array` of `Associations`.
#
def associations
Association.reference[self]
end
end
end
end
class Object #:nodoc:
include Hashery::Association::Kernel
end
#--
# Setup the >> method in classes that use it already.
#
# This is a bad idea b/c it can cause backward compability issues.
#
# class Bignum
# alias_method( :rshift, :>>) if method_defined?(:>>)
# remove_method :>>
# end
#
# class Fixnum
# alias_method( :rshift, :>>) if method_defined?(:>>)
# remove_method :>>
# end
#
# class Date
# alias_method( :months_later, :>>) if method_defined?(:>>)
# remove_method :>>
# end
#
# class IPAddr
# alias_method( :rshift, :>>) if method_defined?(:>>)
# remove_method :>>
# end
#
# class Process::Status
# alias_method( :rshift, :>>) if method_defined?(:>>)
# remove_method :>>
# end
#++
# Copyright (c) 2005 Rubyworks, Thomas Sawyer
@@ -0,0 +1,164 @@
require 'hashery/crud_hash'
module Hashery
# CastingHash is just like CRUDHash, except that both keys and values
# can be passed through casting procedures.
#
class CastingHash < CRUDHash
#
# Like `#new` but can take a priming Hash or Array-pairs.
#
# hash - Hash-like object.
#
# Examples
#
# CastingHash[:a,1,:b,2]
#
# Returns `CastingHash`.
#
def self.[](hash)
s = new
hash.each{ |k,v| s[k] = v }
s
end
#
# Unlike traditional Hash a CastingHash's block argument
# coerces key/value pairs when #store is called.
#
# default - Default value.
# cast_proc - Casting procedure.
#
def initialize(default=nil, &cast_proc)
@cast_proc = cast_proc
super(default, &nil)
end
#
# The cast procedure.
#
# proc - Casting procedure.
#
# Returns `Proc` used for casting.
#
def cast_proc(&proc)
@cast_proc = proc if proc
@cast_proc
end
#
# Set `cast_proc`. This procedure must take two arguments (`key, value`)
# and return the same.
#
# proc - Casting procedure.
#
# Returns +proc+.
#
def cast_proc=(proc)
raise ArgumentError unless Proc === proc or NilClass === proc
@cast_proc = proc
end
#
# CRUD method for create and update. Unlike the parent class
# the key, value pair are passed threw the cast_proc before
# being set in the underlying hash table.
#
# key - Key of entry.
# value - Value of entry.
#
# Returns the +value+.
#
def store(key, value)
super(*cast_pair(key, value))
end
#
# Replace current entries with those from another Hash,
# or Hash-like object. Each entry is run through the
# casting procedure as it is added.
#
# other - Hash-like object.
#
# Returns +self+.
#
def replace(other)
super cast(other)
end
#
# Convert the CastingHash to a regular Hash.
#
# Returns an ordinary `Hash`.
#
def to_hash
h = {}; each{ |k,v| h[k] = v }; h
end
#
# Returns an ordinary `Hash`.
#
alias_method :to_h, :to_hash
#
# Recast all entries via the cast procedure.
#
# TODO: Isn't this the same as `#rehash`?
#
# Returns +self+.
#
def recast!
replace self
end
private
#
# If `cast_proc` is defined then use it to process key-value pair,
# otherwise return them as is.
#
# key - Key of entry.
# value - Value of entry.
#
# Returns `Array` of key-value pair.
#
def cast_pair(key, value)
if cast_proc
return cast_proc.call(key, value)
else
return key, value
end
end
#
# Cast a given +hash+ according to the `#key_proc` and `#value_proc`.
#
# hash - A `Hash` or anything the responds to `#each` like a hash.
#
# Returns a recasted `Hash`.
#
def cast(hash)
h = {}
hash.each do |k,v|
k, v = cast_pair(k, v)
h[k] = v
end
h
end
end
end
# TODO: Should we add #to_casting_hash to Hash classs?
#class Hash
#
# # Convert a Hash to a CastingHash.
# def to_casting_hash(value_cast=nil, &key_cast)
# CastingHash.new(self, value_cast, &key_cast)
# end
#
#end
@@ -0,0 +1,119 @@
class Hash
#
# Create a hash given an `initial_hash`.
#
# initial_hash - Hash or hash-like object to use as priming data.
# block - Procedure used by initialize (e.g. default_proc).
#
# Returns a `Hash`.
#
def self.create(initial_hash={}, &block)
o = new &block
o.update(initial_hash)
o
end
#
# Like #fetch but returns the results of calling `default_proc`, if defined,
# otherwise `default`.
#
# key - Hash key to lookup.
#
# Returns value of Hash entry or `nil`.
#
def retrieve(key)
fetch(key, default_proc ? default_proc[self, key] : default)
end
#
# Convert to Hash.
#
def to_hash
dup # -or- `h = {}; each{ |k,v| h[k] = v }; h` ?
end \
unless method_defined?(:to_hash)
#
# For a Hash, `#to_h` is the same as `#to_hash`.
#
alias :to_h :to_hash \
unless method_defined?(:to_h)
#
# Synonym for Hash#rekey, but modifies the receiver in place (and returns it).
#
# key_map - Hash of old key to new key.
# block - Procedure to convert keys, which can take just the key
# or both key and value as arguments.
#
# Examples
#
# foo = { :name=>'Gavin', :wife=>:Lisa }
# foo.rekey!{ |k| k.to_s } #=> { "name"=>"Gavin", "wife"=>:Lisa }
# foo.inspect #=> { "name"=>"Gavin", "wife"=>:Lisa }
#
# Returns `Hash`.
#
def rekey(key_map=nil, &block)
if !(key_map or block)
block = lambda{|k| k.to_sym}
end
key_map ||= {}
hash = {}
(keys - key_map.keys).each do |key|
hash[key] = self[key]
end
key_map.each do |from, to|
hash[to] = self[from] if key?(from)
end
hash2 = {}
if block
case block.arity
when 0
raise ArgumentError, "arity of 0 for #{block.inspect}"
when 2
hash.each do |k,v|
nk = block.call(k,v)
hash2[nk] = v
end
else
hash.each do |k,v|
nk = block[k]
hash2[nk] = v
end
end
else
hash2 = hash
end
hash2
end
#
# Synonym for Hash#rekey, but modifies the receiver in place (and returns it).
#
# key_map - Hash of old key to new key.
# block - Procedure to convert keys, which can take just the key
# or both key and value as arguments.
#
# Examples
#
# foo = { :name=>'Gavin', :wife=>:Lisa }
# foo.rekey!{ |k| k.to_s } #=> { "name"=>"Gavin", "wife"=>:Lisa }
# foo #=> { "name"=>"Gavin", "wife"=>:Lisa }
#
# Returns `Hash`.
#
def rekey!(key_map=nil, &block)
replace(rekey(key_map, &block))
end
end
@@ -0,0 +1,405 @@
require 'hashery/core_ext'
module Hashery
# The CRUDHash is essentailly the same as the Hash class, but it reduces the
# the set of necessary methods to the fundametal CRUD requirements. All other
# methods route through these CRUD methods. This is a better general design,
# although it is, of course, a little bit slower. The utility of this class
# becomes appearent when subclassing or delegating, as only a handful of methods
# need to be changed for all other methods to work accordingly.
#
# In addition to the CRUD features, CRUDHash supports a `#key_proc`, akin to
# `#default_proc`, that can be used to normalize keys.
#
# The CRUD methods are:
#
# * key?
# * fetch
# * store
# * delete
#
# In addition to these main methods, there are these supporting "CRUD" methods:
#
# * default
# * default_proc
# * default_proc=
# * key_proc
# * key_proc=
#
class CRUDHash < ::Hash
#
# Dummy object for null arguments.
#
NA = Object.new
#
# This method is overridden to ensure that new entries pass through
# the `#store` method.
#
# hash - [#each] Single Hash, associative array or just a list of pairs.
#
def self.[](*hash)
h = new
if hash.size == 1
hash.first.each do |k,v|
h.store(k, v)
end
else
hash.each do |(k,v)|
h.store(k, v)
end
end
h
end
#
# Alternate to #new which auto-creates sub-dictionaries as needed.
# By default the `default_proc` procuced a empty Hash and is
# self-referential so every such Hash also has the same `default_proc`.
#
# args - Pass-thru arguments to `#new`.
# block - Alternate internal procedure for default proc.
#
# Examples
#
# d = CRUDHash.auto
# d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
#
# Returns `Hash`.
#
def self.auto(*args, &block)
if block
leet = lambda { |hsh, key| hsh[key] = block.call(hsh, key) }
else
leet = lambda { |hsh, key| hsh[key] = new(&leet) }
end
new(*args, &leet)
end
#
# Set `key_proc`.
#
# Examples
#
# ch = CRUDHash.new
# ch.key_proc = Proc.new{ |key| key.to_sym }
#
# Returns `Proc`.
#
def key_proc=(proc)
raise ArgumentError unless Proc === proc or NilClass === proc
@key_proc = proc
end
#
# Get/set `key_proc`.
#
# Examples
#
# ch = CRUDHash.new
# ch.key_proc
#
# Returns `Proc`.
#
def key_proc(&block)
@key_proc = block if block
@key_proc
end
#
# Allow `#default_proc` to take a block.
#
# block - The `Proc` object to set the `default_proc`.
#
# Returns `Proc`, the `default_proc`.
#
def default_proc(&block)
self.default_proc = block if block
super()
end
#
# CRUD method for checking if key exists.
#
# key - Hash key to lookup.
#
# Returns `true/false`.
#
def key?(key)
super cast_key(key)
end
#
# CRUD method for read. This method gets the value for a given key.
# An error is raised if the key is not present, but an optional argument
# can be provided to be returned instead.
#
# key - Hash key to lookup.
# default - Value to return if key is not present.
#
# Raises KeyError when key is not found and default has not been given.
#
# Returns the `Object` that is the Hash entry's value.
#
def fetch(key, *default)
super(cast_key(key), *default)
end
#
# CRUD method for create and update.
#
# key - The `Object` to act as indexing key.
# value - The `Object` to associate with key.
#
# Returns +value+.
#
def store(key, value)
super(cast_key(key), value)
end
#
# CRUD method for delete.
#
# key - Hash key to remove.
#
# Returns value of deleted Hash entry.
#
def delete(key)
super cast_key(key)
end
# END OF CRUD METHODS
#
# Like #fetch but returns the results of calling `default_proc`, if defined,
# otherwise `default`.
#
# key - Hash key to lookup.
#
# Returns value of Hash entry or `nil`.
#
def retrieve(key)
if key?(key)
fetch(key)
else
default_proc ? default_proc.call(self, key) : default
end
end
#
# Method for reading value. Returns `nil` if key is not present.
#
# Note that this method used to be the CRUD method instead of #retrieve. Complaints about
# #read being indicative of an IO object (though in my opinion that is a bad asumption) have
# led to this method's deprecation.
#
# key - Hash key to lookup.
#
# Returns value of Hash entry.
#
def read(key)
warn "The #read method as been deprecated. Use #retrieve instead."
retrieve(key)
end
#
# Update Hash with +assoc+.
#
# assoc - Two-element `Array` or a `Hash`.
#
# Returns +assoc+.
#
def <<(assoc)
case assoc
when Hash
update(assoc)
when Array
assoc.each_slice(2) do |(k,v)|
store(k,v)
end
else
raise ArgumentError # or TypeError ?
end
end
#
# Operator for `#retrieve`.
#
# key - Index key to lookup.
#
# Returns `Object` value of key.
#
def [](key)
retrieve(key)
end
#
# Operator for `#store`.
#
# key - The `Object` to act as indexing key.
# value - The `Object` to associate with key.
#
# Returns +value+.
#
def []=(key,value)
store(key,value)
end
#
# Update the Hash with another hash.
#
# other - Other hash or hash-like object to add to the hash.
#
# Returns +self+.
#
def update(other)
other.each do |k,v|
store(k, v)
end
self
end
#
# Alias for `#update`.
#
alias merge! update
#
# Merge the Hash with another hash, returning a new Hash.
#
# other - Other hash or hash-like object to add to the hash.
#
# Returns `Hash`.
#
def merge(other)
#super(other.rekey{ |key| cast_key(key) })
copy = dup
other.each{ |k,v| copy.store(k, v) }
copy
end
#
# Iterate over each hash pair.
#
def each #:yield:
if block_given?
keys.each do |k|
yield(k, retrieve(k))
end
else
to_enum(:each)
end
end
#
# Alias for #each.
#
alias each_pair each
#
# Alias for `#key?`.
#
alias has_key? key?
#
# Alias for `#key?`.
#
alias member? key?
#
# Alias for `#key?`.
#
alias include? key? # why isn't it an alias for `#has_value?` ?
#
# Replace current entries with those from another Hash,
# or Hash-like object. Each entry is run through the
# casting procedure as it is added.
#
# other - Hash-like object.
#
# Returns +self+.
#
def replace(other)
super cast(other)
end
#
# Get the values at.
#
# keys - List of keys to lookup.
#
# Returns `Array` of values.
#
def values_at(*keys)
super *keys.map{ |key| cast_key(key) }
end
# Convert CRUDHash to regular Hash.
#
# TODO: Since a CRUDHash is a subclass of Hash should #to_hash just `#dup`
# insted of converting to traditional Hash?
#
def to_hash
h = {}; each{ |k,v| h[k] = v }; h
end #unless method_defined?(:to_hash)
#
# Convert CRUDHash to regular Hash.
#
# TODO: Since a CRUDHash is a subclass of Hash should #to_h just `#dup`
# insted of converting to traditional Hash?
#
# Returns `Hash`.
#
alias :to_h :to_hash
private
#
# Cast a given `hash` in accordance to the `#key_proc`.
#
# hash - Any object the responds to `#each` like a Hash.
#
# Returns `Hash`.
#
def cast(hash)
h = {}
hash.each do |k,v|
h[cast_key(k)] = v
end
h
end
#
# Callback for normalizing hash keys.
#
# key - Index key.
#
# Returns key after passing through the `key_proc`.
#
def cast_key(key)
@key_proc ? @key_proc.call(key) : key
end
# TODO: Consider value callback procs for future version of CRUDHash.
#
# #
# # Callback for writing value.
# #
# def cast_write(value)
# @write_proc ? @write_proc.call(value) : value
# end
#
# #
# # Callback for reading value.
# #
# def cast_read(value)
# @read_proc ? @read_proc.call(value) : value
# end
end
end
@@ -0,0 +1,641 @@
module Hashery
# The Dictionary class is a Hash that preserves order.
# So it has some array-like extensions also. By defualt
# a Dictionary object preserves insertion order, but any
# order can be specified including alphabetical key order.
#
# Using a Dictionary is almost the same as using a Hash.
#
# # You can do simply
# hsh = Dictionary.new
# hsh['z'] = 1
# hsh['a'] = 2
# hsh['c'] = 3
# p hsh.keys #=> ['z','a','c']
#
# # or using Dictionary[] method
# hsh = Dictionary['z', 1, 'a', 2, 'c', 3]
# p hsh.keys #=> ['z','a','c']
#
# # but this don't preserve order
# hsh = Dictionary['z'=>1, 'a'=>2, 'c'=>3]
# p hsh.keys #=> ['a','c','z']
#
# # Dictionary has useful extensions: push, pop and unshift
# p hsh.push('to_end', 15) #=> true, key added
# p hsh.push('to_end', 30) #=> false, already - nothing happen
# p hsh.unshift('to_begin', 50) #=> true, key added
# p hsh.unshift('to_begin', 60) #=> false, already - nothing happen
# p hsh.keys #=> ["to_begin", "a", "c", "z", "to_end"]
# p hsh.pop #=> ["to_end", 15], if nothing remains, return nil
# p hsh.keys #=> ["to_begin", "a", "c", "z"]
# p hsh.shift #=> ["to_begin", 30], if nothing remains, return nil
#
# == Notes
#
# * You can use #order_by to set internal sort order.
# * #<< takes a two element [k,v] array and inserts.
# * Use ::auto which creates Dictionay sub-entries as needed.
# * And ::alpha which creates a new Dictionary sorted by key.
#
# == Acknowledgments
#
# Dictionary is a port of OrderHash 2.0 Copyright (c) 2005 Jan Molic.
#
# People who have contributed to this class since then include:
#
# * Andrew Johnson (merge, to_a, inspect, shift and Hash[])
# * Jeff Sharpe (reverse and reverse!)
# * Thomas Leitner (has_key? and key?)
#
# OrderedHash is public domain.
#
class Dictionary
include Enumerable
class << self
#
# Create a new Dictionary storing argument pairs as an initial mapping.
#
# TODO: Is this needed? Doesn't the super class do this?
#
# Returns Dictionary instance.
#
def [](*args)
hsh = new
if Hash === args[0]
hsh.replace(args[0])
elsif (args.size % 2) != 0
raise ArgumentError, "odd number of elements for Hash"
else
while !args.empty?
hsh[args.shift] = args.shift
end
end
hsh
end
#
# Like #new but the block sets the order instead of the default.
#
# Dictionary.new_by{ |k,v| k }
#
def new_by(*args, &blk)
new(*args).order_by(&blk)
end
#
# Alternate to #new which creates a dictionary sorted by the key as a string.
#
# d = Dictionary.alphabetic
# d["z"] = 1
# d["y"] = 2
# d["x"] = 3
# d #=> {"x"=>3,"y"=>2,"z"=>2}
#
# This is equivalent to:
#
# Dictionary.new.order_by { |key,value| key.to_s }
#
def alphabetic(*args, &block)
new(*args, &block).order_by { |key,value| key.to_s }
end
# DEPRECATED: Use #alphabetic instead.
alias :alpha :alphabetic
#
# Alternate to #new which auto-creates sub-dictionaries as needed.
#
# Examples
#
# d = Dictionary.auto
# d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}}
#
def auto(*args)
#AutoDictionary.new(*args)
leet = lambda { |hsh, key| hsh[key] = new(&leet) }
new(*args, &leet)
end
end
#
# New Dictiionary.
#
def initialize(*args, &blk)
@order = []
@order_by = nil
if blk
dict = self # This ensures automatic key entry effect the
oblk = lambda{ |hsh, key| blk[dict,key] } # dictionary rather then just the interal hash.
@hash = Hash.new(*args, &oblk)
else
@hash = Hash.new(*args)
end
end
#
# Order of keys.
#
# Returns [Array].
#
def order
reorder if @order_by
@order
end
#
# Keep dictionary sorted by a specific sort order.
#
# block - Ordering procedure.
#
# Returns +self+.
#
def order_by( &block )
@order_by = block
order
self
end
#
# Keep dictionary sorted by key.
#
# d = Dictionary.new.order_by_key
# d["z"] = 1
# d["y"] = 2
# d["x"] = 3
# d #=> {"x"=>3,"y"=>2,"z"=>2}
#
# This is equivalent to:
#
# Dictionary.new.order_by { |key,value| key }
#
# The initializer Dictionary#alpha also provides this.
#
# Returns +self+.
#
def order_by_key
if block_given?
@order_by = Proc.new{ |k,v| yield(k) }
else
@order_by = Proc.new{ |k,v| k }
end
order
self
end
#
# Keep dictionary sorted by value.
#
# d = Dictionary.new.order_by_value
# d["z"] = 1
# d["y"] = 2
# d["x"] = 3
# d #=> {"x"=>3,"y"=>2,"z"=>2}
#
# This is equivalent to:
#
# Dictionary.new.order_by { |key,value| value }
#
def order_by_value
if block_given?
@order_by = Proc.new{ |k,v| yield(v) }
else
@order_by = Proc.new{ |k,v| v }
end
order
self
end
#
# Re-apply the sorting procedure.
#
def reorder
if @order_by
assoc = @order.collect{ |k| [k,@hash[k]] }.sort_by(&@order_by)
@order = assoc.collect{ |k,v| k }
end
@order
end
#def ==( hsh2 )
# return false if @order != hsh2.order
# super hsh2
#end
#
# Is the dictionary instance equivalent to another?
#
def ==(hsh2)
if hsh2.is_a?( Dictionary )
@order == hsh2.order &&
@hash == hsh2.instance_variable_get("@hash")
else
false
end
end
#
# Lookup entry with key.
#
def [] key
@hash[ key ]
end
#
# Featch entry given +key+.
#
def fetch(key, *a, &b)
@hash.fetch(key, *a, &b)
end
#
# Store operator.
#
# h[key] = value
#
# Or with additional index.
#
# h[key,index] = value
#
def []=(k, i=nil, v=nil)
if v
insert(i,k,v)
else
store(k,i)
end
end
#
# Insert entry into dictionary at specific index position.
#
# index - [Integer] Position of order placement.
# key - [Object] Key to associate with value.
# value - [Object] Value to associate with key.
#
# Returns `value` stored.
#
def insert(index, key, value)
@order.insert(index, key)
@hash.store(key, value)
end
#
# Add entry into dictionary.
#
# Returns `value`.
#
def store(key, value)
@order.push(key) unless @hash.has_key?(key)
@hash.store(key, value)
end
#
# Clear dictionary of all entries.
#
def clear
@order = []
@hash.clear
end
#
# Delete the entry with given +key+.
#
def delete(key)
@order.delete(key)
@hash.delete(key)
end
#
# Iterate over each key.
#
def each_key
order.each { |k| yield( k ) }
self
end
#
# Iterate over each value.
#
def each_value
order.each { |k| yield( @hash[k] ) }
self
end
#
# Iterate over each key-value pair.
#
def each
order.each { |k| yield( k,@hash[k] ) }
self
end
alias each_pair each
#
# Delete entry if it fits conditional block.
#
def delete_if
order.clone.each { |k| delete k if yield(k,@hash[k]) }
self
end
#
# List of all dictionary values.
#
# Returns [Array].
#
def values
ary = []
order.each { |k| ary.push @hash[k] }
ary
end
#
# List of all dictionary keys.
#
# Returns [Array].
#
def keys
order
end
#
# Invert the dictionary.
#
# Returns [Dictionary] New dictionary that is inverse of the original.
#
def invert
hsh2 = self.class.new
order.each { |k| hsh2[@hash[k]] = k }
hsh2
end
#
# Reject entries based on give condition block and return
# new dictionary.
#
# Returns [Dictionary].
#
def reject(&block)
self.dup.delete_if(&block)
end
#
# Reject entries based on give condition block.
#
# Returns [Hash] of rejected entries.
#
# FIXME: This looks like it is implemented wrong!!!
#
def reject!( &block )
hsh2 = reject(&block)
self == hsh2 ? nil : hsh2
end
#
# Replace dictionary entries with new table.
#
def replace(hsh2)
case hsh2
when Dictionary
@order = hsh2.order
@hash = hsh2.to_h
when Hash
@hash = hsh2
@order = @hash.keys
else
@hash = hsh2.to_h
@order = @hash.keys
end
reorder
end
#
# Remove entry from the to top of dictionary.
#
def shift
key = order.first
key ? [key,delete(key)] : super
end
#
# Push entry on to the top of dictionary.
#
def unshift( k,v )
unless @hash.include?( k )
@order.unshift( k )
@hash.store( k,v )
true
else
false
end
end
#
# Same as #push.
#
def <<(kv)
push(*kv)
end
#
# Push entry on to bottom of the dictionary.
#
def push(k,v)
unless @hash.include?( k )
@order.push( k )
@hash.store( k,v )
true
else
false
end
end
#
# Pop entry off the bottom of dictionary.
#
def pop
key = order.last
key ? [key,delete(key)] : nil
end
#
# Inspection string for Dictionary.
#
# Returns [String].
#
def inspect
ary = []
each {|k,v| ary << k.inspect + "=>" + v.inspect}
'{' + ary.join(", ") + '}'
end
#
# Duplicate dictionary.
#
# Returns [Dictionary].
#
def dup
a = []
each{ |k,v| a << k; a << v }
self.class[*a]
end
#
# Update dictionary with other hash.
#
# Returns self.
#
def update( hsh2 )
hsh2.each { |k,v| self[k] = v }
reorder
self
end
alias :merge! update
#
# Merge other hash creating new dictionary.
#
# Returns [Dictionary].
#
def merge(hsh2)
self.dup.update(hsh2)
end
#
# Select items from dictiornary.
#
# Returns [Array] of two-element arrays.
#
def select
ary = []
each { |k,v| ary << [k,v] if yield k,v }
ary
end
#
# Reverse the order of the dictionary.
#
# Returns self.
#
def reverse!
@order.reverse!
self
end
#
# Reverse the order of duplicte dictionary.
#
# Returns [Dictionary].
#
def reverse
dup.reverse!
end
#
# Get/set initial entry value.
#
def first(x=nil)
return @hash[order.first] unless x
order.first(x).collect { |k| @hash[k] }
end
#
# Get/set last entry value.
#
def last(x=nil)
return @hash[order.last] unless x
order.last(x).collect { |k| @hash[k] }
end
#
# Number of items in the dictionary.
#
def length
@order.length
end
alias :size :length
#
# Is the dictionary empty?
#
# Returns `true` or `false`.
#
def empty?
@hash.empty?
end
#
# Does the dictionary have a given +key+.
#
# Returns `true` or `false`.
#
def has_key?(key)
@hash.has_key?(key)
end
#
# Does the dictionary have a given +key+.
#
# Returns `true` or `false`.
#
def key?(key)
@hash.key?(key)
end
#
# Convert to array.
#
# Returns [Array] of two-element arrays.
#
def to_a
ary = []
each { |k,v| ary << [k,v] }
ary
end
#
# Convert to array then to string.
#
# Returns [String].
#
def to_s
self.to_a.to_s
end
#
# Get a duplicate of the underlying hash table.
#
# Returns [Hash].
#
def to_hash
@hash.dup
end
#
# Get a duplicate of the underlying hash table.
#
# Returns [Hash].
#
def to_h
@hash.dup
end
protected
#
# Underlying hash table.
#
def hash_table
@hash
end
end
end
@@ -0,0 +1,206 @@
require 'set'
module Hashery
# FuzzyHash is a weird hash with special semantics for regex keys.
#
# This is useful when you want to have a lookup table that can either contain strings or regexes.
# For instance, you might want a catch all for certain regexes that perform a certain logic.
#
# >> hash = FuzzyHash.new
# >> hash[/^\d+$/] = 'number'
# >> hash[/.*/] = 'something'
# >> hash['chunky'] = 'bacon'
# >> hash['foo'] = 'vader'
#
# >> hash['foo']
# << 'vader'
# >> hash['food']
# << 'something'
# >> hash['123']
# << 'number'
#
# This class is based on Joshua Hull's original FuzzyHash class.
#
class FuzzyHash
#
#
#
def initialize(init_hash = nil)
@fuzzies = []
@hash_reverse = {}
@fuzzies_reverse = {}
@fuzzy_hash = {}
@hash = {}
init_hash.each{ |key,value| self[key] = value } if init_hash
end
#
#
#
def clear
hash.clear
fuzzies.clear
hash_reverse.clear
fuzzies_reverse.clear
end
#
#
#
def size
hash.size + fuzzies.size
end
alias_method :count, :size
#
#
#
def ==(o)
o.is_a?(FuzzyHash)
o.send(:hash) == hash &&
o.send(:fuzzies) == fuzzies
end
#
#
#
def empty?
hash.empty? && fuzzies.empty?
end
#
#
#
def keys
hash.keys + fuzzy_hash.keys
end
#
#
#
def values
hash.values + fuzzies.collect{|r| r.last}
end
#
#
#
def each
hash.each{|k,v| yield k,v }
fuzzies.each{|v| yield v.first, v.last }
end
#
#
#
def delete_value(value)
hash.delete(hash_reverse[value]) || ((rr = fuzzies_reverse[value]) && fuzzies.delete_at(rr[0]))
end
#
#
#
def []=(key, value)
if Regexp === key
fuzzies.delete_if{|f| f.first.inspect.hash == key.inspect.hash}
fuzzies_reverse.delete_if{|k, v| v[1].inspect.hash == key.inspect.hash}
hash_reverse.delete_if{|k,v| v.inspect.hash == key.inspect.hash}
fuzzy_hash[key] = value
fuzzies << [key, value]
reset_fuzz_test!
fuzzies_reverse[value] = [fuzzies.size - 1, key, value]
else
hash[key] = value
hash_reverse.delete_if{|k,v| v.hash == key.hash}
hash_reverse[value] = key
end
value
end
#
#
#
def replace(src, dest)
if hash_reverse.key?(src)
key = hash_reverse[src]
hash[key] = dest
hash_reverse.delete(src)
hash_reverse[dest] = key
elsif fuzzies_reverse.key?(src)
key = fuzzies_reverse[src]
fuzzies[rkey[0]] = [rkey[1], dest]
fuzzies_reverse.delete(src)
fuzzies_reverse[dest] = [rkey[0], rkey[1], dest]
end
end
#
#
#
def [](key)
(hash.key?(key) && hash[key]) ||
((lookup = fuzzy_lookup(key)) && lookup && lookup.first) ||
fuzzy_hash[key]
end
#
#
#
def match_with_result(key)
if hash.key?(key)
[hash[key], key]
else
fuzzy_lookup(key)
end
end
private
attr_reader :fuzzies, :hash_reverse, :fuzzies_reverse, :hash, :fuzzy_hash
attr_writer :fuzz_test
#
#
#
def reset_fuzz_test!
self.fuzz_test = nil
end
#
#
#
def fuzz_test
unless @fuzz_test
@fuzz_test = Object.new
@fuzz_test.instance_variable_set(:'@fuzzies', fuzzies)
method = "
def match(str)
case str\n
"
fuzzies.each_with_index do |reg, index|
method << "when #{reg.first.inspect}; [@fuzzies[#{index}][1], Regexp.last_match(0)];"
end
method << "end\nend\n"
@fuzz_test.instance_eval method
end
@fuzz_test
end
#
#
#
def fuzzy_lookup(key)
if !fuzzies.empty? && (value = fuzz_test.match(key))
value
end
end
end
end
# Copyright (c) 2009 Joshua Hull
@@ -0,0 +1,321 @@
module Hashery
# Hash class with methods to read from and write into ini files.
#
# A ini file is a text file in a specific format,
# it may include several fields which are sparated by
# field headlines which are enclosured by "[]".
# Each field may include several key-value pairs.
#
# Each key-value pair is represented by one line and
# the value is sparated from the key by a "=".
#
# == Examples
#
# === Example ini file
#
# # this is the first comment which will be saved in the comment attribute
# mail=info@example.com
# domain=example.com # this is a comment which will not be saved
# [database]
# db=example
# user=john
# passwd=very-secure
# host=localhost
# # this is another comment
# [filepaths]
# tmp=/tmp/example
# lib=/home/john/projects/example/lib
# htdocs=/home/john/projects/example/htdocs
# [ texts ]
# wellcome=Wellcome on my new website!
# Website description = This is only a example. # and another comment
#
# === Example object
#
# Ini#comment stores:
#
# "this is the first comment which will be saved in the comment attribute"
#
# Ini's internal hash stores:
#
# {
# "mail" => "info@example.com",
# "domain" => "example.com",
# "database" => {
# "db" => "example",
# "user" => "john",
# "passwd" => "very-secure",
# "host" => "localhost"
# },
# "filepaths" => {
# "tmp" => "/tmp/example",
# "lib" => "/home/john/projects/example/lib",
# "htdocs" => "/home/john/projects/example/htdocs"
# }
# "texts" => {
# "wellcome" => "Wellcome on my new website!",
# "Website description" => "This is only a example."
# }
# }
#
# As you can see this module gets rid of all comments, linebreaks
# and unnecessary spaces at the beginning and the end of each
# field headline, key or value.
#
# === Using the object
#
# Using the object is stright forward:
#
# ini = IniHash.new("path/settings.ini")
# ini["mail"] = "info@example.com"
# ini["filepaths"] = { "tmp" => "/tmp/example" }
# ini.comment = "This is\na comment"
# puts ini["filepaths"]["tmp"]
# # => /tmp/example
# ini.write()
#
# == Acknowlegements
#
# IniHash is based on ini.rb.
#
# Copyright (C) 2007 Jeena Paradies <info@jeenaparadies.net>
class IniHash
# TODO: Use class method for loading from file, not initializer.
#
# NOTE: In future versions, `#new` will not take a path, and `#load`
# will have to be used.
#
def self.load(path, load=true)
new(path, load)
end
#
# The hash which holds all INI data.
#
attr_accessor :inihash
#
# The string which holds the comments on the top of the file
#
attr_accessor :comment
#
# Creating a new IniHash object.
#
# path - is a path to the ini file
# load - if nil restores the data if possible
# if true restores the data, if not possible raises an error
# if false does not resotre the data
#
def initialize(path, load=nil)
@path = path if String === path
@inihash = (Hash === path ? path.dup : {})
if load or ( load.nil? and FileTest.readable_real? @path )
restore()
end
end
#
# Retrive the ini data for the key +key+
#
def [](key)
@inihash[key]
end
#
# Set the ini data for the key +key+
#
# key - Index key.
# value - The value to index.
#
# Returns +value+.
#
def []=(key, value)
#raise TypeError, "String expected" unless key.is_a? String
key = key.to_str
#raise TypeError, "String or Hash expected" unless value.is_a? String or value.is_a? Hash
value = value.to_str unless Hash === value
@inihash[key] = value
end
#
# Restores the data from file into the object
#
def restore
@inihash = IniHash.read_from_file(@path)
@comment = IniHash.read_comment_from_file(@path)
end
#
# Store data from the object in the file.
#
def save
IniHash.write_to_file(@path, @inihash, @comment)
end
#
# Deprecated: Save INI data to file path. Use #save instead.
#
def update
warn 'IniHash#update is deprecated for this use, use IniHash#save instead.'
save
end
#
# Convert to hash by duplicating the underlying hash table.
#
def to_h
@inihash.dup
end
alias :inspect :to_s
#
# Turn a hash (up to 2 levels deepness) into a ini string
#
# inihash - Hash representing the ini File. Default is a empty hash.
#
# Returns a string in the ini file format.
#
def to_s
str = ""
inihash.each do |key, value|
if value.is_a? Hash
str << "[#{key.to_s}]\n"
value.each do |under_key, under_value|
str << "#{under_key.to_s}=#{under_value.to_s unless under_value.nil?}\n"
end
else
str << "#{key.to_s}=#{value.to_s unless value.nil?}\n"
end
end
str
end
#
# Delegate missing mthods to underlying Hash.
#
# TODO: Sublcass Hash instead of delegating.
#
def method_missing(s,*a,&b)
@inihash.send(s, *a, &b) if @inihash.respond_to?(s)
end
#
# Reading data from file
#
# path - a path to the ini file
#
# Returns a `Hash` which represents the data from the file.
#
def self.read_from_file(path)
raise "file not found - #{path}" unless File.file?(path)
inihash = {}
headline = nil
IO.foreach(path) do |line|
line = line.strip.split(/#/)[0].to_s
# read it only if the line doesn't begin with a "=" and is long enough
unless line.length < 2 and line[0,1] == "="
# it's a headline if the line begins with a "[" and ends with a "]"
if line[0,1] == "[" and line[line.length - 1, line.length] == "]"
# get rid of the [] and unnecessary spaces
headline = line[1, line.length - 2 ].strip
inihash[headline] = {}
else
key, value = line.split(/=/, 2)
key = key.strip unless key.nil?
value = value.strip unless value.nil?
unless headline.nil?
inihash[headline][key] = value
else
inihash[key] = value unless key.nil?
end
end
end
end
inihash
end
#
# Reading comments from file
#
# path - a path to the INI file
#
# Returns a `String` with the comments from the beginning of the INI file.
#
def self.read_comment_from_file(path)
comment = ""
IO.foreach(path) do |line|
line.strip!
break unless line[0,1] == "#" or line == ""
comment_line = line[1, line.length].to_s
comment << "#{comment_line.strip}\n"
end
comment
end
#
# Writing a ini hash into a file
#
# path - Path to the INI file.
# inihash - Hash representing the ini File. Default is a empty hash.
# comment - String with comments which appear on the
# top of the file. Each line will get a "#" before.
# Default is no comment.
#
def self.write_to_file(path, inihash={}, comment=nil)
raise TypeError, "String expected" unless comment.is_a? String or comment.nil?
raise TypeError, "Hash expected" unless inihash.is_a? Hash
File.open(path, "w") { |file|
unless comment.nil?
comment.each do |line|
file << "# #{line}"
end
end
file << IniHash.text(inihash)
}
end
#
# Turn a hash (up to 2 levels deepness) into a ini string
#
# inihash - Hash representing the ini File. Default is a empty hash.
#
# Returns a String in the ini file format.
#
# TODO: Rename `IniHash.text` method to something else ?
#
def self.text(inihash={})
new(inihash).to_s
end
class << self
# @deprecated
alias_method :to_s, :text
end
end
end
@@ -0,0 +1,61 @@
require 'hashery/crud_hash'
module Hashery
# The KeyHash class is a Hash class which accepts a block for
# normalizing keys.
#
# The KeyHash class is essentially the same as a normal Hash.
# But notice the significant distinction of indifferent key
# access.
#
# s = KeyHash.new
# s[:x] = 1
# s[:x] #=> 1
# s['x'] #=> 1
#
# We can see that internally the key has indeed been converted
# to a String.
#
# s.to_h #=> {'x'=>1 }
#
# By default all keys are converted to strings. This has two advantages
# over a regular Hash is many usecases. First it means hash entries have
# indifferent access. <tt>1</tt>, <tt>"1"</tt> and <tt>:1</tt> are all
# equivalent --any object that defines <tt>#to_s</tt> can be used as a key.
# Secondly, since strings are garbage collected so will default KeyHash
# objects.
#
# But keys can be normalized by any function. Theses functions can be quite
# unique.
#
# h = KeyHash.new(0){ |k| k.to_i }
# h[1.34] += 1
# h[1.20] += 1
# h[1.00] += 1
# h #=> { 1 => 3 }
#
class KeyHash < CRUDHash
#
# Unlike a regular Hash, a KeyHash's block sets the `key_proc` rather
# than the `default_proc`.
#
def initialize(*default, &block)
super(*default)
@key_proc = block || Proc.new{ |k| k.to_s }
end
end
end
#class Hash
# #
# # Convert a Hash to a KeyHash object.
# #
# def to_keyhash
# Hashery::KeyHash[self]
# end
#end
@@ -0,0 +1,249 @@
require 'enumerator'
module Hashery
# LinkedList implements a simple doubly linked list with efficient
# hash-like element access.
#
# This is a simple linked-list implementation with efficient random
# access of data elements. It was inspired by George Moscovitis'
# LRUCache implementation found in Facets 1.7.30, but unlike the
# linked-list in that cache, this one does not require the use of a
# mixin on any class to be stored. The linked-list provides the
# push, pop, shift, unshift, first, last, delete and length methods
# which work just like their namesakes in the Array class, but it
# also supports setting and retrieving values by key, just like a
# hash.
#
# LinkedList was ported from the original in Kirk Hanes IOWA web framework.
#
# == Acknowledgements
#
# LinkedList is based on the LinkedList library by Kirk Haines.
#
# Copyright (C) 2006 Kirk Haines <khaines@enigo.com>.
#
class LinkedList
include Enumerable
# Represents a single node of the linked list.
#
class Node
attr_accessor :key, :value, :prev_node, :next_node
def initialize(key=nil,value=nil,prev_node=nil,next_node=nil)
@key = key
@value = value
@prev_node = prev_node
@next_node = next_node
end
end
#
# Initialize new LinkedList instance.
#
def initialize
@head = Node.new
@tail = Node.new
@lookup = Hash.new
node_join(@head,@tail)
end
#
# Lookup entry by key.
#
def [](key)
@lookup[key].value
end
#
# Add node to linked list.
#
def []=(k,v)
if @lookup.has_key?(k)
@lookup[k].value = v
else
n = Node.new(k,v,@head,@head.next_node)
node_join(n,@head.next_node)
node_join(@head,n)
@lookup[k] = n
end
v
end
#
# Is linked list empty?
#
def empty?
@lookup.empty?
end
#
# Remove node idenified by key.
#
def delete(key)
n = @lookup.delete(key)
v = n ? node_purge(n) : nil
v
end
#
# Get value of first node.
#
def first
@head.next_node.value
end
#
# Get value of last node.
#
def last
@tail.prev_node.value
end
#
#
#
def shift
k = @head.next_node.key
n = @lookup.delete(k)
node_delete(n) if n
end
#
#
#
def unshift(v)
if @lookup.has_key?(v)
n = @lookup[v]
node_delete(n)
node_join(n,@head.next_node)
node_join(@head,n)
else
n = Node.new(v,v,@head,@head.next_node)
node_join(n,@head.next_node)
node_join(@head,n)
@lookup[v] = n
end
v
end
#
#
#
def pop
k = @tail.prev_node.key
n = @lookup.delete(k)
node_delete(n) if n
end
#
#
#
def push(v)
if @lookup.has_key?(v)
n = @lookup[v]
node_delete(n)
node_join(@tail.prev_node,n)
node_join(n,@tail)
else
n = Node.new(v,v,@tail.prev_node,@tail)
node_join(@tail.prev_node,n)
node_join(n,@tail)
@lookup[v] = n
end
v
end
alias :<< :push
#
# Produces an Array of key values.
#
# Returns [Array].
#
def queue
r = []
n = @head
while (n = n.next_node) and n != @tail
r << n.key
end
r
end
#
# Converts to an Array of node values.
#
# Returns [Array].
#
def to_a
r = []
n = @head
while (n = n.next_node) and n != @tail
r << n.value
end
r
end
#
# Number of nodes.
#
def length
@lookup.length
end
alias size length
#
# Iterate over nodes, starting with the head node
# and ending with the tail node.
#
def each
n = @head
while (n = n.next_node) and n != @tail
yield(n.key,n.value)
end
end
private
#
# Delete a node.
#
# n - A node.
#
def node_delete(n)
node_join(n.prev_node,n.next_node)
v = n.value
end
#
# Purge a node.
#
# n - A node.
#
def node_purge(n)
node_join(n.prev_node,n.next_node)
v = n.value
n.value = nil
n.key = nil
n.next_node = nil
n.prev_node = nil
v
end
# Join two nodes.
#
# a - A node.
# b - A node.
#
def node_join(a,b)
a.next_node = b
b.prev_node = a
end
end
end
@@ -0,0 +1,363 @@
require 'enumerator'
module Hashery
# Hash with LRU expiry policy. There are at most max_size elements in a
# LRUHash. When adding more elements old elements are removed according
# to LRU policy.
#
# Based on Robert Klemme's LRUHash class.
#
# LRUHash, Copyright (c) 2010 Robert Klemme.
#
class LRUHash
include Enumerable
attr_reader :max_size
attr_accessor :default
attr_accessor :default_proc
attr_accessor :release_proc
#
# Initialize new LRUHash instance.
#
# max_size -
# default_value -
# block -
#
def initialize(max_size, default_value=nil, &block)
@max_size = normalize_max(max_size)
@default = default_value
@default_proc = block
@h = {}
@head = Node.new
@tail = front(Node.new)
end
#
# Iterate over each pair.
#
def each_pair
if block_given?
each_node do |n|
yield [n.key, n.value]
end
else
enum_for :each_pair
end
end
#
# Same as each pair.
#
alias each each_pair
#
# Iterate over each key.
#
def each_key
if block_given?
each_node do |n|
yield n.key
end
else
enum_for :each_key
end
end
#
# Iterate over each value.
#
def each_value
if block_given?
each_node do |n|
yield n.value
end
else
enum_for :each_value
end
end
#
# Size of the hash.
#
def size
@h.size
end
#
#
#
def empty?
@head.succ.equal? @tail
end
#
#
#
def fetch(key, &b)
n = @h[key]
if n
front(n).value
else
(b || FETCH)[key]
end
end
#
#
#
def [](key)
fetch(key) do |k|
@default_proc ? @default_proc[self, k] : default
end
end
#
#
#
def keys
@h.keys
end
#
#
#
def values
@h.map {|k,n| n.value}
end
#
#
#
def has_key?(key)
@h.has_key? key
end
alias key? has_key?
alias member? has_key?
alias include? has_key?
#
#
#
def has_value?(value)
each_pair do |k, v|
return true if value.eql? v
end
false
end
alias value? has_value?
def values_at(*key_list)
key_list.map {|k| self[k]}
end
#
#
#
def assoc(key)
n = @h[key]
if n
front(n)
[n.key, n.value]
end
end
#
#
#
def rassoc(value)
each_node do |n|
if value.eql? n.value
front(n)
return [n.key, n.value]
end
end
nil
end
#
#
#
def key(value)
pair = rassoc(value) and pair.first
end
#
#
#
def store(key, value)
# same optimization as in Hash
key = key.dup.freeze if String === key && !key.frozen?
n = @h[key]
unless n
if size == max_size
# reuse node to optimize memory usage
n = delete_oldest
n.key = key
n.value = value
else
n = Node.new key, value
end
@h[key] = n
end
front(n).value = value
end
alias []= store
#
#
#
def delete(key)
n = @h[key] and remove_node(n).value
end
#
#
#
def delete_if
each_node do |n|
remove_node n if yield n.key, n.value
end
end
#
#
#
def max_size=(limit)
limit = normalize_max(limit)
while size > limit
delete_oldest
end
@max_size = limit
end
#
#
#
def clear
until empty?
delete_oldest
end
self
end
#
#
#
def to_s
s = nil
each_pair {|k, v| (s ? (s << ', ') : s = '{') << k.to_s << '=>' << v.to_s}
s ? (s << '}') : '{}'
end
alias inspect to_s
private
#
# Iterate nodes.
#
def each_node
n = @head.succ
until n.equal? @tail
succ = n.succ
yield n
n = succ
end
self
end
#
# Move node to front.
#
# node - [Node]
#
def front(node)
node.insert_after(@head)
end
#
# Remove the node and invoke release_proc
# if set
#
# node - [Node]
#
def remove_node(node)
n = @h.delete(node.key)
n.unlink
release_proc and release_proc[n.key, n.value]
n
end
#
# Remove the oldest node returning the node
#
def delete_oldest
n = @tail.pred
raise "Cannot delete from empty hash" if @head.equal? n
remove_node n
end
#
# Normalize the argument in order to be usable as max_size
# criterion is that n.to_i must be an Integer and it must
# be larger than zero.
#
# n - [#to_i] max size
#
def normalize_max(n)
n = n.to_i
raise ArgumentError, 'Invalid max_size: %p' % n unless Integer === n && n > 0
n
end
#
FETCH = Proc.new {|k| raise KeyError, 'key not found'}
# A single node in the doubly linked LRU list of nodes.
Node = Struct.new :key, :value, :pred, :succ do
def unlink
pred.succ = succ if pred
succ.pred = pred if succ
self.succ = self.pred = nil
self
end
def insert_after(node)
raise 'Cannot insert after self' if equal? node
return self if node.succ.equal? self
unlink
self.succ = node.succ
self.pred = node
node.succ.pred = self if node.succ
node.succ = self
self
end
end
end
end
@@ -0,0 +1,167 @@
require 'hashery/open_hash'
module Hashery
# OpenCascade is subclass of OpenHash. It differs in a few
# significant ways. The reason this class is called "cascade" is that
# every internal Hash is transformed into an OpenCascade dynamically
# upon access. This makes it easy to create "cascading" references.
#
# h = { :x => { :y => { :z => 1 } } }
# c = OpenCascade[h]
# c.x.y.z #=> 1
#
# As soon as you access a node it automatically becomes an OpenCascade.
#
# c = OpenCascade.new #=> #<OpenCascade:0x7fac3680ccf0 {}>
# c.r #=> #<OpenCascade:0x7fac368084c0 {}>
# c.a.b #=> #<OpenCascade:0x7fac3680a4f0 {}>
#
# But if you set a node, then that will be that value.
#
# c.a.b = 4 #=> 4
#
# To query a node without causing the auto-creation of an OpenCasade
# instance, use the `?`-mark.
#
# c.a.z? #=> nil
#
# OpenCascade also transforms Hashes within Arrays.
#
# h = { :x=>[ {:a=>1}, {:a=>2} ], :y=>1 }
# c = OpenCascade[h]
# c.x.first.a.assert == 1
# c.x.last.a.assert == 2
#
# Finally, you can set call a private method via bang methods using the `!`-mark.
#
# c = OpenCascade.new #=> #<OpenCascade:0x7fac3680ccf0 {}>
# c.each = 4
# c.each! do |k,v|
# ...
# end
#
# c.x!(4).y!(3) #=> #<OpenCascade:0x7fac3680ccf0 {:x=>4, :y=>3}>
#
# Subclassing OpenCascade with cause the new subclass to become the class that
# is auto-created. If this is not the behavior desired, consider using delegation
# instead of subclassing.
#
class OpenCascade < OpenHash
#
#def self.[](hash)
# oc = new
# hash.each{ |(k,v)| oc.store(k,v) }
# oc
#end
#
# Initialize new OpenCascade instance.
#
# default - The usual default object.
#
def initialize(*default)
@read = {}
leet = lambda { |h,k| h[k] = self.class.new(&leet) }
super(*default, &leet)
end
#
# Alias for original read method.
#
alias :retrieve! :retrieve
#
# Read value given a +key+.
#
# key - Index key to lookup.
#
# Returns value.
#
def retrieve(key)
ckey = cast_key(key)
if @read[ckey]
super(key)
else
@read[ckey] = store(key, cast_value(super(key)))
end
end
#
#
#
def method_missing(sym, *args, &blk)
type = sym.to_s[-1,1]
name = sym.to_s.gsub(/[=!?]$/, '').to_sym
case type
when '='
store(name, args.first)
when '?'
key?(name) ? retrieve!(name) : nil # key?(name)
when '!'
__send__(name, *args, &blk)
else
#if key?(name)
retrieve(name)
#else
# #default = OpenCascade.new #self.class.new
# #default = default_proc ? default_proc.call(self, name) : default
# store(name, read(name))
#end
end
end
def respond_to?(sym, include_private = false)
sym != :to_ary && super
end
#def each
# super do |key, entry|
# yield([key, transform_entry(entry)])
# end
#end
private
#
# Cast value, such that Hashes are converted to OpenCascades.
# And Hashes in Arrays are converted to OpenCascades as well.
#
def cast_value(entry)
case entry
when Hash
e = OpenCascade.new
e.key_proc = key_proc if key_proc
e.merge!(entry)
e
when Array
entry.map{ |e| cast_value(e) }
else
entry
end
end
end
end
#--
# Last, when an entry is not found, 'null' is returned rather then 'nil'.
# This allows for run-on entries withuot error. Eg.
#
# o = OpenCascade.new
# o.a.b.c #=> null
#
# Unfortuately this requires an explict test for null? in 'if' conditions.
#
# if o.a.b.c.null? # true if null
# if o.a.b.c.nil? # true if nil or null
# if o.a.b.c.not? # true if nil or null or false
#
# So be sure to take that into account.
#++
@@ -0,0 +1,145 @@
require 'hashery/crud_hash'
module Hashery
# OpenHash is a Hash, but also supports open properties much like
# OpenStruct.
#
# Only names that are name methods of Hash can be used as open slots.
# To open a slot for a name that would otherwise be a method, the
# method needs to be made private. The `#open!` method can be used
# to handle this.
#
# Examples
#
# o = OpenHash.new
# o.open!(:send)
# o.send = 4
#
class OpenHash < CRUDHash
alias :object_class :class
#FILTER = /(^__|^\W|^instance_|^object_|^to_)/
#methods = Hash.instance_methods(true).select{ |m| m !~ FILTER }
#methods = methods - [:each, :inspect, :send] # :class, :as]
#private *methods
#
# Initialize new OpenHash instance.
#
# TODO: Maybe `safe` should be the first argument?
#
def initialize(default=nil, safe=false, &block)
@safe = safe
super(*[default].compact, &block)
end
#
# If safe is set to true, then public methods cannot be overriden
# by hash keys.
#
attr_accessor :safe
#
# Alias to original store method.
#
#alias :store! :store
#
# Index `value` to `key`. Unless safe mode, will also open up the
# key if it is not already open.
#
# key - Index key to associate with value.
# value - Value to be associate with key.
#
# Returns +value+.
#
def store(key, value)
#open!(key)
super(key, value)
end
#
# Open up a slot that that would normally be a Hash method.
#
# The only methods that can't be opened are ones starting with `__`.
#
# methods - [Array<String,Symbol>] method names
#
# Returns Array of slot names that were opened.
#
def open!(*methods)
# Only select string and symbols, any other type of key is allowed,
# it just won't be accessible via dynamic methods.
methods = methods.select{ |x| String === x || Symbol === x }
if methods.any?{ |m| m.to_s.start_with?('__') }
raise ArgumentError, "cannot open shadow methods"
end
# only public methods need be made protected
methods = methods.map{ |x| x.to_sym }
methods = methods & public_methods(true).map{ |x| x.to_sym }
if @safe
raise ArgumentError, "cannot set public method" unless methods.empty?
else
(class << self; self; end).class_eval{ protected *methods }
end
methods
end
# @deprecated
alias :omit! :open!
#
# Is a slot open?
#
# method - [String,Symbol] method name
#
# Returns `true` or `false`.
#
def open?(method)
methods = public_methods(true).map{ |m| m.to_sym }
! methods.include?(method.to_sym)
end
#
# Make specific Hash methods available for use that have previously opened.
#
# methods - [Array<String,Symbol>] method names
#
# Returns +methods+.
#
def close!(*methods)
(class << self; self; end).class_eval{ public *methods }
methods
end
#
#
#
def method_missing(s,*a, &b)
type = s.to_s[-1,1]
name = s.to_s.sub(/[!?=]$/, '')
key = name.to_sym
case type
when '='
store(key, a.first)
when '?'
key?(key)
when '!'
# call an underlying private method
# TODO: limit this to omitted methods (from included) ?
__send__(name, *a, &b)
else
#if key?(key)
retrieve(key)
#else
# super(s,*a,&b)
#end
end
end
end
end
@@ -0,0 +1,174 @@
module Hashery
# OrderedHash is a simple ordered hash implmentation, for users of
# Ruby 1.8.7 or less.
#
# NOTE: As of Ruby 1.9+ this class is not needed, since
# Ruby 1.9's standard Hash tracks inseration order.
#
# This implementation derives from the same class in
# ActiveSupport library.
#
class OrderedHash < ::Hash
def to_yaml_type
"!tag:yaml.org,2002:omap"
end
def to_yaml(opts = {})
YAML.quick_emit(self, opts) do |out|
out.seq(taguri, to_yaml_style) do |seq|
each do |k, v|
seq.add(k => v)
end
end
end
end
# Hash is ordered in Ruby 1.9!
if RUBY_VERSION < '1.9'
def initialize(*args, &block)
super
@keys = []
end
def self.[](*args)
ordered_hash = new
if (args.length == 1 && args.first.is_a?(Array))
args.first.each do |key_value_pair|
next unless (key_value_pair.is_a?(Array))
ordered_hash[key_value_pair[0]] = key_value_pair[1]
end
return ordered_hash
end
unless (args.size % 2 == 0)
raise ArgumentError.new("odd number of arguments for Hash")
end
args.each_with_index do |val, ind|
next if (ind % 2 != 0)
ordered_hash[val] = args[ind + 1]
end
ordered_hash
end
def initialize_copy(other)
super(other)
@keys = other.keys
end
def []=(key, value)
@keys << key unless key?(key)
super(key, value)
end
def delete(key)
if has_key? key
index = @keys.index(key)
@keys.delete_at(index)
end
super(key)
end
def delete_if
super
sync_keys!
self
end
def reject!
super
sync_keys!
self
end
def reject(&block)
dup.reject!(&block)
end
def keys
@keys.dup
end
def values
@keys.collect{ |key| self[key] }
end
def to_hash
self
end
def to_a
@keys.map{ |key| [ key, self[key] ] }
end
def each_key
@keys.each{ |key| yield(key) }
end
def each_value
@keys.each{ |key| yield(self[key]) }
end
def each
@keys.each{ |key| yield(key, self[key]) }
end
alias_method :each_pair, :each
def clear
super
@keys.clear
self
end
def shift
k = @keys.first
v = delete(k)
[k, v]
end
def merge!(other_hash)
other_hash.each{ |k,v| self[k] = v }
self
end
def merge(other_hash)
dup.merge!(other_hash)
end
# When replacing with another hash, the initial order of our
# keys must come from the other hash, ordered or not.
def replace(other)
super
@keys = other.keys
self
end
def inspect
"#<OrderedHash #{super}>"
end
private
def sync_keys!
@keys.delete_if{ |k| !key?(k) }
end
end
end
end
require 'yaml'
YAML.add_builtin_type("omap") do |type, val|
OrderedHash[val.map(&:to_a).map(&:first)]
end
@@ -0,0 +1,238 @@
module Hashery
# A PathHash is a hash whose values can be accessed in the normal manner,
# or with keys that are slash (`/`) separated strings. To get the whole hash
# as a single flattened level, call `#flat`. All keys are converted to strings.
# All end-of-the-chain values are kept in whatever value they are.
#
# s = PathHash['a' => 'b', 'c' => {'d' => :e}]
# s['a'] #=> 'b'
# s['c'] #=> {slashed: 'd'=>:e}
# s['c']['d'] #=> :e
# s['c/d'] #=> :e
#
# PathHash is derived from the SlashedHash class in the HashMagic project
# by Daniel Parker <gems@behindlogic.com>.
#
# Copyright (c) 2006 BehindLogic (http://hash_magic.rubyforge.org)
#
# Authors: Daniel Parker
#
# TODO: This class is very much a work in progess and will be substantially rewritten
# for future versions.
#
class PathHash < Hash
#
# Initialize PathHash.
#
# hsh - Priming Hash.
#
def initialize(hsh={})
raise ArgumentError, "must be a hash or array of slashed values" unless hsh.is_a?(Hash) || hsh.is_a?(Array)
@constructor = hsh.is_a?(Hash) ? hsh.class : Hash
@flat = flatten_to_hash(hsh)
end
# Standard Hash methods, plus the overwritten ones
#include StandardHashMethodsInRuby
# Behaves like the usual Hash#[] method, but you can access nested hash
# values by composing a single key of the traversing keys joined by '/':
#
# hash['c']['d'] # is the same as:
# hash['c/d']
#
def [](key)
rg = Regexp.new("^#{key}/?")
start_obj = if @constructor == OrderedHash
@constructor.new((@flat.instance_variable_get(:@keys_in_order) || []).collect {|e| e.gsub(rg,'')})
else
@constructor.new
end
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})
v.is_a?(self.class) && v.empty? ? nil : v
end
#
# Same as above, except sets value rather than retrieving it.
#
def []=(key,value)
@flat.reject! {|k,v| k == key || k =~ Regexp.new("^#{key}/")}
if value.is_a?(Hash)
flatten_to_hash(value).each do |hk,hv|
@flat[key.to_s+'/'+hk.to_s] = hv
end
else
@flat[key.to_s] = value
end
end
def clear # :nodoc:
@flat.clear
end
#
#
#
def fetch(key,default=:ehisehoah0928309q98y30,&block) # :nodoc:
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})
if value.is_a?(self.class) && value.empty?
if default == :ehisehoah0928309q98y30
if block_given?
block.call(key)
else
raise IndexError
end
value
else
default
end
else
value
end
end
#
# Delete entry from Hash. Slashed keys can be used here, too.
#
# key - The key to delete.
# block - Produces the return value if key not found.
#
# Returns delete value.
#
def delete(key,&block)
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})
return block.call(key) if value.is_a?(self.class) && value.empty? && block_given?
@flat.keys.reject {|k| !(k == key || k =~ Regexp.new("^#{key}/"))}.each {|k| @flat.delete(k)}
return value
end
#
def empty?
@flat.empty?
end
# This gives you the slashed key of the value, no matter where the value is in the tree.
def index(value)
@flat.index(value)
end
#
def inspect
@flat.inspect.insert(1,'slashed: ')
end
# This gives you only the top-level keys, no slashes. To get the list of slashed keys, do hash.flat.keys
def keys
@flat.inject([]) {|a,(k,v)| a << [k.split('/',2)].flatten[0]; a}.uniq
end
# This is rewritten to mean something slightly different than usual: Use this to restructure the hash, for cases when you
# end up with an array holding several hashes.
def rehash # :nodoc:
@flat.rehash
end
# Gives a list of all keys in all levels in the multi-level hash, joined by slashes.
#
# {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed.flat.keys
# #=> ['a/b', 'a/c', 'b']
#
def flat
@flat
end
# Expands the whole hash to Hash objects ... not useful very often, it seems.
def expand
inject({}) {|h,(k,v)| h[k] = v.is_a?(SlashedHash) ? v.expand : v; h}
end
def to_string_array
flatten_to_array(flat,[])
end
def slashed # :nodoc:
self
end
# Same as ordered! but returns a new SlashedHash object instead of modifying the same.
def ordered(*keys_in_order)
dup.ordered!(*keys_in_order)
end
# Sets the SlashedArray as ordered. The *keys_in_order must be a flat array
# of slashed keys that specify the order for each level:
#
# s = {'a'=>{'b'=>'c', 'c'=>'d'}, 'b'=>'c'}.slashed
# s.ordered!('b', 'a/c', 'a/b')
# s.expand # => {'b'=>'c', 'a'=>{'c'=>'d', 'b'=>'c'}}
# # Note that the expanded hashes will *still* be ordered!
#
def ordered!(*keys_in_order)
return self if @constructor == OrderedHash
@constructor = OrderedHash
@flat = @flat.ordered(*keys_in_order)
self
end
#
def ==(other)
case other
when SlashedHash
@slashed == other.instance_variable_get(:@slashed)
when Hash
self == SlashedHash.new(other)
else
raise TypeError, "Cannot compare #{other.class.name} with SlashedHash"
end
end
private
def flatten_to_hash(hsh)
flat = @constructor.new
if hsh.is_a?(Array)
hsh.each do |e|
flat.merge!(flatten_to_hash(e))
end
elsif hsh.is_a?(Hash)
hsh.each do |k,v|
if v.is_a?(Hash)
flatten_to_hash(v).each do |hk,hv|
flat[k.to_s+'/'+hk.to_s] = hv
end
else
flat[k.to_s] = v
end
end
else
ks = hsh.split('/',-1)
v = ks.pop
ks = ks.join('/')
if !flat[ks].nil?
if flat[ks].is_a?(Array)
flat[ks] << v
else
flat[ks] = [flat[ks], v]
end
else
flat[ks] = v
end
end
flat
end
def flatten_to_array(value,a)
if value.is_a?(Array)
value.each {|e| flatten_to_array(e,a)}
elsif value.is_a?(Hash)
value.inject([]) {|aa,(k,v)| flatten_to_array(v,[]).each {|vv| aa << k+'/'+vv.to_s}; aa}.each {|e| a << e}
else
a << value.to_s
end
a
end
end
end
@@ -0,0 +1,161 @@
require 'hashery/crud_hash'
module Hashery
# A PropertyHash is the same as a regular Hash except it strictly limits the
# allowed keys.
#
# There are two ways to use it.
#
# 1) As an object in itself.
#
# h = PropertyHash.new(:a=>1, :b=>2)
# h[:a] #=> 1
# h[:a] = 3
# h[:a] #=> 3
#
# But if we try to set key that was not fixed, then we will get an error.
#
# h[:x] = 5 #=> ArgumentError
#
# 2) As a superclass.
#
# class MyPropertyHash < PropertyHash
# property :a, :default => 1
# property :b, :default => 2
# end
#
# h = MyPropertyHash.new
# h[:a] #=> 1
# h[:a] = 3
# h[:a] #=> 3
#
# Again, if we try to set key that was not fixed, then we will get an error.
#
# h[:x] = 5 #=> ArgumentError
#
class PropertyHash < CRUDHash
#
# Get a list of properties with default values.
#
# Returns [Hash] of properties and their default values.
#
def self.properties
@properties ||= (
parent = ancestors[1]
if parent.respond_to?(:properties)
parent.properties
else
{}
end
)
end
#
# Define a property.
#
# key - Name of property.
# opts - Property options.
# :default - Default value of property.
#
# Returns default value.
#
def self.property(key, opts={})
properties[key] = opts[:default]
end
#
# Initialize new instance of PropertyHash.
#
# properties - [Hash] Priming properties with default values, or
# if it doesn't respond to #each_pair, a default object.
# default_proc - [Proc] Procedure for default value of properties
# for properties without specific defaults.
#
def initialize(properties={}, &default_proc)
if properties.respond_to?(:each_pair)
super(&default_proc)
fixed = self.class.properties.merge(properties)
fixed.each_pair do |key, value|
store!(key, value)
end
else
super(*[properties].compact, &default_proc)
end
end
# Alias original #store method and make private.
alias :store! :store
private :store!
#
# Create a new property, on-the-fly.
#
# key - Name of property.
# opts - Property options.
# :default - Default value of property.
#
# Returns default value.
#
def property(key, opts={})
if opts[:default]
store!(key, opts[:default])
else
store!(key, retrieve(key))
end
end
#
# Store key value pair, ensuring the key is a valid property first.
#
# key - The `Object` to act as indexing key.
# value - The `Object` to associate with key.
#
# Raises ArgumentError if key is not a valid property.
#
# Returns +value+.
#
def store(key, value)
assert_key!(key)
super(key, value)
end
#
#def update(h)
# h.keys.each{ |k| assert_key!(k) }
# super(h)
#end
#
#def merge!(h)
# h.keys.each{ |k| assert_key!(k) }
# super(h)
#end
#
# Like #store but takes a two-element Array of `[key, value]`.
#
# Returns value.
#
#def <<(a)
# k,v = *a
# store(k,v)
#end
private
#
# Asserta that a key is a defined property.
#
# Raises ArgumentError if key is not a property.
#
def assert_key!(key)
unless key?(key)
raise ArgumentError, "property is not defined -- #{key.inspect}"
end
end
end
end
@@ -0,0 +1,91 @@
module Hashery
require 'hashery/key_hash'
# QueryHash is essentially a Hash class, but with some OpenStruct-like features.
#
# q = QueryHash.new
#
# Entries can be added to the Hash via a setter method.
#
# q.a = 1
#
# Then looked up via a query method.
#
# q.a? #=> 1
#
# The can also be looked up via a bang method.
#
# q.a! #=> 1
#
# The difference between query methods and bang methods is that the bang method
# will auto-instantiate the entry if not present, where as a query method will not.
#
# A QueryHash might not be quite as elegant as an OpenHash in that reader
# methods must end in `?` or `!`, but it remains fully compatible with Hash
# regardless of it's settings.
#
class QueryHash < CRUDHash
#
# By default the `key_proc` is set to convert all keys to strings via `#to_s`.
#
# default - Default object, or
# default_proc - Default procedure.
#
def initialize(*default, &default_proc)
@key_proc = Proc.new{ |k| k.to_s }
super(*default, &default_proc)
end
#
# Route get and set calls.
#
# s - [Symbol] Name of method.
# a - [Array] Method arguments.
# b - [Proc] Block argument.
#
# Examples
#
# o = QueryHash.new
# o.a = 1
# o.a? #=> 1
# o.b? #=> nil
#
def method_missing(s,*a, &b)
type = s.to_s[-1,1]
name = s.to_s.sub(/[!?=]$/, '')
key = name #key = cast_key(name)
case type
when '='
store(key, a.first)
when '!'
default = (default_proc ? default_proc.call(self, key) : default)
key?(key) ? fetch(key) : store(key, default)
when '?'
key?(key) ? fetch(key) : nil
else
# return self[key] if key?(key)
super(s,*a,&b)
end
end
#
# Custom #respond_to to account for #method_missing.
#
# name - The method name to check.
#
# Returns `true` or `false`.
#
def respond_to?(name)
return true if name.to_s.end_with?('=')
return true if name.to_s.end_with?('?')
return true if name.to_s.end_with?('!')
#key?(name.to_sym) || super(name)
super(name)
end
end
end
@@ -0,0 +1,14 @@
require 'hashery/key_hash'
module Hashery
# Stash is the original name for the KeyHash.
Stash = KeyHash
end
class Hash
# Convert Hash to Stash.
def to_stash
Hashery::Stash[self]
end
end
@@ -0,0 +1,53 @@
module Hashery
# StaticHash ia a Hash object which raises an error if any
# previously-defined key attempts to be set again.
#
# foo = StaticHash.new
# foo['name'] = 'Tom' #=> 'Tom'
# foo['age'] = 30 #=> 30
# foo['name'] = 'Bob'
#
# produces
#
# ArgumentError: Duplicate key for StaticHash -- 'name'
#
# StaticHash has it's orgins in Gavin Kistner's WriteOnceHash
# class found in his +basiclibrary.rb+ script.
#
# TODO: Maybe StaticHash isn't bets name for this class?
#
class StaticHash < CRUDHash
#
# Set a value for a key. Raises an error if that key already
# exists with a different value.
#
# key - Index key to associate with value.
# value - Value to associate with key.
#
# Retruns value.
#
def store(key, value)
if key?(key) && fetch(key) != value
raise ArgumentError, "Duplicate key for StaticHash -- #{key.inspect}"
end
super(key, value)
end
#
#def update(hash)
# dups = (keys | hash.keys)
# if dups.empty?
# super(hash)
# else
# raise ArgumentError, "Duplicate key for StaticHash -- #{dups.inspect}"
# end
#end
#
#alias_method :merge!, :update
end
end
@@ -0,0 +1,103 @@
require 'helper'
# must be required independently
require 'hashery/association'
testcase Association do
class_method :new do
test do
Association.new(:A, :B)
end
end
class_method :[] do
test do
a = Association[:a, 1]
a.assert.index == :a
a.assert.value == 1
end
end
method :index do
test do
a = Association.new(:a,1)
a.index.assert == :a
end
end
method :value do
test do
a = Association.new(:a,1)
a.value.assert == 1
end
end
method :to_ary do
test do
k,v = [],[]
ohash = [ 'A' >> '3', 'B' >> '2', 'C' >> '1' ]
ohash.each { |e1,e2| k << e1 ; v << e2 }
k.assert == ['A','B','C']
v.assert == ['3','2','1']
end
end
method :index do
test do
complex = [ 'Drop Menu' >> [ 'Button 1', 'Button 2', 'Button 3' ], 'Help' ]
complex[0].index.assert == 'Drop Menu'
end
end
method :<=> do
test 'when differnt in value' do
a = Association.new(:a,1)
b = Association.new(:b,2)
(a <=> b).assert == -1
(b <=> a).assert == 1
end
test 'when equal value' do
a = Association.new(:a,1)
b = Association.new(:b,1)
(a <=> b).assert == 0
end
end
method :invert! do
test do
a = Association.new(:a,1)
a.invert!
a.index.assert == 1
a.value.assert == :a
end
end
method :inspect do
test do
a = Association.new(:a,1)
a.inspect.assert == ":a >> 1"
end
end
method :to_s do
test do
a = Association.new(:a,1)
a.to_s.assert == "a >> 1"
end
end
end
testcase Object do
method :associations do
test do
s = 'a'
complex = [ s >> :b, s >> :c ]
s.associations.assert == [:b, :c]
end
end
end
@@ -0,0 +1,71 @@
require 'helper'
test_case CastingHash do
class_method :[] do
test do
h = CastingHash[:a=>1, :b=>2]
end
end
class_method :new do
test do
h = CastingHash.new
end
test 'with default' do
h = CastingHash.new(0)
h['a'].assert == 0
end
test 'with casting procedure' do
h = CastingHash.new{ |k,v| [k.to_sym, v] }
h['a'] = 1
h.assert == {:a=>1}
end
test 'with default and casting procedure' do
h = CastingHash.new(0){ |k,v| [k.to_sym, v] }
h['a'].assert == 0
h['b'] = 2
h.assert == {:b=>2}
end
end
method :recast! do
test do
h = CastingHash[:a=>1, :b=>2]
h.cast_proc{ |k,v| [k.to_s, v] }
h.recast!
h.assert == {'a'=>1, 'b'=>2}
end
end
method :cast_proc= do
test do
h = CastingHash[:a=>1, :b=>2]
h.cast_proc = Proc.new{ |k,v| [k.to_s, v] }
h.recast!
h.assert == {'a'=>1, 'b'=>2}
end
end
method :to_hash do
test do
h = CastingHash[:a=>1, :b=>2]
h.to_hash
::Hash.assert === h
h.assert == {:a=>1, :b=>2}
end
end
method :to_h do
test do
h = CastingHash[:a=>1, :b=>2]
h.to_h
::Hash.assert === h
h.assert == {:a=>1, :b=>2}
end
end
end
@@ -0,0 +1,79 @@
require 'helper'
test_case Hash do
method :rekey do
test "default" do
foo = { "a"=>1, "b"=>2 }
foo.rekey.assert == { :a=>1, :b=>2 }
foo.assert == { "a"=>1, "b"=>2 }
end
test "specific key" do
bar = { :a=>1, :b=>2 }
foo = bar.rekey(:a=>:c)
foo[:c].assert == 1
foo[:b].assert == 2
foo[:a].assert == nil
end
test "with block" do
bar = { :a=>1, :b=>2 }
foo = bar.rekey{ |k| k.to_s }
foo['a'].assert == 1
foo['b'].assert == 2
foo[:a].assert == nil
foo[:b].assert == nil
foo.assert == { 'a'=>1, 'b'=>2 }
end
test "symbol proc" do
foo = { :a=>1, :b=>2 }
foo.rekey(&:to_s).assert == { "a"=>1, "b"=>2 }
foo.assert == { :a =>1, :b=>2 }
end
end
method :rekey! do
test "default" do
foo = { "a"=>1, "b"=>2 }
foo.rekey!.assert == { :a=>1, :b=>2 }
foo.assert == { :a=>1, :b=>2 }
end
test "specific key" do
foo = { :a=>1, :b=>2 }
foo.rekey!(:a=>:c)
foo[:c].assert == 1
foo[:b].assert == 2
foo[:a].assert == nil
end
test "with block" do
foo = { :a=>1, :b=>2 }
foo.rekey!{ |k| k.to_s }
foo['a'].assert == 1
foo['b'].assert == 2
foo[:a].assert == nil
foo[:b].assert == nil
foo.assert == { 'a'=>1, 'b'=>2 }
end
test "symbol proc" do
foo = { :a=>1, :b=>2 }
foo.rekey!(&:to_s).assert == { "a"=>1, "b"=>2 }
foo.assert == { "a"=>1, "b"=>2 }
end
test "no conflict between keys" do
r = {1 => :a, 2 => :b}.rekey!{ |k| k + 1 }
r.refute = {3 => :a}
r.assert = {2 => :a, 3 => :b}
end
end
end
@@ -0,0 +1,29 @@
require 'helper'
test_case CRUDHash do
class_method :create do
test do
h = CRUDHash.create(:a=>1,:b=>2)
h.assert == {:a=>1,:b=>2}
end
end
class_method :auto do
test 'without a block' do
h = CRUDHash.auto
h[:a].assert == {}
end
test 'with a block' do
h = CRUDHash.auto{ [] }
h[:a].assert == []
end
end
end
#
# OT: Why not make `:a=>1` a Pair object?
#
@@ -0,0 +1,328 @@
require 'helper'
testcase Dictionary do
include AE::Legacy::Assertions
class_method :[] do
test do
d = Dictionary['z', 1, 'a', 2, 'c', 3]
assert_equal( ['z','a','c'], d.keys )
end
end
class_method :new do
test "with default" do
d = Dictionary.new{ |hash,key| hash[key] = 0 }
d[:a] = 0
d[:b] += 1
assert_equal [0, 1], d.values
assert_equal [:a,:b], d.keys
end
end
method :[] do
test do
d = Dictionary['a', 1]
d['a'].assert == 1
end
end
method :[]= do
test do
d = Dictionary.new
d['z'] = 1
d['a'] = 2
d['c'] = 3
assert_equal( ['z','a','c'], d.keys )
end
end
method :[]= do
test do
d = Dictionary[]
d[:a] = 1
d[:c] = 3
assert_equal( [1,3], d.values )
d[:b,1] = 2
assert_equal( [1,2,3], d.values )
assert_equal( [:a,:b,:c], d.keys )
end
end
method :push do
test do
d = Dictionary['a', 1, 'c', 2, 'z', 3]
assert( d.push('end', 15) )
assert_equal( 15, d['end'] )
assert( ! d.push('end', 30) )
assert( d.unshift('begin', 50) )
assert_equal( 50, d['begin'] )
assert( ! d.unshift('begin', 60) )
assert_equal( ["begin", "a", "c", "z", "end"], d.keys )
assert_equal( ["end", 15], d.pop )
assert_equal( ["begin", "a", "c", "z"], d.keys )
assert_equal( ["begin", 50], d.shift )
end
end
method :insert do
test "front" do
d = Dictionary['a', 1, 'b', 2, 'c', 3]
r = Dictionary['d', 4, 'a', 1, 'b', 2, 'c', 3]
assert_equal( 4, d.insert(0,'d',4) )
assert_equal( r, d )
end
test "back" do
d = Dictionary['a', 1, 'b', 2, 'c', 3]
r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4]
assert_equal( 4, d.insert(-1,'d',4) )
assert_equal( r, d )
end
end
method :update do
test "with other orderred hash" do
d = Dictionary['a', 1, 'b', 2, 'c', 3]
c = Dictionary['d', 4]
r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4]
assert_equal( r, d.update(c) )
assert_equal( r, d )
end
test "with other hash" do
d = Dictionary['a', 1, 'b', 2, 'c', 3]
c = { 'd' => 4 }
r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4]
assert_equal( r, d.update(c) )
assert_equal( r, d )
end
end
method :merge do
test "with other orderred hash" do
d = Dictionary['a', 1, 'b', 2, 'c', 3]
c = Dictionary['d', 4]
r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4]
assert_equal( r, d.merge(c) )
end
test "with other hash" do
d = Dictionary['a', 1, 'b', 2, 'c', 3]
c = { 'd' => 4 }
r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4]
assert_equal( r, d.merge(c) )
end
end
method :order_by do
test do
d = Dictionary['a', 3, 'b', 2, 'c', 1]
d.order_by{ |k,v| v }
assert_equal( [1,2,3], d.values )
assert_equal( ['c','b','a'], d.keys )
end
end
method :reverse! do
test do
d = Dictionary['z', 1, 'a', 2, 'c', 3]
d.reverse!
assert_equal( ['c','a','z'], d.keys )
end
end
method :collect do
test "enumerable method" do
d = Dictionary[]
d[:a] = "a"
d[:c] = "b"
r = d.collect{|k,v| v.capitalize}
r.assert == ["A","B"]
end
end
method :dup do
test "with array values" do
d = Dictionary.new
d.dup
d[:a]=['t',5]
assert_equal(d, d.dup)
end
end
method :first do
test do
d = Dictionary[]
d[:a] = "a"
d[:b] = "b"
d[:c] = "c"
d.first.assert == "a"
d.first(0).assert == []
assert_equal ["a"] , d.first(1)
assert_equal ["a", "b"] , d.first(2)
end
end
method :last do
test do
d = Dictionary[]
d[:a] = "a"
d[:b] = "b"
d[:c] = "c"
d.last.assert == "c"
d.last(0).assert == []
d.last(1).assert == ["c"]
d.last(2).assert == ["b", "c"]
end
end
method :select do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
r = d.select{ |k,v| v % 2 == 1 }
r.assert == [[:a, 1], [:c, 3]]
end
end
method :to_h do
test do
d = Dictionary[:a=>1, :b=>2]
h = d.to_h
h.assert == {:a=>1, :b=>2}
end
end
method :replace do
test do
d1 = Dictionary[:a=>1, :b=>2]
d2 = Dictionary[:c=>3, :d=>4]
d1.replace(d2)
d1.to_h.assert == {:c=>3, :d=>4}
end
end
method :reverse do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
r = d.reverse
r.first.assert == 3
end
end
method :invert do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
r = d.invert
Dictionary.assert === r
r.to_h.assert == {1=>:a, 2=>:b, 3=>:c}
end
end
method :each_key do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.order_by_key
a = []
d.each_key{ |k| a << k }
a.assert == [:a, :b, :c]
end
method :each_value do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.order_by_value
a = []
d.each_value{ |v| a << v }
a.assert == [1, 2, 3]
end
method :clear do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.clear
d.to_a.assert == []
end
method :fetch do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.fetch(:a).assert == 1
end
method :key? do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.assert.key?(:a)
d.refute.key?(:d)
end
end
method :has_key? do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.assert.has_key?(:a)
d.refute.has_key?(:d)
end
end
method :length do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.length.assert == 3
end
end
method :to_a do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.to_a.assert == [[:a,1], [:b,2], [:c,3]]
end
end
method :to_hash do
test do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.to_hash.assert == {:a=>1, :b=>2, :c=>3}
end
end
method :empty? do
test "is emtpy" do
d = Dictionary[]
d.assert.empty?
end
test 'is not emtpy' do
d = Dictionary[:a=>1, :b=>2, :c=>3]
d.refute.empty?
end
end
method :order_by_key do
test do
d = Dictionary[:b=>1, :c=>2, :a=>4]
d.order_by_key
d.order.assert == [:a, :b, :c]
end
end
method :order_by_value do
test do
d = Dictionary[:b=>1, :c=>2, :a=>4]
d.order_by_value
d.order.assert == [:b, :c, :a]
end
end
class_method :alpha do
test do
d = Dictionary.alpha
d.update(:b=>1, :c=>2, :a=>4)
d.order.assert == [:a, :b, :c]
end
end
class_method :auto do
test do
d = Dictionary.auto
s = d[:foo]
s.class.assert == Dictionary
end
end
end
@@ -0,0 +1,56 @@
require 'helper'
test_case IniHash do
class_method :new do
test do
h = IniHash.new('foo.ini', false)
end
end
method :[]= do
test do
h = IniHash.new('foo.ini', false)
h['a'] = '1'
h['a'].assert = '1'
end
end
method :[] do
test do
h = IniHash.new('foo.ini', false)
h['a'] = '1'
h['a'].assert = '1'
end
end
method :to_h do
test do
h = IniHash.new('foo.ini', false)
h['a'] = '1'
h.to_h.assert = {'a'=>'1'}
end
end
method :to_s do
test do
h = IniHash.new('foo.ini', false)
h['a'] = '1'
h.to_s.assert == "a=1\n"
end
test do
h = IniHash.new('foo.ini', false)
h['a'] = '1'
h['b'] = {'c'=>3}
h.to_s.assert == "a=1\n[b]\nc=3\n"
end
end
class_method :load do
h = IniHash.load('test/fixture/example.ini')
h['a'].assert == '1'
end
end
@@ -0,0 +1,182 @@
require 'helper'
testcase KeyHash do
class_method :[] do
test 'creates new KeyHash' do
s = KeyHash[]
KeyHash.assert === s
end
test 'pre-assigns values' do
s = KeyHash[:a=>1, :b=>2]
s[:a].assert == 1
s[:b].assert == 2
end
end
method :[] do
test 'instance level fetch' do
s = KeyHash[:a=>1, :b=>2]
s[:a].assert == 1
s[:b].assert == 2
end
test 'by default keys are converted to strings' do
s = KeyHash[:a=>1, :b=>2]
s['a'].assert == 1
s['b'].assert == 2
end
end
method :[]= do
test do
s = KeyHash.new
s[:a] = 1
s[:b] = 2
s[:a].assert == 1
s[:b].assert == 2
s['a'].assert == 1
s['b'].assert == 2
end
end
method :initialize do
test do
StandardError.refute.raised? do
s = KeyHash.new
end
end
end
method :to_hash do
test do
s = KeyHash[:a=>1, :b=>2]
s.to_hash.assert == {'a'=>1, 'b'=>2}
end
end
method :to_h do
test do
s = KeyHash[:a=>1, :b=>2]
s.to_h.assert == {'a'=>1, 'b'=>2}
end
end
method :replace do
test do
s = KeyHash.new
s.replace(:a=>1, :b=>2)
s.to_h.assert == {'a'=>1, 'b'=>2}
end
end
method :delete do
test do
s = KeyHash[:a=>1, :b=>2]
s.delete(:a)
s.to_h.assert == {'b'=>2}
end
end
method :each do
test do
s = KeyHash[:a=>1, :b=>2]
s.each do |k,v|
String.assert === k
end
end
end
method :store do
test do
s = KeyHash.new
s.store(:a, 1)
s.to_h.assert == {'a'=>1}
end
end
method :update do
test do
s1 = KeyHash[:a=>1,:b=>2]
s2 = KeyHash[:c=>3,:d=>4]
s1.update(s2)
s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4}
end
end
method :rekey do
test do
s = KeyHash[:a=>1,:b=>2,:c=>3]
x = s.rekey{ |k| k.upcase }
x.to_h.assert == {'A'=>1,'B'=>2,'C'=>3}
end
end
method :rekey! do
test do
s = KeyHash[:a=>1,:b=>2,:c=>3]
s.rekey!{ |k| k.upcase }
s.to_h.assert == {'A'=>1,'B'=>2,'C'=>3}
end
end
method :key? do
test do
s = KeyHash[:a=>1]
s.assert.key?(:a)
s.assert.key?('a')
end
end
method :has_key? do
test do
s = KeyHash[:a=>1]
s.assert.has_key?(:a)
s.assert.has_key?('a')
end
end
method :<< do
test do
s = KeyHash.new
s << [:a, 1]
s << [:b, 2]
s.to_h.assert == {'a'=>1, 'b'=>2}
end
end
method :merge! do
test do
s1 = KeyHash[:a=>1,:b=>2]
s2 = KeyHash[:c=>3,:d=>4]
s1.merge!(s2)
s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4}
end
end
method :values_at do
test do
s = KeyHash[:a=>1,:b=>2,:c=>3]
s.values_at(:a, :b).assert == [1,2]
s.values_at('a','b').assert == [1,2]
end
end
method :fetch do
test do
s = KeyHash[:a=>1,:b=>2,:c=>3]
s.fetch(:a).assert == 1
s.fetch('a').assert == 1
end
end
#method :cast_key do
# test do
# s = KeyHash.new
# s.send(:cast_key, :a).assert == 'a'
# end
#end
end
@@ -0,0 +1,167 @@
require 'helper'
test_case LinkedList do
class_method :new do
ll = LinkedList.new
LinkedList.assert === ll
end
method :to_a do
test 'empty' do
ll = LinkedList.new
ll.to_a.assert == []
end
test 'not empty' do
ll = LinkedList.new
ll.push :a
ll.to_a.assert == [:a]
end
end
method :empty? do
test do
ll = LinkedList.new
ll.assert.empty?
end
end
method :delete do
test do
ll = LinkedList.new
ll.push :a
ll.to_a.assert == [:a]
ll.delete(:a)
ll.to_a.assert == []
end
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll.push :c
ll.to_a.assert == [:a, :b, :c]
ll.delete(:b)
ll.to_a.assert == [:a, :c]
end
end
method :each do
test do
a = []
ll = LinkedList.new
ll.push :a
ll.each do |e|
a << e
end
a.assert == [:a]
end
end
method :length do
test do
ll = LinkedList.new
ll.push :a
ll.length.assert == 1
end
end
method :push do
test do
ll = LinkedList.new
ll.push :a
ll.to_a.assert == [:a]
end
end
method :unshift do
test do
ll = LinkedList.new
ll.unshift :a
ll.to_a.assert == [:a]
end
test do
ll = LinkedList.new
ll.push :a
ll.unshift :b
ll.to_a.assert == [:b, :a]
end
end
method :pop do
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll.to_a.assert == [:a, :b]
ll.pop
ll.to_a.assert == [:a]
end
end
method :shift do
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll.to_a.assert == [:a, :b]
ll.shift
ll.to_a.assert == [:b]
end
end
method :first do
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll.to_a.assert == [:a, :b]
ll.first.assert == :a
end
end
method :last do
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll.to_a.assert == [:a, :b]
ll.last.assert == :b
end
end
method :queue do
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll.queue.assert == [:a, :b]
end
end
method :[]= do
test do
ll = LinkedList.new
ll[:a] = :b
ll.to_a.assert == [:b]
ll[:a].assert == :b
end
end
method :[] do
test do
ll = LinkedList.new
ll.push :a
ll[:a].assert == :a
end
test do
ll = LinkedList.new
ll.push :a
ll.push :b
ll[:a].assert == :a
ll[:b].assert == :b
end
end
end
@@ -0,0 +1,162 @@
require 'helper'
test_case LRUHash do
class_method :new do
h = LRUHash.new(10)
LRUHash.assert === h
end
method :max_size= do
test do
h = LRUHash.new(10)
h.max_size = 100
end
end
method :store do
test do
h = LRUHash.new(10)
h.store(:a, 1)
h[:a].assert == 1
end
end
method :[] do
test do
h = LRUHash.new(10)
h.store(:a, 1)
h[:a].assert == 1
end
test do
h = LRUHash.new(10){ |h,k| h[k] = 0 }
h[:a].assert == 0
end
end
method :empty? do
test do
h = LRUHash.new(10)
h.assert.empty?
end
end
method :key do
test do
h = LRUHash.new(10)
h[:a] = 1
h.key(1).assert == :a
end
end
method :keys do
test do
h = LRUHash.new(10)
h[:a] = 1
h.keys.assert == [:a]
end
end
method :values do
test do
h = LRUHash.new(10)
h[:a] = 1
h.values.assert == [1]
end
end
method :values_at do
test do
h = LRUHash.new(10)
h[:a] = 1
h[:b] = 2
h.values_at(:a).assert == [1]
end
end
method :has_key? do
test do
h = LRUHash.new(10)
h[:a] = 1
h.assert.has_key?(:a)
end
end
method :has_value? do
test do
h = LRUHash.new(10)
h[:a] = 1
h.assert.has_value?(1)
end
end
method :assoc do
test do
h = LRUHash.new(10)
h[:a] = 1
h[:b] = 2
h.assoc(:a).assert == [:a,1]
end
end
method :rassoc do
test do
h = LRUHash.new(10)
h[:a] = 1
h[:b] = 2
h.rassoc(1).assert == [:a,1]
end
end
method :each_key do
test do
h = LRUHash.new(10)
h[:a] = 1
h[:b] = 2
h.each_key do |k|
[:a,:b].assert.include?(k)
end
end
end
method :each_value do
test do
h = LRUHash.new(10)
h[:a] = 1
h[:b] = 2
h.each_value do |v|
[1,2].assert.include?(v)
end
end
end
method :clear do
test do
h = LRUHash.new(10)
h[:a] = 1
h[:b] = 2
h.clear
h.assert.empty?
end
end
method :delete do
test do
h = LRUHash.new(10)
h[:a] = 1
h.delete(:a)
h.assert.empty?
end
end
method :delete_if do
test do
h = LRUHash.new(10)
h[:a] = 1
h.delete_if{ |k,v| k == :a }
h.assert.empty?
end
end
end
@@ -0,0 +1,122 @@
require 'helper'
testcase OpenCascade do
include AE::Legacy::Assertions
class_method :new do
OpenCascade[:a=>1]
end
class_method :[] do
test "hash" do
o = OpenCascade[:a=>1,:b=>2]
assert_equal(1, o.a)
assert_equal(2, o.b)
end
test "hash in hash" do
o = OpenCascade[:a=>1,:b=>2,:c=>{:x=>9}]
assert_equal(9, o.c.x)
end
test "hash in hash in hash" do
h = {:a=>1,:x=>{:y=>{:z=>1}}}
c = OpenCascade[h]
assert_equal(1, c.x.y.z)
end
end
method :[] do
test "basic assignment" do
o = OpenCascade.new
o[:a] = 1
assert_equal(1, o[:a])
end
end
method :[]= do
test "basic assignment with primed OpenCascade" do
o = OpenCascade[:a=>1]
o[:b] = 2
o.to_h.assert == {:a=>1,:b=>2}
end
end
method :to_a do
test do
o = OpenCascade[:a=>1,:b=>2]
a = o.to_a
a.assert.include?([:a,1])
a.assert.include?([:b,2])
a.size.assert == 2
end
end
method :to_h do
test do
o = OpenCascade[:a=>1]
assert_equal({:a=>1}, o.to_h)
end
end
method :merge! do
test do
o = OpenCascade[:f0=>"f0"]
r = OpenCascade[:f0=>"f0", :h0=>"h0"]
h = { :h0=>"h0" }
assert_equal(r, o.merge!(h))
assert_equal({:f0=>"f0", :h0=>"h0"}, h.merge(o))
end
end
method :update do
test do
o = OpenCascade[:f1=>"f1"]
o.update(:h1=>"h1")
o.assert == OpenCascade[:f1=>"f1", :h1=>"h1"]
end
end
method :method_missing do
test "writer and reader" do
o = OpenCascade.new
10.times{ |i| o.__send__("n#{i}=", 1 ) }
10.times{ |i| assert_equal(1, o.__send__("n#{i}")) }
end
end
method :<< do
test do
c = OpenCascade.new
c << [:x,8]
c << [:y,9]
assert_equal(8, c.x)
assert_equal(9, c.y)
end
end
end
testcase Hash do
method :update do
test "hash can be updated by OpenCascade" do
o = OpenCascade[:f1=>"f1"]
h = {:h1=>"h1"}
h.update(o)
h.assert == {:f1=>"f1", :h1=>"h1"}
end
end
end
testcase Array do
method :flatten do
test "array can be flattened if contains OpenCascade" do
cascade = OpenCascade[:foo=>"bar"]
array = [cascade]
array.flatten # should not raise error
end
end
end
@@ -0,0 +1,87 @@
require 'helper'
testcase OpenHash do
class_method :[] do
test do
o = OpenHash[:a=>1, :b=>2]
o.a.assert == 1
o.b.assert == 2
end
test do
o = OpenHash[:a=>1, :b=>2]
o.a.assert == 1
o.b.assert == 2
end
end
method :open? do
test do
o = OpenHash[:a=>1, :b=>2]
o.assert.open?(:foo)
o.refute.open?(:each)
end
end
method :open! do
test do
o = OpenHash[:a=>1, :b=>2]
o.open!(:each)
o.assert.open?(:each)
o.each = 10
o.each.assert == 10
end
end
method :close! do
test do
o = OpenHash[:a=>1, :b=>2]
o.open!(:each)
o.assert.open?(:each)
o.each = 10
o.each.assert == 10
o.close!(:each)
o.each.refute == 10
end
end
method :method_missing do
test 'bang method' do
o = OpenHash[]
o.open!(:each)
o.each = 10
o.each.assert == 10
a = []
o.each! do |k,v|
a << [k,v]
end
a.assert == [[:each,10]]
end
test 'query method' do
o = OpenHash[]
o.a = 1
o.assert.a?
o.refute.b?
end
end
method :send do
test do
o = OpenHash[]
o.open!(:each)
o.each = 10
o.each.assert == 10
a = []
o.send(:each) do |k,v|
a << [k,v]
end
a.assert == [[:each,10]]
end
end
end
@@ -0,0 +1,21 @@
require 'helper'
test_case PropertyHash do
class_method :new do
test do
PropertyHash.new
end
end
method :update do
test do
h = PropertyHash.new
h.property :a
h.property :b
h.update(:a=>1, :b=>2)
h.assert == {:a=>1, :b=>2}
end
end
end
@@ -0,0 +1,226 @@
require 'helper'
testcase QueryHash do
class_method :new do
test 'new QueryHash with default proc' do
q = QueryHash.new{ |h,k| h[k] = 1 }
q[:a].assert == 1
end
end
class_method :[] do
test 'creates new QueryHash' do
s = QueryHash[]
QueryHash.assert === s
end
test 'pre-assigns values' do
s = QueryHash[:a=>1, :b=>2]
s[:a].assert == 1
s[:b].assert == 2
end
end
method :[] do
test 'instance level fetch' do
s = QueryHash[:a=>1, :b=>2]
s[:a].assert == 1
s[:b].assert == 2
#s['a'].assert == 1
#s['b'].assert == 2
end
end
method :[]= do
test do
s = QueryHash.new
s[:a] = 1
s[:b] = 2
s[:a].assert == 1
s[:b].assert == 2
#s['a'].assert == 1
#s['b'].assert == 2
end
end
method :respond_to? do
test 'responds to all query methods' do
q = QueryHash.new
q.assert.respond_to?(:anything?)
end
test 'responds to all bang methods' do
q = QueryHash.new
q.assert.respond_to?(:anything!)
end
test 'responds to all setter methods' do
q = QueryHash.new
q.assert.respond_to?(:anything=)
end
test 'responds to usual methods' do
q = QueryHash.new
q.assert.respond_to?(:each)
end
end
method :to_hash do
test do
s = QueryHash[:a=>1, :b=>2]
s.to_hash.assert == {'a'=>1, 'b'=>2}
end
end
method :to_h do
test do
s = QueryHash[:a=>1, :b=>2]
s.to_h.assert == {'a'=>1, 'b'=>2}
end
end
method :replace do
test do
s = QueryHash.new
s.replace(:a=>1, :b=>2)
s.to_h.assert == {'a'=>1, 'b'=>2}
end
end
method :delete do
test do
s = QueryHash[:a=>1, :b=>2]
s.delete(:a)
s.to_h.assert == {'b'=>2}
end
end
method :each do
test do
s = QueryHash[:a=>1, :b=>2]
s.each do |k,v|
String.assert === k
end
end
end
method :store do
test do
s = QueryHash.new
s.store(:a, 1)
s.to_h.assert == {'a'=>1}
end
end
method :update do
test do
s1 = QueryHash[:a=>1,:b=>2]
s2 = QueryHash[:c=>3,:d=>4]
s1.update(s2)
s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4}
end
end
method :rekey do
test do
s = QueryHash[:a=>1,:b=>2,:c=>3]
x = s.rekey{ |k| k.upcase }
x.to_h.assert == {'A'=>1,'B'=>2,'C'=>3}
end
end
method :rekey! do
test do
s = QueryHash[:a=>1,:b=>2,:c=>3]
s.rekey!{ |k| k.upcase }
s.to_h.assert == {'A'=>1,'B'=>2,'C'=>3}
end
end
method :key? do
test do
s = QueryHash[:a=>1]
s.assert.key?(:a)
s.assert.key?('a')
end
end
method :has_key? do
test do
s = QueryHash[:a=>1]
s.assert.has_key?(:a)
s.assert.has_key?('a')
end
end
method :<< do
test do
s = QueryHash.new
s << [:a, 1]
s << [:b, 2]
s.to_h.assert == {'a'=>1, 'b'=>2}
end
end
method :merge! do
test do
s1 = QueryHash[:a=>1,:b=>2]
s2 = QueryHash[:c=>3,:d=>4]
s1.merge!(s2)
s1.to_h.assert == {'a'=>1,'b'=>2,'c'=>3,'d'=>4}
end
end
method :values_at do
test do
s = QueryHash[:a=>1,:b=>2,:c=>3]
s.values_at(:a, :b).assert == [1,2]
s.values_at('a','b').assert == [1,2]
end
end
method :fetch do
test do
s = QueryHash[:a=>1,:b=>2,:c=>3]
s.fetch(:a).assert == 1
s.fetch('a').assert == 1
end
end
method :cast_key do
test do
s = QueryHash.new
s.pry.cast_key(:a).assert == 'a'
end
end
method :method_missing do
test 'dynamic query methods can look-up values' do
q = QueryHash[:a=>1,:b=>2,:c=>3]
q.a?.assert == 1
q.b?.assert == 2
q.c?.assert == 3
end
test 'dynamic bang methods can looks up values too' do
q = QueryHash[:a=>1,:b=>2,:c=>3]
q.a!.assert == 1
q.b!.assert == 2
q.c!.assert == 3
end
test 'dynamic bang methods will auto-instantiate' do
q = QueryHash.new
q.default_proc{ |h,k| h[k] = 'default' }
q.foo!.assert == 'default'
end
test 'dynamic query methods will NOT auto-instantiate' do
q = QueryHash.new
q.default_proc{ |h,k| h[k] = 'default' }
q.foo?.assert == nil
end
end
end
@@ -0,0 +1,8 @@
# top comment
a = 1
[section]
x = 2
y = 3
+8
View File
@@ -0,0 +1,8 @@
require 'lemon'
require 'ae'
require 'ae/legacy' # bacause imitation BasicObject sucks
require 'ae/pry'
require 'hashery'
include Hashery