docs: def parse_ls(out):
      """
      Parser for Android's ls -lR output.
      Takes a string, returns parsed structure.
      """
  
      # assumed ls -lR line format:
      # -rw-r--r-- root     shell           0 2013-07-05 02:26 tasks
      # drwxr-xr-x root     root              2013-07-05 02:26 log
      # brw------- root     root     179,   0 2013-07-05 02:26 mmcblk0
      # lrwxrwxrwx root     root              2013-07-05 02:34 subsystem -> ../class/bdi
  
      # CAVE: format may change through versions.
      # TODO: implement plausibility test.
  
      mode = r'^(.)'
      field = r'([^ ]+) +'
      dev = r'(\d+), +(\d+) '
      date = r'(\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}) '
      name = r'(.+)$'
      link = r'(.+) -> (.+)$'
  
      logger = get_default_logger()
  
      # adb returns newline as \r\n
      # but mozdevice uses \n
      for dirstr in out[2:-2].split('\r\n\r\n'):
          lines = dirstr.split('\r\n')
          dirname = lines[0][:-1]
          if len(lines) == 2 and lines[1].startswith("opendir failed"):
              continue
          for filestr in lines[1:]:
              if filestr.endswith(": No such file or directory"):
                  continue
              if filestr.endswith(": Permission denied"):
                  continue
              specs = None
              if filestr[0] in 'dsp':  # directory, socket, pipe
                  regexp = mode + field * 3 + date + name
                  m = re.search(regexp, filestr)
                  specs = {
                  'mode': m.group(1),
                  'perm': m.group(2),
                  'uid': m.group(3),
                  'gid': m.group(4),
                  'date': m.group(5),
                  'name': m.group(6)
                  }
              elif filestr[0] == 'l':  # symbolic link
                  regexp = mode + field * 3 + date + link
                  m = re.search(regexp, filestr)
                  specs = {
                  'mode': m.group(1),
                  'perm': m.group(2),
                  'uid': m.group(3),
                  'gid': m.group(4),
                  'date': m.group(5),
                  'name': m.group(6),
                  'link': m.group(7)
                  }
              elif filestr[0] in 'cb':  # device
                  regexp = mode + field * 3 + dev + date + name
                  m = re.search(regexp, filestr)
                  specs = {
                  'mode': m.group(1),
                  'perm': m.group(2),
                  'uid': m.group(3),
                  'gid': m.group(4),
                  'major': m.group(5),
                  'minor': m.group(6),
                  'date': m.group(7),
                  'name': m.group(8)
                  }
              else:  # rest
                  try:
                      regexp = mode + field * 4 + date + name
                      m = re.search(regexp, filestr)
                      specs = {
                      'mode': m.group(1),
                      'perm': m.group(2),
                      'uid': m.group(3),
                      'gid': m.group(4),
                      'size': m.group(5),
                      'date': m.group(6),
                      'name': m.group(7)
                      }
                  except:
                      logger.error("parse error on %s" % filestr)
  
              try:
                  specs['name'] = '/' + os.path.relpath("%s/%s" % (dirname, specs['name']), '/')
                  if 'link' in specs.keys():
                      specs['link'] = '/' + os.path.relpath("%s/%s" % (dirname, specs['link']), '/')
              except:
                  logger.warning("no name from %s" % filestr)
  
              yield specs