facets 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY.rdoc +135 -294
- data/MANIFEST +40 -91
- data/NOTES +1 -1
- data/README.rdoc +10 -8
- data/Rakefile +11 -34
- data/demo/{hook.rd → hook.rdoc} +2 -0
- data/demo/{scenario_require.rd → scenario_require.rdoc} +3 -0
- data/lib/core/facets-live.rb +7 -5
- data/lib/core/facets.rb +379 -359
- data/lib/core/facets/array/conjoin.rb +2 -2
- data/lib/core/facets/array/pad.rb +1 -1
- data/lib/core/facets/array/recursively.rb +2 -2
- data/lib/core/facets/array/splice.rb +1 -1
- data/lib/core/facets/binding/caller.rb +2 -4
- data/lib/core/facets/comparable/comparable.rb +2 -2
- data/lib/core/facets/dir/ascend.rb +3 -0
- data/lib/core/facets/dir/recurse.rb +4 -0
- data/lib/core/facets/duplicable.rb +6 -8
- data/lib/core/facets/enumerable/count.rb +22 -13
- data/lib/core/facets/enumerable/map_detect.rb +28 -0
- data/lib/core/facets/enumerable/mash.rb +13 -5
- data/lib/core/facets/enumerable/per.rb +3 -1
- data/lib/core/facets/hash/count.rb +14 -0
- data/lib/core/facets/hash/data.rb +14 -0
- data/lib/core/facets/kernel/__method__.rb +1 -1
- data/lib/core/facets/kernel/d.rb +9 -8
- data/lib/core/facets/kernel/eigenclass.rb +20 -0
- data/lib/core/facets/kernel/extend.rb +10 -0
- data/lib/core/facets/kernel/instance_class.rb +1 -0
- data/lib/core/facets/kernel/instance_variables.rb +6 -6
- data/lib/core/facets/kernel/meta_alias.rb +18 -0
- data/lib/core/facets/kernel/meta_class.rb +17 -0
- data/lib/core/facets/kernel/meta_def.rb +18 -0
- data/lib/core/facets/kernel/meta_eval.rb +18 -0
- data/lib/core/facets/kernel/object_hexid.rb +21 -6
- data/lib/core/facets/kernel/object_state.rb +4 -2
- data/lib/core/facets/kernel/populate.rb +3 -1
- data/lib/core/facets/kernel/with.rb +1 -1
- data/lib/core/facets/metaid.rb +6 -93
- data/lib/core/facets/module/class_def.rb +2 -0
- data/lib/core/facets/module/extend.rb +10 -11
- data/lib/core/facets/module/is.rb +5 -5
- data/lib/core/facets/module/module_def.rb +31 -0
- data/lib/core/facets/string/camelcase.rb +14 -12
- data/lib/core/facets/string/cleanlines.rb +35 -0
- data/lib/core/facets/string/edit_distance.rb +62 -0
- data/lib/core/facets/string/indent.rb +86 -4
- data/lib/core/facets/string/index_all.rb +24 -0
- data/lib/core/facets/string/lines.rb +3 -6
- data/lib/core/facets/string/margin.rb +2 -1
- data/lib/core/facets/string/newlines.rb +35 -0
- data/lib/core/facets/string/op_div.rb +14 -0
- data/lib/core/facets/string/range.rb +2 -22
- data/lib/core/facets/string/range_all.rb +1 -0
- data/lib/core/facets/string/range_of_line.rb +1 -0
- data/lib/core/facets/string/similarity.rb +92 -0
- data/lib/core/facets/string/start_with.rb +6 -6
- data/lib/core/facets/string/titlecase.rb +1 -1
- data/lib/more/facets/basicobject.rb +16 -15
- data/lib/more/facets/blankslate.rb +8 -0
- data/lib/more/facets/class_extend.rb +126 -1
- data/lib/more/facets/continuation.rb +53 -54
- data/lib/more/facets/dictionary.rb +9 -63
- data/lib/more/facets/erb.rb +63 -0
- data/lib/more/facets/filelist.rb +5 -5
- data/lib/more/facets/hashbuilder.rb +101 -0
- data/lib/more/facets/inheritor.rb +36 -45
- data/lib/more/facets/ini.rb +267 -0
- data/lib/more/facets/instance_eval.rb +4 -4
- data/lib/more/facets/ioredirect.rb +7 -60
- data/lib/more/facets/linkedlist.rb +195 -0
- data/lib/more/facets/matcher.rb +140 -0
- data/lib/more/facets/memoizer.rb +64 -0
- data/lib/more/facets/methodspace.rb +9 -4
- data/lib/more/facets/module/class_extend.rb +2 -121
- data/lib/more/facets/ostruct.rb +9 -9
- data/lib/more/facets/pathlist.rb +1 -9
- data/lib/more/facets/pathname.rb +11 -4
- data/lib/more/facets/plugin_manager.rb +50 -0
- data/lib/more/facets/random.rb +25 -3
- data/lib/more/facets/roman.rb +174 -0
- data/lib/more/facets/semaphore.rb +92 -0
- data/lib/more/facets/shellwords.rb +21 -48
- data/lib/more/facets/succ.rb +1 -1
- data/meta/{modified → released} +0 -0
- data/meta/repository +1 -0
- data/meta/suite +1 -0
- data/meta/version +1 -1
- data/script/conflicts +63 -0
- data/script/methods +49 -0
- data/test/core/binding/test_caller.rb +11 -4
- data/test/core/enumerable/test_count.rb +19 -10
- data/test/core/enumerable/test_map_detect.rb +75 -0
- data/test/core/enumerable/test_take.rb +1 -1
- data/test/core/kernel/test_object_hexid.rb +2 -1
- data/test/core/proc/test_to_method.rb +1 -1
- data/test/core/string/test_cleanlines.rb +11 -0
- data/test/core/string/test_indent.rb +66 -4
- data/test/core/string/test_lines.rb +2 -1
- data/test/core/string/test_newlines.rb +13 -0
- data/test/core/time/test_change.rb +1 -1
- data/test/core/time/test_stamp.rb +4 -7
- data/test/core/unboundmethod/test_name.rb +1 -1
- data/test/more/test_basicobject.rb +1 -20
- data/test/more/test_class_extend.rb +7 -0
- data/test/more/test_continuation.rb +8 -6
- data/test/more/test_inheritor.rb +12 -6
- data/test/more/test_random.rb +19 -10
- data/test/more/test_shellwords.rb +33 -0
- metadata +60 -31
- data/TODO +0 -5
- data/doc/README.core +0 -102
- data/doc/README.more +0 -61
- data/doc/manual/about.rb +0 -47
- data/doc/manual/annotations.rdoc +0 -60
- data/doc/manual/associations.rdoc +0 -55
- data/doc/manual/blockups.rdoc +0 -101
- data/doc/manual/capsule.rdoc +0 -34
- data/doc/manual/command.rdoc +0 -177
- data/doc/manual/core.rdoc +0 -37
- data/doc/manual/faq.rdoc +0 -32
- data/doc/manual/typecast.html +0 -112
- data/lib/more/facets/capsule.rb +0 -258
- data/lib/more/facets/coroutine.rb +0 -159
- data/lib/more/facets/enumerablepass.rb +0 -3
- data/lib/more/facets/fileable.rb +0 -162
- data/lib/more/facets/progressbar.rb +0 -253
- data/lib/more/facets/recorder.rb +0 -108
- data/meta/releases +0 -14
- data/test/more/test_coroutine.rb +0 -46
@@ -0,0 +1,267 @@
|
|
1
|
+
# ini.rb - read and write ini files
|
2
|
+
#
|
3
|
+
# Copyright (C) 2007 Jeena Paradies
|
4
|
+
# License: GPL
|
5
|
+
# Author: Jeena Paradies (info@jeenaparadies.net)
|
6
|
+
#
|
7
|
+
# This file provides a read-wite handling for ini files.
|
8
|
+
# The data of a ini file is represented by a object which
|
9
|
+
# is populated with strings.
|
10
|
+
|
11
|
+
# Class with methods to read from and write into ini files.
|
12
|
+
#
|
13
|
+
# A ini file is a text file in a specific format,
|
14
|
+
# it may include several fields which are sparated by
|
15
|
+
# field headlines which are enclosured by "[]".
|
16
|
+
# Each field may include several key-value pairs.
|
17
|
+
#
|
18
|
+
# Each key-value pair is represented by one line and
|
19
|
+
# the value is sparated from the key by a "=".
|
20
|
+
#
|
21
|
+
# == Examples
|
22
|
+
#
|
23
|
+
# === Example ini file
|
24
|
+
#
|
25
|
+
# # this is the first comment which will be saved in the comment attribute
|
26
|
+
# mail=info@example.com
|
27
|
+
# domain=example.com # this is a comment which will not be saved
|
28
|
+
# [database]
|
29
|
+
# db=example
|
30
|
+
# user=john
|
31
|
+
# passwd=very-secure
|
32
|
+
# host=localhost
|
33
|
+
# # this is another comment
|
34
|
+
# [filepaths]
|
35
|
+
# tmp=/tmp/example
|
36
|
+
# lib=/home/john/projects/example/lib
|
37
|
+
# htdocs=/home/john/projects/example/htdocs
|
38
|
+
# [ texts ]
|
39
|
+
# wellcome=Wellcome on my new website!
|
40
|
+
# Website description = This is only a example. # and another comment
|
41
|
+
#
|
42
|
+
# === Example object
|
43
|
+
#
|
44
|
+
# A Ini#comment stores:
|
45
|
+
# "this is the first comment which will be saved in the comment attribute"
|
46
|
+
#
|
47
|
+
# A Ini object stores:
|
48
|
+
#
|
49
|
+
# {
|
50
|
+
# "mail" => "info@example.com",
|
51
|
+
# "domain" => "example.com",
|
52
|
+
# "database" => {
|
53
|
+
# "db" => "example",
|
54
|
+
# "user" => "john",
|
55
|
+
# "passwd" => "very-secure",
|
56
|
+
# "host" => "localhost"
|
57
|
+
# },
|
58
|
+
# "filepaths" => {
|
59
|
+
# "tmp" => "/tmp/example",
|
60
|
+
# "lib" => "/home/john/projects/example/lib",
|
61
|
+
# "htdocs" => "/home/john/projects/example/htdocs"
|
62
|
+
# }
|
63
|
+
# "texts" => {
|
64
|
+
# "wellcome" => "Wellcome on my new website!",
|
65
|
+
# "Website description" => "This is only a example."
|
66
|
+
# }
|
67
|
+
# }
|
68
|
+
#
|
69
|
+
# As you can see this module gets rid of all comments, linebreaks
|
70
|
+
# and unnecessary spaces at the beginning and the end of each
|
71
|
+
# field headline, key or value.
|
72
|
+
#
|
73
|
+
# === Using the object
|
74
|
+
#
|
75
|
+
# Using the object is stright forward:
|
76
|
+
#
|
77
|
+
# ini = Ini.new("path/settings.ini")
|
78
|
+
# ini["mail"] = "info@example.com"
|
79
|
+
# ini["filepaths"] = { "tmp" => "/tmp/example" }
|
80
|
+
# ini.comment = "This is\na comment"
|
81
|
+
# puts ini["filepaths"]["tmp"]
|
82
|
+
# # => /tmp/example
|
83
|
+
# ini.write()
|
84
|
+
#
|
85
|
+
|
86
|
+
class Ini
|
87
|
+
|
88
|
+
|
89
|
+
#
|
90
|
+
# :inihash is a hash which holds all ini data
|
91
|
+
# :comment is a string which holds the comments on the top of the file
|
92
|
+
#
|
93
|
+
attr_accessor :inihash, :comment
|
94
|
+
|
95
|
+
#
|
96
|
+
# Creating a new Ini object
|
97
|
+
#
|
98
|
+
# +path+ is a path to the ini file
|
99
|
+
# +load+ if nil restores the data if possible
|
100
|
+
# if true restores the data, if not possible raises an error
|
101
|
+
# if false does not resotre the data
|
102
|
+
#
|
103
|
+
def initialize(path, load=nil)
|
104
|
+
@path = path
|
105
|
+
@inihash = {}
|
106
|
+
|
107
|
+
if load or ( load.nil? and FileTest.readable_real? @path )
|
108
|
+
restore()
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Retrive the ini data for the key +key+
|
114
|
+
#
|
115
|
+
def [](key)
|
116
|
+
@inihash[key]
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Set the ini data for the key +key+
|
121
|
+
#
|
122
|
+
def []=(key, value)
|
123
|
+
raise TypeError, "String expected" unless key.is_a? String
|
124
|
+
raise TypeError, "String or Hash expected" unless value.is_a? String or value.is_a? Hash
|
125
|
+
|
126
|
+
@inihash[key] = value
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# Restores the data from file into the object
|
131
|
+
#
|
132
|
+
def restore()
|
133
|
+
@inihash = Ini.read_from_file(@path)
|
134
|
+
@comment = Ini.read_comment_from_file(@path)
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Store data from the object in the file
|
139
|
+
#
|
140
|
+
def update()
|
141
|
+
Ini.write_to_file(@path, @inihash, @comment)
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
def to_h
|
146
|
+
@inihash.dup
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Reading data from file
|
151
|
+
#
|
152
|
+
# +path+ is a path to the ini file
|
153
|
+
#
|
154
|
+
# returns a hash which represents the data from the file
|
155
|
+
#
|
156
|
+
def Ini.read_from_file(path)
|
157
|
+
|
158
|
+
inihash = {}
|
159
|
+
headline = nil
|
160
|
+
|
161
|
+
IO.foreach(path) do |line|
|
162
|
+
|
163
|
+
line = line.strip.split(/#/)[0]
|
164
|
+
|
165
|
+
# read it only if the line doesn't begin with a "=" and is long enough
|
166
|
+
unless line.length < 2 and line[0,1] == "="
|
167
|
+
|
168
|
+
# it's a headline if the line begins with a "[" and ends with a "]"
|
169
|
+
if line[0,1] == "[" and line[line.length - 1, line.length] == "]"
|
170
|
+
|
171
|
+
# get rid of the [] and unnecessary spaces
|
172
|
+
headline = line[1, line.length - 2 ].strip
|
173
|
+
inihash[headline] = {}
|
174
|
+
else
|
175
|
+
|
176
|
+
key, value = line.split(/=/, 2)
|
177
|
+
|
178
|
+
key = key.strip unless key.nil?
|
179
|
+
value = value.strip unless value.nil?
|
180
|
+
|
181
|
+
unless headline.nil?
|
182
|
+
inihash[headline][key] = value
|
183
|
+
else
|
184
|
+
inihash[key] = value unless key.nil?
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
inihash
|
191
|
+
end
|
192
|
+
|
193
|
+
#
|
194
|
+
# Reading comments from file
|
195
|
+
#
|
196
|
+
# +path+ is a path to the ini file
|
197
|
+
#
|
198
|
+
# Returns a string with comments from the beginning of the
|
199
|
+
# ini file.
|
200
|
+
#
|
201
|
+
def Ini.read_comment_from_file(path)
|
202
|
+
comment = ""
|
203
|
+
|
204
|
+
IO.foreach(path) do |line|
|
205
|
+
line.strip!
|
206
|
+
break unless line[0,1] == "#" or line == ""
|
207
|
+
|
208
|
+
comment << "#{line[1, line.length ].strip}\n"
|
209
|
+
end
|
210
|
+
|
211
|
+
comment
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# Writing a ini hash into a file
|
216
|
+
#
|
217
|
+
# +path+ is a path to the ini file
|
218
|
+
# +inihash+ is a hash representing the ini File. Default is a empty hash.
|
219
|
+
# +comment+ is a string with comments which appear on the
|
220
|
+
# top of the file. Each line will get a "#" before.
|
221
|
+
# Default is no comment.
|
222
|
+
#
|
223
|
+
def Ini.write_to_file(path, inihash={}, comment=nil)
|
224
|
+
raise TypeError, "String expected" unless comment.is_a? String or comment.nil?
|
225
|
+
|
226
|
+
raise TypeError, "Hash expected" unless inihash.is_a? Hash
|
227
|
+
File.open(path, "w") { |file|
|
228
|
+
|
229
|
+
unless comment.nil?
|
230
|
+
comment.each do |line|
|
231
|
+
file << "# #{line}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
file << Ini.to_s(inihash)
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Turn a hash (up to 2 levels deepness) into a ini string
|
241
|
+
#
|
242
|
+
# +inihash+ is a hash representing the ini File. Default is a empty hash.
|
243
|
+
#
|
244
|
+
# Returns a string in the ini file format.
|
245
|
+
#
|
246
|
+
def Ini.to_s(inihash={})
|
247
|
+
str = ""
|
248
|
+
|
249
|
+
inihash.each do |key, value|
|
250
|
+
|
251
|
+
if value.is_a? Hash
|
252
|
+
str << "[#{key.to_s}]\n"
|
253
|
+
|
254
|
+
value.each do |under_key, under_value|
|
255
|
+
str << "#{under_key.to_s}=#{under_value.to_s unless under_value.nil?}\n"
|
256
|
+
end
|
257
|
+
|
258
|
+
else
|
259
|
+
str << "#{key.to_s}=#{value.to_s unless value2.nil?}\n"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
str
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
@@ -31,16 +31,16 @@ class Object
|
|
31
31
|
#
|
32
32
|
# TODO: Will only support calls with blocks as of Ruby 1.9+.
|
33
33
|
#
|
34
|
-
def instance_eval(&block)
|
35
|
-
return super if block
|
34
|
+
def instance_eval(*args, &block)
|
35
|
+
return super if block or !args.empty?
|
36
36
|
@_instance_eval ||= Functor.new do |op, *a|
|
37
37
|
instance_eval{ send(op, *a) }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
# TODO for Ruby 1.9
|
42
|
-
#def instance_eval(&block)
|
43
|
-
# return super if block
|
42
|
+
#def instance_eval(*args, &block)
|
43
|
+
# return super if block or !args.empty?
|
44
44
|
# @_instance_functor ||= Functor.new do |op, *a, &b|
|
45
45
|
# fcall(op, *a, &b)
|
46
46
|
# end
|
@@ -1,22 +1,8 @@
|
|
1
|
+
warn "IORedirect is being deprecated. It is not a robust solution. If you use this library, please consider contributing to Facets by rewritting the library so we can keep it in Facets."
|
2
|
+
|
1
3
|
# = IORedirect
|
2
4
|
#
|
3
|
-
#
|
4
|
-
# or any other object with a write() method.
|
5
|
-
#
|
6
|
-
# s = StringIO.new
|
7
|
-
# r = Redirector.redirect($stdout, s) do
|
8
|
-
# $stdout.puts "this is a test"
|
9
|
-
# end
|
10
|
-
#
|
11
|
-
# == History
|
12
|
-
#
|
13
|
-
# * IORedirect was ported from Paul Brannan's Ruby Treasures.
|
14
|
-
#
|
15
|
-
# == Authors
|
16
|
-
#
|
17
|
-
# * Paul Brannan <paul@atdesk.com>
|
18
|
-
#
|
19
|
-
# = Copying
|
5
|
+
# IORedirect was ported from Paul Brannan's Ruby Treasures.
|
20
6
|
#
|
21
7
|
# Copyright (C) 2002 Paul Brannan <paul@atdesk.com>
|
22
8
|
#
|
@@ -29,6 +15,7 @@
|
|
29
15
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
30
16
|
# FOR A PARTICULAR PURPOSE.
|
31
17
|
|
18
|
+
require 'thread'
|
32
19
|
|
33
20
|
# = IORedirect
|
34
21
|
#
|
@@ -36,7 +23,7 @@
|
|
36
23
|
# or any other object with a write() method.
|
37
24
|
#
|
38
25
|
# s = StringIO.new
|
39
|
-
# r =
|
26
|
+
# r = IORedirect.redirect($stdout, s) do
|
40
27
|
# $stdout.puts "this is a test"
|
41
28
|
# end
|
42
29
|
#
|
@@ -55,7 +42,7 @@ class IORedirect
|
|
55
42
|
def start
|
56
43
|
raise "Redirection already in progress" if @t
|
57
44
|
tmp = @from.dup
|
58
|
-
r, w = IO.pipe
|
45
|
+
r, w = *IO.pipe
|
59
46
|
@from.reopen(w)
|
60
47
|
@t = Thread.new do
|
61
48
|
begin
|
@@ -78,53 +65,13 @@ class IORedirect
|
|
78
65
|
|
79
66
|
# An exception-safe class method for redirection
|
80
67
|
def self.redirect(from, to)
|
81
|
-
s =
|
68
|
+
s = new(from, to)
|
82
69
|
begin
|
83
70
|
yield
|
84
71
|
ensure
|
85
72
|
s.stop
|
86
73
|
end
|
87
74
|
end
|
88
|
-
end
|
89
|
-
|
90
|
-
|
91
|
-
# --- test ---
|
92
|
-
|
93
|
-
if __FILE__ == $0 then
|
94
|
-
|
95
|
-
class SimpleStringIO
|
96
|
-
attr_reader :str
|
97
|
-
def initialize; @str = ''; end
|
98
|
-
def write(str); @str << str; end
|
99
|
-
end
|
100
|
-
|
101
|
-
Thread.abort_on_exception = true
|
102
|
-
s = SimpleStringIO.new
|
103
|
-
r = Redirector.redirect($stdout, s) do
|
104
|
-
$stdout.puts "this is a test"
|
105
|
-
$stdout.puts "of the StringIO redirection system"
|
106
|
-
end
|
107
|
-
puts "Done redirecting."
|
108
|
-
puts "Result:\n#{s.str}"
|
109
75
|
|
110
76
|
end
|
111
77
|
|
112
|
-
|
113
|
-
|
114
|
-
# _____ _
|
115
|
-
# |_ _|__ ___| |_
|
116
|
-
# | |/ _ \/ __| __|
|
117
|
-
# | | __/\__ \ |_
|
118
|
-
# |_|\___||___/\__|
|
119
|
-
#
|
120
|
-
|
121
|
-
# TODO
|
122
|
-
|
123
|
-
=begin #testing
|
124
|
-
|
125
|
-
require 'test/unit'
|
126
|
-
|
127
|
-
=end
|
128
|
-
|
129
|
-
|
130
|
-
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# LinkedList
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006 Kirk Haines <khaines@enigo.com>.
|
4
|
+
#
|
5
|
+
# General Public License (GPL)
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
# a copy of this software and associated documentation files (the
|
9
|
+
# "Software"), to deal in the Software without restriction, including
|
10
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
# the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
require 'enumerator'
|
27
|
+
|
28
|
+
# LinkedList implements a simple doubly linked list with efficient
|
29
|
+
# hash-like element access.
|
30
|
+
#
|
31
|
+
# This is a simple linked list implementation with efficient random
|
32
|
+
# access of data elements. It was inspired by George Moscovitis'
|
33
|
+
# LRUCache implementation found in Facets 1.7.30, but unlike the
|
34
|
+
# linked list in that cache, this one does not require the use of a
|
35
|
+
# mixin on any class to be stored. The linked list provides the
|
36
|
+
# push, pop, shift, unshift, first, last, delete and length methods
|
37
|
+
# which work just like their namesakes in the Array class, but it
|
38
|
+
# also supports setting and retrieving values by key, just like a
|
39
|
+
# hash.
|
40
|
+
#
|
41
|
+
# LinkedList was ported from the original in Kirk Hanes IOWA web framework.
|
42
|
+
#
|
43
|
+
class LinkedList
|
44
|
+
|
45
|
+
include Enumerable
|
46
|
+
|
47
|
+
# Represents a single node of the linked list.
|
48
|
+
|
49
|
+
class Node
|
50
|
+
attr_accessor :key, :value, :prev_node, :next_node
|
51
|
+
|
52
|
+
def initialize(key=nil,value=nil,prev_node=nil,next_node=nil)
|
53
|
+
@key = key
|
54
|
+
@value = value
|
55
|
+
@prev_node = prev_node
|
56
|
+
@next_node = next_node
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
@head = Node.new
|
62
|
+
@tail = Node.new
|
63
|
+
@lookup = Hash.new
|
64
|
+
node_join(@head,@tail)
|
65
|
+
end
|
66
|
+
|
67
|
+
def [](v)
|
68
|
+
@lookup[v].value
|
69
|
+
end
|
70
|
+
|
71
|
+
def []=(k,v)
|
72
|
+
if @lookup.has_key?(k)
|
73
|
+
@lookup[k].value = v
|
74
|
+
else
|
75
|
+
n = Node.new(k,v,@head,@head.next_node)
|
76
|
+
node_join(n,@head.next_node)
|
77
|
+
node_join(@head,n)
|
78
|
+
@lookup[k] = n
|
79
|
+
end
|
80
|
+
v
|
81
|
+
end
|
82
|
+
|
83
|
+
def empty?
|
84
|
+
@lookup.empty?
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete(k)
|
88
|
+
n = @lookup.delete(k)
|
89
|
+
v = n ? node_purge(n) : nil
|
90
|
+
v
|
91
|
+
end
|
92
|
+
|
93
|
+
def first
|
94
|
+
@head.next_node.value
|
95
|
+
end
|
96
|
+
|
97
|
+
def last
|
98
|
+
@tail.prev_node.value
|
99
|
+
end
|
100
|
+
|
101
|
+
def shift
|
102
|
+
k = @head.next_node.key
|
103
|
+
n = @lookup.delete(k)
|
104
|
+
node_delete(n) if n
|
105
|
+
end
|
106
|
+
|
107
|
+
def unshift(v)
|
108
|
+
if @lookup.has_key?(v)
|
109
|
+
n = @lookup[v]
|
110
|
+
node_delete(n)
|
111
|
+
node_join(n,@head.next_node)
|
112
|
+
node_join(@head,n)
|
113
|
+
else
|
114
|
+
n = Node.new(v,v,@head,@head.next_node)
|
115
|
+
node_join(n,@head.next_node)
|
116
|
+
node_join(@head,n)
|
117
|
+
@lookup[v] = n
|
118
|
+
end
|
119
|
+
v
|
120
|
+
end
|
121
|
+
|
122
|
+
def pop
|
123
|
+
k = @tail.prev_node.key
|
124
|
+
n = @lookup.delete(k)
|
125
|
+
node_delete(n) if n
|
126
|
+
end
|
127
|
+
|
128
|
+
def push(v)
|
129
|
+
if @lookup.has_key?(v)
|
130
|
+
n = @lookup[v]
|
131
|
+
node_delete(n)
|
132
|
+
node_join(@tail.prev_node,n)
|
133
|
+
node_join(n,@tail)
|
134
|
+
else
|
135
|
+
n = Node.new(v,v,@tail.prev_node,@tail)
|
136
|
+
node_join(@tail.prev_node,n)
|
137
|
+
node_join(n,@tail)
|
138
|
+
@lookup[v] = n
|
139
|
+
end
|
140
|
+
v
|
141
|
+
end
|
142
|
+
|
143
|
+
def queue
|
144
|
+
r = []
|
145
|
+
n = @head
|
146
|
+
while (n = n.next_node) and n != @tail
|
147
|
+
r << n.key
|
148
|
+
end
|
149
|
+
r
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_a
|
153
|
+
r = []
|
154
|
+
n = @head
|
155
|
+
while (n = n.next_node) and n != @tail
|
156
|
+
r << n.value
|
157
|
+
end
|
158
|
+
r
|
159
|
+
end
|
160
|
+
|
161
|
+
def length
|
162
|
+
@lookup.length
|
163
|
+
end
|
164
|
+
|
165
|
+
def each
|
166
|
+
n = @head
|
167
|
+
while (n = n.next_node) and n != @tail
|
168
|
+
yield(n.key,n.value)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def node_delete(n)
|
175
|
+
node_join(n.prev_node,n.next_node)
|
176
|
+
v = n.value
|
177
|
+
end
|
178
|
+
|
179
|
+
def node_purge(n)
|
180
|
+
node_join(n.prev_node,n.next_node)
|
181
|
+
v = n.value
|
182
|
+
n.value = nil
|
183
|
+
n.key = nil
|
184
|
+
n.next_node = nil
|
185
|
+
n.prev_node = nil
|
186
|
+
v
|
187
|
+
end
|
188
|
+
|
189
|
+
def node_join(a,b)
|
190
|
+
a.next_node = b
|
191
|
+
b.prev_node = a
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|