1 # -*- coding: utf-8 -*-
3 # glitcherature.py - Functions to glitch and generate text.
4 # Copyright (C) 2012 Rob Myers <rob@robmyers.org>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 from nltk.corpus import wordnet
24 ################################################################################
26 ################################################################################
29 """ Return True 1/n times, False the rest of the time.
30 n can be int or floate"""
31 return random.random() * n <= 1
33 def maybe(fun, default, n):
34 """ Return the results of calling fun() 1/n times, otherwise default"""
40 ################################################################################
42 ################################################################################
44 def repeat_run(item, length):
45 """Return a string repeating item for the given length"""
46 return ''.join(item * length)
48 def random_run(items, length):
49 """Return a string of length randomly chosen items"""
50 return ''.join([random.choice(items) for i in xrange(length)])
52 ################################################################################
53 # General: apply to words, sentences, paragraphs
54 ################################################################################
56 def wrap(item, before, after):
57 """Wrap item in before and after"""
58 return "%s%s%s" % (before, item, after)
60 ################################################################################
62 # Operate on (and generate) strings of characters without whitespace
63 ################################################################################
65 def split_word(word, probability=1):
66 # Find subword using nltk or something, e.g.: antidote -> anti, dote
70 """Try to get a Wordnet synset for word, trying noun then verb"""
73 synset = wordnet.synset("%s.n.01" % word)
76 result = wordnet.synset("%s.v.01" % word)
81 def synset_name(synset):
82 """Get the English word for the synset"""
83 name = re.match(r"[^.]+", synset.name).group(0)
84 name = re.sub("_", " ", name)
88 """Choose a random hypernym for the word"""
92 synonym = random.choice(synset.hypernyms())
93 result = synset_name(synonym)
99 """Choose a random hyponym for the word"""
101 synset = wordnet_synset(word)
103 hyponym = random.choice(synset.hyponyms)
104 result = synset_name(hyponym)
110 """Get a piece of text that is an example of using the word, or None"""
112 synset = wordnet_synset(word)
114 result = random.choice(synset.examples)
117 ################################################################################
119 # Operate on strings of whitespace-separated words with punctuation.
120 ################################################################################
122 def replace_spaces(text, replacement, n=1):
123 """Replace spaces with replacement 1/n times"""
125 lambda match: replacement if one_in(n) else match.group(0),
129 """Make everything lower-case"""
133 """Make everything upper-case"""
136 def random_upper_letters(text, n=2):
137 """Randomly set letters to upper-case"""
138 return ''.join([letter.upper() if one_in(n) else letter for letter in text])
140 def random_upper_words(text, n=2):
141 """Randomly set words to all upper-case"""
142 return re.sub(r"\w+",
143 lambda word: word.group(0).upper() if one_in(n) \
147 def sub_text(text, subs, n=1):
148 """Substitute keys from subs for values from subs in text 1/n of the time"""
149 return re.sub(r"\w+",
150 lambda word: subs.get(word, word) if one_in(n) else word,
153 leet = {'a': ('@', '/\\', '/-\\', 'λ'),
154 'b': ('8', '|3', '6', ']3'),
155 'c': ('(', '{', '<', '©'),
156 'd': ('|)', '|]', '])', '∂'),
157 'e': ('3', '£', '€', '='),
158 'f': ('ʃ', '|=', ']=', ')='),
159 'g': ('6', '9', '&', 'C-'),
160 'h': ('|-|', '#', '}{', ')-('),
161 'i': ('!', '1', '|', '`|'),
162 'j': ('_|', ']', '_/', '_)'),
163 'k': ('|<', '|{', '|X', ']<'),
164 'l': ('1', '7', '|_', '|'),
165 'm': ('44', '/\\/\\', '|\\/|', '|v|'),
166 'n': ('|\\|', '/\\/', 'И', '~'),
167 'o': ('()', '[]', '0', 'Ø'),
168 'p': ('|*', '?', '9', '|"'),
169 'q': ('0_', '0,', '(),', '¶'),
170 'r': ('®', 'Я', 'I^', '|2'),
171 's': ('$', '5', '§', '_\-'),
172 't': ('7', '+', '†', '|['),
173 'u': ('\\/', '|_|', 'μ', '/_/'),
174 'v': ('\\\\//', '\\/', '√', 'V'),
175 'w': ('vv', '\\/\\/', 'Ш', '\\^/'),
176 'x': ('%', '><', '*', 'Ж'),
177 'y': ('`/', "'/", '`(', '-/'),
178 'z': ('2', '3', '`/_', '%')}
180 leet_kinds_count = len(leet['a'])
182 # Make sure we have the same number of options for every letter
184 assert(all([len(leets) == leet_kinds_count for leets in leet.itervalues()]))
186 # Make sure no mappings appear twice in the same column
188 assert(all([len(set(row[i] for row in leet.itervalues())) == len(leet)
189 for i in xrange(0, len(leet['a']))]))
191 def sub_1337_vowels(text, n=1):
192 """Replace vowels in the text with 1337 1/n of the time"""
193 trans = string.maketrans("aeioul", "@3|0\"1")
194 return ''.join([c.translate(trans) if one_in(n) else c for c in text])
196 def sub_1337_char(char, kind, n=1):
197 """Substitute the char for 1337 kind 1/n of the time"""
199 if char in leet and one_in(n):
200 result = leet[char][kind]
203 def sub_1337(text, kind, n=1):
204 """Replace letters in the text with the given kind (1..4) of 1337 1/n"""
205 return ''.join([sub_1337_char(c, kind, n) for c in text])
214 def sub_txt(text, n=1):
215 """Substitute words in text for their txt equivalents 1/n of the time"""
216 for key in txt.iterkeys():
217 text = re.sub("\\b%s" % key,
218 lambda x: txt[key] if one_in(n) else key,
223 def sub_space_runs(text, sub, min_len, max_len):
224 """Substitute spaces with runs of sub character, or choices from sub list"""
225 if type(sub) != list:
227 return re.sub(r"\s+",
228 lambda spaces: repeat_run(random.choice(sub),
229 random.randint(min_len, max_len)),
232 def drop_definite(text, n=1):
233 """Drop definite articles 1/n of the time"""
234 return re.sub(r"\bthe\b\s*",
235 lambda the: "" if one_in(n) else the.group(0),
239 def replace_and(text, repl, n=1):
240 """Replace ' and ' with replacement 1/n or the time"""
241 return re.sub(r"\s*\band\b\s*",
242 lambda et: repl if one_in(n) else et.group(0),
246 def wrap_words(text, before, after, n=1):
247 """Wrap 1/n words in text with before and after"""
248 return re.sub(r"\w+",
249 lambda word: wrap(word.group(0), before, after) if one_in(n) \
253 ################################################################################
255 ################################################################################
257 t1 = "The quick brown fox jumped over the lazy dog."
259 t2 = "Rocks and pebbles wrapped in brown paper and tied with string, then returned to nature's bosom."
261 t3 = "No no no there is no way that can be no..."
263 t4 = "notational sublimity undermining expositional transitivity"
265 t5 = "I see you hate being late"
267 ts = [t1, t2, t3, t4, t5]
271 print repeat_run('-', 80)
272 print random_run(list('.#+-:@'), random.randint(10, 50))
273 print wrap(t, "++++", "----")
274 print replace_spaces(t, ".")
277 print random_upper_letters(t)
278 print random_upper_words(t)
279 print sub_1337_vowels(t)
282 print sub_space_runs(t, ['.', '_'], 1, 5)
283 print drop_definite(t)
284 print replace_and(t, '+')
285 print wrap_words(t, '[', ']', 1.4)
287 if __name__ == "__main__":
290 ################################################################################
292 ################################################################################
296 # FIXME: maintain case of letter and word substitutions
300 # Insert . or _ etc. randomly into words but not spaces
301 # So 2 complimentary functions, one to replace spaces, one letters
302 # i.nternet intern_et, etc.