nitro 0.1.2
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/AUTHORS +8 -0
- data/ChangeLog +1546 -0
- data/LICENCE +32 -0
- data/README +278 -0
- data/RELEASES +7 -0
- data/Rakefile +79 -0
- data/bin/cluster.rb +219 -0
- data/doc/architecture.txt +28 -0
- data/doc/bugs.txt +7 -0
- data/doc/css.txt +20 -0
- data/doc/ideas.txt +120 -0
- data/doc/pg.txt +47 -0
- data/doc/svn.txt +82 -0
- data/doc/todo.txt +30 -0
- data/etc/new-project.rb +18 -0
- data/examples/simple/README +15 -0
- data/examples/simple/app.rb +31 -0
- data/examples/simple/conf/apache.conf +100 -0
- data/examples/simple/conf/config.rb +89 -0
- data/examples/simple/conf/debug-config.rb +53 -0
- data/examples/simple/conf/live-config.rb +48 -0
- data/examples/simple/conf/overrides.rb +9 -0
- data/examples/simple/conf/requires.rb +51 -0
- data/examples/simple/ctl +32 -0
- data/examples/simple/env.rb +33 -0
- data/examples/simple/install.rb +12 -0
- data/examples/simple/lib/articles/entities.rb +35 -0
- data/examples/simple/lib/articles/lc-en.rb +36 -0
- data/examples/simple/lib/articles/methods.rb +55 -0
- data/examples/simple/lib/articles/part.rb +58 -0
- data/examples/simple/logs/access_log +2 -0
- data/examples/simple/logs/apache.log +3 -0
- data/examples/simple/logs/app.log +1 -0
- data/examples/simple/logs/events.log +1 -0
- data/examples/simple/root/add-article.sx +15 -0
- data/examples/simple/root/article-form.ss +20 -0
- data/examples/simple/root/comments-form.ss +16 -0
- data/examples/simple/root/comments.si +30 -0
- data/examples/simple/root/index.sx +44 -0
- data/examples/simple/root/shader/shader.xsl +100 -0
- data/examples/simple/root/shader/style.css +9 -0
- data/examples/simple/root/view-article.sx +30 -0
- data/examples/tiny/app.rb +30 -0
- data/examples/tiny/conf/apache.conf +100 -0
- data/examples/tiny/conf/config.rb +67 -0
- data/examples/tiny/conf/requires.rb +40 -0
- data/examples/tiny/ctl +31 -0
- data/examples/tiny/logs/access_log +9 -0
- data/examples/tiny/logs/apache.log +9 -0
- data/examples/tiny/root/index.sx +35 -0
- data/lib/n/app/cluster.rb +219 -0
- data/lib/n/app/cookie.rb +86 -0
- data/lib/n/app/filters/autologin.rb +50 -0
- data/lib/n/app/fragment.rb +67 -0
- data/lib/n/app/handlers.rb +120 -0
- data/lib/n/app/handlers/code-handler.rb +184 -0
- data/lib/n/app/handlers/page-handler.rb +612 -0
- data/lib/n/app/request-part.rb +59 -0
- data/lib/n/app/request.rb +653 -0
- data/lib/n/app/script.rb +398 -0
- data/lib/n/app/server.rb +53 -0
- data/lib/n/app/session.rb +224 -0
- data/lib/n/app/user.rb +47 -0
- data/lib/n/app/webrick-servlet.rb +213 -0
- data/lib/n/app/webrick.rb +70 -0
- data/lib/n/application.rb +187 -0
- data/lib/n/config.rb +31 -0
- data/lib/n/db.rb +217 -0
- data/lib/n/db/README +232 -0
- data/lib/n/db/connection.rb +369 -0
- data/lib/n/db/make-release.sh +26 -0
- data/lib/n/db/managed.rb +235 -0
- data/lib/n/db/mixins.rb +282 -0
- data/lib/n/db/mysql.rb +342 -0
- data/lib/n/db/psql.rb +378 -0
- data/lib/n/db/tools.rb +110 -0
- data/lib/n/db/utils.rb +99 -0
- data/lib/n/events.rb +118 -0
- data/lib/n/l10n.rb +22 -0
- data/lib/n/logger.rb +33 -0
- data/lib/n/macros.rb +53 -0
- data/lib/n/mixins.rb +46 -0
- data/lib/n/parts.rb +154 -0
- data/lib/n/properties.rb +194 -0
- data/lib/n/server.rb +61 -0
- data/lib/n/server/PLAYBACK.txt +8 -0
- data/lib/n/server/RESEARCH.txt +13 -0
- data/lib/n/server/filter.rb +77 -0
- data/lib/n/shaders.rb +167 -0
- data/lib/n/sitemap.rb +188 -0
- data/lib/n/std.rb +69 -0
- data/lib/n/sync/clc.rb +108 -0
- data/lib/n/sync/handler.rb +221 -0
- data/lib/n/sync/server.rb +170 -0
- data/lib/n/tools/README +11 -0
- data/lib/n/ui/date-select.rb +74 -0
- data/lib/n/ui/pager.rb +187 -0
- data/lib/n/ui/popup.rb +45 -0
- data/lib/n/ui/select.rb +41 -0
- data/lib/n/ui/tabs.rb +34 -0
- data/lib/n/utils/array.rb +92 -0
- data/lib/n/utils/cache.rb +144 -0
- data/lib/n/utils/gfx.rb +108 -0
- data/lib/n/utils/hash.rb +148 -0
- data/lib/n/utils/html.rb +147 -0
- data/lib/n/utils/http.rb +98 -0
- data/lib/n/utils/mail.rb +28 -0
- data/lib/n/utils/number.rb +31 -0
- data/lib/n/utils/pool.rb +66 -0
- data/lib/n/utils/string.rb +297 -0
- data/lib/n/utils/template.rb +38 -0
- data/lib/n/utils/time.rb +91 -0
- data/lib/n/utils/uri.rb +193 -0
- data/lib/xsl/base.xsl +205 -0
- data/lib/xsl/ce.xsl +30 -0
- data/lib/xsl/localization.xsl +23 -0
- data/lib/xsl/xforms.xsl +26 -0
- data/test/run.rb +95 -0
- metadata +187 -0
data/lib/n/ui/pager.rb
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# = Pager
|
|
2
|
+
#
|
|
3
|
+
# code: gmosx
|
|
4
|
+
#
|
|
5
|
+
# (c) 2004 Navel, all rights reserved.
|
|
6
|
+
# $Id: pager.rb 71 2004-10-18 10:50:22Z gmosx $
|
|
7
|
+
|
|
8
|
+
module N; module UI
|
|
9
|
+
|
|
10
|
+
# === Design:
|
|
11
|
+
#
|
|
12
|
+
# The new version is carefully designed for scaleability. It stores
|
|
13
|
+
# only the items for one page.
|
|
14
|
+
# The name parameter is needed, multiple pagers can coexist in
|
|
15
|
+
# a single page. Unlike the v1/v2 pagers this pager leverages the
|
|
16
|
+
# SQL LIMIT option to optimize database interaction.
|
|
17
|
+
#
|
|
18
|
+
# The pager does not extend Array (it includes an Array instead) to
|
|
19
|
+
# avoid a concat() in the initialization step.
|
|
20
|
+
#
|
|
21
|
+
# TODO:
|
|
22
|
+
# - Extend from array? (would be more elegant)
|
|
23
|
+
#
|
|
24
|
+
# === Example:
|
|
25
|
+
#
|
|
26
|
+
# INVESTIGATE:
|
|
27
|
+
# mysql> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
|
|
28
|
+
# -> WHERE id > 100 LIMIT 10;
|
|
29
|
+
# mysql> SELECT FOUND_ROWS();
|
|
30
|
+
#
|
|
31
|
+
class Pager < Array
|
|
32
|
+
attr_accessor :name, :idx, :page, :page_count
|
|
33
|
+
# total count of items
|
|
34
|
+
attr_accessor :total_count
|
|
35
|
+
# page items
|
|
36
|
+
attr_accessor :page_items
|
|
37
|
+
# read needed variables from the request.
|
|
38
|
+
attr_accessor :request
|
|
39
|
+
|
|
40
|
+
def initialize(name, request, items_per_page, items = nil)
|
|
41
|
+
raise "items_per_page should be > 0" unless items_per_page > 0
|
|
42
|
+
|
|
43
|
+
@request, @name = request, name
|
|
44
|
+
@page = request.get("__pg#{@name}", 1)
|
|
45
|
+
@items_per_page = items_per_page
|
|
46
|
+
@start_idx = (@page - 1) * items_per_page
|
|
47
|
+
|
|
48
|
+
if items
|
|
49
|
+
set(items.size())
|
|
50
|
+
# gmosx, FIXME: not exactly what i want!
|
|
51
|
+
items.slice!(@start_idx, @items_per_page)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set(total_count)
|
|
56
|
+
@total_count = total_count
|
|
57
|
+
@page_count = (@total_count.to_f() / @items_per_page).ceil()
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def first_page
|
|
61
|
+
return 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def last_page
|
|
65
|
+
return @page_count
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def previous_page
|
|
69
|
+
return [@page - 1, 1].max()
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def next_page
|
|
73
|
+
return [@page + 1, @page_count].min()
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Iterator
|
|
77
|
+
#
|
|
78
|
+
def each(&block)
|
|
79
|
+
@page_items.each(&block)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Iterator
|
|
83
|
+
# Returns 1-based index.
|
|
84
|
+
#
|
|
85
|
+
def each_with_index
|
|
86
|
+
idx = @start_idx
|
|
87
|
+
for item in @page_items
|
|
88
|
+
yield(idx + 1, item)
|
|
89
|
+
idx += 1
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def empty?
|
|
94
|
+
return @items.empty?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def size
|
|
98
|
+
return @items.size()
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the range of the current page.
|
|
102
|
+
#
|
|
103
|
+
def page_range
|
|
104
|
+
s = @idx
|
|
105
|
+
e = [@idx + @items_per_page - 1, all_total_count].min
|
|
106
|
+
|
|
107
|
+
return [s, e]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Override if needed.
|
|
111
|
+
#
|
|
112
|
+
def nav_range
|
|
113
|
+
# effective range = 10 pages.
|
|
114
|
+
s = [@page - 5, 1].max()
|
|
115
|
+
e = [@page + 9, @page_count].min()
|
|
116
|
+
|
|
117
|
+
d = 9 - (e - s)
|
|
118
|
+
e += d if d < 0
|
|
119
|
+
|
|
120
|
+
return (s..e)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Override this method in your application
|
|
124
|
+
# if needed.
|
|
125
|
+
# TODO: better markup.
|
|
126
|
+
#
|
|
127
|
+
def navigation
|
|
128
|
+
nav = ""
|
|
129
|
+
|
|
130
|
+
unless @page == first_page()
|
|
131
|
+
nav << %{
|
|
132
|
+
<div class="first"><a href="#{target_uri(first_page())}">����</a></div>
|
|
133
|
+
<div class="previous"><a href="#{target_uri(previous_page())}">�����������</a></div>
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
unless @page == last_page()
|
|
138
|
+
nav << %{
|
|
139
|
+
<div class="last"><a href="#{target_uri(last_page())}">�����</a></div>
|
|
140
|
+
<div class="next"><a href="#{target_uri(next_page())}">�������</a></div>
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
nav << %{<ul>}
|
|
145
|
+
|
|
146
|
+
for i in nav_range()
|
|
147
|
+
if i == @page
|
|
148
|
+
nav << %{
|
|
149
|
+
<li class="active">#{i}</li>
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
nav << %{
|
|
153
|
+
<li><a href="#{target_uri(i)}">#{i}</a></li>
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
nav << %{</ul>}
|
|
159
|
+
|
|
160
|
+
return nav
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Create an appropriate SQL limit clause.
|
|
164
|
+
# Returns postgres/mysql compatible limit.
|
|
165
|
+
#
|
|
166
|
+
def sql_limit
|
|
167
|
+
if @start_idx > 0
|
|
168
|
+
return "LIMIT #{@items_per_page} OFFSET #{@start_idx}"
|
|
169
|
+
else
|
|
170
|
+
# gmosx: perhaps this is optimized ? naaaaaah...
|
|
171
|
+
return "LIMIT #{@items_per_page}"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# ------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
# Generate the target URI.
|
|
178
|
+
#
|
|
179
|
+
def target_uri(page)
|
|
180
|
+
params = {"__pg#{@name}" => page}
|
|
181
|
+
return N::UriUtils.update_query_string(@request.uri, params)
|
|
182
|
+
end
|
|
183
|
+
private :target_uri
|
|
184
|
+
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
end; end # module
|
data/lib/n/ui/popup.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# = Popup
|
|
2
|
+
#
|
|
3
|
+
# code: gmosx
|
|
4
|
+
#
|
|
5
|
+
# (c) 2004 Navel, all rights reserved.
|
|
6
|
+
# $Id: popup.rb 71 2004-10-18 10:50:22Z gmosx $
|
|
7
|
+
|
|
8
|
+
module N; module UI
|
|
9
|
+
|
|
10
|
+
# = Popup
|
|
11
|
+
#
|
|
12
|
+
class Popup
|
|
13
|
+
|
|
14
|
+
# Emit the needed javascript.
|
|
15
|
+
# Alternatively use this code: <script src="/r/js/std.js">#{}</script>
|
|
16
|
+
#
|
|
17
|
+
def self.script
|
|
18
|
+
%[
|
|
19
|
+
function newWin(url, name, w, h, scroll) {
|
|
20
|
+
var pleft = (screen.width - w) / 2;
|
|
21
|
+
var ptop = (screen.height - h) / 2;
|
|
22
|
+
var settings = 'height=' + h + ',width=' + w + ',top=' + ptop + ',left=' + pleft + ',scrollbars=' + scroll + ',resizable';
|
|
23
|
+
|
|
24
|
+
win = window.open(url, name, settings);
|
|
25
|
+
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# gmosx: keep the leading / to be IE friendly.
|
|
32
|
+
#
|
|
33
|
+
def self.onclick(uri, width, height, title = "Popup", type="PAGE", options = "scrollbars=yes,resizable", container = "/p/glue/popup.sx")
|
|
34
|
+
%[javascript: var pwl = (screen.width - #{width}) / 2; var pwt = (screen.height - #{height}) / 2; window.open('#{container}?uri=#{uri};type=#{type}', '#{title}', 'width=#{width},height=#{height},top='+pwt+',left='+pwl+', #{options}'); return false"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# gmosx: keep the leading / to be IE friendly.
|
|
38
|
+
#
|
|
39
|
+
def self.link(uri, width, height, link = "link", title = "Popup", type="PAGE", options = "scrollbars=yes,resizable", container = "/p/glue/popup.sx")
|
|
40
|
+
%[<a href="#" onclick="#{self.onclick(uri, width, height, title, type, options, container)}">#{link}</a>]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end; end # module
|
|
45
|
+
|
data/lib/n/ui/select.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# = Select
|
|
2
|
+
#
|
|
3
|
+
# code: gmosx
|
|
4
|
+
#
|
|
5
|
+
# (c) 2004 Navel, all rights reserved.
|
|
6
|
+
# $Id: select.rb 71 2004-10-18 10:50:22Z gmosx $
|
|
7
|
+
|
|
8
|
+
module N; module UI
|
|
9
|
+
|
|
10
|
+
class Select
|
|
11
|
+
|
|
12
|
+
# Generalized select. Dont create the enclosing selects to
|
|
13
|
+
# be flexible.
|
|
14
|
+
# Example:
|
|
15
|
+
#
|
|
16
|
+
# <select name="param" onchange="submit()">
|
|
17
|
+
# #{N::UI::Select.render_options(
|
|
18
|
+
# paramvalue,
|
|
19
|
+
# [-1, 1, 2, 3],
|
|
20
|
+
# ["-- Enter Options --", "Man", "Woman", "None"]
|
|
21
|
+
# )}
|
|
22
|
+
# </select>
|
|
23
|
+
#
|
|
24
|
+
def self.render_options(paramvalue, values, options)
|
|
25
|
+
str = ""
|
|
26
|
+
|
|
27
|
+
values.each_with_index { |val, idx|
|
|
28
|
+
if paramvalue == val
|
|
29
|
+
str << %{<option value="#{val}" selected="1">#{options[idx]}</option>}
|
|
30
|
+
else
|
|
31
|
+
str << %{<option value="#{val}">#{options[idx]}</option>}
|
|
32
|
+
end
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return str
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end; end # module
|
|
41
|
+
|
data/lib/n/ui/tabs.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# = Tabs
|
|
2
|
+
#
|
|
3
|
+
# code: gmosx
|
|
4
|
+
#
|
|
5
|
+
# (c) 2004 Navel, all rights reserved.
|
|
6
|
+
# $Id: tabs.rb 71 2004-10-18 10:50:22Z gmosx $
|
|
7
|
+
|
|
8
|
+
module N; module UI
|
|
9
|
+
|
|
10
|
+
# Render a tabs bar
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
#
|
|
14
|
+
# #{N::UI.tabs(request,
|
|
15
|
+
# ["Page1", "Page2", "Page3"]
|
|
16
|
+
# }
|
|
17
|
+
#
|
|
18
|
+
def self.tabs(request, options, param)
|
|
19
|
+
tabs = []
|
|
20
|
+
|
|
21
|
+
selected = request.get(param, 0)
|
|
22
|
+
|
|
23
|
+
options.each_with_index { |opt, idx|
|
|
24
|
+
if idx == selected
|
|
25
|
+
tabs << %|<strong>#{opt}</strong>|
|
|
26
|
+
else
|
|
27
|
+
tabs << %|<a href="#{request.expand_uri(param => idx)}">#{opt}</a>|
|
|
28
|
+
end
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return %|<div class="tabs">#{tabs.join('<span class="sep">|</span>')}</div>|
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end; end # module
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# = General arrays utilities collection
|
|
2
|
+
#
|
|
3
|
+
# === Design:
|
|
4
|
+
#
|
|
5
|
+
# Implement as a module to avoid class polution. You can still Ruby's
|
|
6
|
+
# advanced features to include the module in your class.
|
|
7
|
+
# Passing the object to act upon allows to check for nil, which isn't
|
|
8
|
+
# possible if you use self.
|
|
9
|
+
#
|
|
10
|
+
# code:: gmosx, ekarak
|
|
11
|
+
#
|
|
12
|
+
# (c) 2002-2003 Navel, all rights reserved.
|
|
13
|
+
# $Id: array.rb 71 2004-10-18 10:50:22Z gmosx $
|
|
14
|
+
|
|
15
|
+
require "sync"
|
|
16
|
+
|
|
17
|
+
module N
|
|
18
|
+
|
|
19
|
+
# == SafeArray
|
|
20
|
+
#
|
|
21
|
+
# A thread-safe array. We use a sync object instead of a mutex,
|
|
22
|
+
# because it is re-entrant.
|
|
23
|
+
# An exclusive lock is needed when writing, a shared lock IS NEEDED
|
|
24
|
+
# when reading
|
|
25
|
+
|
|
26
|
+
class SafeArray < Array
|
|
27
|
+
|
|
28
|
+
attr :sync
|
|
29
|
+
|
|
30
|
+
# gmosx: delegator is not used.
|
|
31
|
+
|
|
32
|
+
def initialize(delegator = nil)
|
|
33
|
+
@sync = ::Sync.new()
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def << (value)
|
|
37
|
+
return @sync.synchronize(::Sync::SH) {
|
|
38
|
+
super
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def delete_if(&block)
|
|
43
|
+
return @sync.synchronize(::Sync::SH) {
|
|
44
|
+
super
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def [](key)
|
|
49
|
+
return @sync.synchronize(::Sync::SH) {
|
|
50
|
+
super
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def []=(key, value)
|
|
55
|
+
return @sync.synchronize(::Sync::EX) {
|
|
56
|
+
super
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete(key)
|
|
61
|
+
return @sync.synchronize(::Sync::EX) {
|
|
62
|
+
super
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def clear
|
|
67
|
+
@sync.synchronize(::Sync::EX) {
|
|
68
|
+
super
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def size
|
|
73
|
+
return @sync.synchronize(::Sync::SH) {
|
|
74
|
+
super
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def shift
|
|
79
|
+
return @sync.synchronize(::Sync::EX) {
|
|
80
|
+
super
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def unshift(el)
|
|
85
|
+
return @sync.synchronize(::Sync::EX) {
|
|
86
|
+
super
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end # SafeArray
|
|
91
|
+
|
|
92
|
+
end # module
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# = Cache
|
|
2
|
+
#
|
|
3
|
+
# Various caching mechanisms.
|
|
4
|
+
#
|
|
5
|
+
# code:
|
|
6
|
+
# George Moschovitis <gm@navel.gr>
|
|
7
|
+
# Anastasios Koutoumanos <ak@navel.gr>
|
|
8
|
+
#
|
|
9
|
+
# (c) 2004 Navel, all rights reserved.
|
|
10
|
+
# $Id: cache.rb 71 2004-10-18 10:50:22Z gmosx $
|
|
11
|
+
|
|
12
|
+
module N;
|
|
13
|
+
|
|
14
|
+
# = LRUCache
|
|
15
|
+
#
|
|
16
|
+
# A cache utilizing a simple LRU (Least Recently Used) policy.
|
|
17
|
+
# The items managed by this cache must respond to the #key method.
|
|
18
|
+
# Attempts to optimize reads rather than inserts!
|
|
19
|
+
#
|
|
20
|
+
# LRU semantics are enforced by inserting the items in a queue.
|
|
21
|
+
# The lru item is always at the tail. Two special sentinels
|
|
22
|
+
# (head, tail) are used to simplify (?) the code.
|
|
23
|
+
#
|
|
24
|
+
class LRUCache < Hash
|
|
25
|
+
|
|
26
|
+
# Mix this in your class to make LRU-managable.
|
|
27
|
+
#
|
|
28
|
+
module Item
|
|
29
|
+
attr_accessor :lru_key, :lru_prev, :lru_next
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# head-tail sentinels
|
|
33
|
+
#
|
|
34
|
+
class Sentinel; include Item; end
|
|
35
|
+
|
|
36
|
+
# the maximum number of items in the cache
|
|
37
|
+
attr_accessor :max_items
|
|
38
|
+
|
|
39
|
+
# the head sentinel
|
|
40
|
+
attr :head
|
|
41
|
+
# the tail sentinel, tail.prev points to the lru item.
|
|
42
|
+
attr :tail
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
#
|
|
46
|
+
def initialize(max_items)
|
|
47
|
+
@max_items = max_items
|
|
48
|
+
lru_clear()
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Lookup an item in the cache
|
|
52
|
+
#
|
|
53
|
+
def [](key)
|
|
54
|
+
if item = super
|
|
55
|
+
return lru_touch(item)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The inserted item is considered mru!
|
|
60
|
+
#
|
|
61
|
+
def []=(key, item)
|
|
62
|
+
item = super
|
|
63
|
+
item.lru_key = key
|
|
64
|
+
lru_insert(item)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Delete an item from the cache
|
|
68
|
+
#
|
|
69
|
+
def delete(key)
|
|
70
|
+
if item = super
|
|
71
|
+
lru_delete(item)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Clear the cache
|
|
76
|
+
#
|
|
77
|
+
def clear
|
|
78
|
+
super
|
|
79
|
+
lru_clear()
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The first (mru) element in the cache
|
|
83
|
+
#
|
|
84
|
+
def first
|
|
85
|
+
@head.lru_next
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The last (lru) element in the cache
|
|
89
|
+
#
|
|
90
|
+
def last
|
|
91
|
+
@tail.lru_prev
|
|
92
|
+
end
|
|
93
|
+
alias_method :lru, :last
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Delete an item from the lru list.
|
|
98
|
+
#
|
|
99
|
+
def lru_delete(item)
|
|
100
|
+
lru_join(item.lru_prev, item.lru_next)
|
|
101
|
+
return item
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Join two items in the lru list
|
|
105
|
+
# Return y to allow for chaining
|
|
106
|
+
#
|
|
107
|
+
def lru_join(x, y)
|
|
108
|
+
x.lru_next = y
|
|
109
|
+
y.lru_prev = x
|
|
110
|
+
return y
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Append a child item to a parent item in the lru list
|
|
114
|
+
# (Re)inserts the child in the list.
|
|
115
|
+
#
|
|
116
|
+
def lru_append(parent, child)
|
|
117
|
+
lru_join(child, parent.lru_next)
|
|
118
|
+
lru_join(parent, child)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Insert an item
|
|
122
|
+
#
|
|
123
|
+
def lru_insert(item)
|
|
124
|
+
delete(last.lru_key) if size() > @max_items
|
|
125
|
+
lru_append(@head, item)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Touch an item, make mru!
|
|
129
|
+
# Returns the item
|
|
130
|
+
#
|
|
131
|
+
def lru_touch(item)
|
|
132
|
+
lru_append(@head, lru_delete(item))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Clear the lru
|
|
136
|
+
#
|
|
137
|
+
def lru_clear
|
|
138
|
+
@head = Sentinel.new
|
|
139
|
+
@tail = Sentinel.new
|
|
140
|
+
lru_join(@head, @tail)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end # module
|