ferret 0.11.3 → 0.11.4

Sign up to get free protection for your applications and to get access to all the features.
data/ext/store.h CHANGED
@@ -705,6 +705,20 @@ extern __inline off_t is_read_voff_t(InStream *is);
705
705
  */
706
706
  extern char *is_read_string(InStream *is);
707
707
 
708
+ /**
709
+ * Read a string from the InStream. A string is an integer +length+ in vint
710
+ * format (see is_read_vint) followed by +length+ bytes. This is the format
711
+ * used by os_write_string. This method is similar to +is_read_string+ except
712
+ * that it will safely free all memory if there is an error reading the
713
+ * string.
714
+ *
715
+ * @param is the InStream to read from
716
+ * @return a null byte delimited string
717
+ * @raise IO_ERROR if there is a error reading from the file-system
718
+ * @raise EOF_ERROR if there is an attempt to read past the end of the file
719
+ */
720
+ extern char *is_read_string_safe(InStream *is);
721
+
708
722
  /**
709
723
  * Copy cnt bytes from Instream _is_ to OutStream _os_.
710
724
  *
@@ -0,0 +1,246 @@
1
+ require 'erb'
2
+
3
+
4
+ module Ferret::Browser
5
+ class Delegator
6
+ def initialize(reader, path)
7
+ @reader, @path = reader, path
8
+ end
9
+
10
+ def run(env)
11
+ controller, action, args = :home, :index, nil
12
+ query_string = env['QUERY_STRING']||''
13
+ params = parse_query_string(query_string)
14
+ req_path = env['PATH_INFO'].gsub(/\/+/, '/')
15
+ case req_path
16
+ when '/'
17
+ # nothing to do
18
+ when /^\/?([-a-zA-Z]+)\/?$/
19
+ controller = $1
20
+ when /^\/?([-a-zA-Z]+)\/([-a-zA-Z]+)\/?(.*)?$/
21
+ controller = $1
22
+ action = $2
23
+ args = $3
24
+ else
25
+ controller = :error
26
+ args = req_path
27
+ end
28
+ controller_vars = {
29
+ :params => params,
30
+ :req_path => req_path,
31
+ :query_string => query_string,
32
+ }
33
+ delegate(controller, action, args, controller_vars)
34
+ end
35
+
36
+ private
37
+
38
+ def delegate(controller, action, args, controller_vars)
39
+ begin
40
+ controller = to_const(controller, 'Controller').
41
+ new(@reader, @path, controller_vars)
42
+ controller.send(action, args)
43
+ rescue Exception => e
44
+ puts e.to_s
45
+ controller_vars[:params][:error] = e
46
+ ErrorController.new(@reader, @path, controller_vars).index
47
+ end
48
+ end
49
+
50
+ def to_const(str, suffix='')
51
+ Ferret::Browser.const_get(str.to_s.split('-').
52
+ map {|w| w.capitalize}.join('') + suffix)
53
+ end
54
+
55
+ # from _why's camping
56
+ def unescape_uri(s)
57
+ s.tr('+', ' ').gsub(/%([\da-f]{2})/in){[$1].pack('H*')}
58
+ end
59
+
60
+ def parse_query_string(query_string, delim = '&;')
61
+ m = proc {|_,o,n| o.update(n, &m) rescue ([*o] << n)}
62
+ (query_string||'').split(/[#{delim}] */n).
63
+ inject({}) { |hash, param| key, val = unescape_uri(param).split('=',2)
64
+ hash.update(key.split(/[\]\[]+/).reverse.
65
+ inject(val) { |x,i| Hash[i,x] }, &m)
66
+ }
67
+ end
68
+ end
69
+
70
+ module ViewHelper
71
+ # truncates the string at the first space after +len+ characters
72
+ def truncate(str, len = 80)
73
+ if str and str.length > len and (add = str[len..-1].index(' '))
74
+ str = str[0, len + add] + '&#8230;'
75
+ end
76
+ str
77
+ end
78
+
79
+ def tick_or_cross(t)
80
+ "<img src=\"/s/i/#{t ?'tick':'cross'}.png\" alt=\"#{t ?'yes':'no'}\"/>"
81
+ end
82
+ end
83
+
84
+ class Controller
85
+ include ViewHelper
86
+ APP_DIR = File.expand_path(File.join(File.dirname(__FILE__), "browser/"))
87
+ STATIC_DIR = File.expand_path(File.join(APP_DIR, "s/"))
88
+
89
+ def initialize(reader, path, vars)
90
+ @reader = reader
91
+ @path = path
92
+ vars.each_pair {|key, val| instance_eval("@#{key} = val")}
93
+ @controller_path = pathify(self.class.to_s.gsub(/.*:/, ''))
94
+ end
95
+
96
+ def method_missing(meth_id, *args)
97
+ render(:action => meth_id)
98
+ end
99
+
100
+ protected
101
+
102
+ def load_page(page)
103
+ File.read(File.join(APP_DIR, page))
104
+ end
105
+
106
+ def render(options = {})
107
+ options = {
108
+ :controller => @controller_path,
109
+ :action => :index,
110
+ :status => 200,
111
+ :content_type => 'text/html',
112
+ :env => nil,
113
+ :layout => 'views/layout.rhtml',
114
+ }.update(options)
115
+
116
+ path = "views/#{options[:controller]}/#{options[:action]}.rhtml"
117
+ content = ERB.new(load_page(path)).result(lambda{})
118
+ if options[:layout]
119
+ content = ERB.new(load_page(options[:layout])).result(lambda{})
120
+ end
121
+
122
+ return options[:status], options[:content_type], content
123
+ end
124
+
125
+ # takes an optional block to set optional attributes in the links
126
+ def paginate(idx, max, url, &b)
127
+ return '' if max == 0
128
+ url = url.gsub(%r{^/?(.*?)/?$}, '\1')
129
+ b ||= lambda{}
130
+ link = lambda {|*args|
131
+ i, title, text = args
132
+ "<a href=\"/#{url}/#{i}#{'?' + @query_string if @query_string}\" " +
133
+ "#{'onclick="return false;"' if (i == idx)} " +
134
+ "class=\"#{'disabled ' if (i == idx)}#{b.call(i)}\" " +
135
+ "title=\"#{title||"Go to page #{i}"}\">#{text||i}</a>"
136
+ }
137
+ res = '<div class="nav">'
138
+ if (idx > 0)
139
+ res << link.call(idx - 1, "Go to previous page", "&#171; Previous")
140
+ else
141
+ res << "<a href=\"/#{url}/0\" onclick=\"return false;\" " +
142
+ "class=\"disabled\" title=\"Disabled\">&#171; Previous</a>"
143
+ end
144
+ if idx < 10
145
+ idx.times {|i| res << link.call(i)}
146
+ else
147
+ (0..2).each {|i| res << link.call(i)}
148
+ res << '&nbsp;&#8230;&nbsp;'
149
+ ((idx-4)...idx).each {|i| res << link.call(i)}
150
+ end
151
+ res << link.call(idx, 'Current Page')
152
+ if idx > (max - 10)
153
+ ((idx+1)...max).each {|i| res << link.call(i)}
154
+ else
155
+ ((idx+1)..(idx+4)).each {|i| res << link.call(i)}
156
+ res << '&nbsp;&#8230;&nbsp;'
157
+ ((max-3)...max).each {|i| res << link.call(i)}
158
+ end
159
+ if (idx < (max - 1))
160
+ res << link.call(idx + 1, "Go to next page", "Next &#187;")
161
+ else
162
+ res << "<a href=\"/#{url}/#{max-1}\" onclick=\"return false;\" " +
163
+ "class=\"disabled\" title=\"Disabled\"}\">Next &#187;</a>"
164
+ end
165
+ res << '</div>'
166
+ end
167
+ private
168
+
169
+ def pathify(str)
170
+ str.gsub(/Controller$/, '').gsub(/([a-z])([A-Z])/) {"#{$1}-#{$2}"}.downcase
171
+ end
172
+ end
173
+
174
+ class ErrorController < Controller
175
+ def index
176
+ render(:status => 404)
177
+ end
178
+ end
179
+
180
+ class HomeController < Controller
181
+ end
182
+
183
+ class DocumentController < Controller
184
+ def list(page = 0)
185
+ @page = (page||0).to_i
186
+ @page_size = @params[:page_size]||10
187
+ @first = @page * @page_size
188
+ @last = [@reader.max_doc, (@page + 1) * @page_size].min
189
+ render(:action => :list)
190
+ end
191
+ alias :index :list
192
+
193
+ def show(doc_id)
194
+ doc_id = @params['doc_id']||doc_id||'0'
195
+ if doc_id !~ /^\d+$/
196
+ raise ArgumentError.new("invalid document number '#{doc_id}'")
197
+ end
198
+ @doc_id = doc_id.to_i
199
+ @doc = @reader[@doc_id].load unless @reader.deleted?(@doc_id)
200
+ render(:action => :show)
201
+ end
202
+
203
+ private
204
+
205
+ def choose_document(doc_id='')
206
+ <<-EOF
207
+ <form action="" method="get" onsubmit="location.href='/document/show/' + document.getElementById('doc_id').value;return false;">
208
+ <label for="doc_id">Go to document:
209
+ <input type="text" id="doc_id" name="doc_id" size="4" value="#{@doc_id}"/>
210
+ </label>
211
+ </form>
212
+ EOF
213
+ end
214
+
215
+ def paginate_docs
216
+ paginate(@doc_id, @reader.max_doc, '/document/show/') {|i|
217
+ 'deleted' if @reader.deleted?(i)
218
+ }
219
+ end
220
+ end
221
+
222
+ class TermController < Controller
223
+ def show(field)
224
+ if field and field.length > 0
225
+ @field = field.to_sym
226
+ @terms = @reader.terms(@field).to_json(:fast)
227
+ end
228
+ render(:action => :index)
229
+ end
230
+
231
+ def termdocs(args)
232
+ args = args.split('/')
233
+ @field = args.shift.intern
234
+ @term = args.join('/')
235
+ render(:action => :termdocs,
236
+ :content_type => 'text/plain',
237
+ :layout => false)
238
+ end
239
+ end
240
+
241
+ class TermVectorController < Controller
242
+ end
243
+
244
+ class HelpController < Controller
245
+ end
246
+ end
@@ -0,0 +1,192 @@
1
+ function AutoSuggestControl(oTextbox, oProvider) {
2
+ this.cur = -1;
3
+ this.layer = null;
4
+ this.provider = oProvider;
5
+ this.textbox = oTextbox;
6
+ this.init();
7
+ }
8
+ AutoSuggestControl.prototype.init = function() {
9
+ var oThis = this;
10
+ this.textbox.setAttribute("autocomplete", "off");
11
+ this.textbox.onkeyup = function(oEvent) {
12
+ if (!oEvent) {
13
+ oEvent = window.event;
14
+ }
15
+ oThis.handleKeyUp(oEvent);
16
+ };
17
+ this.textbox.onkeydown = function (oEvent) {
18
+ if (!oEvent) {
19
+ oEvent = window.event;
20
+ }
21
+ oThis.handleKeyDown(oEvent);
22
+ };
23
+ this.textbox.onblur = function () {
24
+ oThis.hideSuggestions();
25
+ };
26
+
27
+ this.createDropDown();
28
+ };
29
+ AutoSuggestControl.prototype.selectRange = function(iStart, iLength) {
30
+ if (this.textbox.createTextRange) {
31
+ var oRange = this.textbox.createTextRange();
32
+ oRange.moveStart("character", iStart);
33
+ oRange.moveEnd("character", iLength - this.textbox.value.length);
34
+ oRange.select();
35
+ } else if (this.textbox.setSelectionRange) {
36
+ this.textbox.setSelectionRange(iStart, iLength);
37
+ }
38
+ this.textbox.focus();
39
+ };
40
+ AutoSuggestControl.prototype.typeAhead = function(sSuggestion) {
41
+ if (this.textbox.createTextRange || this.textbox.setSelectionRange) {
42
+ var iLen = this.textbox.value.length;
43
+ this.textbox.value = sSuggestion;
44
+ this.selectRange(iLen, sSuggestion.length);
45
+ }
46
+ };
47
+ AutoSuggestControl.prototype.autosuggest = function(aSuggestions, bTypeAhead) {
48
+ if (aSuggestions.length > 0) {
49
+ if (bTypeAhead) {
50
+ this.typeAhead(aSuggestions[0]);
51
+ }
52
+ this.showSuggestions(aSuggestions);
53
+ } else {
54
+ this.hideSuggestions();
55
+ }
56
+ };
57
+ AutoSuggestControl.prototype.handleKeyUp = function(oEvent) {
58
+ var iKeyCode = oEvent.keyCode;
59
+
60
+ if (iKeyCode == 8 || iKeyCode == 46) {
61
+ this.provider.requestSuggestions(this, false);
62
+
63
+ } else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode <= 46) || (iKeyCode >= 112 && iKeyCode <= 123)) {
64
+ //ignore
65
+ } else {
66
+ this.provider.requestSuggestions(this, true);
67
+ }
68
+ };
69
+ AutoSuggestControl.prototype.handleKeyDown = function (oEvent) {
70
+ switch(oEvent.keyCode) {
71
+ case 38: //up arrow
72
+ this.previousSuggestion();
73
+ break;
74
+ case 40: //down arrow
75
+ this.nextSuggestion();
76
+ break;
77
+ case 13: //enter
78
+ this.hideSuggestions();
79
+ break;
80
+ }
81
+ };
82
+ AutoSuggestControl.prototype.hideSuggestions = function() {
83
+ this.layer.style.visibility = "hidden";
84
+ };
85
+ AutoSuggestControl.prototype.highlightSuggestion = function(oSuggestionNode) {
86
+ for (var i=0; i < this.layer.childNodes.length; i++) {
87
+ var oNode = this.layer.childNodes[i];
88
+ if (oNode == oSuggestionNode) {
89
+ oNode.className = "current"
90
+ } else if (oNode.className == "current") {
91
+ oNode.className = "";
92
+ }
93
+ }
94
+ };
95
+ AutoSuggestControl.prototype.createDropDown = function() {
96
+
97
+ this.layer = document.createElement("div");
98
+ this.layer.className = "suggestions";
99
+ this.layer.style.visibility = "hidden";
100
+ this.layer.style.width = this.textbox.offsetWidth;
101
+ document.body.appendChild(this.layer);
102
+
103
+ var oThis = this;
104
+
105
+ this.layer.onmousedown = this.layer.onmouseup =
106
+ this.layer.onmouseover = function(oEvent) {
107
+ oEvent = oEvent || window.event;
108
+ oTarget = oEvent.target || oEvent.srcElement;
109
+
110
+ if (oEvent.type == "mousedown") {
111
+ oThis.textbox.value = oTarget.firstChild.nodeValue;
112
+ oThis.hideSuggestions();
113
+ } else if (oEvent.type == "mouseover") {
114
+ oThis.highlightSuggestion(oTarget);
115
+ } else {
116
+ oThis.textbox.focus();
117
+ }
118
+ };
119
+ };
120
+ AutoSuggestControl.prototype.getLeft = function() {
121
+
122
+ var oNode = this.textbox;
123
+ var iLeft = 0;
124
+
125
+ while (oNode.tagName != "BODY") {
126
+ iLeft += oNode.offsetLeft;
127
+ oNode = oNode.offsetParent;
128
+ }
129
+
130
+ return iLeft;
131
+ };
132
+ AutoSuggestControl.prototype.getTop = function() {
133
+
134
+ var oNode = this.textbox;
135
+ var iTop = 0;
136
+
137
+ while (oNode.tagName != "BODY") {
138
+ iTop += oNode.offsetTop;
139
+ oNode = oNode.offsetParent;
140
+ }
141
+
142
+ return iTop;
143
+ };
144
+ AutoSuggestControl.prototype.showSuggestions = function(aSuggestions) {
145
+
146
+ var oDiv = null;
147
+ this.layer.innerHTML = "";
148
+
149
+ for (var i = 0; i < aSuggestions.length; i++) {
150
+ oDiv = document.createElement("div");
151
+ oDiv.appendChild(document.createTextNode(aSuggestions[i]));
152
+ this.layer.appendChild(oDiv);
153
+ }
154
+
155
+ this.layer.style.left = this.getLeft() + "px";
156
+ this.layer.style.top = (this.getTop()+this.textbox.offsetHeight) + "px";
157
+ this.layer.style.width = this.textbox.offsetWidth + "px";
158
+ this.layer.style.visibility = "visible";
159
+ };
160
+ AutoSuggestControl.prototype.nextSuggestion = function() {
161
+ var cSuggestionNodes = this.layer.childNodes;
162
+
163
+ if (cSuggestionNodes.length > 0 && this.cur < cSuggestionNodes.length-1) {
164
+ var oNode = cSuggestionNodes[++this.cur];
165
+ this.highlightSuggestion(oNode);
166
+ this.textbox.value = oNode.firstChild.nodeValue;
167
+ }
168
+ };
169
+ AutoSuggestControl.prototype.previousSuggestion = function() {
170
+ var cSuggestionNodes = this.layer.childNodes;
171
+
172
+ if (cSuggestionNodes.length > 0 && this.cur > 0) {
173
+ var oNode = cSuggestionNodes[--this.cur];
174
+ this.highlightSuggestion(oNode);
175
+ this.textbox.value = oNode.firstChild.nodeValue;
176
+ }
177
+ };
178
+
179
+ function bsearch(aArray, item, less_than) {
180
+ var left = -1,
181
+ right = aArray.length,
182
+ mid;
183
+ while (right > left + 1) {
184
+ mid = (left + right) >>> 1;
185
+ if (less_than(aArray[mid], item)) {
186
+ left = mid;
187
+ } else {
188
+ right = mid;
189
+ }
190
+ }
191
+ return right;
192
+ }