Parser for Android's ls -lR output.
    Takes a string, returns parsed structure.

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