-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsearch.py
121 lines (109 loc) · 3.38 KB
/
search.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#
# Extremely, extremely low-rent search.
# I am going to burn in some small hell for this.
import htmlrends, views
import re
#
# Okay, less low-rent than it used to be.
# Find an identifier in some data.
# Implementation: regexp search looking for word boundaries around the
# identifier (as literal text).
def find_id_in(iden, data):
iden = re.escape(iden)
pat = re.compile(r"(?:\b|[a-z0-9])%s(?:\b|[A-Z])" % iden)
return bool(pat.search(data))
def find_in(what, data):
if find_id_in(what, data):
return True
what = re.escape(what)
pat = re.compile(r"(?:\b|_)%s" % what, re.IGNORECASE)
return bool(pat.search(data))
# This is a brute-force search of all pages from the root downwards.
# Our sole concession to efficiency is that we turn off page caching
# during the search.
def search_pages(context, args, findfunc = find_id_in):
flist = context.model.get_page("").descendants(context)
# We brute-force search all pages.
hitlist = []
context.model.set_cache(False)
for mtime, path in flist:
if path == context.page.path:
continue
pg = context.model.get_page(path)
if not pg.realpage():
continue
for iden in args:
if pg.path == iden or \
findfunc(iden, pg.path):
hitlist.append(path)
break
# We only bother with expensive access
# control checks if it's a hit to start
# with. The odds are against us.
if findfunc(iden, pg.contents()) and \
pg.access_ok(context):
hitlist.append(path)
break
context.model.set_cache(True)
return hitlist
def can_search(context):
if "search-on" not in context:
return False
if context["search-on"] == "authenticated":
if not context.model.has_authentication() or \
not context.current_user():
return False
return True
#
# Renderers to display the results of all of this minimal effort.
search_form = '<form method=get action="%s">Search: <input name=search size=15></form>'
def searchbox(context):
"""Create the search form, if searching is enabled."""
if not can_search(context):
return ''
# We use the root instead of some synthetic paths because that
# seems representative of what's actually going on.
# This is a GET form, so we are not screwing ourselves on various
# issues that way.
return search_form % context.web.url_from_path("")
htmlrends.register("search::enter", searchbox)
def display_results(context):
"""Display the results of a search."""
if not can_search(context):
return ''
data = context.getviewvar("search")
if not data:
return ''
data = data.strip()
if not data:
return ''
hlist = search_pages(context, [data], find_in)
if not hlist:
return ''
hlist.sort()
res = []
res.append("<ul>\n")
for path in hlist:
res.append("<li>")
res.append(htmlrends.makelink(path,
context.web.url_from_path(path)))
res.append("\n")
res.append("</ul>\n")
return ''.join(res)
htmlrends.register("search::display", display_results)
# View registration.
#
# Search view is always a view on the root, so it can't be redirected
# off the root.
class SearchView(views.TemplateView):
def render(self):
if not can_search(self.context):
self.error("badaccess")
else:
super(SearchView, self).render()
def redirect_root(self):
return False
# In theory you can use ?search=... on anything, although we only
# generate it on the root.
# We do not POST to search, so this is actually disallowed.
views.register("search", SearchView, getParams = ("search",), onDir = True)