facets 2.7.0 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|