| 1 |  |  | # -*- coding: utf-8 -*- | 
            
                                                                                                            
                            
            
                                    
            
            
                | 2 |  |  | # ----------------------------------------------------------------------------- | 
            
                                                                                                            
                            
            
                                    
            
            
                | 3 |  |  | # Copyright (c) 2015 Yann Lanthony | 
            
                                                                                                            
                            
            
                                    
            
            
                | 4 |  |  | # Copyright (c) 2017-2018 Spyder Project Contributors | 
            
                                                                                                            
                            
            
                                    
            
            
                | 5 |  |  | # | 
            
                                                                                                            
                            
            
                                    
            
            
                | 6 |  |  | # Licensed under the terms of the MIT License | 
            
                                                                                                            
                            
            
                                    
            
            
                | 7 |  |  | # (See LICENSE.txt for details) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 8 |  |  | # ----------------------------------------------------------------------------- | 
            
                                                                                                            
                            
            
                                    
            
            
                | 9 |  |  | """The filesystem watcher api.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 10 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 11 |  |  | # yapf: disable | 
            
                                                                                                            
                            
            
                                    
            
            
                | 12 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 13 |  |  | from __future__ import absolute_import | 
            
                                                                                                            
                            
            
                                    
            
            
                | 14 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 15 |  |  | # Standard library imports | 
            
                                                                                                            
                            
            
                                    
            
            
                | 16 |  |  | import functools | 
            
                                                                                                            
                            
            
                                    
            
            
                | 17 |  |  | import logging | 
            
                                                                                                            
                            
            
                                    
            
            
                | 18 |  |  | import time | 
            
                                                                                                            
                            
            
                                    
            
            
                | 19 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 20 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 21 |  |  | _log = logging.getLogger(__name__) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 22 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 23 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 24 |  |  | def retry(n, interval=0.1): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 25 |  |  |     """Retry a function or method n times before raising an exception. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 26 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 27 |  |  |     :param n: Number of times to retry | 
            
                                                                                                            
                            
            
                                    
            
            
                | 28 |  |  |     :param interval: Time to sleep before attempts | 
            
                                                                                                            
                            
            
                                    
            
            
                | 29 |  |  |     """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 30 |  |  |     def decorate(fn): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 31 |  |  |         @functools.wraps(fn) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 32 |  |  |         def attempt(*args, **kwargs): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 33 |  |  |             attempts = 0 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 34 |  |  |             while True: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 35 |  |  |                 try: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 36 |  |  |                     return fn(*args, **kwargs) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 37 |  |  |                 except Exception: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 38 |  |  |                     attempts += 1 | 
            
                                                                                                            
                            
            
                                    
            
            
                | 39 |  |  |                     if n <= attempts: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 40 |  |  |                         raise | 
            
                                                                                                            
                            
            
                                    
            
            
                | 41 |  |  |                     time.sleep(interval) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 42 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 43 |  |  |         return attempt | 
            
                                                                                                            
                            
            
                                    
            
            
                | 44 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 45 |  |  |     return decorate | 
            
                                                                                                            
                            
            
                                    
            
            
                | 46 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 47 |  |  | # yapf: enable | 
            
                                                                                                            
                            
            
                                    
            
            
                | 48 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 49 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 50 |  |  | class Watcher(object): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 51 |  |  |     """Watcher base class. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 52 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 53 |  |  |     Watchers monitor a file or directory and call the on_change method when a | 
            
                                                                                                            
                            
            
                                    
            
            
                | 54 |  |  |     change occurs. The on_change method should trigger the compiler function | 
            
                                                                                                            
                            
            
                                    
            
            
                | 55 |  |  |     passed in during construction and dispatch the result to all connected | 
            
                                                                                                            
                            
            
                                    
            
            
                | 56 |  |  |     callbacks. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 57 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 58 |  |  |     Watcher implementations must inherit from this base class. Subclasses | 
            
                                                                                                            
                            
            
                                    
            
            
                | 59 |  |  |     should perform any setup required in the setup method, rather than | 
            
                                                                                                            
                            
            
                                    
            
            
                | 60 |  |  |     overriding __init__. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 61 |  |  |     """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 62 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 63 |  |  |     def __init__(self, watch_dir, compiler, args=None, kwargs=None): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 64 |  |  |         """Store initialization values and call Watcher.setup.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 65 |  |  |         self._watch_dir = watch_dir | 
            
                                                                                                            
                            
            
                                    
            
            
                | 66 |  |  |         self._compiler = compiler | 
            
                                                                                                            
                            
            
                                    
            
            
                | 67 |  |  |         self._args = args or () | 
            
                                                                                                            
                            
            
                                    
            
            
                | 68 |  |  |         self._kwargs = kwargs or {} | 
            
                                                                                                            
                            
            
                                    
            
            
                | 69 |  |  |         self._callbacks = set() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 70 |  |  |         self._log = _log | 
            
                                                                                                            
                            
            
                                    
            
            
                | 71 |  |  |         self.setup() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 72 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 73 |  |  |     def setup(self): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 74 |  |  |         """Perform any setup required here. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 75 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 76 |  |  |         Rather than implement __init__, subclasses can perform any setup in | 
            
                                                                                                            
                            
            
                                    
            
            
                | 77 |  |  |         this method. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 78 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 79 |  |  |         return NotImplemented | 
            
                                                                                                            
                            
            
                                    
            
            
                | 80 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 81 |  |  |     def start(self): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 82 |  |  |         """Start this Watcher.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 83 |  |  |         return NotImplemented | 
            
                                                                                                            
                            
            
                                    
            
            
                | 84 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 85 |  |  |     def stop(self): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 86 |  |  |         """Stop this Watcher.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 87 |  |  |         return NotImplemented | 
            
                                                                                                            
                            
            
                                    
            
            
                | 88 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 89 |  |  |     def join(self): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 90 |  |  |         """Wait for this Watcher to finish.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 91 |  |  |         return NotImplemented | 
            
                                                                                                            
                            
            
                                    
            
            
                | 92 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 93 |  |  |     @retry(5) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 94 |  |  |     def compile(self): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 95 |  |  |         """Call the Watcher's compiler.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 96 |  |  |         self._log.debug( | 
            
                                                                                                            
                            
            
                                    
            
            
                | 97 |  |  |             'Compiling sass...%s(*%s, **%s)', | 
            
                                                                                                            
                            
            
                                    
            
            
                | 98 |  |  |             self._compiler, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 99 |  |  |             self._args, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 100 |  |  |             self._kwargs, | 
            
                                                                                                            
                            
            
                                    
            
            
                | 101 |  |  |         ) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 102 |  |  |         return self._compiler(*self._args, **self._kwargs) | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 103 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 104 |  |  |     def compile_and_dispatch(self): | 
            
                                                                        
                            
            
                                    
            
            
                | 105 |  |  |         """Compile and dispatch the resulting css to connected callbacks.""" | 
            
                                                                        
                            
            
                                    
            
            
                | 106 |  |  |         self._log.debug('Compiling and dispatching....') | 
            
                                                                        
                            
            
                                    
            
            
                | 107 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 108 |  |  |         try: | 
            
                                                                        
                            
            
                                    
            
            
                | 109 |  |  |             css = self.compile() | 
            
                                                                        
                            
            
                                    
            
            
                | 110 |  |  |         except Exception: | 
            
                                                                        
                            
            
                                    
            
            
                | 111 |  |  |             self._log.exception('Failed to compile...') | 
            
                                                                        
                            
            
                                    
            
            
                | 112 |  |  |             return | 
            
                                                                        
                            
            
                                    
            
            
                | 113 |  |  |  | 
            
                                                                        
                            
            
                                    
            
            
                | 114 |  |  |         self.dispatch(css) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 115 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 116 |  |  |     def dispatch(self, css): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 117 |  |  |         """Dispatch css to connected callbacks.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 118 |  |  |         self._log.debug('Dispatching callbacks...') | 
            
                                                                                                            
                            
            
                                    
            
            
                | 119 |  |  |         for callback in self._callbacks: | 
            
                                                                                                            
                            
            
                                    
            
            
                | 120 |  |  |             callback(css) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 121 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 122 |  |  |     def on_change(self): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 123 |  |  |         """Call when a change is detected. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 124 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 125 |  |  |         Subclasses must call this method when they detect a change. Subclasses | 
            
                                                                                                            
                            
            
                                    
            
            
                | 126 |  |  |         may also override this method in order to manually compile and dispatch | 
            
                                                                                                            
                            
            
                                    
            
            
                | 127 |  |  |         callbacks. For example, a Qt implementation may use signals and slots | 
            
                                                                                                            
                            
            
                                    
            
            
                | 128 |  |  |         to ensure that compiling and executing callbacks happens in the main | 
            
                                                                                                            
                            
            
                                    
            
            
                | 129 |  |  |         GUI thread. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 130 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 131 |  |  |         self._log.debug('Change detected...') | 
            
                                                                                                            
                            
            
                                    
            
            
                | 132 |  |  |         self.compile_and_dispatch() | 
            
                                                                                                            
                            
            
                                    
            
            
                | 133 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 134 |  |  |     def connect(self, fn): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 135 |  |  |         """Connect a callback to this Watcher. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 136 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 137 |  |  |         All callbacks are called when a change is detected. Callbacks are | 
            
                                                                                                            
                            
            
                                    
            
            
                | 138 |  |  |         passed the compiled css. | 
            
                                                                                                            
                            
            
                                    
            
            
                | 139 |  |  |         """ | 
            
                                                                                                            
                            
            
                                    
            
            
                | 140 |  |  |         self._log.debug('Connecting callback: %s', fn) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 141 |  |  |         self._callbacks.add(fn) | 
            
                                                                                                            
                            
            
                                    
            
            
                | 142 |  |  |  | 
            
                                                                                                            
                            
            
                                    
            
            
                | 143 |  |  |     def disconnect(self, fn): | 
            
                                                                                                            
                            
            
                                    
            
            
                | 144 |  |  |         """Disconnect a callback from this Watcher.""" | 
            
                                                                                                            
                            
            
                                    
            
            
                | 145 |  |  |         self._log.debug('Disconnecting callback: %s', fn) | 
            
                                                                                                            
                                                                
            
                                    
            
            
                | 146 |  |  |         self._callbacks.discard(fn) | 
            
                                                        
            
                                    
            
            
                | 147 |  |  |  |