]> gitweb.pimeys.fr Git - bots/parrot.git/blob - parrot.py
Changement d'ops
[bots/parrot.git] / parrot.py
1 #!/usr/bin/python
2 # -*- encoding: utf-8 -*-
3
4 """ Un bot IRC qui enregistre et ressort des citations """
5
6 import threading
7 import random
8 import time
9 import json
10 import re
11 import os
12 import signal
13 import sys
14
15 # Oui, j'ai recodé ma version d'irclib pour pouvoir rattrapper les SIGHUP
16 sys.path.insert(0, "/home/vincent/scripts/python-myirclib")
17 import irclib
18 import ircbot
19
20 from commands import getstatusoutput as ex
21
22 #: Config du bot
23 import config
24 #: Module définissant les erreurs
25 import errors
26 #: Module de gestion des quotes
27 import quotes
28
29 # Je veux pouvoir éditer ce que crée ce bot
30 os.umask(002)
31
32 def get_config_logfile(serveur):
33 """Renvoie le nom du fichier de log en fonction du ``serveur`` et de la config."""
34 serveurs = {"irc.crans.org" : "crans"}
35 return config.logfile_template % (serveurs[serveur],)
36
37 def log(serveur, channel, auteur=None, message=None):
38 """Enregistre une ligne de log."""
39 if auteur == message == None:
40 # alors c'est que c'est pas un channel mais juste une ligne de log
41 chain = u"%s %s" % (time.strftime("%F %T"), channel)
42 else:
43 chain = u"%s [%s:%s] %s" % (time.strftime("%F %T"), channel, auteur, message)
44 f = open(get_config_logfile(serveur), "a")
45 f.write((chain + u"\n").encode("utf-8"))
46 f.close()
47 if config.debug_stdout:
48 print chain.encode("utf-8")
49
50 def ignore_event(serv, ev):
51 """Retourne ``True`` si il faut ignorer cet évènement."""
52 for (blackmask, exceptlist) in config.blacklisted_masks:
53 usermask = ev.source()
54 blackit = bool(irclib.mask_matches(usermask, blackmask))
55 exceptit = any([bool(irclib.mask_matches(usermask, exceptmask)) for exceptmask in exceptlist])
56 if exceptit: # Il est exempté
57 return False
58 else:
59 if blackit: # Il n'est pas exempté et matche la blacklist
60 return True
61
62
63 def bot_unicode(chain):
64 """Essaye de décoder ``chain`` en UTF-8.
65 Lève une py:class:`errors.UnicodeBotError` en cas d'échec."""
66 try:
67 return chain.decode("utf-8")
68 except UnicodeDecodeError as exc:
69 raise errors.UnicodeBotError
70
71
72 class Parrot(ircbot.SingleServerIRCBot):
73 """Classe principale : définition du bot."""
74 def __init__(self, serveur, debug=False):
75 temporary_pseudo = config.irc_pseudo + str(random.randrange(10000,100000))
76 ircbot.SingleServerIRCBot.__init__(self, [(serveur, 6667)],
77 temporary_pseudo, "Parrot, le bot irc. [Codé par 20-100]", 10)
78 self.debug = debug
79 self.serveur = serveur
80 self.overops = config.overops
81 self.ops = self.overops + config.ops
82 self.report_bugs_to = config.report_bugs_to
83 self.chanlist = config.chanlist
84 self.stay_channels = config.stay_channels
85 self.quiet_channels = config.quiet_channels
86 self.last_perdu = 0
87
88 self.quotedb = quotes.QuoteDB()
89 self.reload_quotes()
90
91
92 ### Utilitaires
93 def _getnick(self):
94 """Récuère le nick effectif du bot sur le serveur."""
95 return self.serv.get_nickname()
96 nick = property(_getnick)
97
98 def give_me_my_pseudo(self, serv):
99 """Récupère le pseudo auprès de NickServ."""
100 serv.privmsg("NickServ", "RECOVER %s %s" % (config.irc_pseudo, config.irc_password))
101 time.sleep(0.3)
102 serv.nick(config.irc_pseudo)
103
104 def pourmoi(self, serv, message):
105 """Renvoie (False, lemessage) ou (True, le message amputé de "pseudo: ")"""
106 pseudo = self.nick
107 pseudo = pseudo.decode("utf-8")
108 size = len(pseudo)
109 if message[:size] == pseudo and len(message) > size and message[size] == ":":
110 return (True, message[size+1:].lstrip(" "))
111 else:
112 return (False, message)
113
114 ### Exécution d'actions
115 def quitter(self, chan, leave_message=None):
116 """Quitter un channel avec un message customisable."""
117 if leave_message == None:
118 leave_message = random.choice(config.leave_messages)
119 self.serv.part(chan, message=leave_message.encode("utf-8"))
120
121 def mourir(self):
122 """Se déconnecter du serveur IRC avec un message customisable."""
123 quit_message = random.choice(config.quit_messages)
124 self.die(msg=quit_message.encode("utf-8"))
125
126 def reload_quotes(self):
127 """ Recharge la base de données des quotes et recompile la regexp de quote """
128 self.quotedb.load()
129 self.quote_pattern = re.compile(config.quote_regexp, flags=re.UNICODE)
130
131 def execute_reload(self, auteur=None):
132 """Recharge la config."""
133 reload(config)
134 self.reload_quotes()
135 if auteur in [None, "SIGHUP"]:
136 towrite = "Config reloaded" + " (SIGHUP received)" * (auteur == "SIGHUP")
137 for to in config.report_bugs_to:
138 self.serv.privmsg(to, towrite)
139 log(self.serveur, towrite)
140 return True, None
141 else:
142 return True, u"Config reloaded"
143
144 def crash(self, who="nobody", chan="nowhere"):
145 """Fait crasher le bot."""
146 where = "en privé" if chan == "priv" else "sur le chan %s" % chan
147 raise errors.CrashError((u"Crash demandé par %s %s" % (who, where)).encode("utf-8"))
148
149 ACTIONS = {
150 "reload" : execute_reload,
151 }
152
153 def execute_something(self, something, params, place=None, auteur=None):
154 """Exécute une action et répond son résultat à ``auteur``
155 sur un chan ou en privé en fonction de ``place``"""
156 action = self.ACTIONS[something]
157 success, message = action(self, **params)
158 if message:
159 if irclib.is_channel(place):
160 message = "%s: %s" % (auteur, message.encode("utf-8"))
161 self.serv.privmsg(place, message)
162 log(self.serveur, place, auteur, something + "%r" % params + ("[successful]" if success else "[failed]"))
163
164
165 def acknowledge(self, asked_by, asked_where, message):
166 """Répond quelque chose au demandeur d'une action.
167 ``asked_where=None`` signifie en privé."""
168 if asked_where is None:
169 self.serv.privmsg(asked_by, message)
170 else:
171 self.serv.privmsg(asked_where, "%s: %s" % (asked_by, message))
172
173 def dump(self, asked_by, asked_where=None):
174 """Dumpe les quotes. ``asked_where=None`` signifie en privé."""
175 quotes.dump(self.quotedb)
176 self.acknowledge(asked_by, asked_where, "Quotes dumpées")
177
178 def restore(self, asked_by, asked_where=None):
179 """Restaure les quotes à partir du dump. ``asked_where=None`` signifie en privé."""
180 self.quotedb = quotes.restore()
181 self.acknowledge(asked_by, asked_where, "Quotes restaurées à partir du dump (pas de backup effectué).")
182 many = self.quotedb.get_clash_authors()
183 if many:
184 self.acknowledge(asked_by, asked_where, "Auteurs de casse différente : %s" % (many))
185
186 ### Surcharge des events du Bot
187 def on_welcome(self, serv, ev):
188 """À l'arrivée sur le serveur."""
189 self.serv = serv # ça serv ira :)
190 self.give_me_my_pseudo(serv)
191 serv.privmsg("NickServ", "IDENTIFY %s" % (config.irc_password))
192 log(self.serveur, "Connected")
193 if self.debug:
194 self.chanlist = ["#bot"]
195 for c in self.chanlist:
196 log(self.serveur, "JOIN %s" % (c))
197 serv.join(c)
198
199 def on_privmsg(self, serv, ev):
200 """À la réception d'un message en privé."""
201 if ignore_event(serv, ev):
202 return
203 message = ev.arguments()[0]
204 auteur = irclib.nm_to_n(ev.source())
205 try:
206 message = bot_unicode(message)
207 except errors.UnicodeBotError:
208 if config.utf8_trigger:
209 serv.privmsg(auteur, random.choice(config.utf8_fail_answers).encode("utf-8"))
210 return
211 message = message.split()
212 cmd = message[0].lower()
213 notunderstood = False
214 if cmd == u"help":
215 op,overop=auteur in self.ops, auteur in self.overops
216 if len(message)==1:
217 helpmsg = config.helpmsg_default
218 if op:
219 helpmsg += config.helpmsg_ops
220 if overop:
221 helpmsg += config.helpmsg_overops
222 else:
223 helpmsgs = config.helpdico.get(message[1].lower(), ["Commande inconnue.", None, None])
224 helpmsg = helpmsgs[0]
225 if op and helpmsgs[1]:
226 if helpmsg:
227 helpmsg += "\n" + helpmsgs[1]
228 else:
229 helpmsg = helpmsgs[1]
230 if overop and helpmsgs[2]:
231 if helpmsg:
232 helpmsg += "\n" + helpmsgs[2]
233 else:
234 helpmsg = helpmsgs[2]
235 for ligne in helpmsg.split("\n"):
236 serv.privmsg(auteur, ligne.encode("utf-8"))
237 elif cmd == u"join":
238 if auteur in self.ops:
239 if len(message) > 1:
240 if message[1] in self.chanlist:
241 serv.privmsg(auteur, "Je suis déjà sur %s" % (message[1]))
242 else:
243 serv.join(message[1])
244 self.chanlist.append(message[1])
245 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
246 log(self.serveur, "priv", auteur, " ".join(message))
247 else:
248 serv.privmsg(auteur, "Channels : " + " ".join(self.chanlist))
249 else:
250 notunderstood = True
251 elif cmd == u"leave":
252 if auteur in self.ops and len(message) > 1:
253 if message[1] in self.chanlist:
254 if not (message[1] in self.stay_channels) or auteur in self.overops:
255 self.quitter(message[1].encode("utf-8"), " ".join(message[2:]))
256 self.chanlist.remove(message[1])
257 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
258 else:
259 serv.privmsg(auteur, "Non, je reste !")
260 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
261 else:
262 serv.privmsg(auteur, "Je ne suis pas sur %s" % (message[1]))
263 else:
264 notunderstood = True
265 elif cmd == u"stay":
266 if auteur in self.overops:
267 if len(message) > 1:
268 if message[1] in self.stay_channels:
269 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
270 serv.privmsg(auteur, "Je stay déjà sur %s." % (message[1]))
271 else:
272 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
273 self.stay_channels.append(message[1])
274 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
275 else:
276 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
277 else:
278 notunderstood = True
279 elif cmd == u"nostay":
280 if auteur in self.overops:
281 if len(message) > 1:
282 if message[1] in self.stay_channels:
283 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
284 self.stay_channels.remove(message[1])
285 serv.privmsg(auteur, "Stay channels : " + " ".join(self.stay_channels))
286 else:
287 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
288 serv.privmsg(auteur, "Je ne stay pas sur %s." % (message[1]))
289 else:
290 notunderstood = True
291 elif cmd == u"die":
292 if auteur in self.overops:
293 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
294 self.mourir()
295 else:
296 notunderstood = True
297 elif cmd == u"crash":
298 if auteur in self.overops:
299 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
300 self.crash(auteur, "priv")
301 else:
302 notunderstood = True
303 elif cmd == u"reload":
304 if auteur in self.ops:
305 self.execute_something("reload", {"auteur" : auteur}, place=auteur, auteur=auteur)
306 else:
307 notunderstood = True
308 elif cmd == u"quiet":
309 if auteur in self.ops:
310 if len(message) > 1:
311 if message[1] in self.quiet_channels:
312 serv.privmsg(auteur, "Je me la ferme déjà sur %s" % (message[1]))
313 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
314 else:
315 self.quiet_channels.append(message[1])
316 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
317 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
318 else:
319 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
320 else:
321 notunderstood = True
322 elif cmd == u"noquiet":
323 if auteur in self.ops:
324 if len(message) > 1:
325 if message[1] in self.quiet_channels:
326 self.quiet_channels.remove(message[1])
327 serv.privmsg(auteur, "Quiet channels : " + " ".join(self.quiet_channels))
328 log(self.serveur, "priv", auteur, " ".join(message) + "[successful]")
329 else:
330 serv.privmsg(auteur, "Je ne me la ferme pas sur %s." % (message[1]))
331 log(self.serveur, "priv", auteur, " ".join(message) + "[failed]")
332 else:
333 notunderstood = True
334 elif cmd == u"say":
335 if auteur in self.overops and len(message) > 2:
336 serv.privmsg(message[1].encode("utf-8"), (u" ".join(message[2:])).encode("utf-8"))
337 log(self.serveur, "priv", auteur, " ".join(message))
338 elif len(message) <= 2:
339 serv.privmsg(auteur, "Syntaxe : SAY <channel> <message>")
340 else:
341 notunderstood = True
342 elif cmd == u"do":
343 if auteur in self.overops and len(message) > 2:
344 serv.action(message[1], " ".join(message[2:]))
345 log(self.serveur, "priv", auteur, " ".join(message))
346 elif len(message) <= 2:
347 serv.privmsg(auteur, "Syntaxe : DO <channel> <action>")
348 else:
349 notunderstood = True
350 elif cmd == u"kick":
351 if auteur in self.overops and len(message) > 2:
352 serv.kick(message[1].encode("utf-8"), message[2].encode("utf-8"), " ".join(message[3:]).encode("utf-8"))
353 log(self.serveur, "priv", auteur, " ".join(message))
354 elif len(message) <= 2:
355 serv.privmsg(auteur, "Syntaxe : KICK <channel> <pseudo> [<raison>]")
356 else:
357 notunderstood = True
358 elif cmd == u"ops":
359 if auteur in self.overops:
360 serv.privmsg(auteur, " ".join(self.ops))
361 else:
362 notunderstood = True
363 elif cmd == u"overops":
364 if auteur in self.overops:
365 serv.privmsg(auteur, " ".join(self.overops))
366 else:
367 notunderstood = True
368 elif cmd == u"dump" and auteur in self.ops:
369 self.dump(asked_by=auteur)
370 elif cmd == u"restore" and auteur in self.overops:
371 self.restore(asked_by=auteur)
372 else:
373 notunderstood = True
374 if notunderstood:
375 serv.privmsg(auteur, "Je n'ai pas compris. Essayez HELP…")
376
377 def on_pubmsg(self, serv, ev):
378 """À la réception d'un message sur un channel."""
379 if ignore_event(serv, ev):
380 return
381 auteur = irclib.nm_to_n(ev.source())
382 canal = ev.target()
383 message = ev.arguments()[0]
384 try:
385 message = bot_unicode(message)
386 except errors.UnicodeBotError:
387 if config.utf8_trigger and not canal in self.quiet_channels:
388 serv.privmsg(canal, (u"%s: %s"% ( auteur, random.choice(config.utf8_fail_answers))).encode("utf-8"))
389 return
390 pour_moi, message = self.pourmoi(serv, message)
391 if pour_moi and message.split()!=[]:
392 cmd = message.split()[0].lower()
393 try:
394 args = " ".join(message.split()[1:])
395 except:
396 args = ""
397 if cmd in [u"meurs", u"die", u"crève"]:
398 if auteur in self.overops:
399 log(self.serveur, canal, auteur, message + "[successful]")
400 self.mourir()
401 else:
402 serv.privmsg(canal,(u"%s: %s"%(auteur, random.choice(config.quit_fail_messages))).encode("utf-8"))
403 log(self.serveur, canal, auteur, message + "[failed]")
404 elif cmd == u"reload":
405 if auteur in self.ops:
406 self.execute_something("reload", {"auteur" : auteur}, place=canal, auteur=auteur)
407 elif cmd == u"crash":
408 if auteur in self.overops:
409 self.crash(auteur, canal)
410 elif cmd in [u"part", u"leave", u"dégage", u"va-t-en"]:
411 if auteur in self.ops and (not (canal in self.stay_channels)
412 or auteur in self.overops):
413 self.quitter(canal)
414 log(self.serveur, canal, auteur, message + "[successful]")
415 if canal in self.chanlist:
416 self.chanlist.remove(canal)
417 else:
418 serv.privmsg(canal,(u"%s: %s" % (auteur, random.choice(config.leave_fail_messages))).encode("utf-8"))
419 log(self.serveur, canal, auteur, message + "[failed]")
420
421 elif cmd in [u"ping"] and not canal in self.quiet_channels:
422 serv.privmsg(canal, "%s: pong" % (auteur))
423 elif cmd in [u"dump"]:
424 self.dump(asked_by=auteur, asked_where=canal)
425 elif cmd in [u"restore"] and auteur in self.overops:
426 self.restore(asked_by=auteur, asked_where=canal)
427 elif cmd in [u"display", u"link", u"url"]:
428 self.serv.privmsg(canal, "%s: %s" % (auteur, config.quote_display_url.encode("utf-8")))
429 else:
430 # Vu que ce bot est prévu pour parser des quotes il va falloir bosser ici
431 match = self.quote_pattern.match(message)
432 if match:
433 d = match.groupdict()
434 # On n'autorise pas les gens à déclarer le quoter
435 d["quoter"] = auteur.decode("utf-8")
436 if self.quotedb.store(**d):
437 serv.privmsg(canal, (u"%s: Ce sera retenu, répété, amplifié" % (auteur,)).encode("utf-8"))
438 self.quotedb.save()
439 else:
440 serv.privmsg(canal, (u"%s: Je le savais déjà." % (auteur,)).encode("utf-8"))
441 # Whou, attention, hack dégueu
442 # on enlève context- au début des !commands si il y est,
443 # et on passe à True le paramètre show_context pour s'en souvenir
444 show_context = False
445 if message.startswith(u"!context-"):
446 show_context = True
447 message = u"!" + message[9:]
448 if message.startswith(u"!quote"):
449 if message.strip() == u"!quote":
450 q = self.quotedb.random()
451 serv.privmsg(canal, q.display(show_context))
452 elif message.startswith("!quote "):
453 author = message[7:].strip()
454 try:
455 q = self.quotedb.randomfrom(author)
456 except IndexError:
457 serv.privmsg(canal, (u"Pas de quote de %s en mémoire." % author).encode("utf-8"))
458 return
459 serv.privmsg(canal, q.display(show_context))
460 elif message.startswith(u"!author") or message.startswith(u"!from"):
461 words = message.split()
462 cmd = words[0].lstrip("!")
463 regexp = any([cmd.endswith(suffix) for suffix in config.regex_suffixes])
464 search = u" ".join(words[1:])
465 authors = self.quotedb.search_authors(search, regexp)
466 if not authors:
467 serv.privmsg(canal, "%s: Pas d'auteur correspondant à la recherche." % (auteur,))
468 return
469 if cmd.startswith("author"):
470 if len(authors) > config.search_max_authors:
471 authors = authors[:config.search_max_authors+1] + ["+%s" % (len(authors) - config.search_max_authors)]
472 serv.privmsg(canal, "%s: %s" % (auteur, (u", ".join(authors)).encode("utf-8")))
473 elif cmd.startswith("from"):
474 quotes = sum([self.quotedb.quotesfrom(a) for a in authors], [])
475 q = random.choice(quotes)
476 serv.privmsg(canal, q.display(show_context))
477 elif message.startswith(u"!search"):
478 words = message.split()
479 cmd = words[0].lstrip("!")
480 regexp = cmd in ["search" + suffix for suffix in config.regex_suffixes]
481 search = u" ".join(words[1:])
482 quotes = self.quotedb.search(inquote=search, regexp=regexp)
483 # On recherche également sur le contexte si on est en !context-search
484 if show_context:
485 quotes += self.quotedb.search(place=search, regexp=regexp)
486 # Pour pas biaiser le choix aléatoire, on enlève les doublons
487 quotes = list(set(quotes))
488 if quotes:
489 q = random.choice(quotes)
490 serv.privmsg(canal, q.display(show_context))
491 else:
492 serv.privmsg(canal, "%s: Pas de quotes correspondant à la recherche." % (auteur,))
493
494 def on_action(self, serv, ev):
495 """À la réception d'une action."""
496 if ignore_event(serv, ev):
497 return
498 action = ev.arguments()[0]
499 auteur = irclib.nm_to_n(ev.source())
500 channel = ev.target()
501 try:
502 action = bot_unicode(action)
503 except errors.UnicodeBotError:
504 if config.utf8_trigger and not channel in self.quiet_channels:
505 serv.privmsg(channel, (u"%s: %s"%(auteur,random.choice(config.utf8_fail_answers))).encode("utf-8"))
506 return
507 mypseudo = self.nick
508
509 def on_kick(self, serv, ev):
510 """À la réception d'une action."""
511 auteur = irclib.nm_to_n(ev.source())
512 channel = ev.target()
513 victime = ev.arguments()[0]
514 raison = ev.arguments()[1]
515 if victime == self.nick:
516 log(self.serveur, ("%s kické de %s par %s (raison : %s)" % (victime, channel, auteur, raison)).decode("utf-8"))
517 time.sleep(2)
518 serv.join(channel)
519
520 ### .fork trick
521 def start_as_daemon(self, outfile):
522 sys.stderr = Logger(outfile)
523 self.start()
524
525
526 class Logger(object):
527 """Pour écrire ailleurs que sur stdout"""
528 def __init__(self, filename="parrot.full.log"):
529 self.filename = filename
530
531 def write(self, message):
532 f = open(self.filename, "a")
533 f.write(message)
534 f.close()
535
536 def main():
537 """Exécution principale : lecture des paramètres et lancement du bot."""
538 if len(sys.argv) == 1:
539 print "Usage : parrot.py <serveur> [--debug] [--no-output] [--daemon [--pidfile]] [--outfile]"
540 print " --outfile sans --no-output ni --daemon n'a aucun effet"
541 exit(1)
542 serveur = sys.argv[1]
543 if "--daemon" in sys.argv:
544 thisfile = os.path.realpath(__file__)
545 thisdirectory = thisfile.rsplit("/", 1)[0]
546 os.chdir(thisdirectory)
547 daemon = True
548 else:
549 daemon = False
550 if "debug" in sys.argv or "--debug" in sys.argv:
551 debug = True
552 else:
553 debug = False
554 if "--quiet" in sys.argv:
555 config.debug_stdout = False
556 serveurs = {
557 "irc" : "irc.crans.org",
558 "crans" : "irc.crans.org",
559 "irc.crans.org" : "irc.crans.org"}
560 if "--no-output" in sys.argv or "--daemon" in sys.argv:
561 outfile = "/var/log/bots/parrot.full.log"
562 for arg in sys.argv:
563 arg = arg.split("=")
564 if arg[0].strip('-') in ["out", "outfile", "logfile"]:
565 outfile = arg[1]
566 sys.stdout = Logger(outfile)
567 try:
568 serveur = serveurs[serveur]
569 except KeyError:
570 print "Server Unknown : %s" % (serveur)
571 exit(404)
572 parrot = Parrot(serveur,debug)
573 # Si on reçoit un SIGHUP, on reload la config
574 def sighup_handler(signum, frame):
575 parrot.execute_reload(auteur="SIGHUP")
576 signal.signal(signal.SIGHUP, sighup_handler)
577 # Daemonization
578 if daemon:
579 child_pid = os.fork()
580 if child_pid == 0:
581 os.setsid()
582 parrot.start_as_daemon(outfile)
583 else:
584 # on enregistre le pid de parrot
585 pidfile = "/var/run/bots/parrot.pid"
586 for arg in sys.argv:
587 arg = arg.split("=")
588 if arg[0].strip('-') in ["pidfile"]:
589 pidfile = arg[1]
590 f = open(pidfile, "w")
591 f.write("%s\n" % child_pid)
592 f.close()
593 else:
594 parrot.start()
595
596 if __name__ == "__main__":
597 main()