rusty_lru 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8262b61f3b89a8fb40f436a3820314887d1f20f91448ee14a8563d05fdc2061a
4
+ data.tar.gz: 52f666bde7eb89754683a33040b4fcb17a03b3fb85b0399f2130f7c13d8e8811
5
+ SHA512:
6
+ metadata.gz: 86da4959c095c2d7517383b001e5239576301601a4c3234885461e732f691514e15af04af06dfc2e269b211fcd47533c4bb5178ee7a830d484a52dbac0b168a6
7
+ data.tar.gz: e5515288bd26ed3ef1016adb86294bb088eb93073ed5cc92171d35605855f5c6e92cf832e44a3d17b77762292b1702de7340714e5b55afc42ecac94bb4eb9801
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+
14
+ #Added by cargo
15
+ #
16
+ #already existing elements are commented out
17
+
18
+ /target
19
+ **/*.rs.bk
20
+ Cargo.lock
21
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,20 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6.3
3
+ Exclude:
4
+ - vendor/**/*
5
+
6
+ Metrics/BlockLength:
7
+ Exclude:
8
+ - spec/**/*.rb
9
+
10
+ Metrics/LineLength:
11
+ Enabled: false
12
+
13
+ Style/FrozenStringLiteralComment:
14
+ Enabled: false
15
+
16
+ Style/SafeNavigation:
17
+ Enabled: false
18
+
19
+ Style/SymbolProc:
20
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,29 @@
1
+ language: ruby
2
+ os:
3
+ - osx
4
+ - linux
5
+ rvm:
6
+ - 2.6
7
+ - 2.5
8
+ - 2.4
9
+ env:
10
+ - RUST_VERSION=stable
11
+ - RUST_VERSION=nightly
12
+ before_install:
13
+ - gem update --system
14
+ - gem install bundler
15
+ - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST_VERSION
16
+ - source $HOME/.cargo/env
17
+ cache:
18
+ bundler: true
19
+ directories:
20
+ - $HOME/.cargo
21
+ script:
22
+ - LD_LIBRARY_PATH=$(ruby -e "print RbConfig::CONFIG['libdir']") cargo test
23
+ - bundle exec rake spec
24
+ - bundle exec rubocop
25
+ matrix:
26
+ allow_failures:
27
+ - env: RUST_VERSION=nightly
28
+ branches:
29
+ only: master
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+ ### Added
9
+ - Implement basic Hash-like functionality by using
10
+ [rutie](https://rubygems.org/gems/rutie) to wrap the
11
+ [lru](https://crates.io/crates/lru) crate.
12
+ - RSpec tests.
13
+
14
+ [Unreleased]: https://github.com/asppsa/rusty_lru/commits/master
data/Cargo.toml ADDED
@@ -0,0 +1,14 @@
1
+ [package]
2
+ name = "rusty_lru"
3
+ version = "0.1.0"
4
+ authors = ["Alastair Pharo <me@asph.dev>"]
5
+ edition = "2018"
6
+
7
+ [dependencies]
8
+ rutie = "0.6.1"
9
+ lru = "0.1.17"
10
+ lazy_static = "1.3.0"
11
+
12
+ [lib]
13
+ name = "rusty_lru"
14
+ crate-type = ["dylib"]
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rusty_lru.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/NOTICE ADDED
@@ -0,0 +1,3 @@
1
+ rusty_lru
2
+ Copyright (C) 2019 Alastair Pharo
3
+
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # RustyLRU
2
+
3
+ [![Build Status](https://travis-ci.org/asppsa/rusty_lru.svg?branch=master)](https://travis-ci.org/asppsa/rusty_lru)
4
+
5
+ This gem provides an [LRU
6
+ cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU))
7
+ for Ruby. It uses [Rutie](https://rubygems.org/gems/rutie) to wrap the
8
+ [Rust](https://rust-lang.org/)'s [lru](https://crates.io/crates/lru) crate.
9
+
10
+ From a Ruby perspective, the API is close to that of the `Hash` class. It
11
+ differs in that it will never grow beyond a capped number of key-value pairs,
12
+ which makes it suitable to use as a cache.
13
+
14
+
15
+ ## Installation
16
+
17
+ Make sure you have Rust installed. Add this line to your application's
18
+ Gemfile:
19
+
20
+ ```ruby
21
+ gem 'rusty_lru'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install rusty_lru
31
+
32
+
33
+ ## Usage
34
+
35
+ The key concept of an LRU cache is the LRU list, which is a list of all the
36
+ key-value pairs in the cache, ordered by how recently they are used. When the
37
+ cache fills to capacity, the pair at the bottom of the list will be dropped
38
+ each time a new pair is added. Accessing/updating an existing pair in the
39
+ cache moves it to the top of the list.
40
+
41
+ Comprehensive documentation is available online:
42
+
43
+ - [Bleeding edge](https://rubydoc.info/github/asppsa/rusty_lru/master)
44
+
45
+ For the impatient, here is an example of basic usage:
46
+
47
+ ~~~ ruby
48
+ # Creates a cache with a cap of 1,000 entries.
49
+ cache = RustyLRU::Cache.new(1000)
50
+
51
+ # Returns true iff the cache is empty.
52
+ cache.empty? #=> true
53
+
54
+ # Returns the number of pairs in the cache (not the cap).
55
+ cache.size #=> 0
56
+
57
+ # Adds a key-value pair to the cache.
58
+ cache['x'] = :y
59
+ cache['y'] = proc { 'anything can go here' }
60
+ cache['z'] = {"x" => 1}
61
+ cache.size #=> 3
62
+
63
+ # Overwriting a key returns the old value.
64
+ cache['x'] = :q #=> :y
65
+
66
+ # Retrieves the least recently used key-value pair without updating the LRU
67
+ # list.
68
+ cache.lru_pair #=> ['x', :q]
69
+
70
+ # Returns true iff the key exists. Does not affect the LRU list.
71
+ cache.key?('x') #=> true
72
+ cache.key?(:test) #=> false
73
+
74
+ # Retrieves a value by key, updating the LRU list.
75
+ cache['x'] #=> :q
76
+ cache.lru_pair #=> ['y', #<Proc:...>]
77
+
78
+ # Retrieves and deletes the least recently used key-value pair.
79
+ cache.pop #=> ['y', #<Proc:...>]
80
+ cache.size #=> 2
81
+ cache.lru_pair #=> ['z', {"x" => 1}]
82
+
83
+ # Retrieves a value by key without updating LRU list.
84
+ cache.peek('z') #=> {"x" => 1}
85
+ cache.lru_pair #=> ['z', {"x" => 1}]
86
+
87
+ # Deletes a key-value pair, returning the deleted value.
88
+ cache.delete('x') #=> :q
89
+ cache.size #=> 1
90
+
91
+ # Deletes all pairs from the cache
92
+ cache.clear #=> nil
93
+
94
+ # Changes the cache's cap. If necessary, elements will be deleted (in LRU
95
+ # order) to accommodate this.
96
+ cache.resize(1)
97
+ cache[:test1] = 'test1'
98
+ cache[:test2] = 'test2'
99
+ cache.size #=> 1
100
+
101
+ # includes Enumerable, so things like conversion to Array and Hash are simple:
102
+ cache.to_a
103
+ cache.to_h
104
+ cache.each_key { |k| cache[k] = 'replaced' }
105
+ ~~~
106
+
107
+
108
+ ## Development
109
+
110
+ For development, check out the repo and then run `bundle install` to install
111
+ development dependencies. `bundle exec rake test` will run RSpec and Rust
112
+ tests.
113
+
114
+ To install this gem onto your local machine, run `bundle exec rake install`. To
115
+ release a new version, update the version number in `version.rb`, and then run
116
+ `bundle exec rake release`, which will create a git tag for the version, push
117
+ git commits and tags, and push the `.gem` file to
118
+ [rubygems.org](https://rubygems.org).
119
+
120
+
121
+ ## Contributing
122
+
123
+ Bug reports and pull requests are welcome on GitHub at
124
+ https://github.com/asppsa/rusty_lru.
125
+
126
+
127
+ ## License
128
+
129
+ Licensed under the Apache License 2.0.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ load 'tasks/cargo.rake'
4
+
5
+ RSpec::Core::RakeTask.new(spec: 'cargo:build')
6
+
7
+ task test: [:spec, 'cargo:test']
8
+ task default: :test
data/ext/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ load File.join(__dir__, '../tasks/cargo.rake')
2
+ task default: 'cargo:build'
@@ -0,0 +1,220 @@
1
+ require 'forwardable'
2
+
3
+ module RustyLRU
4
+ # This class implements an
5
+ # {https://en.wikipedia.org/wiki/Cache_replacement_policies LRU Cache}.
6
+ #
7
+ # {RustyLRU::Cache} objects behave a lot like Ruby's {https://ruby-doc.org/core/Hash.html Hash}
8
+ # class, but with the important difference that internally a list is
9
+ # maintained of all keys in order from most recently used to least
10
+ # recently used (LRU).
11
+ #
12
+ # Additionally, if the cache is capped (see
13
+ # {#initialize}), then when the cache is full to capacity an operation that
14
+ # adds a new key will cause the key-value pair at the bottom of the LRU list
15
+ # (i.e. the least-recently-used pair) to be dropped from the cache. This way
16
+ # the cache never grows beyond a pre-determined size.
17
+ #
18
+ # To precent items from being dropped, some operations (notably {#[]} and
19
+ # {#[]=} move the key to the front of the list.
20
+ #
21
+ # Like {https://ruby-doc.org/core/Hash.html Hash}, this class includes the
22
+ # {https://ruby-doc.org/core/Enumerable.html Enumerable} module, so it
23
+ # responds to a number of methods not listed here, such as #map, #reduce, #to_a,
24
+ # #to_h, etc.
25
+ #
26
+ # Like a Hash, values can be any object whatsoever, and keys can be any
27
+ # object that responds to #hash and #eql? correctly.
28
+ class Cache
29
+ # @!method initialize(cap=nil)
30
+ # Initializes a new cache object.
31
+ #
32
+ # If no cap is specified, the cache will be uncapped. A cap can be added
33
+ # later using {#resize}.
34
+ #
35
+ # @example Create a LRU cache with no cap
36
+ # cache = RustyLRU::Cache.new
37
+ #
38
+ # @example Create a LRU cache with a maximum of 100 items
39
+ # cache = RustyLRU::Cache.new(100)
40
+
41
+ # @!method [](key)
42
+ # Retrieves a value from the cache by key, updating the LRU list.
43
+ #
44
+ # @example Retrieve a value
45
+ # cache["x"] #=> "y"
46
+ #
47
+ # @param key [Object] The key to look up in the cache.
48
+ # @return [Object, nil] The value corresponding to the key, or nil.
49
+
50
+ # @!method []=(key, value)
51
+ # Stores a key-value pair in the cache, updating the LRU list.
52
+ #
53
+ # The stored pair becomes the most recently used upon insertion/update.
54
+ #
55
+ # @example Store a pair
56
+ # cache[:x] = "z"
57
+ # cache[:x] = "y" #=> "z"
58
+ #
59
+ # @param key [Object] The key to store
60
+ # @param value [Object] The value to store
61
+ # @return [Object, nil] The previous value, or `nil`.
62
+
63
+ # @!method delete(key)
64
+ # Deletes the key-value pair corresponding to the given key from the
65
+ # cache.
66
+ #
67
+ # @example Store and delete a pair
68
+ # cache["key"] = :value
69
+ # cache.delete("key") #=> :value
70
+ #
71
+ # @param key [Object] The key to delete
72
+ # @return [Object, nil] The corresponding value, or nil
73
+
74
+ # @!method pop()
75
+ # Deletes and returns the least-recently used key-value pair.
76
+ #
77
+ # @example Create a cache with two elements, pop the first one.
78
+ # cache = RustyLRU::cache.new
79
+ # cache[:a] = 1
80
+ # cache[:b] = 2
81
+ # cache.pop #=> [:a, 1]
82
+ #
83
+ # @return [<(Object, Object)>, nil] The least-recently-used pair, or nil.
84
+
85
+ # @!method peek(key)
86
+ # Retrieves a value from the cache by key without updating the LRU list.
87
+ #
88
+ # This method is equivalent to {#[]}, except that the LRU list is not
89
+ # affected.
90
+ #
91
+ # @example Peek into the cache
92
+ # cache[:x] = "y"
93
+ # cache.peek(:x) #=> "y"
94
+ #
95
+ # @param key [Object] The key too look up
96
+ # @return [Object, nil] The corresponding value, or nil
97
+
98
+ # @!method lru_pair()
99
+ # Returns the least-recently used pair without affecting the LRU list.
100
+ #
101
+ # This method is similar to {#pop}, but does not mutate the cache.
102
+ #
103
+ # @example Conditionally remove the LRU pair
104
+ # cache.pop if cache.peek_lru == [:x, :y]
105
+ #
106
+ # @return [<(Object, Object)>, nil] The least-recently-used pair, or nil.
107
+
108
+ # @!method empty?()
109
+ # Returns true iff the cache is empty.
110
+ #
111
+ # @return [Boolean]
112
+
113
+ # @!method has_key?(key)
114
+ # Returns true iff the given key is present. Does not affect the LRU list.
115
+ #
116
+ # @param key [Object] The key to test for.
117
+ # @return [Boolean]
118
+
119
+ # @!method length()
120
+ # Returns the number of key-value pairs stored in the cache.
121
+ #
122
+ # @return [Integer]
123
+
124
+ # @!method resize(cap)
125
+ # Alters the store's cap.
126
+ #
127
+ # @param cap [Integer] The new cap.
128
+ # @return [nil]
129
+
130
+ # @!method clear()
131
+ # Removes all key-value pairs from the cache.
132
+ #
133
+ # @return [nil]
134
+
135
+ # @!method each_pair()
136
+ # @overload each_pair()
137
+ # Yields each key-value pair in the cache to the caller.
138
+ #
139
+ # @example {#each_pair} with a block
140
+ # cache.each_pair do |key, value|
141
+ # puts "#{key} = #{value}"
142
+ # end
143
+ #
144
+ # @yieldparam key [Object] each key
145
+ # @yieldparam value [Object] each value
146
+ # @return [self]
147
+ #
148
+ # @overload each_pair()
149
+ # Returns an {https://ruby-doc.org/core/Enumerator.html Enumerator}
150
+ # that will enumerate all key-value pairs.
151
+ #
152
+ # @example Get a hash of values to keys
153
+ # cache.each_pair.map { |key, value| [value, key }.to_h
154
+ #
155
+ # @return [Enumerator]
156
+
157
+ # @!method each_key()
158
+ # @overload each_key()
159
+ # Yields each key in the cache to the caller.
160
+ #
161
+ # @yieldparam key [Object] each key
162
+ # @return [self]
163
+ #
164
+ # @overload each_key()
165
+ # Returns an {https://ruby-doc.org/core/Enumerator.html Enumerator}
166
+ # that will enumerate the keys in the cache.
167
+ #
168
+ # @return [Enumerator]
169
+
170
+ # @!method each_value()
171
+ # @overload each_value()
172
+ # Yields each value in the cache to the caller.
173
+ #
174
+ # @yieldparam value [Object] each value
175
+ # @return [self]
176
+ #
177
+ # @overload each_value()
178
+ # Returns an {https://ruby-doc.org/core/Enumerator.html Enumerator} for
179
+ # enumerating the values in the cache.
180
+ #
181
+ # @return [Enumerator]
182
+
183
+ # @api private
184
+ module EnumHelpers
185
+ def each_pair
186
+ block_given? ? super : enum_for(:each_pair) { size }
187
+ end
188
+
189
+ def each_key
190
+ block_given? ? super : enum_for(:each_key) { size }
191
+ end
192
+
193
+ def each_value
194
+ block_given? ? super : enum_for(:each_value) { size }
195
+ end
196
+ end
197
+
198
+ prepend EnumHelpers
199
+ extend Forwardable
200
+ include Enumerable
201
+
202
+ # @!method keys()
203
+ # Returns all keys in the cache as an array
204
+ #
205
+ # @return [Array<Object>] All keys
206
+ def_delegator :each_key, :to_a, :keys
207
+
208
+ # @!method values()
209
+ # Returns all values in the cache as an array
210
+ #
211
+ # @return [Array<Object>] All values
212
+ def_delegator :each_value, :to_a, :values
213
+
214
+ alias store []=
215
+ alias key? has_key?
216
+ alias member? has_key?
217
+ alias size length
218
+ alias each each_pair
219
+ end
220
+ end
@@ -0,0 +1,3 @@
1
+ module RustyLRU
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/rusty_lru.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'rusty_lru/version'
2
+ require 'rutie'
3
+
4
+ # This is the container module for the {RustyLRU::Cache} class.
5
+ module RustyLRU
6
+ # Loads Rust code
7
+ Rutie.new(:rusty_lru).init 'Init_rusty_lru', __dir__
8
+
9
+ # Now load the additional methods
10
+ require_relative './rusty_lru/cache.rb'
11
+ end
data/rusty_lru.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rusty_lru/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rusty_lru'
7
+ spec.version = RustyLRU::VERSION
8
+ spec.authors = ['Alastair Pharo']
9
+ spec.email = ['me@asph.dev']
10
+
11
+ spec.summary = 'An LRU cache implemented in Rust'
12
+ spec.description = 'This gem wraps a the lru crate from Rustland, providing a Hash-like interface'
13
+ spec.homepage = 'https://github.com/asppsa/rusty_lru'
14
+ spec.license = 'Apache-2.0'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/asppsa/rusty_lru.git'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/asppsa/rusty_lru/blob/master/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+ spec.extensions << 'ext/Rakefile'
29
+
30
+ spec.add_runtime_dependency 'rutie', '~> 0.0.3'
31
+
32
+ spec.add_development_dependency 'bundler', '~> 2.0'
33
+ spec.add_development_dependency 'irb', '~> 1.0.0'
34
+ spec.add_development_dependency 'rake', '~> 10.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.0'
36
+ spec.add_development_dependency 'rubocop', '~> 0.74.0'
37
+ end
data/src/lib.rs ADDED
@@ -0,0 +1,335 @@
1
+ #[macro_use] extern crate rutie;
2
+ #[macro_use] extern crate lazy_static;
3
+
4
+ use lru::LruCache;
5
+ use std::ops::{Deref, DerefMut};
6
+ use rutie::{AnyObject, Class, Module, Array, Integer, Boolean, AnyException, GC, NilClass, Object, VM};
7
+ use rutie::types::Value;
8
+
9
+ pub struct HashableObject {
10
+ value: Value,
11
+ hash: i64
12
+ }
13
+
14
+ impl HashableObject {
15
+ fn get_hash<T: Object>(object: &T) -> i64 {
16
+ object.send("hash", &[])
17
+ .try_convert_to::<Integer>()
18
+ .map_err(|e| VM::raise_ex(e))
19
+ .unwrap()
20
+ .to_i64()
21
+ }
22
+ }
23
+
24
+ impl<T: Object> From<&T> for HashableObject {
25
+ fn from(object: &T) -> Self {
26
+ Self {
27
+ hash: Self::get_hash(object),
28
+ value: object.value()
29
+ }
30
+ }
31
+ }
32
+
33
+ impl From<Value> for HashableObject {
34
+ fn from(value: Value) -> Self {
35
+ Self::from(&AnyObject::from(value))
36
+ }
37
+ }
38
+
39
+ impl Object for HashableObject {
40
+ #[inline]
41
+ fn value(&self) -> Value {
42
+ self.value
43
+ }
44
+ }
45
+
46
+ impl PartialEq for HashableObject {
47
+ fn eq(&self, other: &Self) -> bool {
48
+ self.is_eql(other)
49
+ }
50
+ }
51
+
52
+ impl Eq for HashableObject {}
53
+
54
+ impl std::hash::Hash for HashableObject {
55
+ // Use the oject's hash if it has one; otherwise, fall back to object ID
56
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
57
+ self.hash.hash(state);
58
+ }
59
+ }
60
+
61
+ pub struct ObjectLruCache {
62
+ inner: LruCache<HashableObject, AnyObject>,
63
+ }
64
+
65
+ impl ObjectLruCache {
66
+ fn new(cap: usize) -> Self {
67
+ ObjectLruCache { inner: LruCache::new(cap) }
68
+ }
69
+
70
+ fn unbounded() -> Self {
71
+ ObjectLruCache { inner: LruCache::unbounded() }
72
+ }
73
+
74
+ fn get(&mut self, key: &AnyObject) -> Option<&AnyObject> {
75
+ self.inner.get(&HashableObject::from(key))
76
+ }
77
+
78
+ fn put(&mut self, key: AnyObject, value: AnyObject) -> Option<AnyObject> {
79
+ self.inner.put(HashableObject::from(&key), value)
80
+ }
81
+
82
+ fn pop(&mut self, key: &AnyObject) -> Option<AnyObject> {
83
+ self.inner.pop(&HashableObject::from(key))
84
+ }
85
+
86
+ fn peek(&self, key: &AnyObject) -> Option<&AnyObject> {
87
+ self.inner.peek(&HashableObject::from(key))
88
+ }
89
+
90
+ fn contains(&self, key: &AnyObject) -> bool {
91
+ self.inner.contains(&HashableObject::from(key))
92
+ }
93
+ }
94
+
95
+ impl Deref for ObjectLruCache {
96
+ type Target = LruCache<HashableObject, AnyObject>;
97
+
98
+ fn deref(&self) -> &LruCache<HashableObject, AnyObject> {
99
+ &self.inner
100
+ }
101
+ }
102
+
103
+ impl DerefMut for ObjectLruCache {
104
+ fn deref_mut(&mut self) -> &mut LruCache<HashableObject, AnyObject> {
105
+ &mut self.inner
106
+ }
107
+ }
108
+
109
+ wrappable_struct! {
110
+ ObjectLruCache,
111
+ ObjectLruCacheWrapper,
112
+ OBJECT_LRU_CACHE_WRAPPER,
113
+
114
+ // Mark each `AnyObject` element of the `inner` cache to prevent garbage collection.
115
+ // `data` is a mutable reference to the wrapped data (`&mut ObjectLruCache`).
116
+ mark(cache) {
117
+ for (key, value) in &cache.inner {
118
+ GC::mark(key);
119
+ GC::mark(value);
120
+ }
121
+ }
122
+ }
123
+
124
+ class!(Cache);
125
+
126
+ impl Cache {
127
+ fn unwrap_or_raise<T>(result: Result<T, AnyException>) -> T {
128
+ result
129
+ .map_err(|e| VM::raise_ex(e))
130
+ .unwrap()
131
+ }
132
+
133
+ fn cache(&self) -> &ObjectLruCache {
134
+ self.get_data(&*OBJECT_LRU_CACHE_WRAPPER)
135
+ }
136
+
137
+ fn cache_mut(&mut self) -> &mut ObjectLruCache {
138
+ self.get_data_mut(&*OBJECT_LRU_CACHE_WRAPPER)
139
+ }
140
+
141
+ fn array_from_pair(pair: (&HashableObject, &AnyObject)) -> Array {
142
+ let mut array = Array::with_capacity(2);
143
+ array.push(pair.0.to_any_object());
144
+ array.push(pair.1.to_any_object());
145
+ array
146
+ }
147
+ }
148
+
149
+ methods! {
150
+ Cache,
151
+ itself,
152
+
153
+ fn new(cap_result: Integer) -> AnyObject {
154
+ let cache = match cap_result {
155
+ Ok(number) => ObjectLruCache::new(number.to_u64() as usize),
156
+ Err(_) => ObjectLruCache::unbounded()
157
+ };
158
+
159
+ Class::from_existing("RustyLRU")
160
+ .get_nested_class("Cache")
161
+ .wrap_data(cache, &*OBJECT_LRU_CACHE_WRAPPER)
162
+ }
163
+
164
+ fn load(key: AnyObject) -> AnyObject {
165
+ match itself.cache_mut().get(&Cache::unwrap_or_raise(key)) {
166
+ Some(value) => value.to_any_object(),
167
+ None => NilClass::new().to_any_object()
168
+ }
169
+ }
170
+
171
+ fn store(key: AnyObject, value: AnyObject) -> AnyObject {
172
+ match itself.cache_mut().put(Cache::unwrap_or_raise(key), Cache::unwrap_or_raise(value)) {
173
+ Some(value) => value.to_any_object(),
174
+ None => NilClass::new().to_any_object()
175
+ }
176
+ }
177
+
178
+ fn delete(key: AnyObject) -> AnyObject {
179
+ match itself.cache_mut().pop(&Cache::unwrap_or_raise(key)) {
180
+ Some(value) => value,
181
+ None => NilClass::new().to_any_object()
182
+ }
183
+ }
184
+
185
+ fn pop() -> AnyObject {
186
+ match itself.cache_mut().pop_lru() {
187
+ Some(pair) => Cache::array_from_pair((&pair.0, &pair.1)).to_any_object(),
188
+ None => NilClass::new().to_any_object()
189
+ }
190
+ }
191
+
192
+ fn peek(key: AnyObject) -> AnyObject {
193
+ match itself.cache().peek(&Cache::unwrap_or_raise(key)) {
194
+ Some(value) => value.to_any_object(),
195
+ None => NilClass::new().to_any_object()
196
+ }
197
+ }
198
+
199
+ fn lru_pair() -> AnyObject {
200
+ match itself.cache().peek_lru() {
201
+ Some(pair) => Cache::array_from_pair(pair).to_any_object(),
202
+ None => NilClass::new().to_any_object()
203
+ }
204
+ }
205
+
206
+ fn has_key(key: AnyObject) -> Boolean {
207
+ let contained = itself.cache().contains(&Cache::unwrap_or_raise(key));
208
+ Boolean::new(contained)
209
+ }
210
+
211
+ fn is_empty() -> Boolean {
212
+ let empty = itself.cache().is_empty();
213
+ Boolean::new(empty)
214
+ }
215
+
216
+ fn length() -> Integer {
217
+ let len = itself.cache().len();
218
+ Integer::new(len as i64)
219
+ }
220
+
221
+ fn resize(cap: Integer) -> NilClass {
222
+ itself.cache_mut().resize(Cache::unwrap_or_raise(cap).to_u64() as usize);
223
+ NilClass::new()
224
+ }
225
+
226
+ fn clear() -> NilClass {
227
+ itself.cache_mut().clear();
228
+ NilClass::new()
229
+ }
230
+
231
+ fn each_pair() -> Cache {
232
+ for pair in &itself.cache().inner {
233
+ VM::yield_object(Cache::array_from_pair(pair));
234
+ }
235
+ itself
236
+ }
237
+
238
+ fn each_key() -> Cache {
239
+ for (key, _) in &itself.cache().inner {
240
+ VM::yield_object(key.to_any_object());
241
+ }
242
+ itself
243
+ }
244
+
245
+ fn each_value() -> Cache {
246
+ for (_, value) in &itself.cache().inner {
247
+ VM::yield_object(value.to_any_object());
248
+ }
249
+ itself
250
+ }
251
+ }
252
+
253
+ #[allow(non_snake_case)]
254
+ #[no_mangle]
255
+ pub extern "C" fn Init_rusty_lru() {
256
+ let parent = Class::from_existing("Object");
257
+ Module::from_existing("RustyLRU")
258
+ .define_nested_class("Cache", Some(&parent))
259
+ .define(|itself| {
260
+ itself.def_self("new", new);
261
+
262
+ itself.def("[]", load);
263
+ itself.def("[]=", store);
264
+ itself.def("delete", delete);
265
+ itself.def("pop", pop);
266
+ itself.def("peek", peek);
267
+ itself.def("lru_pair", lru_pair);
268
+ itself.def("empty?", is_empty);
269
+ itself.def("has_key?", has_key);
270
+ itself.def("length", length);
271
+ itself.def("resize", resize);
272
+ itself.def("clear", clear);
273
+ itself.def("each_pair", each_pair);
274
+ itself.def("each_key", each_key);
275
+ itself.def("each_value", each_value);
276
+ });
277
+ }
278
+
279
+ #[cfg(test)]
280
+ mod tests {
281
+ use super::*;
282
+ use std::collections::hash_map::DefaultHasher;
283
+ use std::hash::Hash;
284
+ use std::hash::Hasher;
285
+ use rutie::RString;
286
+
287
+ #[test]
288
+ fn test() {
289
+ VM::init();
290
+
291
+ /*
292
+ * Test HashableObject hashes as intended
293
+ */
294
+ let nil = NilClass::new();
295
+ let ho = HashableObject::from(&nil);
296
+
297
+ let mut hasher1 = DefaultHasher::new();
298
+ let mut hasher2 = DefaultHasher::new();
299
+
300
+ nil.send("hash", &[]).try_convert_to::<Integer>().unwrap().to_i64().hash(&mut hasher1);
301
+ ho.hash(&mut hasher2);
302
+
303
+ let hash1 = hasher1.finish();
304
+ let hash2 = hasher2.finish();
305
+ assert_eq!(hash1, hash2);
306
+
307
+ /*
308
+ * Create a cache and make sure it works
309
+ */
310
+ let mut cache = ObjectLruCache::new(2);
311
+ let key1 = RString::new_utf8("key1").to_any_object();
312
+ let key2 = RString::new_utf8("key2").to_any_object();
313
+
314
+ let val1 = RString::new_utf8("val1").to_any_object();
315
+ let val2 = RString::new_utf8("val2").to_any_object();
316
+
317
+ // We use to_any_object() to clone the reference to the ruby object, so that it can be put into
318
+ // the cache
319
+ assert_eq!(cache.put(key1.to_any_object(), val1.to_any_object()), None);
320
+ // Returns the old value if there was one
321
+ assert_eq!(cache.put(key1.to_any_object(), val2.to_any_object()), Some(val1));
322
+ assert_eq!(cache.contains(&key1), true);
323
+
324
+ // These have different caching behaviours, but should all return the same
325
+ assert_eq!(cache.get(&key2), None);
326
+ assert_eq!(cache.peek(&key2), None);
327
+ assert_eq!(cache.pop(&key2), None);
328
+
329
+ assert_eq!(cache.get(&key1), Some(&val2));
330
+ assert_eq!(cache.peek(&key1), Some(&val2));
331
+ assert_eq!(cache.pop(&key1), Some(val2));
332
+ assert_eq!(cache.pop(&key1), None);
333
+ assert_eq!(cache.peek(&key1), None);
334
+ }
335
+ }
data/tasks/cargo.rake ADDED
@@ -0,0 +1,9 @@
1
+ namespace :cargo do
2
+ task :build do
3
+ sh 'cargo build --release'
4
+ end
5
+
6
+ task :test do
7
+ sh 'cargo test'
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rusty_lru
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alastair Pharo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rutie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: irb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.74.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.74.0
97
+ description: This gem wraps a the lru crate from Rustland, providing a Hash-like interface
98
+ email:
99
+ - me@asph.dev
100
+ executables: []
101
+ extensions:
102
+ - ext/Rakefile
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".rubocop.yml"
108
+ - ".travis.yml"
109
+ - CHANGELOG.md
110
+ - Cargo.toml
111
+ - Gemfile
112
+ - LICENSE
113
+ - NOTICE
114
+ - README.md
115
+ - Rakefile
116
+ - ext/Rakefile
117
+ - lib/rusty_lru.rb
118
+ - lib/rusty_lru/cache.rb
119
+ - lib/rusty_lru/version.rb
120
+ - rusty_lru.gemspec
121
+ - src/lib.rs
122
+ - tasks/cargo.rake
123
+ homepage: https://github.com/asppsa/rusty_lru
124
+ licenses:
125
+ - Apache-2.0
126
+ metadata:
127
+ homepage_uri: https://github.com/asppsa/rusty_lru
128
+ source_code_uri: https://github.com/asppsa/rusty_lru.git
129
+ changelog_uri: https://github.com/asppsa/rusty_lru/blob/master/CHANGELOG.md
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 3.0.4
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: An LRU cache implemented in Rust
149
+ test_files: []