]> code.communitydata.science - rises_declines_wikia_code.git/blob - mediawiki_dump_tools/Mediawiki-Utilities/mw/lib/persistence/state.py
add copy of the GPL
[rises_declines_wikia_code.git] / mediawiki_dump_tools / Mediawiki-Utilities / mw / lib / persistence / state.py
1 from hashlib import sha1
2
3 from . import defaults
4 from .. import reverts
5 from .tokens import Token, Tokens
6
7
8 class Version:
9     __slots__ = ('tokens')
10
11     def __init__(self):
12         self.tokens = None
13
14
15 class State:
16     """
17     Represents the state of word persistence in a page.
18     See `<https://meta.wikimedia.org/wiki/Research:Content_persistence>`_
19
20     :Parameters:
21         tokenize : function( `str` ) --> list( `str` )
22             A tokenizing function
23         diff : function(list( `str` ), list( `str` )) --> list( `ops` )
24             A function to perform a difference between token lists
25         revert_radius : int
26             a positive integer indicating the maximum revision distance that a revert can span.
27         revert_detector : :class:`mw.lib.reverts.Detector`
28             a revert detector to start process with
29     :Example:
30         >>> from pprint import pprint
31         >>> from mw.lib import persistence
32         >>>
33         >>> state = persistence.State()
34         >>>
35         >>> pprint(state.process("Apples are red.", revision=1))
36         ([Token(text='Apples', revisions=[1]),
37           Token(text=' ', revisions=[1]),
38           Token(text='are', revisions=[1]),
39           Token(text=' ', revisions=[1]),
40           Token(text='red', revisions=[1]),
41           Token(text='.', revisions=[1])],
42          [Token(text='Apples', revisions=[1]),
43           Token(text=' ', revisions=[1]),
44           Token(text='are', revisions=[1]),
45           Token(text=' ', revisions=[1]),
46           Token(text='red', revisions=[1]),
47           Token(text='.', revisions=[1])],
48          [])
49         >>> pprint(state.process("Apples are blue.", revision=2))
50         ([Token(text='Apples', revisions=[1, 2]),
51           Token(text=' ', revisions=[1, 2]),
52           Token(text='are', revisions=[1, 2]),
53           Token(text=' ', revisions=[1, 2]),
54           Token(text='blue', revisions=[2]),
55           Token(text='.', revisions=[1, 2])],
56          [Token(text='blue', revisions=[2])],
57          [Token(text='red', revisions=[1])])
58         >>> pprint(state.process("Apples are red.", revision=3)) # A revert!
59         ([Token(text='Apples', revisions=[1, 2, 3]),
60           Token(text=' ', revisions=[1, 2, 3]),
61           Token(text='are', revisions=[1, 2, 3]),
62           Token(text=' ', revisions=[1, 2, 3]),
63           Token(text='red', revisions=[1, 3]),
64           Token(text='.', revisions=[1, 2, 3])],
65          [],
66          [])
67     """
68
69     def __init__(self, tokenize=defaults.TOKENIZE, diff=defaults.DIFF,
70                  revert_radius=reverts.defaults.RADIUS,
71                  revert_detector=None):
72         self.tokenize = tokenize
73         self.diff = diff
74
75         # Either pass a detector or the revert radius so I can make one
76         if revert_detector is None:
77             self.revert_detector = reverts.Detector(int(revert_radius))
78         else:
79             self.revert_detector = revert_detector
80
81         # Stores the last tokens
82         self.last = None
83
84     def process(self, text, revision=None, checksum=None):
85         """
86         Modifies the internal state based a change to the content and returns
87         the sets of words added and removed.
88
89         :Parameters:
90             text : str
91                 The text content of a revision
92             revision : `mixed`
93                 Revision meta data
94             checksum : str
95                 A checksum hash of the text content (will be generated if not provided)
96
97         :Returns:
98             Three :class:`~mw.lib.persistence.Tokens` lists
99
100             current_tokens : :class:`~mw.lib.persistence.Tokens`
101                 A sequence of :class:`~mw.lib.persistence.Token` for the
102                 processed revision
103             tokens_added : :class:`~mw.lib.persistence.Tokens`
104                 A set of tokens that were inserted by the processed revision
105             tokens_removed : :class:`~mw.lib.persistence.Tokens`
106                 A sequence of :class:`~mw.lib.persistence.Token` removed by the
107                 processed revision
108
109         """
110         if checksum is None:
111             checksum = sha1(bytes(text, 'utf8')).hexdigest()
112
113         version = Version()
114
115         revert = self.revert_detector.process(checksum, version)
116         if revert is not None:  # Revert
117
118             # Empty words.
119             tokens_added = Tokens()
120             tokens_removed = Tokens()
121
122             # Extract reverted_to revision
123             _, _, reverted_to = revert
124             version.tokens = reverted_to.tokens
125
126         else:
127
128             if self.last is None:  # First version of the page!
129
130                 version.tokens = Tokens(Token(t) for t in self.tokenize(text))
131                 tokens_added = version.tokens
132                 tokens_removed = Tokens()
133
134             else:
135
136                 # NOTICE: HEAVY COMPUTATION HERE!!!
137                 #
138                 # OK.  It's not that heavy.  It's just performing a diff,
139                 # but you're still going to spend most of your time here.
140                 # Diffs usually run in O(n^2) -- O(n^3) time and most tokenizers
141                 # produce a lot of tokens.
142                 version.tokens, tokens_added, tokens_removed = \
143                     self.last.tokens.compare(self.tokenize(text), self.diff)
144
145         version.tokens.persist(revision)
146
147         self.last = version
148
149         return version.tokens, tokens_added, tokens_removed

Community Data Science Collective || Want to submit a patch?