forked from sopel-irc/sopel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.py
executable file
·364 lines (309 loc) · 14.2 KB
/
config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/env python
"""
*Availability: 3+ for all functions; attributes may vary.*
The config class is, essentially, a representation of the active Willie config
file. As such, the details of its members depend entirely upon what is written
there.
Running the ``config.py`` file directly will give the user an interactive series
of dialogs to create the configuration file. This will guide the user through
creating settings for the Willie core, the settings database, and any modules
which have a configuration function.
The configuration function, if used, must be declared with the signature
``configure(config)`` and return a string which will be written to the config
file. The ``configure`` function should not write to the file, as this is done
by the utility.
"""
"""
Config - A config class and writing/updating utility for Willie
Copyright 2012, Edward Powell, embolalia.net
Licensed under the Eiffel Forum License 2.
http://dft.ba/-williesource
"""
import os, sys, imp
import getpass
from textwrap import dedent as trim
from bot import enumerate_modules
class Config(object):
def __init__(self, filename, load=True):
"""
Return a configuration object. The given filename will be associated
with the configuration, and is the file which will be written if write()
is called. If load is not given or True, the configuration object will
load the attributes from the file at filename.
A few default values will be set here if they are not defined in the
config file, or a config file is not loaded. They are documented below.
"""
self.filename = filename
"""The config object's associated file, as noted above."""
self.prefix = r'\.'
"""
This indicates the prefix for commands. (i.e, the . in the .w
command.) Note that this is used in a regular expression, so regex
syntax and special characters apply.
"""
self.name = 'Willie Embosbot, http://willie.dftba.net'
"""The "real name" used for the bot's whois."""
self.port = 6667
"""The port to connect on"""
self.password = None
"""The nickserv password"""
self.host = 'irc.example.net'
"""
The host to connect to. This is set to irc.example.net by default,
which serves as a sanity check to make sure that the bot has been
configured.
"""
if load:
module = imp.load_source('Config', filename)
for attrib in dir(module):
setattr(self, attrib, getattr(module, attrib))
def set_attr(self, attr, value):
"""
Set attr to value. This will succeed regardless of whether the attribute
is already set, and regardless of whether the given and current values
are of the same type.
"""
setattr(self, attr, value)
def write(self):
"""
Writes the current configuration to the file from which the current
configuration is derived. Changes made through ``set_attr`` may not be
properly written by this function.
"""
f = open(self.filename, 'w')
if hasattr(self, 'password') and self.password:
password_line = "password = '"+self.password+"'"
else:
password_line = "# password = 'example'"
if hasattr(self, 'serverpass') and self.serverpass:
serverpass_line = "serverpass = '"+self.serverpass+"'"
else:
serverpass_line = "# serverpass = 'example'"
if hasattr(self, 'enable') and self.enable:
enable_line = "enable = "+str(self.enable)
else:
enable_line = "# enable = []"
extra = self.extra.append(os.getcwd() + '/modules/')
output = trim("""\
nick = '"""+self.nick+"""'
host = '"""+self.host+"""'
port = """+str(self.port)+"""
channels = """+str(self.channels)+"""
owner = '"""+self.owner+"""'
# Channel where debug messages should be sent.
debug_target = '"""+self.debug_target+"""'
# Verbosity level for debug messages.
verbose = '"""+self.verbose+"""'
# List of other bots, whose outputs should be ignored
other_bots = """+str(self.other_bots)+"""
# password is the NickServ password, serverpass is the server password
"""+password_line+"""
"""+serverpass_line+"""
# The oper name and password, if the bot is allowed to /oper
"""+self.operline+"""
# These are people who will be able to use admin.py's functions...
admins = """+str(self.admins)+"""
# But admin.py is disabled by default, as follows:
exclude = """+str(self.exclude)+"""
# If you want to enumerate a list of modules rather than disabling
# some, use "enable = ['example']", which takes precedent over exclude
#
"""+enable_line+"""
# Directories to load user modules from
# e.g. /path/to/my/modules
extra = """+str(extra)+"""
# Services to load: maps channel names to white or black lists
#
# ?? Doesn't seem to do anything?
# external = {
# '#liberal': ['!'], # allow all
# '#conservative': [], # allow none
# '*': ['!'] # default whitelist, allow all
#}
""")+(self.settings_chunk+trim("""
#-----------------------MODULE SETTINGS-----------------------
""")+self.modules_chunk)+trim("""
# EOF
""")
print >> f, output
f.close()
def interactive_add(self, attrib, prompt, default=None, ispass=False):
"""
Ask user in terminal for the value to assign to ``attrib``. If ``default``
is passed, it will be shown as the default value in the prompt. If
``attrib`` is already defined, it will be used instead of ``default``,
regardless of wheather ``default`` is passed.
"""
if hasattr(self, attrib):
atr = getattr(self, attrib)
if ispass == True:
setattr(self, attrib, getpass.getpass(prompt+' [%s]: ' % atr) or atr)
else:
setattr(self, attrib, raw_input(prompt+' [%s]: ' % atr) or atr)
elif default:
if ispass == True:
setattr(self, attrib, getpass.getpass(prompt+' [%s]: ' % default) or default)
else:
setattr(self, attrib, raw_input(prompt+' [%s]: ' % default) or default)
else:
inp = ''
while not inp:
if ispass == True:
inp = getpass.getpass(prompt+': ')
else:
inp = raw_input(prompt+': ')
setattr(self, attrib, inp)
def add_list(self, attrib, message, prompt):
"""
Ask user in terminal for a list to assign to ``attrib``. If
``self.attrib`` is already defined, show the user the current values and
ask if the user would like to keep them. If so, additional values can be
entered.
"""
print message
lst = []
if hasattr(self, attrib) and getattr(self, attrib):
m = "You currently have "
for c in getattr(self, attrib): m = m + c + ', '
if self.option(m[:-2]+'. Would you like to keep them', True):
lst = getattr(self, attrib)
mem = raw_input(prompt)
while mem:
lst.append(mem)
mem = raw_input(prompt)
setattr(self, attrib, lst)
def option(self, question, default=False):
"""
Show user in terminal a "y/n" prompt, and return true or false based on
the response. If default is passed as true, the default will be shown as
``[y]``, else it will be ``[n]``. ``question`` should be phrased as a
question, but without a question mark at the end.
"""
d = 'n'
if default: d = 'y'
ans = raw_input(question+' (y/n)? ['+d+']')
if not ans: ans = d
return (ans is 'y' or ans is 'Y')
def _core(self):
self.interactive_add('nick', 'Enter the nickname for your bot', 'Willie')
self.interactive_add('name', 'Enter the "real name" of you bot for WHOIS responses',
'Willie Embosbot, http://willie.dftba.net')
self.interactive_add('host', 'Enter the server to connect to', 'irc.dftba.net')
self.interactive_add('port', 'Enter the port to connect on', '6667')
c='Enter the channels to connect to by default, one at a time. When done, hit enter again.'
self.add_list('channels', c, 'Channel:')
self.interactive_add('owner', "Enter your own IRC name (or that of the bot's owner)")
self.interactive_add('debug_target', 'Enter the channel to print debugging messages to. If set to stdio, debug messages will be printed to standard output', 'stdio')
self.interactive_add('verbose', 'Verbosity level. If None, all debug messages will be discarded. Valid options are warning/verbose/none', 'None') #FIXME: Make this a bit more user friendly
c="List users you'd like "+self.nick+" to ignore (e.g. other bots), one at a time. Hit enter when done."
self.add_list('other_bots', c, 'Nick:')
self.interactive_add('password', "Enter the bot's NickServ password", 'None', ispass=True)
self.interactive_add('serverpass', "Enter the bot's server password", 'None', ispass=True)
oper = self.option("Will this bot have IRC Operator privilages")
if oper:
opername = raw_input("Operator name:")
operpass = getpass.getpass("Operator password:")
self.operline = "Oper = ('"+opername+"', '"+operpass+"')"
else: self.operline = "# Oper = ('opername', 'operpass')"
#Note that this won't include owner. Will insert that later.
c='Enter other users who can perform some adminstrative tasks, one at a time. When done, hit enter again.'
self.add_list('admins', c, 'Nick:')
c=trim("""\
If you have any modules you do not wish this bot to load, enter them now, one at
a time. When done, hit enter. (If you'd rather whitelist modules, leave this empty.)""")
self.add_list('exclude', c, 'Module:')
if not self.exclude:
wl = self.option("Would you like to create a module whitelist")
self.enable = []
if wl:
c="Enter the modules to use, one at a time. Hit enter when done."
self.add_list('enable', c, 'Module:')
c = trim("""\
If you'd like to include modules from other directories, enter them one at a
time, and hit enter again when done.""")
self.add_list('extra', c, 'Directory:')
def _settings(self):
try:
import settings
self.settings_chunk = trim(settings.write_config(self))
self.settings = True
except:
self.settings = False
self.settings_chunk = trim("""\
# ------------------ USER DATABASE CONFIGURATION ------------------
# The user database was not set up at install. Please consult the documentation,
# or run the configuration utility if you wish to use it.""")
print trim("""
The configuration utility will now attempt to find modules with their own
configuration needs.
""")
def _modules(self):
home = os.getcwd()
modules_dir = os.path.join(home, 'modules')
self.modules_chunk = ''
filenames = enumerate_modules(self)
os.sys.path.insert(0,modules_dir)
for filename in filenames:
name = os.path.basename(filename)[:-3]
if name in self.exclude: continue
try: module = imp.load_source(name, filename)
except Exception, e:
print >> sys.stderr, "Error loading %s: %s (in config.py)" % (name, e)
else:
if hasattr(module, 'configure'):
chunk = module.configure(self)
if chunk and isinstance(chunk, basestring):
self.modules_chunk += trim(chunk)
def _config_names(dotdir, config):
config = config or 'default'
def files(d):
names = os.listdir(d)
return list(os.path.join(d, fn) for fn in names if fn.endswith('.py'))
here = os.path.join('.', config)
if os.path.isfile(here):
return [here]
if os.path.isfile(here + '.py'):
return [here + '.py']
if os.path.isdir(here):
return files(here)
there = os.path.join(dotdir, config)
if os.path.isfile(there):
return [there]
if os.path.isfile(there + '.py'):
return [there + '.py']
if os.path.isdir(there):
return files(there)
sys.exit(1)
def main(argv=None):
import optparse
parser = optparse.OptionParser('%prog [options]')
parser.add_option('-c', '--config', metavar='fn',
help='use this configuration file or directory')
opts, args = parser.parse_args(argv)
dotdir = os.path.expanduser('~/.willie')
configpath = os.path.join(dotdir, (opts.config or 'default')+'.py')
create_config(configpath)
def create_config(configpath):
print "Please answer the following questions to create your configuration file:\n"
dotdir = os.path.expanduser('~/.willie')
if not os.path.isdir(dotdir):
print 'Creating a config directory at ~/.willie...'
try: os.mkdir(dotdir)
except Exception, e:
print >> sys.stderr, 'There was a problem creating %s:' % dotdir
print >> sys.stderr, e.__class__, str(e)
print >> sys.stderr, 'Please fix this and then run Willie again.'
sys.exit(1)
try:
config = Config(configpath, os.path.isfile(configpath))
config._core()
config._settings()
config._modules()
config.write()
except Exception, e:
print "Encountered an error while writing the config file. This shouldn't happen. Check permissions."
print e
sys.exit(1)
print "Config file written sucessfully!"
if __name__ == '__main__':
main()