whoosh3 3.0.0__py2.py3-none-any.whl
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.
- whoosh/__init__.py +50 -0
- whoosh/analysis/__init__.py +119 -0
- whoosh/analysis/acore.py +157 -0
- whoosh/analysis/analyzers.py +315 -0
- whoosh/analysis/filters.py +533 -0
- whoosh/analysis/intraword.py +509 -0
- whoosh/analysis/morph.py +268 -0
- whoosh/analysis/ngrams.py +250 -0
- whoosh/analysis/tokenizers.py +362 -0
- whoosh/automata/__init__.py +0 -0
- whoosh/automata/fsa.py +704 -0
- whoosh/automata/fst.py +1574 -0
- whoosh/automata/glob.py +90 -0
- whoosh/automata/lev.py +27 -0
- whoosh/automata/reg.py +126 -0
- whoosh/classify.py +385 -0
- whoosh/codec/__init__.py +32 -0
- whoosh/codec/base.py +872 -0
- whoosh/codec/memory.py +343 -0
- whoosh/codec/plaintext.py +458 -0
- whoosh/codec/whoosh2.py +2275 -0
- whoosh/codec/whoosh3.py +1351 -0
- whoosh/collectors.py +1175 -0
- whoosh/columns.py +1421 -0
- whoosh/externalsort.py +238 -0
- whoosh/fields.py +1680 -0
- whoosh/filedb/__init__.py +0 -0
- whoosh/filedb/compound.py +337 -0
- whoosh/filedb/fileindex.py +612 -0
- whoosh/filedb/filepostings.py +463 -0
- whoosh/filedb/filereading.py +246 -0
- whoosh/filedb/filestore.py +678 -0
- whoosh/filedb/filetables.py +736 -0
- whoosh/filedb/filewriting.py +307 -0
- whoosh/filedb/gae.py +164 -0
- whoosh/filedb/misc.py +53 -0
- whoosh/filedb/pools.py +476 -0
- whoosh/filedb/structfile.py +410 -0
- whoosh/formats.py +491 -0
- whoosh/highlight.py +1119 -0
- whoosh/idsets.py +951 -0
- whoosh/index.py +728 -0
- whoosh/lang/__init__.py +169 -0
- whoosh/lang/dmetaphone.py +509 -0
- whoosh/lang/isri.py +457 -0
- whoosh/lang/lovins.py +574 -0
- whoosh/lang/morph_en.py +1147 -0
- whoosh/lang/paicehusk.py +251 -0
- whoosh/lang/phonetic.py +117 -0
- whoosh/lang/porter.py +184 -0
- whoosh/lang/porter2.py +346 -0
- whoosh/lang/snowball/__init__.py +74 -0
- whoosh/lang/snowball/bases.py +133 -0
- whoosh/lang/snowball/danish.py +159 -0
- whoosh/lang/snowball/dutch.py +188 -0
- whoosh/lang/snowball/english.py +517 -0
- whoosh/lang/snowball/finnish.py +354 -0
- whoosh/lang/snowball/french.py +495 -0
- whoosh/lang/snowball/german.py +153 -0
- whoosh/lang/snowball/hungarian.py +420 -0
- whoosh/lang/snowball/italian.py +372 -0
- whoosh/lang/snowball/norwegian.py +120 -0
- whoosh/lang/snowball/portugese.py +336 -0
- whoosh/lang/snowball/romanian.py +484 -0
- whoosh/lang/snowball/russian.py +830 -0
- whoosh/lang/snowball/spanish.py +414 -0
- whoosh/lang/snowball/swedish.py +110 -0
- whoosh/lang/stopwords.py +296 -0
- whoosh/lang/wordnet.py +241 -0
- whoosh/legacy.py +81 -0
- whoosh/matching/__init__.py +62 -0
- whoosh/matching/binary.py +794 -0
- whoosh/matching/combo.py +316 -0
- whoosh/matching/mcore.py +625 -0
- whoosh/matching/wrappers.py +583 -0
- whoosh/multiproc.py +394 -0
- whoosh/qparser/__init__.py +83 -0
- whoosh/qparser/common.py +61 -0
- whoosh/qparser/dateparse.py +988 -0
- whoosh/qparser/default.py +447 -0
- whoosh/qparser/plugins.py +1430 -0
- whoosh/qparser/syntax.py +652 -0
- whoosh/qparser/taggers.py +95 -0
- whoosh/query/__init__.py +86 -0
- whoosh/query/compound.py +658 -0
- whoosh/query/nested.py +416 -0
- whoosh/query/positional.py +277 -0
- whoosh/query/qcolumns.py +118 -0
- whoosh/query/qcore.py +738 -0
- whoosh/query/ranges.py +451 -0
- whoosh/query/spans.py +930 -0
- whoosh/query/terms.py +570 -0
- whoosh/query/wrappers.py +196 -0
- whoosh/reading.py +1328 -0
- whoosh/scoring.py +627 -0
- whoosh/searching.py +1709 -0
- whoosh/sorting.py +1149 -0
- whoosh/spelling.py +343 -0
- whoosh/support/__init__.py +0 -0
- whoosh/support/base85.py +104 -0
- whoosh/support/bench.py +773 -0
- whoosh/support/bitstream.py +69 -0
- whoosh/support/bitvector.py +494 -0
- whoosh/support/charset.py +1378 -0
- whoosh/support/levenshtein.py +71 -0
- whoosh/support/relativedelta.py +490 -0
- whoosh/support/unicode.py +532 -0
- whoosh/system.py +76 -0
- whoosh/util/__init__.py +141 -0
- whoosh/util/cache.py +101 -0
- whoosh/util/filelock.py +161 -0
- whoosh/util/loading.py +84 -0
- whoosh/util/numeric.py +605 -0
- whoosh/util/numlists.py +631 -0
- whoosh/util/testing.py +133 -0
- whoosh/util/text.py +135 -0
- whoosh/util/times.py +516 -0
- whoosh/util/varints.py +102 -0
- whoosh/util/versions.py +169 -0
- whoosh/writing.py +1330 -0
- whoosh3-3.0.0.dist-info/METADATA +182 -0
- whoosh3-3.0.0.dist-info/RECORD +125 -0
- whoosh3-3.0.0.dist-info/WHEEL +6 -0
- whoosh3-3.0.0.dist-info/licenses/LICENSE.txt +26 -0
- whoosh3-3.0.0.dist-info/top_level.txt +1 -0
whoosh/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright 2008 Matt Chaput. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
|
5
|
+
#
|
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
# this list of conditions and the following disclaimer.
|
|
8
|
+
#
|
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
# documentation and/or other materials provided with the distribution.
|
|
12
|
+
#
|
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
|
|
14
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
15
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
16
|
+
# EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
17
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
18
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
19
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
20
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
21
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
22
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
23
|
+
#
|
|
24
|
+
# The views and conclusions contained in the software and documentation are
|
|
25
|
+
# those of the authors and should not be interpreted as representing official
|
|
26
|
+
# policies, either expressed or implied, of Matt Chaput.
|
|
27
|
+
|
|
28
|
+
__version__ = (3, 0, 0)
|
|
29
|
+
__version_str__ = "3.0.0"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def versionstring(build=True, extra=True):
|
|
33
|
+
"""Returns the version number of Whoosh as a string.
|
|
34
|
+
|
|
35
|
+
:param build: Whether to include the build number in the string.
|
|
36
|
+
:param extra: Whether to include alpha/beta/rc etc. tags. Only
|
|
37
|
+
checked if build is True.
|
|
38
|
+
:rtype: str
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if build:
|
|
42
|
+
first = 3
|
|
43
|
+
else:
|
|
44
|
+
first = 2
|
|
45
|
+
|
|
46
|
+
s = ".".join(str(n) for n in __version__[:first])
|
|
47
|
+
if build and extra:
|
|
48
|
+
s += "".join(str(n) for n in __version__[3:])
|
|
49
|
+
|
|
50
|
+
return s
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright 2007 Matt Chaput. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
|
5
|
+
#
|
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
# this list of conditions and the following disclaimer.
|
|
8
|
+
#
|
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
# documentation and/or other materials provided with the distribution.
|
|
12
|
+
#
|
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
|
|
14
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
15
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
16
|
+
# EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
17
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
18
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
19
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
20
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
21
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
22
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
23
|
+
#
|
|
24
|
+
# The views and conclusions contained in the software and documentation are
|
|
25
|
+
# those of the authors and should not be interpreted as representing official
|
|
26
|
+
# policies, either expressed or implied, of Matt Chaput.
|
|
27
|
+
|
|
28
|
+
"""Classes and functions for turning a piece of text into an indexable stream
|
|
29
|
+
of "tokens" (usually equivalent to words). There are three general classes
|
|
30
|
+
involved in analysis:
|
|
31
|
+
|
|
32
|
+
* Tokenizers are always at the start of the text processing pipeline. They take
|
|
33
|
+
a string and yield Token objects (actually, the same token object over and
|
|
34
|
+
over, for performance reasons) corresponding to the tokens (words) in the
|
|
35
|
+
text.
|
|
36
|
+
|
|
37
|
+
Every tokenizer is a callable that takes a string and returns an iterator of
|
|
38
|
+
tokens.
|
|
39
|
+
|
|
40
|
+
* Filters take the tokens from the tokenizer and perform various
|
|
41
|
+
transformations on them. For example, the LowercaseFilter converts all tokens
|
|
42
|
+
to lowercase, which is usually necessary when indexing regular English text.
|
|
43
|
+
|
|
44
|
+
Every filter is a callable that takes a token generator and returns a token
|
|
45
|
+
generator.
|
|
46
|
+
|
|
47
|
+
* Analyzers are convenience functions/classes that "package up" a tokenizer and
|
|
48
|
+
zero or more filters into a single unit. For example, the StandardAnalyzer
|
|
49
|
+
combines a RegexTokenizer, LowercaseFilter, and StopFilter.
|
|
50
|
+
|
|
51
|
+
Every analyzer is a callable that takes a string and returns a token
|
|
52
|
+
iterator. (So Tokenizers can be used as Analyzers if you don't need any
|
|
53
|
+
filtering).
|
|
54
|
+
|
|
55
|
+
You can compose tokenizers and filters together using the ``|`` character::
|
|
56
|
+
|
|
57
|
+
my_analyzer = RegexTokenizer() | LowercaseFilter() | StopFilter()
|
|
58
|
+
|
|
59
|
+
The first item must be a tokenizer and the rest must be filters (you can't put
|
|
60
|
+
a filter first or a tokenizer after the first item).
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
from whoosh.analysis.acore import (
|
|
64
|
+
Composable,
|
|
65
|
+
CompositionError,
|
|
66
|
+
Token,
|
|
67
|
+
entoken,
|
|
68
|
+
unstopped,
|
|
69
|
+
)
|
|
70
|
+
from whoosh.analysis.analyzers import (
|
|
71
|
+
Analyzer,
|
|
72
|
+
FancyAnalyzer,
|
|
73
|
+
IDAnalyzer,
|
|
74
|
+
KeywordAnalyzer,
|
|
75
|
+
LanguageAnalyzer,
|
|
76
|
+
RegexAnalyzer,
|
|
77
|
+
SimpleAnalyzer,
|
|
78
|
+
StandardAnalyzer,
|
|
79
|
+
StemmingAnalyzer,
|
|
80
|
+
)
|
|
81
|
+
from whoosh.analysis.filters import (
|
|
82
|
+
STOP_WORDS,
|
|
83
|
+
CharsetFilter,
|
|
84
|
+
Composable,
|
|
85
|
+
DelimitedAttributeFilter,
|
|
86
|
+
Filter,
|
|
87
|
+
LoggingFilter,
|
|
88
|
+
LowercaseFilter,
|
|
89
|
+
MultiFilter,
|
|
90
|
+
PassFilter,
|
|
91
|
+
ReverseTextFilter,
|
|
92
|
+
StopFilter,
|
|
93
|
+
StripFilter,
|
|
94
|
+
SubstitutionFilter,
|
|
95
|
+
TeeFilter,
|
|
96
|
+
url_pattern,
|
|
97
|
+
)
|
|
98
|
+
from whoosh.analysis.intraword import (
|
|
99
|
+
BiWordFilter,
|
|
100
|
+
CompoundWordFilter,
|
|
101
|
+
IntraWordFilter,
|
|
102
|
+
ShingleFilter,
|
|
103
|
+
)
|
|
104
|
+
from whoosh.analysis.morph import DoubleMetaphoneFilter, PyStemmerFilter, StemFilter
|
|
105
|
+
from whoosh.analysis.ngrams import (
|
|
106
|
+
NgramAnalyzer,
|
|
107
|
+
NgramFilter,
|
|
108
|
+
NgramTokenizer,
|
|
109
|
+
NgramWordAnalyzer,
|
|
110
|
+
)
|
|
111
|
+
from whoosh.analysis.tokenizers import (
|
|
112
|
+
CharsetTokenizer,
|
|
113
|
+
CommaSeparatedTokenizer,
|
|
114
|
+
IDTokenizer,
|
|
115
|
+
PathTokenizer,
|
|
116
|
+
RegexTokenizer,
|
|
117
|
+
SpaceSeparatedTokenizer,
|
|
118
|
+
Tokenizer,
|
|
119
|
+
)
|
whoosh/analysis/acore.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Copyright 2007 Matt Chaput. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
|
5
|
+
#
|
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
# this list of conditions and the following disclaimer.
|
|
8
|
+
#
|
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
# documentation and/or other materials provided with the distribution.
|
|
12
|
+
#
|
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
|
|
14
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
15
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
16
|
+
# EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
17
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
18
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
19
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
20
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
21
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
22
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
23
|
+
#
|
|
24
|
+
# The views and conclusions contained in the software and documentation are
|
|
25
|
+
# those of the authors and should not be interpreted as representing official
|
|
26
|
+
# policies, either expressed or implied, of Matt Chaput.
|
|
27
|
+
|
|
28
|
+
# Exceptions
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CompositionError(Exception):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Utility functions
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def unstopped(tokenstream):
|
|
39
|
+
"""Removes tokens from a token stream where token.stopped = True."""
|
|
40
|
+
return (t for t in tokenstream if not t.stopped)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def entoken(
|
|
44
|
+
textstream, positions=False, chars=False, start_pos=0, start_char=0, **kwargs
|
|
45
|
+
):
|
|
46
|
+
"""Takes a sequence of unicode strings and yields a series of Token objects
|
|
47
|
+
(actually the same Token object over and over, for performance reasons),
|
|
48
|
+
with the attributes filled in with reasonable values (for example, if
|
|
49
|
+
``positions`` or ``chars`` is True, the function assumes each token was
|
|
50
|
+
separated by one space).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
pos = start_pos
|
|
54
|
+
char = start_char
|
|
55
|
+
t = Token(positions=positions, chars=chars, **kwargs)
|
|
56
|
+
|
|
57
|
+
for text in textstream:
|
|
58
|
+
t.text = text
|
|
59
|
+
|
|
60
|
+
if positions:
|
|
61
|
+
t.pos = pos
|
|
62
|
+
pos += 1
|
|
63
|
+
|
|
64
|
+
if chars:
|
|
65
|
+
t.startchar = char
|
|
66
|
+
char = char + len(text)
|
|
67
|
+
t.endchar = char
|
|
68
|
+
|
|
69
|
+
yield t
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Token object
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Token:
|
|
76
|
+
"""
|
|
77
|
+
Represents a "token" (usually a word) extracted from the source text being
|
|
78
|
+
indexed.
|
|
79
|
+
|
|
80
|
+
See "Advanced analysis" in the user guide for more information.
|
|
81
|
+
|
|
82
|
+
Because object instantiation in Python is slow, tokenizers should create
|
|
83
|
+
ONE SINGLE Token object and YIELD IT OVER AND OVER, changing the attributes
|
|
84
|
+
each time.
|
|
85
|
+
|
|
86
|
+
This trick means that consumers of tokens (i.e. filters) must never try to
|
|
87
|
+
hold onto the token object between loop iterations, or convert the token
|
|
88
|
+
generator into a list. Instead, save the attributes between iterations,
|
|
89
|
+
not the object::
|
|
90
|
+
|
|
91
|
+
def RemoveDuplicatesFilter(self, stream):
|
|
92
|
+
# Removes duplicate words.
|
|
93
|
+
lasttext = None
|
|
94
|
+
for token in stream:
|
|
95
|
+
# Only yield the token if its text doesn't
|
|
96
|
+
# match the previous token.
|
|
97
|
+
if lasttext != token.text:
|
|
98
|
+
yield token
|
|
99
|
+
lasttext = token.text
|
|
100
|
+
|
|
101
|
+
...or, call token.copy() to get a copy of the token object.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self, positions=False, chars=False, removestops=True, mode="", **kwargs
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
:param positions: Whether tokens should have the token position in the
|
|
109
|
+
'pos' attribute.
|
|
110
|
+
:param chars: Whether tokens should have character offsets in the
|
|
111
|
+
'startchar' and 'endchar' attributes.
|
|
112
|
+
:param removestops: whether to remove stop words from the stream (if
|
|
113
|
+
the tokens pass through a stop filter).
|
|
114
|
+
:param mode: contains a string describing the purpose for which the
|
|
115
|
+
analyzer is being called, i.e. 'index' or 'query'.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
self.positions = positions
|
|
119
|
+
self.chars = chars
|
|
120
|
+
self.stopped = False
|
|
121
|
+
self.boost = 1.0
|
|
122
|
+
self.removestops = removestops
|
|
123
|
+
self.mode = mode
|
|
124
|
+
self.__dict__.update(kwargs)
|
|
125
|
+
|
|
126
|
+
def __repr__(self):
|
|
127
|
+
parms = ", ".join(f"{name}={value!r}" for name, value in self.__dict__.items())
|
|
128
|
+
return f"{self.__class__.__name__}({parms})"
|
|
129
|
+
|
|
130
|
+
def copy(self):
|
|
131
|
+
# This is faster than using the copy module
|
|
132
|
+
return Token(**self.__dict__)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Composition support
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Composable:
|
|
139
|
+
is_morph = False
|
|
140
|
+
|
|
141
|
+
def __or__(self, other):
|
|
142
|
+
from whoosh.analysis.analyzers import CompositeAnalyzer
|
|
143
|
+
|
|
144
|
+
if not isinstance(other, Composable):
|
|
145
|
+
raise TypeError(f"{self!r} is not composable with {other!r}")
|
|
146
|
+
return CompositeAnalyzer(self, other)
|
|
147
|
+
|
|
148
|
+
def __repr__(self):
|
|
149
|
+
attrs = ""
|
|
150
|
+
if self.__dict__:
|
|
151
|
+
attrs = ", ".join(
|
|
152
|
+
f"{key}={value!r}" for key, value in self.__dict__.items()
|
|
153
|
+
)
|
|
154
|
+
return self.__class__.__name__ + f"({attrs})"
|
|
155
|
+
|
|
156
|
+
def has_morph(self):
|
|
157
|
+
return self.is_morph
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Copyright 2007 Matt Chaput. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
|
5
|
+
#
|
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
# this list of conditions and the following disclaimer.
|
|
8
|
+
#
|
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
# documentation and/or other materials provided with the distribution.
|
|
12
|
+
#
|
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
|
|
14
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
15
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
16
|
+
# EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
17
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
18
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
19
|
+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
20
|
+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
21
|
+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
22
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
23
|
+
#
|
|
24
|
+
# The views and conclusions contained in the software and documentation are
|
|
25
|
+
# those of the authors and should not be interpreted as representing official
|
|
26
|
+
# policies, either expressed or implied, of Matt Chaput.
|
|
27
|
+
|
|
28
|
+
from whoosh.analysis.acore import Composable, CompositionError
|
|
29
|
+
from whoosh.analysis.filters import STOP_WORDS, LowercaseFilter, StopFilter
|
|
30
|
+
from whoosh.analysis.intraword import IntraWordFilter
|
|
31
|
+
from whoosh.analysis.morph import StemFilter
|
|
32
|
+
from whoosh.analysis.tokenizers import (
|
|
33
|
+
CommaSeparatedTokenizer,
|
|
34
|
+
IDTokenizer,
|
|
35
|
+
RegexTokenizer,
|
|
36
|
+
SpaceSeparatedTokenizer,
|
|
37
|
+
Tokenizer,
|
|
38
|
+
default_pattern,
|
|
39
|
+
)
|
|
40
|
+
from whoosh.lang.porter import stem
|
|
41
|
+
|
|
42
|
+
# Analyzers
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Analyzer(Composable):
|
|
46
|
+
"""Abstract base class for analyzers."""
|
|
47
|
+
|
|
48
|
+
def __repr__(self):
|
|
49
|
+
return f"{self.__class__.__name__}()"
|
|
50
|
+
|
|
51
|
+
def __eq__(self, other):
|
|
52
|
+
return (
|
|
53
|
+
other
|
|
54
|
+
and self.__class__ is other.__class__
|
|
55
|
+
and self.__dict__ == other.__dict__
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def __call__(self, value, **kwargs):
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
|
|
61
|
+
def clean(self):
|
|
62
|
+
# This method is intentionally left empty.
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CompositeAnalyzer(Analyzer):
|
|
67
|
+
def __init__(self, *composables):
|
|
68
|
+
self.items = []
|
|
69
|
+
|
|
70
|
+
for comp in composables:
|
|
71
|
+
if isinstance(comp, CompositeAnalyzer):
|
|
72
|
+
self.items.extend(comp.items)
|
|
73
|
+
else:
|
|
74
|
+
self.items.append(comp)
|
|
75
|
+
|
|
76
|
+
# Tokenizers must start a chain, and then only filters after that
|
|
77
|
+
# (because analyzers take a string and return a generator of tokens,
|
|
78
|
+
# and filters take and return generators of tokens)
|
|
79
|
+
for item in self.items[1:]:
|
|
80
|
+
if isinstance(item, Tokenizer):
|
|
81
|
+
raise CompositionError(
|
|
82
|
+
f"Only one tokenizer allowed at the start of the analyzer: {self.items}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def __repr__(self):
|
|
86
|
+
return "{}({})".format(
|
|
87
|
+
self.__class__.__name__,
|
|
88
|
+
", ".join(repr(item) for item in self.items),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def __call__(self, value, no_morph=False, **kwargs):
|
|
92
|
+
items = self.items
|
|
93
|
+
# Start with tokenizer
|
|
94
|
+
gen = items[0](value, **kwargs)
|
|
95
|
+
# Run filters
|
|
96
|
+
for item in items[1:]:
|
|
97
|
+
if not (no_morph and hasattr(item, "is_morph") and item.is_morph):
|
|
98
|
+
gen = item(gen)
|
|
99
|
+
return gen
|
|
100
|
+
|
|
101
|
+
def __getitem__(self, item):
|
|
102
|
+
return self.items.__getitem__(item)
|
|
103
|
+
|
|
104
|
+
def __len__(self):
|
|
105
|
+
return len(self.items)
|
|
106
|
+
|
|
107
|
+
def __eq__(self, other):
|
|
108
|
+
return other and self.__class__ is other.__class__ and self.items == other.items
|
|
109
|
+
|
|
110
|
+
def clean(self):
|
|
111
|
+
for item in self.items:
|
|
112
|
+
if hasattr(item, "clean"):
|
|
113
|
+
item.clean()
|
|
114
|
+
|
|
115
|
+
def has_morph(self):
|
|
116
|
+
return any(item.is_morph for item in self.items)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Functions that return composed analyzers
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def IDAnalyzer(lowercase=False):
|
|
123
|
+
"""Deprecated, just use an IDTokenizer directly, with a LowercaseFilter if
|
|
124
|
+
desired.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
tokenizer = IDTokenizer()
|
|
128
|
+
if lowercase:
|
|
129
|
+
tokenizer = tokenizer | LowercaseFilter()
|
|
130
|
+
return tokenizer
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def KeywordAnalyzer(lowercase=False, commas=False):
|
|
134
|
+
"""Parses whitespace- or comma-separated tokens.
|
|
135
|
+
|
|
136
|
+
>>> ana = KeywordAnalyzer()
|
|
137
|
+
>>> [token.text for token in ana("Hello there, this is a TEST")]
|
|
138
|
+
["Hello", "there,", "this", "is", "a", "TEST"]
|
|
139
|
+
|
|
140
|
+
:param lowercase: whether to lowercase the tokens.
|
|
141
|
+
:param commas: if True, items are separated by commas rather than
|
|
142
|
+
whitespace.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
if commas:
|
|
146
|
+
tokenizer = CommaSeparatedTokenizer()
|
|
147
|
+
else:
|
|
148
|
+
tokenizer = SpaceSeparatedTokenizer()
|
|
149
|
+
if lowercase:
|
|
150
|
+
tokenizer = tokenizer | LowercaseFilter()
|
|
151
|
+
return tokenizer
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def RegexAnalyzer(expression=r"\w+(\.?\w+)*", gaps=False):
|
|
155
|
+
"""Deprecated, just use a RegexTokenizer directly."""
|
|
156
|
+
|
|
157
|
+
return RegexTokenizer(expression=expression, gaps=gaps)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def SimpleAnalyzer(expression=default_pattern, gaps=False):
|
|
161
|
+
"""Composes a RegexTokenizer with a LowercaseFilter.
|
|
162
|
+
|
|
163
|
+
>>> ana = SimpleAnalyzer()
|
|
164
|
+
>>> [token.text for token in ana("Hello there, this is a TEST")]
|
|
165
|
+
["hello", "there", "this", "is", "a", "test"]
|
|
166
|
+
|
|
167
|
+
:param expression: The regular expression pattern to use to extract tokens.
|
|
168
|
+
:param gaps: If True, the tokenizer *splits* on the expression, rather
|
|
169
|
+
than matching on the expression.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
return RegexTokenizer(expression=expression, gaps=gaps) | LowercaseFilter()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def StandardAnalyzer(
|
|
176
|
+
expression=default_pattern, stoplist=STOP_WORDS, minsize=2, maxsize=None, gaps=False
|
|
177
|
+
):
|
|
178
|
+
"""Composes a RegexTokenizer with a LowercaseFilter and optional
|
|
179
|
+
StopFilter.
|
|
180
|
+
|
|
181
|
+
>>> ana = StandardAnalyzer()
|
|
182
|
+
>>> [token.text for token in ana("Testing is testing and testing")]
|
|
183
|
+
["testing", "testing", "testing"]
|
|
184
|
+
|
|
185
|
+
:param expression: The regular expression pattern to use to extract tokens.
|
|
186
|
+
:param stoplist: A list of stop words. Set this to None to disable
|
|
187
|
+
the stop word filter.
|
|
188
|
+
:param minsize: Words smaller than this are removed from the stream.
|
|
189
|
+
:param maxsize: Words longer that this are removed from the stream.
|
|
190
|
+
:param gaps: If True, the tokenizer *splits* on the expression, rather
|
|
191
|
+
than matching on the expression.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
ret = RegexTokenizer(expression=expression, gaps=gaps)
|
|
195
|
+
chain = ret | LowercaseFilter()
|
|
196
|
+
if stoplist is not None:
|
|
197
|
+
chain = chain | StopFilter(stoplist=stoplist, minsize=minsize, maxsize=maxsize)
|
|
198
|
+
return chain
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def StemmingAnalyzer(
|
|
202
|
+
expression=default_pattern,
|
|
203
|
+
stoplist=STOP_WORDS,
|
|
204
|
+
minsize=2,
|
|
205
|
+
maxsize=None,
|
|
206
|
+
gaps=False,
|
|
207
|
+
stemfn=stem,
|
|
208
|
+
ignore=None,
|
|
209
|
+
cachesize=50000,
|
|
210
|
+
):
|
|
211
|
+
"""Composes a RegexTokenizer with a lower case filter, an optional stop
|
|
212
|
+
filter, and a stemming filter.
|
|
213
|
+
|
|
214
|
+
>>> ana = StemmingAnalyzer()
|
|
215
|
+
>>> [token.text for token in ana("Testing is testing and testing")]
|
|
216
|
+
["test", "test", "test"]
|
|
217
|
+
|
|
218
|
+
:param expression: The regular expression pattern to use to extract tokens.
|
|
219
|
+
:param stoplist: A list of stop words. Set this to None to disable
|
|
220
|
+
the stop word filter.
|
|
221
|
+
:param minsize: Words smaller than this are removed from the stream.
|
|
222
|
+
:param maxsize: Words longer that this are removed from the stream.
|
|
223
|
+
:param gaps: If True, the tokenizer *splits* on the expression, rather
|
|
224
|
+
than matching on the expression.
|
|
225
|
+
:param ignore: a set of words to not stem.
|
|
226
|
+
:param cachesize: the maximum number of stemmed words to cache. The larger
|
|
227
|
+
this number, the faster stemming will be but the more memory it will
|
|
228
|
+
use. Use None for no cache, or -1 for an unbounded cache.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
ret = RegexTokenizer(expression=expression, gaps=gaps)
|
|
232
|
+
chain = ret | LowercaseFilter()
|
|
233
|
+
if stoplist is not None:
|
|
234
|
+
chain = chain | StopFilter(stoplist=stoplist, minsize=minsize, maxsize=maxsize)
|
|
235
|
+
return chain | StemFilter(stemfn=stemfn, ignore=ignore, cachesize=cachesize)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def FancyAnalyzer(
|
|
239
|
+
expression=r"\s+",
|
|
240
|
+
stoplist=STOP_WORDS,
|
|
241
|
+
minsize=2,
|
|
242
|
+
gaps=True,
|
|
243
|
+
splitwords=True,
|
|
244
|
+
splitnums=True,
|
|
245
|
+
mergewords=False,
|
|
246
|
+
mergenums=False,
|
|
247
|
+
):
|
|
248
|
+
"""Composes a RegexTokenizer with an IntraWordFilter, LowercaseFilter, and
|
|
249
|
+
StopFilter.
|
|
250
|
+
|
|
251
|
+
>>> ana = FancyAnalyzer()
|
|
252
|
+
>>> [token.text for token in ana("Should I call getInt or get_real?")]
|
|
253
|
+
["should", "call", "getInt", "get", "int", "get_real", "get", "real"]
|
|
254
|
+
|
|
255
|
+
:param expression: The regular expression pattern to use to extract tokens.
|
|
256
|
+
:param stoplist: A list of stop words. Set this to None to disable
|
|
257
|
+
the stop word filter.
|
|
258
|
+
:param minsize: Words smaller than this are removed from the stream.
|
|
259
|
+
:param maxsize: Words longer that this are removed from the stream.
|
|
260
|
+
:param gaps: If True, the tokenizer *splits* on the expression, rather
|
|
261
|
+
than matching on the expression.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
RegexTokenizer(expression=expression, gaps=gaps)
|
|
266
|
+
| IntraWordFilter(
|
|
267
|
+
splitwords=splitwords,
|
|
268
|
+
splitnums=splitnums,
|
|
269
|
+
mergewords=mergewords,
|
|
270
|
+
mergenums=mergenums,
|
|
271
|
+
)
|
|
272
|
+
| LowercaseFilter()
|
|
273
|
+
| StopFilter(stoplist=stoplist, minsize=minsize)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def LanguageAnalyzer(lang, expression=default_pattern, gaps=False, cachesize=50000):
|
|
278
|
+
"""Configures a simple analyzer for the given language, with a
|
|
279
|
+
LowercaseFilter, StopFilter, and StemFilter.
|
|
280
|
+
|
|
281
|
+
>>> ana = LanguageAnalyzer("es")
|
|
282
|
+
>>> [token.text for token in ana("Por el mar corren las liebres")]
|
|
283
|
+
['mar', 'corr', 'liebr']
|
|
284
|
+
|
|
285
|
+
The list of available languages is in `whoosh.lang.languages`.
|
|
286
|
+
You can use :func:`whoosh.lang.has_stemmer` and
|
|
287
|
+
:func:`whoosh.lang.has_stopwords` to check if a given language has a
|
|
288
|
+
stemming function and/or stop word list available.
|
|
289
|
+
|
|
290
|
+
:param expression: The regular expression pattern to use to extract tokens.
|
|
291
|
+
:param gaps: If True, the tokenizer *splits* on the expression, rather
|
|
292
|
+
than matching on the expression.
|
|
293
|
+
:param cachesize: the maximum number of stemmed words to cache. The larger
|
|
294
|
+
this number, the faster stemming will be but the more memory it will
|
|
295
|
+
use.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
from whoosh.lang import NoStemmer, NoStopWords
|
|
299
|
+
|
|
300
|
+
# Make the start of the chain
|
|
301
|
+
chain = RegexTokenizer(expression=expression, gaps=gaps) | LowercaseFilter()
|
|
302
|
+
|
|
303
|
+
# Add a stop word filter
|
|
304
|
+
try:
|
|
305
|
+
chain = chain | StopFilter(lang=lang)
|
|
306
|
+
except NoStopWords:
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
# Add a stemming filter
|
|
310
|
+
try:
|
|
311
|
+
chain = chain | StemFilter(lang=lang, cachesize=cachesize)
|
|
312
|
+
except NoStemmer:
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
return chain
|