r""" Use the SpaCy parser to parse and tag natural language strings.

    Load the SpaCy parser language model lazily and share it among all nlpia modules.
    Probably unnecessary, since SpaCy probably takes care of this with `spacy.load()`

    >>> _parse is None
    True
    >>> doc = nlp("Domo arigatto Mr. Roboto.")
    >>> doc.text
    'Domo arigatto Mr. Roboto.'
    >>> doc.ents
    (Roboto,)
    >>> docs = nlp("Hey Mr. Tangerine Man!\nPlay a song for me.\n", linesep='\n')
    >>> doc = docs[0]
    >>> [t for t in doc]
    [Hey, Mr., Tangerine, Man, !]
    >>> [tok.text for tok in doc]
    ['Hey', 'Mr.', 'Tangerine', 'Man', '!']
    >>> [(tok.text, tok.tag_) for tok in doc]
    [('Hey', 'UH'),
     ('Mr.', 'NNP'),
     ('Tangerine', 'NNP'),
     ('Man', 'NN'),
     ('!', '.')]
    >>> [(ent.text, ent.ent_id, ent.has_vector, ent.vector[:3].round(3)) for ent in doc.ents]
    [('Tangerine Man', 0, True, array([0.72 , 1.913, 2.675], dtype=float32))]

def nlp(texts, lang='en', linesep=None, verbose=True):
    r""" Use the SpaCy parser to parse and tag natural language strings.

    Load the SpaCy parser language model lazily and share it among all nlpia modules.
    Probably unnecessary, since SpaCy probably takes care of this with `spacy.load()`

    >>> _parse is None
    True
    >>> doc = nlp("Domo arigatto Mr. Roboto.")
    >>> doc.text
    'Domo arigatto Mr. Roboto.'
    >>> doc.ents
    (Roboto,)
    >>> docs = nlp("Hey Mr. Tangerine Man!\nPlay a song for me.\n", linesep='\n')
    >>> doc = docs[0]
    >>> [t for t in doc]
    [Hey, Mr., Tangerine, Man, !]
    >>> [tok.text for tok in doc]
    ['Hey', 'Mr.', 'Tangerine', 'Man', '!']
    >>> [(tok.text, tok.tag_) for tok in doc]
    [('Hey', 'UH'),
     ('Mr.', 'NNP'),
     ('Tangerine', 'NNP'),
     ('Man', 'NN'),
     ('!', '.')]
    >>> [(ent.text, ent.ent_id, ent.has_vector, ent.vector[:3].round(3)) for ent in doc.ents]
    [('Tangerine Man', 0, True, array([0.72 , 1.913, 2.675], dtype=float32))]
    """
    # doesn't let you load a different model anywhere else in the module
    linesep = os.linesep if linesep in ('default', True, 1, 'os') else linesep
    tqdm_prog = no_tqdm if (not verbose or (hasattr(texts, '__len__') and len(texts) < 3)) else tqdm
    global _parse
    if not _parse:
        try:
            _parse = spacy.load(lang)
        except (OSError, IOError):
            try:
                spacy.cli.download(lang)
            except URLError:
                logger.warning("Unable to download Spacy language model '{}' so nlp(text) just returns text.split()".format(lang))
    parse = _parse or str.split
    # TODO: reverse this recursion (str first then sequence) to allow for sequences of sequences of texts
    if isinstance(texts, str):
        if linesep:
            return nlp(texts.split(linesep))
        else:
            return nlp([texts])
    if hasattr(texts, '__len__'):
        if len(texts) == 1:
            return parse(texts[0])
        elif len(texts) > 1:
            return [(parse or str.split)(text) for text in tqdm_prog(texts)]
        else:
            return None
    else:
        # return generator if sequence of strings doesn't have __len__ which means its an iterable or generator itself
        return (parse(text) for text in tqdm_prog(texts))