def _nl(self): self._o('\n')
- def _pline(self, pirate, puzstrs, extra):
+ def _pline(self, lhs, puzstrs, extra):
if (self._linecount > 0
and self._gap_every is not None
and not (self._linecount % self._gap_every)):
self._nl()
- self._o(' %-*s' % (max(max_pirate_namelen, 14), pirate))
+ self._o('%-*s' % (max(max_pirate_namelen+1, 15), lhs))
for v in puzstrs:
self._o(' %-*.*s' % (self._cw,self._cw, v))
if extra:
self._nl()
self._linecount = 0
def pirate_dummy(self, name, standingstring, extra=None):
- self._pline(name, standingstring * len(self._puzzles), extra)
+ standings = standingstring * len(self._puzzles)
+ self._pline(' '+name, standings, extra)
def pirate(self, pi, extra=None):
puzstrs = [self._puzstr(pi,puz) for puz in self._puzzles]
- self._pline(pi.name, puzstrs, extra)
+ self._pline(' '+pi.name, puzstrs, extra)
#---------- chat log parser ----------
class PirateAboard:
# This is essentially a transparent, dumb, data class.
- # pa.v
+ # pa.v may be None
# pa.name
# pa.last_time
# pa.last_event
# pa.last_chat_chan
# pa.pi
+ # Also used for jobbing applicants:
+ # happens when expires (to "-")
+ # - disembark, leaves crew no
+ # aboard evidence of them being aboard no
+ # applied "has applied for the job" 120s, configurable
+ # ashore "has taken a job" 30min, configurable
+ # declined "declined the job offer" 30s, configurable
+ # invited "has been invited to job" 120s, configurable
+ #
+ # pa.jobber None, 'ashore', 'applied', 'invited', 'declined'
+ # pa.expires expiry time time
+
def __init__(pa, pn, v, time, event):
pa.name = pn
pa.v = v
pa.last_chat_chan = None
pa.gunner = False
pa.pi = None
+ pa.jobber = None
+ pa.expires = None
def pirate_info(pa):
now = time.time()
# self._v = self._vl[self._vessel]
self._date = None
self._myself = myself_pi
- self._f = file(logfn)
self._lbuf = ''
- self._progress = [0, os.fstat(self._f.fileno()).st_size]
+ self._f = file(logfn)
+ flen = os.fstat(self._f.fileno()).st_size
+ max_backlog = 500000
+ if flen > max_backlog:
+ startpos = flen - max_backlog
+ self._f.seek(startpos)
+ self._f.readline()
+ self._progress = [0, flen - self._f.tell()]
self._disembark_myself()
self._need_redisplay = False
self._lastvessel = None
self._need_redisplay = True
def _vessel_updated(self, v, timestamp):
+ if v is None: return
v['#lastinfo'] = timestamp
self.force_redisplay()
- def _onboard_event(self,v,timestamp,pirate,event):
+ def _onboard_event(self,v,timestamp,pirate,event,jobber=None):
pa = self._pl.get(pirate, None)
if pa is not None and pa.v is v:
pa.last_time = timestamp
pa.last_event = event
else:
- if pa is not None: del pa.v[pirate]
+ if pa is not None and pa.v is not None:
+ del pa.v[pirate]
pa = PirateAboard(pirate, v, timestamp, event)
self._pl[pirate] = pa
- v[pirate] = pa
+ if v is not None: v[pirate] = pa
+ pa.jobber = jobber
+
+ if jobber is None: timeout = None
+ else: timeout = getattr(opts, 'timeout_'+jobber)
+ if timeout is None: pa.expires = None
+ else: pa.expires = timestamp + timeout
self._vessel_updated(v, timestamp)
return pa
+ def _expire_jobbers(self, now):
+ for pa in self._pl.values():
+ if pa.expires is None: continue
+ if pa.expires >= now: continue
+ v = pa.v
+ del self._pl[pa.name]
+ if v is not None: del v[pa.name]
+ self.force_redisplay()
+
def _trash_vessel(self, v):
for pn in v:
if pn.startswith('#'): continue
def _debug_line_disposition(self,timestamp,l,m):
debug('CLT %13s %-40s %s' % (timestamp,m,l))
+ def _rm_crew_l(self,re,l):
+ m = regexp.match(re,l)
+ if m and m.group(2) == self._myself.crew[1]:
+ return m.group(1)
+ else:
+ return None
+
def chatline(self,l):
rm = lambda re: regexp.match(re,l)
d = lambda m: self._debug_line_disposition(timestamp,l,m)
+ rm_crew = lambda re: self._rm_crew_l(re,l)
timestamp = None
m = rm('=+ (\\d+)/(\\d+)/(\\d+) =+$')
def ob1(did): ob_x(m.group(1), did); return d(did)
def oba(did): return ob1('%s %s' % (did, m.group(2)))
+ def jb(pirate,jobber):
+ return self._onboard_event(
+ None, timestamp, pirate,
+ ("jobber %s" % jobber),
+ jobber=jobber
+ )
+
def disembark(v, timestamp, pirate, event):
self._onboard_event(
v, timestamp, pirate, 'leaving '+event)
def chat_core(speaker, chan):
try: pa = self._pl[speaker]
except KeyError: return 'mystery'
- if pa.v is not self._v: return 'elsewhere'
+ if pa.v is not None and pa.v is not self._v:
+ return 'elsewhere'
pa.last_chat_time = timestamp
pa.last_chat_chan = chan
self.force_redisplay()
(cmdr, metacmd) = m.groups()
metacmd = regexp.sub('\\s+', ' ', metacmd).strip()
m2 = regexp.match(
- '/([ad]) (?:([A-Za-z* ]+)\\s*:)?([A-Za-z ]+)$',
- metacmd)
+ '/([adj]) (?:([A-Za-z* ]+)\\s*:)?([A-Za-z ]+)$',
+ metacmd)
if not m2: return chat(chan)
(cmd, pattern, targets) = m2.groups()
dml = ['cmd', chan, cmd]
if cmd == 'a': each = self._onboard_event
- else: each = disembark
+ elif cmd == 'd': each = disembark
+ else: each = lambda *l: self._onboard_event(*l,
+ **{'jobber':'applied'})
if cmdr == self._myself.name:
dml.append('self')
dml.append('other')
how = 'cmd: %s %s' % (cmd,cmdr)
- v = self._find_matching_vessel(
- pattern, timestamp, cmdr, dml, create=True)
+ if cmd == 'j':
+ if pattern is not None:
+ return chat(chan)
+ v = None
+ else:
+ v = self._find_matching_vessel(
+ pattern, timestamp, cmdr,
+ dml, create=True)
- if v is not None:
+ if cmd == 'j' or v is not None:
targets = targets.strip().split(' ')
dml.append(`len(targets)`)
for target in targets:
' to the rest of the ship\\.')
if m: return ob1('boarder unrepelled');
+ pirate = rm_crew("(\\w+) has taken a job with '(.*)'\\.")
+ if pirate: return jb(pirate, 'ashore')
+
+ pirate = rm_crew("(\\w+) has left '(.*)'\\.")
+ if pirate:
+ disembark(self._v, timestamp, pirate, 'left crew')
+ return d('left crew')
+
+ m = rm('(\w+) has applied for the posted job\.')
+ if m: return jb(m.group(1), 'applied')
+
+ pirate= rm_crew("(\\w+) has been invited to job for '(.*)'\\.")
+ if pirate: return jb(pirate, 'invited')
+
+ pirate = rm_crew("(\\w+) declined the job offer for '(.*)'\\.")
+ if pirate: return jb(pirate, 'declined')
+
m = rm('(\\w+) has left the vessel\.')
if m:
pirate = m.group(1)
return d('not-matched')
+ def _str_pa(self, pn, pa):
+ assert self._pl[pn] == pa
+ s = ' '*20 + "%s %-*s %13s %-30s %13s %-20s %13s" % (
+ (' ','G')[pa.gunner],
+ max_pirate_namelen, pn,
+ pa.last_time, pa.last_event,
+ pa.last_chat_time, pa.last_chat_chan,
+ pa.jobber)
+ if pa.expires is not None:
+ s += " %-5d" % (pa.expires - pa.last_time)
+ s += "\n"
+ return s
+
def _str_vessel(self, vn, v):
s = ' vessel %s\n' % vn
s += ' '*20 + "%-*s %13s\n" % (
if pn.startswith('#'): continue
pa = v[pn]
assert pa.v == v
- assert self._pl[pn] == pa
- s += ' '*20 + "%s %-*s %13s %-30s %13s %s\n" % (
- (' ','G')[pa.gunner],
- max_pirate_namelen, pn,
- pa.last_time, pa.last_event,
- pa.last_chat_time, pa.last_chat_chan)
+ s += self._str_pa(pn,pa)
return s
def __str__(self):
for vn in sorted(self._vl.keys()):
if vn == self._vessel: continue
s += self._str_vessel(vn, self._vl[vn])
+ s += " elsewhere\n"
for p in self._pl:
pa = self._pl[p]
- assert pa.v[p] is pa
- assert pa.v in self._vl.values()
+ if pa.v is not None:
+ assert pa.v[p] is pa
+ assert pa.v in self._vl.values()
+ else:
+ s += self._str_pa(pa.name, pa)
s += '>\n'
return s
self._lbuf = ''
if opts.debug >= 2:
debug(self.__str__())
+ self._expire_jobbers(time.time())
+
if progress: progress.caughtup()
def changed(self):
return [ v[pn]
for pn in sorted(v.keys())
if not pn.startswith('#') ]
+ def jobbers(self):
+ # returns a the jobbers' PirateAboards,
+ # sorted by jobber class and reverse of expiry time
+ l = [ pa
+ for pa in self._pl.values()
+ if pa.jobber is not None
+ ]
+ def compar_key(pa):
+ return (pa.jobber, -pa.expires)
+ l.sort(key = compar_key)
+ return l
#---------- implementations of actual operation modes ----------
s += kreader.info()
s += '\n'
+ tbl_s = StringIO()
+ tbl = StandingsTable(tbl_s)
+
aboard = track.aboard(vn)
sort.lsort_pa(aboard)
- tbl_s = StringIO()
- tbl = StandingsTable(tbl_s)
+ jobbers = track.jobbers()
- if track.vesselname(): howmany = ' %d aboard' % len(aboard)
+ if track.vesselname(): howmany = 'aboard: %2d' % len(aboard)
else: howmany = ''
tbl.headings(howmany, ' sorted by '+sort.desc())
- for pa in aboard:
+ last_jobber = None
+
+ for pa in aboard + jobbers:
+ if pa.jobber != last_jobber:
+ last_jobber = pa.jobber
+ tbl.literalline('')
+ tbl.literalline('jobbers '+last_jobber)
+
pi = pa.pirate_info()
xs = ''
type='choice', choices=['dumb','overwrite'],
help='how to display ship aid')
+ ao_jt = lambda wh, t: ao(
+ '--timeout-sa-'+wh, action='store', dest='timeout_'+wh,
+ default=t, help=('set timeout for expiring %s jobbers' % wh))
+ ao_jt('applied', 120)
+ ao_jt('invited', 120)
+ ao_jt('declined', 30)
+ ao_jt('ashore', 1800)
+
ao('--ship-duty', action='store_true', dest='ship_duty',
help='show ship duty station puzzles')
ao('--all-puzzles', action='store_false', dest='ship_duty',