##// END OF EJS Templates
UI fixes for searching
marcink -
r2389:324b8382 beta
parent child Browse files
Show More
@@ -1,241 +1,241
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.indexers.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Whoosh indexing module for RhodeCode
7 7
8 8 :created_on: Aug 17, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27 import traceback
28 28 import logging
29 29 from os.path import dirname as dn, join as jn
30 30
31 31 #to get the rhodecode import
32 32 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
33 33
34 34 from string import strip
35 35 from shutil import rmtree
36 36
37 37 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
38 38 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
39 39 from whoosh.index import create_in, open_dir
40 40 from whoosh.formats import Characters
41 41 from whoosh.highlight import highlight, HtmlFormatter, ContextFragmenter
42 42
43 from webhelpers.html.builder import escape
43 from webhelpers.html.builder import escape, literal
44 44 from sqlalchemy import engine_from_config
45 45
46 46 from rhodecode.model import init_model
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.repo import RepoModel
49 49 from rhodecode.config.environment import load_environment
50 50 from rhodecode.lib.utils2 import LazyProperty
51 51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
52 52 load_rcextensions
53 53
54 54 # CUSTOM ANALYZER wordsplit + lowercase filter
55 55 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
56 56
57 57
58 58 #INDEX SCHEMA DEFINITION
59 59 SCHEMA = Schema(
60 60 fileid=ID(unique=True),
61 61 owner=TEXT(),
62 62 repository=TEXT(stored=True),
63 63 path=TEXT(stored=True),
64 64 content=FieldType(format=Characters(), analyzer=ANALYZER,
65 65 scorable=True, stored=True),
66 66 modtime=STORED(),
67 67 extension=TEXT(stored=True)
68 68 )
69 69
70 70 IDX_NAME = 'HG_INDEX'
71 71 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
72 72 FRAGMENTER = ContextFragmenter(200)
73 73
74 74
75 75 class MakeIndex(BasePasterCommand):
76 76
77 77 max_args = 1
78 78 min_args = 1
79 79
80 80 usage = "CONFIG_FILE"
81 81 summary = "Creates index for full text search given configuration file"
82 82 group_name = "RhodeCode"
83 83 takes_config_file = -1
84 84 parser = Command.standard_parser(verbose=True)
85 85
86 86 def command(self):
87 87 logging.config.fileConfig(self.path_to_ini_file)
88 88 from pylons import config
89 89 add_cache(config)
90 90 engine = engine_from_config(config, 'sqlalchemy.db1.')
91 91 init_model(engine)
92 92 index_location = config['index_dir']
93 93 repo_location = self.options.repo_location \
94 94 if self.options.repo_location else RepoModel().repos_path
95 95 repo_list = map(strip, self.options.repo_list.split(',')) \
96 96 if self.options.repo_list else None
97 97 repo_update_list = map(strip, self.options.repo_update_list.split(',')) \
98 98 if self.options.repo_update_list else None
99 99 load_rcextensions(config['here'])
100 100 #======================================================================
101 101 # WHOOSH DAEMON
102 102 #======================================================================
103 103 from rhodecode.lib.pidlock import LockHeld, DaemonLock
104 104 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
105 105 try:
106 106 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
107 107 WhooshIndexingDaemon(index_location=index_location,
108 108 repo_location=repo_location,
109 109 repo_list=repo_list,
110 110 repo_update_list=repo_update_list)\
111 111 .run(full_index=self.options.full_index)
112 112 l.release()
113 113 except LockHeld:
114 114 sys.exit(1)
115 115
116 116 def update_parser(self):
117 117 self.parser.add_option('--repo-location',
118 118 action='store',
119 119 dest='repo_location',
120 120 help="Specifies repositories location to index OPTIONAL",
121 121 )
122 122 self.parser.add_option('--index-only',
123 123 action='store',
124 124 dest='repo_list',
125 125 help="Specifies a comma separated list of repositores "
126 126 "to build index on. If not given all repositories "
127 127 "are scanned for indexing. OPTIONAL",
128 128 )
129 129 self.parser.add_option('--update-only',
130 130 action='store',
131 131 dest='repo_update_list',
132 132 help="Specifies a comma separated list of repositores "
133 133 "to re-build index on. OPTIONAL",
134 134 )
135 135 self.parser.add_option('-f',
136 136 action='store_true',
137 137 dest='full_index',
138 138 help="Specifies that index should be made full i.e"
139 139 " destroy old and build from scratch",
140 140 default=False)
141 141
142 142
143 143 class WhooshResultWrapper(object):
144 144 def __init__(self, search_type, searcher, matcher, highlight_items,
145 145 repo_location):
146 146 self.search_type = search_type
147 147 self.searcher = searcher
148 148 self.matcher = matcher
149 149 self.highlight_items = highlight_items
150 150 self.fragment_size = 200
151 151 self.repo_location = repo_location
152 152
153 153 @LazyProperty
154 154 def doc_ids(self):
155 155 docs_id = []
156 156 while self.matcher.is_active():
157 157 docnum = self.matcher.id()
158 158 chunks = [offsets for offsets in self.get_chunks()]
159 159 docs_id.append([docnum, chunks])
160 160 self.matcher.next()
161 161 return docs_id
162 162
163 163 def __str__(self):
164 164 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
165 165
166 166 def __repr__(self):
167 167 return self.__str__()
168 168
169 169 def __len__(self):
170 170 return len(self.doc_ids)
171 171
172 172 def __iter__(self):
173 173 """
174 174 Allows Iteration over results,and lazy generate content
175 175
176 176 *Requires* implementation of ``__getitem__`` method.
177 177 """
178 178 for docid in self.doc_ids:
179 179 yield self.get_full_content(docid)
180 180
181 181 def __getitem__(self, key):
182 182 """
183 183 Slicing of resultWrapper
184 184 """
185 185 i, j = key.start, key.stop
186 186
187 187 slices = []
188 188 for docid in self.doc_ids[i:j]:
189 189 slices.append(self.get_full_content(docid))
190 190 return slices
191 191
192 192 def get_full_content(self, docid):
193 193 res = self.searcher.stored_fields(docid[0])
194 194 full_repo_path = jn(self.repo_location, res['repository'])
195 195 f_path = res['path'].split(full_repo_path)[-1]
196 196 f_path = f_path.lstrip(os.sep)
197 197
198 198 content_short = self.get_short_content(res, docid[1])
199 199 res.update({'content_short': content_short,
200 200 'content_short_hl': self.highlight(content_short),
201 201 'f_path': f_path})
202 202
203 203 return res
204 204
205 205 def get_short_content(self, res, chunks):
206 206
207 207 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
208 208
209 209 def get_chunks(self):
210 210 """
211 211 Smart function that implements chunking the content
212 212 but not overlap chunks so it doesn't highlight the same
213 213 close occurrences twice.
214 214
215 215 :param matcher:
216 216 :param size:
217 217 """
218 218 memory = [(0, 0)]
219 219 for span in self.matcher.spans():
220 220 start = span.startchar or 0
221 221 end = span.endchar or 0
222 222 start_offseted = max(0, start - self.fragment_size)
223 223 end_offseted = end + self.fragment_size
224 224
225 225 if start_offseted < memory[-1][1]:
226 226 start_offseted = memory[-1][1]
227 227 memory.append((start_offseted, end_offseted,))
228 228 yield (start_offseted, end_offseted,)
229 229
230 230 def highlight(self, content, top=5):
231 231 if self.search_type != 'content':
232 232 return ''
233 233 hl = highlight(
234 text=escape(content),
234 text=content,
235 235 terms=self.highlight_items,
236 236 analyzer=ANALYZER,
237 237 fragmenter=FRAGMENTER,
238 238 formatter=FORMATTER,
239 239 top=top
240 240 )
241 241 return hl
@@ -1,80 +1,81
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Search')}
5 5 ${'"%s"' % c.cur_query if c.cur_query else None}
6 6 %if c.repo_name:
7 7 ${_('in repository: ') + c.repo_name}
8 8 %else:
9 9 ${_('in all repositories')}
10 10 %endif
11 11 - ${c.rhodecode_name}
12 12 </%def>
13 13 <%def name="breadcrumbs()">
14 14 ${c.rhodecode_name}
15 15 </%def>
16 16 <%def name="page_nav()">
17 17 ${self.menu('home')}
18 18 </%def>
19 19 <%def name="main()">
20 20
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 <h5>${_('Search')}
25 25 %if c.repo_name:
26 26 ${_('in repository: ') + c.repo_name}
27 27 %else:
28 28 ${_('in all repositories')}
29 29 %endif
30 30 </h5>
31 31 </div>
32 32 <!-- end box / title -->
33 33 %if c.repo_name:
34 34 ${h.form(h.url('search_repo',search_repo=c.repo_name),method='get')}
35 35 %else:
36 36 ${h.form(h.url('search'),method='get')}
37 37 %endif
38 38 <div class="form">
39 39 <div class="fields">
40 40 <div class="field field-first field-noborder">
41 41 <div class="label">
42 42 <label for="q">${_('Search term')}</label>
43 43 </div>
44 44 <div class="input">${h.text('q',c.cur_query,class_="small")}
45 45 <div class="button highlight">
46 46 <input type="submit" value="${_('Search')}" class="ui-button"/>
47 47 </div>
48 48 </div>
49 49 <div style="font-weight: bold;clear:Both;margin-left:200px">${c.runtime}</div>
50 50 </div>
51 51
52 52 <div class="field">
53 53 <div class="label">
54 54 <label for="type">${_('Search in')}</label>
55 55 </div>
56 56 <div class="select">
57 57 ${h.select('type',c.cur_type,[('content',_('File contents')),
58 58 ##('commit',_('Commit messages')),
59 59 ('path',_('File names')),
60 60 ##('repository',_('Repository names')),
61 61 ])}
62 62 </div>
63 63 </div>
64 64
65 65 </div>
66 66 </div>
67 67 ${h.end_form()}
68
68 <div class="search">
69 69 %if c.cur_search == 'content':
70 70 <%include file='search_content.html'/>
71 71 %elif c.cur_search == 'path':
72 72 <%include file='search_path.html'/>
73 73 %elif c.cur_search == 'commit':
74 74 <%include file='search_commit.html'/>
75 75 %elif c.cur_search == 'repository':
76 76 <%include file='search_repository.html'/>
77 77 %endif
78 </div>
78 79 </div>
79 80
80 81 </%def>
General Comments 0
You need to be logged in to leave comments. Login now