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.
Files changed (125) hide show
  1. whoosh/__init__.py +50 -0
  2. whoosh/analysis/__init__.py +119 -0
  3. whoosh/analysis/acore.py +157 -0
  4. whoosh/analysis/analyzers.py +315 -0
  5. whoosh/analysis/filters.py +533 -0
  6. whoosh/analysis/intraword.py +509 -0
  7. whoosh/analysis/morph.py +268 -0
  8. whoosh/analysis/ngrams.py +250 -0
  9. whoosh/analysis/tokenizers.py +362 -0
  10. whoosh/automata/__init__.py +0 -0
  11. whoosh/automata/fsa.py +704 -0
  12. whoosh/automata/fst.py +1574 -0
  13. whoosh/automata/glob.py +90 -0
  14. whoosh/automata/lev.py +27 -0
  15. whoosh/automata/reg.py +126 -0
  16. whoosh/classify.py +385 -0
  17. whoosh/codec/__init__.py +32 -0
  18. whoosh/codec/base.py +872 -0
  19. whoosh/codec/memory.py +343 -0
  20. whoosh/codec/plaintext.py +458 -0
  21. whoosh/codec/whoosh2.py +2275 -0
  22. whoosh/codec/whoosh3.py +1351 -0
  23. whoosh/collectors.py +1175 -0
  24. whoosh/columns.py +1421 -0
  25. whoosh/externalsort.py +238 -0
  26. whoosh/fields.py +1680 -0
  27. whoosh/filedb/__init__.py +0 -0
  28. whoosh/filedb/compound.py +337 -0
  29. whoosh/filedb/fileindex.py +612 -0
  30. whoosh/filedb/filepostings.py +463 -0
  31. whoosh/filedb/filereading.py +246 -0
  32. whoosh/filedb/filestore.py +678 -0
  33. whoosh/filedb/filetables.py +736 -0
  34. whoosh/filedb/filewriting.py +307 -0
  35. whoosh/filedb/gae.py +164 -0
  36. whoosh/filedb/misc.py +53 -0
  37. whoosh/filedb/pools.py +476 -0
  38. whoosh/filedb/structfile.py +410 -0
  39. whoosh/formats.py +491 -0
  40. whoosh/highlight.py +1119 -0
  41. whoosh/idsets.py +951 -0
  42. whoosh/index.py +728 -0
  43. whoosh/lang/__init__.py +169 -0
  44. whoosh/lang/dmetaphone.py +509 -0
  45. whoosh/lang/isri.py +457 -0
  46. whoosh/lang/lovins.py +574 -0
  47. whoosh/lang/morph_en.py +1147 -0
  48. whoosh/lang/paicehusk.py +251 -0
  49. whoosh/lang/phonetic.py +117 -0
  50. whoosh/lang/porter.py +184 -0
  51. whoosh/lang/porter2.py +346 -0
  52. whoosh/lang/snowball/__init__.py +74 -0
  53. whoosh/lang/snowball/bases.py +133 -0
  54. whoosh/lang/snowball/danish.py +159 -0
  55. whoosh/lang/snowball/dutch.py +188 -0
  56. whoosh/lang/snowball/english.py +517 -0
  57. whoosh/lang/snowball/finnish.py +354 -0
  58. whoosh/lang/snowball/french.py +495 -0
  59. whoosh/lang/snowball/german.py +153 -0
  60. whoosh/lang/snowball/hungarian.py +420 -0
  61. whoosh/lang/snowball/italian.py +372 -0
  62. whoosh/lang/snowball/norwegian.py +120 -0
  63. whoosh/lang/snowball/portugese.py +336 -0
  64. whoosh/lang/snowball/romanian.py +484 -0
  65. whoosh/lang/snowball/russian.py +830 -0
  66. whoosh/lang/snowball/spanish.py +414 -0
  67. whoosh/lang/snowball/swedish.py +110 -0
  68. whoosh/lang/stopwords.py +296 -0
  69. whoosh/lang/wordnet.py +241 -0
  70. whoosh/legacy.py +81 -0
  71. whoosh/matching/__init__.py +62 -0
  72. whoosh/matching/binary.py +794 -0
  73. whoosh/matching/combo.py +316 -0
  74. whoosh/matching/mcore.py +625 -0
  75. whoosh/matching/wrappers.py +583 -0
  76. whoosh/multiproc.py +394 -0
  77. whoosh/qparser/__init__.py +83 -0
  78. whoosh/qparser/common.py +61 -0
  79. whoosh/qparser/dateparse.py +988 -0
  80. whoosh/qparser/default.py +447 -0
  81. whoosh/qparser/plugins.py +1430 -0
  82. whoosh/qparser/syntax.py +652 -0
  83. whoosh/qparser/taggers.py +95 -0
  84. whoosh/query/__init__.py +86 -0
  85. whoosh/query/compound.py +658 -0
  86. whoosh/query/nested.py +416 -0
  87. whoosh/query/positional.py +277 -0
  88. whoosh/query/qcolumns.py +118 -0
  89. whoosh/query/qcore.py +738 -0
  90. whoosh/query/ranges.py +451 -0
  91. whoosh/query/spans.py +930 -0
  92. whoosh/query/terms.py +570 -0
  93. whoosh/query/wrappers.py +196 -0
  94. whoosh/reading.py +1328 -0
  95. whoosh/scoring.py +627 -0
  96. whoosh/searching.py +1709 -0
  97. whoosh/sorting.py +1149 -0
  98. whoosh/spelling.py +343 -0
  99. whoosh/support/__init__.py +0 -0
  100. whoosh/support/base85.py +104 -0
  101. whoosh/support/bench.py +773 -0
  102. whoosh/support/bitstream.py +69 -0
  103. whoosh/support/bitvector.py +494 -0
  104. whoosh/support/charset.py +1378 -0
  105. whoosh/support/levenshtein.py +71 -0
  106. whoosh/support/relativedelta.py +490 -0
  107. whoosh/support/unicode.py +532 -0
  108. whoosh/system.py +76 -0
  109. whoosh/util/__init__.py +141 -0
  110. whoosh/util/cache.py +101 -0
  111. whoosh/util/filelock.py +161 -0
  112. whoosh/util/loading.py +84 -0
  113. whoosh/util/numeric.py +605 -0
  114. whoosh/util/numlists.py +631 -0
  115. whoosh/util/testing.py +133 -0
  116. whoosh/util/text.py +135 -0
  117. whoosh/util/times.py +516 -0
  118. whoosh/util/varints.py +102 -0
  119. whoosh/util/versions.py +169 -0
  120. whoosh/writing.py +1330 -0
  121. whoosh3-3.0.0.dist-info/METADATA +182 -0
  122. whoosh3-3.0.0.dist-info/RECORD +125 -0
  123. whoosh3-3.0.0.dist-info/WHEEL +6 -0
  124. whoosh3-3.0.0.dist-info/licenses/LICENSE.txt +26 -0
  125. 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
+ )
@@ -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