Completed
Pull Request — master (#1522)
by Abdeali
03:09
created

coalib.collecting.collect_bears()   A

Complexity

Conditions 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 3
dl 0
loc 22
rs 9.2
1
import functools
2
import os
3
4
from coalib.bears.BEAR_KIND import BEAR_KIND
5
from coalib.collecting.Importers import iimport_objects
6
from coalib.misc.Decorators import yield_once
7
from coalib.output.printers.LOG_LEVEL import LOG_LEVEL
8
from coalib.parsing.Globbing import fnmatch, iglob
9
10
11
def _get_kind(bear_class):
12
    try:
13
        return bear_class.kind()
14
    except NotImplementedError:
15
        return None
16
17
18
def _import_bears(file_path, kinds):
19
    # recursive imports:
20
    for bear_list in iimport_objects(file_path,
21
                                     names='__additional_bears__',
22
                                     types=list):
23
        for bear_class in bear_list:
24
            if _get_kind(bear_class) in kinds:
25
                yield bear_class
26
    # normal import
27
    for bear_class in iimport_objects(file_path,
28
                                      attributes='kind',
29
                                      local=True):
30
        if _get_kind(bear_class) in kinds:
31
            yield bear_class
32
33
34
@yield_once
35
def icollect(file_paths, ignored_globs=None):
36
    """
37
    Evaluate globs in file paths and return all matching files.
38
39
    :param file_paths:    file path or list of such that can include globs
40
    :param ignored_globs: list of globs to ignore when matching files
41
    :return:              iterator that yields tuple of path of a matching
42
                          file, the glob where it was found
43
    """
44
    if isinstance(file_paths, str):
45
        file_paths = [file_paths]
46
47
    for file_path in file_paths:
48
        for match in iglob(file_path):
49
            if not ignored_globs or not fnmatch(match, ignored_globs):
50
                yield match, file_path
51
52
53
def collect_files(file_paths, log_printer, ignored_file_paths=None,
54
                  limit_file_paths=None):
55
    """
56
    Evaluate globs in file paths and return all matching files
57
58
    :param file_paths:         file path or list of such that can include globs
59
    :param ignored_file_paths: list of globs that match to-be-ignored files
60
    :param limit_file_paths:   list of globs that the files are limited to
61
    :return:                   list of paths of all matching files
62
    """
63
    if limit_file_paths:
64
        limit_fnmatch = functools.partial(fnmatch, patterns=limit_file_paths)
65
    else:
66
        limit_fnmatch = lambda fname: True  # Always in the limit_files
67
68
    valid_files = list(filter(lambda fname: os.path.isfile(fname[0]),
69
                              icollect(file_paths, ignored_file_paths)))
70
71
    # Find globs that gave no files and warn the user
72
    if valid_files:
73
        collected_files, file_globs_with_files = zip(*valid_files)
74
    else:
75
        collected_files, file_globs_with_files = [], []
76
77
    _warn_if_unused_glob(log_printer, file_paths, file_globs_with_files,
78
                         "No files matching '{}' were found.")
79
    limited_files = list(filter(limit_fnmatch, collected_files))
80
    return limited_files
81
82
83
def collect_dirs(dir_paths, ignored_dir_paths=None):
84
    """
85
    Evaluate globs in directory paths and return all matching directories
86
87
    :param dir_paths:         file path or list of such that can include globs
88
    :param ignored_dir_paths: list of globs that match to-be-ignored dirs
89
    :return:                  list of paths of all matching directories
90
    """
91
    valid_dirs = list(filter(lambda fname: os.path.isdir(fname[0]),
92
                             icollect(dir_paths, ignored_dir_paths)))
93
    if valid_dirs:
94
        collected_dirs, dummy = zip(*valid_dirs)
95
        return list(collected_dirs)
96
    else:
97
        return []
98
99
100
@yield_once
101
def icollect_bears(bear_dirs, bear_globs, kinds, log_printer):
102
    """
103
    Collect all bears from bear directories that have a matching kind.
104
105
    :param bear_dirs:   directory name or list of such that can contain bears
106
    :param bear_globs:  globs of bears to collect
107
    :param kinds:       list of bear kinds to be collected
108
    :param log_printer: log_printer to handle logging
109
    :return:            iterator that yields a tuple with bear class and
110
                        which bear_glob was used to find that bear class.
111
    """
112
    for bear_dir, dir_glob in filter(lambda x: os.path.isdir(x[0]),
113
                                     icollect(bear_dirs)):
114
        for bear_glob in bear_globs:
115
            for matching_file in iglob(
116
                    os.path.join(bear_dir, bear_glob + '.py')):
117
118
                try:
119
                    for bear in _import_bears(matching_file, kinds):
120
                        yield bear, bear_glob
121
                except BaseException as exception:
122
                    log_printer.log_exception(
123
                        "Unable to collect bears from {file}. Probably the "
124
                        "file is malformed or the module code raises an "
125
                        "exception.".format(file=matching_file),
126
                        exception,
127
                        log_level=LOG_LEVEL.WARNING)
128
129
130
def collect_bears(bear_dirs, bear_globs, kinds, log_printer):
131
    """
132
    Collect all bears from bear directories that have a matching kind
133
    matching the given globs.
134
135
    :param bear_dirs:   directory name or list of such that can contain bears
136
    :param bear_globs:  globs of bears to collect
137
    :param kinds:       list of bear kinds to be collected
138
    :param log_printer: log_printer to handle logging
139
    :return:            tuple of list of matching bear classes based on kind.
140
                        The lists are in the same order as `kinds`
141
    """
142
    bears_found = tuple([] for i in range(len(kinds)))
143
    bear_globs_with_bears = set()
144
    for bear, glob in icollect_bears(bear_dirs, bear_globs, kinds, log_printer):
145
        index = kinds.index(_get_kind(bear))
146
        bears_found[index].append(bear)
147
        bear_globs_with_bears.add(glob)
148
149
    _warn_if_unused_glob(log_printer, bear_globs, bear_globs_with_bears,
150
                         "No bears were found matching '{}'.")
151
    return bears_found
152
153
154
def collect_all_bears_from_sections(sections, log_printer):
155
    """
156
    Collect all kinds of bears from bear directories given in the sections.
157
158
    :param bear_dirs:   directory name or list of such that can contain bears
159
    :param log_printer: log_printer to handle logging
160
    """
161
    local_bears = {}
162
    global_bears = {}
163
    for section in sections:
164
        bear_dirs = sections[section].bear_dirs()
165
        local_bears[section], global_bears[section] = collect_bears(
166
            bear_dirs,
167
            ["**"],
168
            [BEAR_KIND.LOCAL, BEAR_KIND.GLOBAL],
169
            log_printer)
170
    return local_bears, global_bears
171
172
173
def _warn_if_unused_glob(log_printer, globs, used_globs, message):
174
    """
175
    Warn if a glob has not been used.
176
177
    :param log_printer: The log_printer to handle logging.
178
    :param globs:       List of globs that were expected to be used.
179
    :param used_globs:  List of globs that were actually used.
180
    :param message:     Warning message to display if a glob is unused.
181
                        The glob which was unused will be added using
182
                        .format()
183
    """
184
    unused_globs = set(globs) - set(used_globs)
185
    for glob in unused_globs:
186
        log_printer.warn(message.format(glob))
187