1"""
2Helpful generators and other tools
3"""
4
5
6import fnmatch
7import re
8
9import salt.utils.files
10
11
12def split(orig, sep=None):
13    """
14    Generator function for iterating through large strings, particularly useful
15    as a replacement for str.splitlines().
16
17    See http://stackoverflow.com/a/3865367
18    """
19    exp = re.compile(r"\s+" if sep is None else re.escape(sep))
20    pos = 0
21    length = len(orig)
22    while True:
23        match = exp.search(orig, pos)
24        if not match:
25            if pos < length or sep is not None:
26                val = orig[pos:]
27                if val:
28                    # Only yield a value if the slice was not an empty string,
29                    # because if it is then we've reached the end. This keeps
30                    # us from yielding an extra blank value at the end.
31                    yield val
32            break
33        if pos < match.start() or sep is not None:
34            yield orig[pos : match.start()]
35        pos = match.end()
36
37
38def read_file(fh_, chunk_size=1048576):
39    """
40    Generator that reads chunk_size bytes at a time from a file/filehandle and
41    yields it.
42    """
43    try:
44        if chunk_size != int(chunk_size):
45            raise ValueError
46    except ValueError:
47        raise ValueError("chunk_size must be an integer")
48    try:
49        while True:
50            try:
51                chunk = fh_.read(chunk_size)
52            except AttributeError:
53                # Open the file and re-attempt the read
54                fh_ = salt.utils.files.fopen(fh_, "rb")  # pylint: disable=W8470
55                chunk = fh_.read(chunk_size)
56            if not chunk:
57                break
58            yield chunk
59    finally:
60        try:
61            fh_.close()
62        except AttributeError:
63            pass
64
65
66def fnmatch_multiple(candidates, pattern):
67    """
68    Convenience function which runs fnmatch.fnmatch() on each element of passed
69    iterable. The first matching candidate is returned, or None if there is no
70    matching candidate.
71    """
72    # Make sure that candidates is iterable to avoid a TypeError when we try to
73    # iterate over its items.
74    try:
75        candidates_iter = iter(candidates)
76    except TypeError:
77        return None
78
79    for candidate in candidates_iter:
80        try:
81            if fnmatch.fnmatch(candidate, pattern):
82                return candidate
83        except TypeError:
84            pass
85    return None
86