def iter_lines(url_or_text, ext=None, mode='rt'):
    r""" Return an iterator over the lines of a file or URI response.

    >>> len(list(iter_lines('cats_and_dogs.txt')))
    263
    >>> len(list(iter_lines(list('abcdefgh'))))
    8
    >>> len(list(iter_lines('abc\n def\n gh\n')))
    3
    >>> len(list(iter_lines('abc\n def\n gh')))
    3
    >>> 20000 > len(list(iter_lines(BOOK_PATH))) > 200
    True
    """
    if url_or_text is None or not url_or_text:
        return []
        # url_or_text = 'https://www.fileformat.info/info/charset/UTF-8/list.htm'
    elif isinstance(url_or_text, (str, bytes, basestring)):
        if '\n' in url_or_text or '\r' in url_or_text:
            return StringIO(url_or_text)
        elif os.path.isfile(os.path.join(DATA_PATH, url_or_text)):
            return open(os.path.join(DATA_PATH, url_or_text), mode=mode)
        elif os.path.isfile(url_or_text):
            return open(os.path.join(url_or_text), mode=mode)
        if os.path.isdir(url_or_text):
            filepaths = [filemeta['path'] for filemeta in find_files(url_or_text, ext=ext)]
            return itertools.chain.from_iterable(map(open, filepaths))
        url = looks_like_url(url_or_text)
        if url:
            for i in range(3):
                return requests.get(url, stream=True, allow_redirects=True, timeout=5)
        else:
            return StringIO(url_or_text)
    elif isinstance(url_or_text, (list, tuple)):
        # FIXME: make this lazy with chain and map so it doesn't gobble up RAM
        text = ''
        for s in url_or_text:
            text += '\n'.join(list(iter_lines(s, ext=ext, mode=mode))) + '\n'
        return iter_lines(text)
r""" Return an iterator over the lines of a file or URI response.

    >>> len(list(iter_lines('cats_and_dogs.txt')))
    263
    >>> len(list(iter_lines(list('abcdefgh'))))
    8
    >>> len(list(iter_lines('abc\n def\n gh\n')))
    3
    >>> len(list(iter_lines('abc\n def\n gh')))
    3
    >>> 20000 > len(list(iter_lines(BOOK_PATH))) > 200
    True