@@ -44,96 +44,97 @@ from webhelpers.html.builder import esca
from sqlalchemy import engine_from_config
from rhodecode.model import init_model
from rhodecode.model.scm import ScmModel
from rhodecode.model.repo import RepoModel
from rhodecode.config.environment import load_environment
from rhodecode.lib.utils2 import LazyProperty
from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
load_rcextensions
log = logging.getLogger(__name__)
# CUSTOM ANALYZER wordsplit + lowercase filter
ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
#INDEX SCHEMA DEFINITION
SCHEMA = Schema(
fileid=ID(unique=True),
owner=TEXT(),
repository=TEXT(stored=True),
path=TEXT(stored=True),
content=FieldType(format=Characters(), analyzer=ANALYZER,
scorable=True, stored=True),
modtime=STORED(),
extension=TEXT(stored=True)
)
IDX_NAME = 'HG_INDEX'
FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
FRAGMENTER = ContextFragmenter(200)
CHGSETS_SCHEMA = Schema(
raw_id=ID(unique=True, stored=True),
date=NUMERIC(stored=True),
last=BOOLEAN(),
repository=ID(unique=True, stored=True),
author=TEXT(stored=True),
message=FieldType(format=Characters(), analyzer=ANALYZER,
parents=TEXT(),
added=TEXT(),
removed=TEXT(),
changed=TEXT(),
CHGSET_IDX_NAME = 'CHGSET_INDEX'
class MakeIndex(BasePasterCommand):
max_args = 1
min_args = 1
usage = "CONFIG_FILE"
summary = "Creates index for full text search given configuration file"
group_name = "RhodeCode"
takes_config_file = -1
parser = Command.standard_parser(verbose=True)
def command(self):
logging.config.fileConfig(self.path_to_ini_file)
from pylons import config
add_cache(config)
engine = engine_from_config(config, 'sqlalchemy.db1.')
init_model(engine)
index_location = config['index_dir']
repo_location = self.options.repo_location \
if self.options.repo_location else RepoModel().repos_path
repo_list = map(strip, self.options.repo_list.split(',')) \
if self.options.repo_list else None
repo_update_list = map(strip, self.options.repo_update_list.split(',')) \
if self.options.repo_update_list else None
load_rcextensions(config['here'])
#======================================================================
# WHOOSH DAEMON
from rhodecode.lib.pidlock import LockHeld, DaemonLock
from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
try:
l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
WhooshIndexingDaemon(index_location=index_location,
repo_location=repo_location,
repo_list=repo_list,
repo_update_list=repo_update_list)\
.run(full_index=self.options.full_index)
l.release()
except LockHeld:
sys.exit(1)
def update_parser(self):
self.parser.add_option('--repo-location',
action='store',
dest='repo_location',
help="Specifies repositories location to index OPTIONAL",
self.parser.add_option('--index-only',
@@ -173,93 +174,98 @@ class WhooshResultWrapper(object):
while self.matcher.is_active():
docnum = self.matcher.id()
chunks = [offsets for offsets in self.get_chunks()]
docs_id.append([docnum, chunks])
self.matcher.next()
return docs_id
def __str__(self):
return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
def __repr__(self):
return self.__str__()
def __len__(self):
return len(self.doc_ids)
def __iter__(self):
"""
Allows Iteration over results,and lazy generate content
*Requires* implementation of ``__getitem__`` method.
for docid in self.doc_ids:
yield self.get_full_content(docid)
def __getitem__(self, key):
Slicing of resultWrapper
i, j = key.start, key.stop
slices = []
for docid in self.doc_ids[i:j]:
slices.append(self.get_full_content(docid))
return slices
def get_full_content(self, docid):
res = self.searcher.stored_fields(docid[0])
log.debug('result: %s' % res)
if self.search_type == 'content':
full_repo_path = jn(self.repo_location, res['repository'])
f_path = res['path'].split(full_repo_path)[-1]
f_path = f_path.lstrip(os.sep)
content_short = self.get_short_content(res, docid[1])
res.update({'content_short': content_short,
'content_short_hl': self.highlight(content_short),
'f_path': f_path
})
elif self.search_type == 'path':
res.update({'f_path': f_path})
elif self.search_type == 'message':
res.update({'message_hl': self.highlight(res['message'])})
return res
def get_short_content(self, res, chunks):
return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
def get_chunks(self):
Smart function that implements chunking the content
but not overlap chunks so it doesn't highlight the same
close occurrences twice.
:param matcher:
:param size:
memory = [(0, 0)]
if self.matcher.supports('positions'):
for span in self.matcher.spans():
start = span.startchar or 0
end = span.endchar or 0
start_offseted = max(0, start - self.fragment_size)
end_offseted = end + self.fragment_size
if start_offseted < memory[-1][1]:
start_offseted = memory[-1][1]
memory.append((start_offseted, end_offseted,))
yield (start_offseted, end_offseted,)
def highlight(self, content, top=5):
if self.search_type not in ['content', 'message']:
return ''
hl = highlight(
text=content,
terms=self.highlight_items,
analyzer=ANALYZER,
fragmenter=FRAGMENTER,
formatter=FORMATTER,
top=top
return hl
@@ -45,48 +45,55 @@ class TestSearchController(TestControlle
def test_search_commit_message(self):
self.log_user()
response = self.app.get(url(controller='search', action='index'),
{'q': 'bother to ask where to fetch repo during tests',
'type': 'commit'})
response.mustcontain('2 results')
response.mustcontain('a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')
response.mustcontain('c6eb379775c578a95dad8ddab53f963b80894850')
def test_search_commit_message_hg_repo(self):
response = self.app.get(url(controller='search', action='index',
search_repo=HG_REPO),
response.mustcontain('1 results')
def test_search_commit_changed_file(self):
{'q': 'changed:tests/utils.py',
def test_search_commit_added_file(self):
{'q': 'added:README.rst',
#HG
response.mustcontain('3803844fdbd3b711175fc3da9bdacfcd6d29a6fb')
#GIT
response.mustcontain('ff7ca51e58c505fec0dd2491de52c622bb7a806b')
def test_search_author(self):
{'q': 'author:marcin@python-blog.com raw_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545',
def test_search_file_name(self):
{'q': 'README.rst', 'type': 'path'})
\ No newline at end of file
Status change: