Imported from archive.
[mudpy.git] / lib / muff / muffmisc.py
1 """Miscellaneous objects for the MUFF Engine"""
2
3 # Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
4 # Licensed per terms in the LICENSE file distributed with this software.
5
6 # used by several functions for random calls
7 import random
8
9 # used to match the 'L' at the end of a long int in repr_long
10 import re
11
12 # random_name uses string.strip
13 import string
14
15 # the log function uses time.asctime for creating timestamps
16 import time
17
18 # hack to load all modules in the muff package
19 import muff
20 for module in muff.__all__:
21         exec("import " + module)
22
23 def broadcast(message):
24         """Send a message to all connected users."""
25         for each_user in muffvars.userlist: each_user.send("$(eol)" + message)
26
27 def log(message):
28         """Log a message."""
29
30         # the time in posix log timestamp format
31         timestamp = time.asctime()[4:19]
32
33         # send the timestamp and message to standard output
34         print(timestamp + " " + message)
35
36 def wrap_ansi_text(text, width):
37         """Wrap text with arbitrary width while ignoring ANSI colors."""
38
39         # the current position in the entire text string, including all
40         # characters, printable or otherwise
41         absolute_position = 0
42
43         # the current text position relative to the begining of the line,
44         # ignoring color escape sequences
45         relative_position = 0
46
47         # whether the current character is part of a color escape sequence
48         escape = False
49
50         # iterate over each character from the begining of the text
51         for each_character in text:
52
53                 # the current character is the escape character
54                 if each_character == chr(27):
55                         escape = True
56
57                 # the current character is within an escape sequence
58                 elif escape:
59
60                         # the current character is m, which terminates the
61                         # current escape sequence
62                         if each_character == "m":
63                                 escape = False
64
65                 # the current character is a newline, so reset the relative
66                 # position (start a new line)
67                 elif each_character == "\n":
68                         relative_position = 0
69
70                 # the current character meets the requested maximum line width,
71                 # so we need to backtrack and find a space at which to wrap
72                 elif relative_position == width:
73
74                         # distance of the current character examined from the
75                         # relative position
76                         wrap_offset = 0
77
78                         # count backwards until we find a space
79                         while text[absolute_position - wrap_offset] != " ":
80                                 wrap_offset += 1
81
82                         # insert an eol in place of the space
83                         text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
84
85                         # increase the absolute position because an eol is two
86                         # characters but the space it replaced was only one
87                         absolute_position += 1
88
89                         # now we're at the begining of a new line, plus the
90                         # number of characters wrapped from the previous line
91                         relative_position = wrap_offset
92
93                 # as long as the character is not a carriage return and the
94                 # other above conditions haven't been met, count it as a
95                 # printable character
96                 elif each_character != "\r":
97                         relative_position += 1
98
99                 # increase the absolute position for every character
100                 absolute_position += 1
101
102         # return the newly-wrapped text
103         return text
104
105 def weighted_choice(data):
106         """Takes a dict weighted by value and returns a random key."""
107
108         # this will hold our expanded list of keys from the data
109         expanded = []
110
111         # create thee expanded list of keys
112         for key in data.keys():
113                 for count in range(data[key]):
114                         expanded.append(key)
115
116         # return one at random
117         return random.choice(expanded)
118
119 def random_name():
120         """Returns a random character name."""
121
122         # the vowels and consonants needed to create romaji syllables
123         vowels = [ "a", "i", "u", "e", "o" ]
124         consonants = ["'", "k", "z", "s", "sh", "z", "j", "t", "ch", "ts", "d", "n", "h", "f", "m", "y", "r", "w" ]
125
126         # this dict will hold our weighted list of syllables
127         syllables = {}
128
129         # generate the list with an even weighting
130         for consonant in consonants:
131                 for vowel in vowels:
132                         syllables[consonant + vowel] = 1
133
134         # we'll build the name into this string
135         name = ""
136
137         # create a name of random length from the syllables
138         for syllable in range(random.randrange(2, 6)):
139                 name += weighted_choice(syllables)
140
141         # strip any leading quotemark, capitalize and return the name
142         return string.strip(name, "'").capitalize()
143
144 def repr_long(value):
145         string_value = repr(value)
146         if re.match('\d*L$', string_value): return string_value.strip("L")
147         else: return string_value
148
149 def getlong(config, section, option):
150         return int(config.get(section, option).strip("L"))
151
152 def setlong(config, section, option, value):
153         return config.set(section, option, repr_long(value))
154
155 def replace_macros(user, text, is_input=False):
156         while True:
157                 macro_start = string.find(text, "$(")
158                 if macro_start == -1: break
159                 macro_end = string.find(text, ")", macro_start) + 1
160                 macro = text[macro_start:macro_end]
161                 if macro in muffvars.macros.keys():
162                         text = string.replace(text, macro, muffvars.macros[macro])
163
164                 # the user's account name
165                 elif macro == "$(account)":
166                         text = string.replace(text, macro, user.name)
167
168                 # if we get here, log and replace it with null
169                 else:
170                         text = string.replace(text, macro, "")
171                         if not is_input:
172                                 log("Unexpected replacement macro " + macro + " encountered.")
173
174         # replace the look-like-a-macro sequence
175         text = string.replace(text, "$_(", "$(")
176
177         return text
178
179 def check_time(frequency):
180         """Check for a factor of the current increment count."""
181         if type(frequency) is str:
182                 frequency = muffconf.getint("time", frequency)
183         return not getlong(muffvars.variable_data, "time", "elapsed") % frequency
184
185 def on_pulse():
186         """The things which should happen on each pulse, aside from reloads."""
187
188         # open the listening socket if it hasn't been already
189         if not muffvars.newsocket: muffsock.initialize_server_socket()
190
191         # assign a user if a new connection is waiting
192         user = muffsock.check_for_connection(muffvars.newsocket)
193         if user: muffvars.userlist.append(user)
194
195         # iterate over the connected users
196         for user in muffvars.userlist: user.pulse()
197
198         # update the log every now and then
199         if check_time("frequency_log"):
200                 log(repr(len(muffvars.userlist)) + " connection(s)")
201
202         # periodically save everything
203         if check_time("frequency_save"):
204                 muffvars.save()
205                 for user in muffvars.userlist: user.save()
206
207         # pause for a configurable amount of time (decimal seconds)
208         time.sleep(muffconf.getfloat("time", "increment"))
209
210         # increment the elapsed increment counter
211         setlong(muffvars.variable_data, "time", "elapsed",
212                 getlong(muffvars.variable_data, "time", "elapsed") + 1)
213
214 def reload_data():
215         """Reload data into new persistent objects."""
216
217         # reload the users
218         temporary_userlist = []
219         for user in muffvars.userlist: temporary_userlist.append(user)
220         for user in temporary_userlist: user.reload()
221         del(temporary_userlist)
222