1from __future__ import unicode_literals 2 3import json 4import logging 5import os 6import shutil 7import sys 8import tempfile 9 10 11CONFIG_FILE = '.reviewboardrc' 12 13tempfiles = [] 14tempdirs = [] 15builtin = {} 16 17 18def is_exe_in_path(name): 19 """Checks whether an executable is in the user's search path. 20 21 This expects a name without any system-specific executable extension. 22 It will append the proper extension as necessary. For example, 23 use "myapp" and not "myapp.exe". 24 25 This will return True if the app is in the path, or False otherwise. 26 27 Taken from djblets.util.filesystem to avoid an extra dependency 28 """ 29 if sys.platform == 'win32' and not name.endswith('.exe'): 30 name += '.exe' 31 32 for dir in os.environ['PATH'].split(os.pathsep): 33 if os.path.exists(os.path.join(dir, name)): 34 return True 35 36 return False 37 38 39def cleanup_tempfiles(): 40 for tmpfile in tempfiles: 41 try: 42 os.unlink(tmpfile) 43 except OSError: 44 pass 45 46 for tmpdir in tempdirs: 47 shutil.rmtree(tmpdir, ignore_errors=True) 48 49 50def _load_python_file(filename, config): 51 with open(filename) as f: 52 exec(compile(f.read(), filename, 'exec'), config) 53 return config 54 55 56def make_tempfile(content=None, prefix='rbtools.', suffix=None, filename=None): 57 """Create a temporary file and return the path. 58 59 If not manually removed, then the resulting temp file will be removed when 60 RBTools exits (or if :py:func:`cleanup_tempfiles` is called). 61 62 This can be given an explicit name for a temporary file, in which case 63 the file will be created inside of a temporary directory (created with 64 :py:func:`make_tempdir`. In this case, the parent directory will only 65 be deleted when :py:func:`cleanup_tempfiles` is called. 66 67 Args: 68 content (bytes, optional): 69 The content for the text file. 70 71 prefix (bool, optional): 72 The prefix for the temp filename. This defaults to ``rbtools.``. 73 74 suffix (bool, optional): 75 The suffix for the temp filename. 76 77 filename (unicode, optional): 78 An explicit name of the file. If provided, this will override 79 ``suffix`` and ``prefix``. 80 81 Returns: 82 unicode: 83 The temp file path. 84 """ 85 if filename is not None: 86 tmpdir = make_tempdir() 87 tmpfile = os.path.join(tmpdir, filename) 88 89 with open(tmpfile, 'wb') as fp: 90 if content: 91 fp.write(content) 92 else: 93 with tempfile.NamedTemporaryFile(prefix=prefix, 94 suffix=suffix or '', 95 delete=False) as fp: 96 tmpfile = fp.name 97 98 if content: 99 fp.write(content) 100 101 tempfiles.append(tmpfile) 102 103 return tmpfile 104 105 106def make_tempdir(parent=None): 107 """Create a temporary directory and return the path. 108 109 The path is stored in an array for later cleanup. 110 111 Args: 112 parent (unicode, optional): 113 An optional parent directory to create the path in. 114 115 Returns: 116 unicode: 117 The name of the new temporary directory. 118 """ 119 tmpdir = tempfile.mkdtemp(prefix='rbtools.', 120 dir=parent) 121 tempdirs.append(tmpdir) 122 123 return tmpdir 124 125 126def make_empty_files(files): 127 """Creates each file in the given list and any intermediate directories.""" 128 for f in files: 129 path = os.path.dirname(f) 130 131 if path and not os.path.exists(path): 132 try: 133 os.makedirs(path) 134 except OSError as e: 135 logging.error('Unable to create directory %s: %s', path, e) 136 continue 137 138 try: 139 with open(f, 'w'): 140 # Set the file access and modified times to the current time. 141 os.utime(f, None) 142 except IOError as e: 143 logging.error('Unable to create empty file %s: %s', f, e) 144 145 146def walk_parents(path): 147 """Walks up the tree to the root directory.""" 148 while os.path.splitdrive(path)[1] != os.sep: 149 yield path 150 path = os.path.dirname(path) 151 152 153def get_home_path(): 154 """Retrieve the homepath.""" 155 if 'HOME' in os.environ: 156 return os.environ['HOME'] 157 elif 'APPDATA' in os.environ: 158 return os.environ['APPDATA'] 159 else: 160 return '' 161 162 163def get_config_paths(): 164 """Return the paths to each :file:`.reviewboardrc` influencing the cwd. 165 166 A list of paths to :file:`.reviewboardrc` files will be returned, where 167 each subsequent list entry should have lower precedence than the previous. 168 i.e. configuration found in files further up the list will take precedence. 169 170 Configuration in the paths set in :envvar:`$RBTOOLS_CONFIG_PATH` will take 171 precedence over files found in the current working directory or its 172 parents. 173 """ 174 config_paths = [] 175 176 # Apply config files from $RBTOOLS_CONFIG_PATH first, ... 177 for path in os.environ.get('RBTOOLS_CONFIG_PATH', '').split(os.pathsep): 178 # Filter out empty paths, this also takes care of if 179 # $RBTOOLS_CONFIG_PATH is unset or empty. 180 if not path: 181 continue 182 183 filename = os.path.realpath(os.path.join(path, CONFIG_FILE)) 184 185 if os.path.exists(filename) and filename not in config_paths: 186 config_paths.append(filename) 187 188 # ... then config files from the current or parent directories. 189 for path in walk_parents(os.getcwd()): 190 filename = os.path.realpath(os.path.join(path, CONFIG_FILE)) 191 192 if os.path.exists(filename) and filename not in config_paths: 193 config_paths.append(filename) 194 195 # Finally, the user's own config file. 196 home_config_path = os.path.realpath(os.path.join(get_home_path(), 197 CONFIG_FILE)) 198 199 if (os.path.exists(home_config_path) and 200 home_config_path not in config_paths): 201 config_paths.append(home_config_path) 202 203 return config_paths 204 205 206def parse_config_file(filename): 207 """Parse a .reviewboardrc file. 208 209 Returns a dictionary containing the configuration from the file. 210 211 The ``filename`` argument should contain a full path to a 212 .reviewboardrc file. 213 """ 214 config = { 215 'TREES': {}, 216 'ALIASES': {}, 217 } 218 219 try: 220 config = _load_python_file(filename, config) 221 except SyntaxError as e: 222 raise Exception('Syntax error in config file: %s\n' 223 'Line %i offset %i\n' 224 % (filename, e.lineno, e.offset)) 225 226 return dict((k, config[k]) 227 for k in set(config.keys()) - set(builtin.keys())) 228 229 230def load_config(): 231 """Load configuration from .reviewboardrc files. 232 233 This will read all of the .reviewboardrc files influencing the 234 cwd and return a dictionary containing the configuration. 235 """ 236 nested_config = { 237 'ALIASES': {}, 238 'COLOR': { 239 'INFO': None, 240 'DEBUG': None, 241 'WARNING': 'yellow', 242 'ERROR': 'red', 243 'CRITICAL': 'red' 244 }, 245 'TREES': {}, 246 } 247 config = {} 248 249 for filename in reversed(get_config_paths()): 250 parsed_config = parse_config_file(filename) 251 252 for key in nested_config: 253 nested_config[key].update(parsed_config.pop(key, {})) 254 255 config.update(parsed_config) 256 257 config.update(nested_config) 258 259 return config 260 261 262# This extracts a dictionary of the built-in globals in order to have a clean 263# dictionary of settings, consisting of only what has been specified in the 264# config file. 265exec('True', builtin) 266