1 | import traceback |
||
2 | from pyprint.Printer import Printer |
||
3 | |||
4 | from coalib.misc.Decorators import enforce_signature |
||
5 | from coalib.output.printers.LogPrinter import LogPrinter |
||
6 | from coalib.settings.FunctionMetadata import FunctionMetadata |
||
7 | from coalib.settings.Section import Section |
||
8 | |||
9 | |||
10 | class Bear(Printer, LogPrinter): |
||
11 | """ |
||
12 | A bear contains the actual subroutine that is responsible for checking |
||
13 | source code for certain specifications. However it can actually do |
||
14 | whatever it wants with the files it gets. If you are missing some Result |
||
15 | type, feel free to contact us and/or help us extending the coalib. |
||
16 | |||
17 | This is the base class for every bear. If you want to write an bear, you |
||
18 | will probably want to look at the GlobalBear and LocalBear classes that |
||
19 | inherit from this class. In any case you'll want to overwrite at least the |
||
20 | run method. You can send debug/warning/error messages through the |
||
21 | debug(), warn(), err() functions. These will send the |
||
22 | appropriate messages so that they are outputted. Be aware that if you use |
||
23 | err(), you are expected to also terminate the bear run-through |
||
24 | immediately. |
||
25 | |||
26 | If you need some setup or teardown for your bear, feel free to overwrite |
||
27 | the set_up() and tear_down() functions. They will be invoked |
||
28 | before/after every run invocation. |
||
29 | |||
30 | Settings are available at all times through self.section. |
||
31 | """ |
||
32 | |||
33 | @enforce_signature |
||
34 | def __init__(self, |
||
35 | section: Section, |
||
36 | message_queue, |
||
37 | timeout=0): |
||
38 | Printer.__init__(self) |
||
39 | LogPrinter.__init__(self, self) |
||
40 | |||
41 | if not hasattr(message_queue, "put") and message_queue is not None: |
||
42 | raise TypeError("message_queue has to be a Queue or None.") |
||
43 | |||
44 | self.section = section |
||
45 | self.message_queue = message_queue |
||
46 | self.timeout = timeout |
||
47 | |||
48 | def _print(self, output, **kwargs): |
||
49 | self.debug(output) |
||
50 | |||
51 | def log_message(self, log_message, timestamp=None, **kwargs): |
||
52 | if self.message_queue is not None: |
||
53 | self.message_queue.put(log_message) |
||
54 | |||
55 | def run(self, *args, dependency_results=None, **kwargs): |
||
56 | raise NotImplementedError |
||
57 | |||
58 | def run_bear_from_section(self, args, kwargs): |
||
59 | try: |
||
60 | kwargs.update( |
||
61 | self.get_metadata().create_params_from_section(self.section)) |
||
62 | except ValueError as err: |
||
63 | self.warn("The bear {} cannot be executed.".format( |
||
64 | type(self).__name__), str(err)) |
||
65 | return |
||
66 | |||
67 | cp = self.check_prerequisites() |
||
68 | if cp is not True: |
||
69 | error_string = ("The bear " + type(self).__name__ + |
||
70 | " does not fulfill all requirements.") |
||
71 | if cp is not False: |
||
72 | error_string += " " + cp |
||
73 | |||
74 | self.warn(error_string) |
||
75 | return |
||
76 | |||
77 | return self.run(*args, **kwargs) |
||
78 | |||
79 | def execute(self, *args, **kwargs): |
||
80 | name = type(self).__name__ |
||
81 | try: |
||
82 | self.debug("Running bear {}...".format(name)) |
||
83 | # If it's already a list it won't change it |
||
84 | return list(self.run_bear_from_section(args, kwargs) or []) |
||
85 | except: |
||
86 | self.warn( |
||
87 | "Bear {} failed to run. Take a look at debug messages for " |
||
88 | "further information.".format(name)) |
||
89 | self.debug( |
||
90 | "The bear {bear} raised an exception. If you are the writer " |
||
91 | "of this bear, please make sure to catch all exceptions. If " |
||
92 | "not and this error annoys you, you might want to get in " |
||
93 | "contact with the writer of this bear.\n\nTraceback " |
||
94 | "information is provided below:\n\n{traceback}" |
||
95 | "\n".format(bear=name, traceback=traceback.format_exc())) |
||
96 | |||
97 | @staticmethod |
||
98 | def kind(): |
||
99 | """ |
||
100 | :return: The kind of the bear |
||
101 | """ |
||
102 | raise NotImplementedError |
||
103 | |||
104 | @classmethod |
||
105 | def get_metadata(cls): |
||
106 | """ |
||
107 | :return: Metadata for the run function. However parameters like `self` |
||
108 | or parameters implicitly used by coala (e.g. filename for |
||
109 | local bears) are already removed. |
||
110 | """ |
||
111 | return FunctionMetadata.from_function( |
||
112 | cls.run, |
||
113 | omit={"self", "dependency_results"}) |
||
114 | |||
115 | @classmethod |
||
116 | def missing_dependencies(cls, lst): |
||
117 | """ |
||
118 | Checks if the given list contains all dependencies. |
||
119 | |||
120 | :param lst: A list of all already resolved bear classes (not |
||
121 | instances). |
||
122 | :return: A list of missing dependencies. |
||
123 | """ |
||
124 | dep_classes = cls.get_dependencies() |
||
125 | |||
126 | for item in lst: |
||
127 | if item in dep_classes: |
||
128 | dep_classes.remove(item) |
||
129 | |||
130 | return dep_classes |
||
131 | |||
132 | @staticmethod |
||
133 | def get_dependencies(): |
||
134 | """ |
||
135 | Retrieves bear classes that are to be executed before this bear gets |
||
136 | executed. The results of these bears will then be passed to the |
||
137 | run method as a dict via the dependency_results argument. The dict |
||
138 | will have the name of the Bear as key and the list of its results as |
||
139 | results. |
||
140 | |||
141 | :return: A list of bear classes. |
||
142 | """ |
||
143 | return [] |
||
144 | |||
145 | @classmethod |
||
146 | def get_non_optional_settings(cls): |
||
147 | """ |
||
148 | This method has to determine which settings are needed by this bear. |
||
149 | The user will be prompted for needed settings that are not available |
||
150 | in the settings file so don't include settings where a default value |
||
151 | would do. |
||
152 | |||
153 | :return: A dictionary of needed settings as keys and a tuple of help |
||
154 | text and annotation as values |
||
155 | """ |
||
156 | return cls.get_metadata().non_optional_params |
||
157 | |||
158 | def check_prerequisites(self): |
||
0 ignored issues
–
show
|
|||
159 | """ |
||
160 | Checks whether needed prerequisites of the bear are satisfied. |
||
161 | |||
162 | This function gets executed before the bear is run. Provided settings |
||
163 | are also parsed before running this function. |
||
164 | |||
165 | :return: True if prerequisites are satisfied, else False or a string |
||
166 | that serves a more detailed description of what's missing. |
||
167 | """ |
||
168 | return True |
||
169 |
If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example
could be written as