coala-analyzer /
coala
| 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