ferret 0.11.3 → 0.11.4

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/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
+ }