def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None):
    """Like `os.walk`, but yields things in case-insensitive sorted,
    breadth-first order.  Directory and file names matching any glob
    pattern in `ignore` are skipped. If `logger` is provided, then
    warning messages are logged there when a directory cannot be listed.
    """
    # Make sure the pathes aren't Unicode strings.
    path = bytestring_path(path)
    ignore = [bytestring_path(i) for i in ignore]

    # Get all the directories and files at this level.
    try:
        contents = os.listdir(syspath(path))
    except OSError as exc:
        if logger:
            logger.warning('could not list directory {}: {}'.format(
                displayable_path(path), exc.strerror
            ))
        return
    dirs = []
    files = []
    for base in contents:
        base = bytestring_path(base)

        # Skip ignored filenames.
        skip = False
        for pat in ignore:
            if fnmatch.fnmatch(base, pat):
                if logger:
                    logger.debug('ignoring {} due to ignore rule {}'.format(
                        base, pat
                    ))
                skip = True
                break
        if skip:
            continue

        # Add to output as either a file or a directory.
        cur = os.path.join(path, base)
        if (ignore_hidden and not hidden.is_hidden(cur)) or not ignore_hidden:
            if os.path.isdir(syspath(cur)):
                dirs.append(base)
            else:
                files.append(base)

    # Sort lists (case-insensitive) and yield the current level.
    dirs.sort(key=bytes.lower)
    files.sort(key=bytes.lower)
    yield (path, dirs, files)

    # Recurse into directories.
    for base in dirs:
        cur = os.path.join(path, base)
        # yield from sorted_walk(...)
        yield from sorted_walk(cur, ignore, ignore_hidden, logger)