chiark / gitweb /
Merge branch 'keyboard'
authorIan Jackson <ian@liberator.relativity.greenend.org.uk>
Wed, 27 May 2009 22:57:54 +0000 (23:57 +0100)
committerIan Jackson <ian@liberator.relativity.greenend.org.uk>
Wed, 27 May 2009 22:57:54 +0000 (23:57 +0100)
1  2 
yoweb-scrape

diff --combined yoweb-scrape
@@@ -14,6 -14,7 +14,7 @@@ import sy
  import re as regexp
  import random
  import curses
+ import termios
  from optparse import OptionParser
  
  from BeautifulSoup import BeautifulSoup
@@@ -27,8 -28,20 +28,20 @@@ puzzles = ('Swordfighting/Bilging/Saili
        '/Drinking/Spades/Hearts/Treasure Drop/Poker/Distilling'+
        '/Alchemistry/Shipwrightery/Blacksmithing/Foraging').split('/')
  
+ core_duty_puzzles = [
+               'Gunning',
+               ['Sailing','Rigging'],
+               'Bilging',
+               'Carpentry',
+               ]
+ duty_puzzles = ([ 'Navigating', 'Battle Navigation' ] +
+               core_duty_puzzles +
+               [ 'Treasure Haul' ])
  standingvals = ('Able/Distinguished/Respected/Master'+
                '/Renowned/Grand-Master/Legendary/Ultimate').split('/')
+ standing_limit = len(standingvals)
  
  pirate_ref_re = regexp.compile('^/yoweb/pirate\\.wm')
  
@@@ -41,9 -54,12 +54,12 @@@ def debug(m)
        if opts.debug > 0:
                print >>opts.debug_file, m
  
- def sleep(seconds):
+ def debug_flush():
        if opts.debug > 0:
-               opts.debug_file.flush()
+               opts.debug_file.flush() 
+ def sleep(seconds):
+       debug_flush()
        time.sleep(seconds)
  
  def format_time_interval(ti):
@@@ -248,7 -264,7 +264,7 @@@ u'\\s*\\S*/([-A-Za-z]+)\\s*$|\\s*\\S*/\
                                skl.msg('puzzle "%s" no standing found' % puzzle)
                                continue
                        standing = sl[0]
-                       for i in range(0, len(standingvals)):
+                       for i in range(0, standing_limit):
                                if standing == standingvals[i]:
                                        self.standings[puzzle] = i
                        if not puzzle in self.standings:
@@@ -332,37 -348,23 +348,30 @@@ class CrewInfo(SomethingSoupInfo)
  #---------- pretty-printer for tables of pirate puzzle standings ----------
  
  class StandingsTable:
 -      def __init__(self, use_puzzles=None, col_width=6):
 +      def __init__(self, use_puzzles=None, col_width=6, gap_every=5):
                if use_puzzles is None:
                        if opts.ship_duty:
-                               use_puzzles=[
-                                       'Navigating','Battle Navigation',
-                                       'Gunning',
-                                       ['Sailing','Rigging'],
-                                       'Bilging',
-                                       'Carpentry',
-                                       'Treasure Haul'
-                               ]
+                               use_puzzles=duty_puzzles
                        else:
                                use_puzzles=puzzles
                self._puzzles = use_puzzles
                self.s = ''
                self._cw = col_width-1
 +              self._gap_every = gap_every
 +              self._linecount = 0
  
        def _pline(self, pirate, puzstrs, extra):
 +              if (self._linecount > 0
 +                  and self._gap_every is not None
 +                  and not (self._linecount % self._gap_every)):
 +                      self.s += '\n'
                self.s += ' %-*s' % (max(max_pirate_namelen, 14), pirate)
                for v in puzstrs:
                        self.s += ' %-*.*s' % (self._cw,self._cw, v)
                if extra:
                        self.s += ' ' + extra
                self.s += '\n'
 +              self._linecount += 1
  
        def _puzstr(self, pi, puzzle):
                if not isinstance(puzzle,list): puzzle = [puzzle]
                        spc = name.find(' ')
                        if spc < 0: return name
                        return name[0:min(4,spc)] + name[spc+1:]
 +              self._linecount = -2
                self._pline('', map(puzn_redact, self._puzzles), None)
 +              self._linecount = 0
        def literalline(self, line):
                self.s += line + '\n'
 +              self._linecount = 0
        def pirate_dummy(self, name, standingstring, extra=None):
                self._pline(name, standingstring * len(self._puzzles), extra)
        def pirate(self, pi, extra=None):
@@@ -845,6 -844,8 +854,8 @@@ class ChatLogTracker
                #  sorted by pirate name
                #  you can pass this None and you'll get []
                #  or True for the current vessel (which is the default)
+               #  the returned value is a fresh list of persistent
+               #  PirateAboard objects
                if vesselname is True: v = self._v
                else: v = self._vl.get(vesselname.title())
                if v is None: return []
@@@ -877,7 -878,6 +888,7 @@@ def do_standings_crew_of(args, bu)
        tab.headings()
        for (rank, members) in ci.crew:
                if not members: continue
 +              tab.literalline('')
                tab.literalline('%s:' % rank)
                for p in members:
                        pi = PirateInfo(p, random.randint(900,1800))
@@@ -1000,16 -1000,56 +1011,56 @@@ def do_ship_aid(args, bu)
        if opts.ship_duty is None: opts.ship_duty = True
  
        displayer = globals()['Display_'+opts.display]()
-       rotate_nya = '/-\\'
  
        (myself, track) = prep_chat_log(args, bu, progress=displayer)
  
-       def timeevent(t,e):
-               if t is None: return ' ' * 22
-               return " %-4s %-16s" % (format_time_interval(now - t),e)
        displayer.realstart()
  
+       if os.isatty(0): kr_create = KeystrokeReader
+       else: kr_create = DummyKeystrokeReader
+       try:
+               kreader = kr_create(0, 10)
+               ship_aid_core(myself, track, displayer, kreader)
+       finally:
+               kreader.stop()
+               print '\n'
+ class KeyBasedSorter:
+       def compar_key_pa(self, pa):
+               return self.compar_key(pa.pirate_info())
+       def lsort_pa(self, l):
+               l.sort(key = self.compar_key_pa)
+ class NameSorter(KeyBasedSorter):
+       def compar_key(self, pi): return pi.name
+ class SkillSorter(NameSorter):
+       def __init__(self, relevant):
+               self._want = frozenset(relevant.split('/'))
+               self._avoid = set()
+               for p in core_duty_puzzles:
+                       if isinstance(p,basestring): self._avoid.add(p)
+                       else: self._avoid |= set(p)
+               self._avoid -= self._want
+       
+       def compar_key(self, pi):
+               best_want = max([
+                       pi.standings.get(puz,-1)
+                       for puz in self._want
+                       ])
+               best_avoid = [
+                       -pi.standings.get(puz,standing_limit)
+                       for puz in self._avoid
+                       ]
+               best_avoid.sort()
+               def negate(x): return -x
+               debug('compar_key %s bw=%s ba=%s' % (pi.name, `best_want`,
+                       `best_avoid`))
+               return (-best_want, map(negate, best_avoid), pi.name)
+ def ship_aid_core(myself, track, displayer, kreader):
        def find_vessel():
                vn = track.vesselname()
                if vn: return (vn, " on board the %s" % vn)
                if vn: return (vn, " ashore from the %s" % vn)
                return (None, " not on a vessel")
  
+       def timeevent(t,e):
+               if t is None: return ' ' * 22
+               return " %-4s %-16s" % (format_time_interval(now - t),e)
        displayer.show(track.myname() + find_vessel()[1] + '...')
  
+       rotate_nya = '/-\\'
+       sort = NameSorter()
        while True:
                track.catchup()
                now = time.time()
  
                (vn, s) = find_vessel()
                s = track.myname() + s
-               s += " at %s\n" % time.strftime("%Y-%m-%d %H:%M:%S")
+               s += " at %s" % time.strftime("%Y-%m-%d %H:%M:%S")
+               s += kreader.info()
+               s += '\n'
  
                tbl = StandingsTable()
                tbl.headings()
  
                aboard = track.aboard(vn)
  
+               sort.lsort_pa(aboard)
                for pa in aboard:
                        pi = pa.pirate_info()
  
                                tbl.pirate(pi, xs)
  
                s += tbl.results()
                displayer.show(s)
-               sleep(1)
-               rotate_nya = rotate_nya[1:3] + rotate_nya[0]
+               k = kreader.getch()
+               if k is None:
+                       rotate_nya = rotate_nya[1:3] + rotate_nya[0]
+                       continue
+               if k == 'q': break
+               elif k == 'g': sort = SkillSorter('Gunning')
+               elif k == 'c': sort = SkillSorter('Carpentry')
+               elif k == 's': sort = SkillSorter('Sailing/Rigging')
+               elif k == 'b': sort = SkillSorter('Bilging')
+               elif k == 'n': sort = SkillSorter('Navigating')
+               elif k == 'd': sort = SkillSorter('Battle Navigation')
+               elif k == 't': sort = SkillSorter('Treasure Haul')
+               elif k == 'a': sort = NameSorter()
+               else: pass # unknown key command
+ #---------- individual keystroke input ----------
+ class DummyKeystrokeReader:
+       def __init__(self,fd,timeout_dummy): pass
+       def stop(self): pass
+       def getch(self): sleep(1); return None
+       def info(self): return ' [noninteractive]'
+ class KeystrokeReader(DummyKeystrokeReader):
+       def __init__(self, fd, timeout_decisec=0):
+               self._fd = fd
+               self._saved = termios.tcgetattr(fd)
+               a = termios.tcgetattr(fd)
+               a[3] &= ~(termios.ECHO | termios.ECHONL |
+                         termios.ICANON | termios.IEXTEN)
+               a[6][termios.VMIN] = 0
+               a[6][termios.VTIME] = timeout_decisec
+               termios.tcsetattr(fd, termios.TCSANOW, a)
+       def stop(self):
+               termios.tcsetattr(self._fd, termios.TCSANOW, self._saved)
+       def getch(self):
+               debug_flush()
+               byte = os.read(self._fd, 1)
+               if not len(byte): return None
+               return byte
+       def info(self):
+               return ''
  
  #---------- main program ----------
  
@@@ -1091,10 -1184,6 +1195,10 @@@ display modes (for --display) apply to 
        ao('--all-puzzles', action='store_false', dest='ship_duty',
                help='show all puzzles, not just ship duty stations')
  
 +      ao('--min-cache-reuse', type='int', dest='min_max_age',
 +              metavar='SECONDS', default=60,
 +              help='always reuse cache yoweb data if no older than this')
 +
        (opts,args) = pa.parse_args()
        random.seed()
  
        except KeyError: pa.error('unknown mode "%s"' % mode)
  
        # fixed parameters
 -      opts.min_max_age = 60
 -      opts.expire_age = 3600
 +      opts.expire_age = max(3600, opts.min_max_age)
 +
        opts.ship_reboard_clearout = 3600
  
        if opts.cache_dir.startswith('~/'):