Clone of official tools

Committer:
screamer
Date:
Mon Aug 29 11:18:36 2016 +0100
Revision:
29:1210849dba19
Parent:
24:25bff2709c20
Child:
31:8ea194f6145b
Port the latest tools patches from https://github.com/ARMmbed/mbed-os

Who changed what in which revision?

UserRevisionLine numberNew contents of line
screamer 0:66f3b5499f7f 1 """
screamer 0:66f3b5499f7f 2 mbed SDK
screamer 0:66f3b5499f7f 3 Copyright (c) 2011-2013 ARM Limited
screamer 0:66f3b5499f7f 4
screamer 0:66f3b5499f7f 5 Licensed under the Apache License, Version 2.0 (the "License");
screamer 0:66f3b5499f7f 6 you may not use this file except in compliance with the License.
screamer 0:66f3b5499f7f 7 You may obtain a copy of the License at
screamer 0:66f3b5499f7f 8
screamer 0:66f3b5499f7f 9 http://www.apache.org/licenses/LICENSE-2.0
screamer 0:66f3b5499f7f 10
screamer 0:66f3b5499f7f 11 Unless required by applicable law or agreed to in writing, software
screamer 0:66f3b5499f7f 12 distributed under the License is distributed on an "AS IS" BASIS,
screamer 0:66f3b5499f7f 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
screamer 0:66f3b5499f7f 14 See the License for the specific language governing permissions and
screamer 0:66f3b5499f7f 15 limitations under the License.
screamer 0:66f3b5499f7f 16 """
screamer 0:66f3b5499f7f 17 import sys
screamer 0:66f3b5499f7f 18 import inspect
screamer 0:66f3b5499f7f 19 import os
screamer 22:9e85236d8716 20 import argparse
screamer 22:9e85236d8716 21 import math
screamer 0:66f3b5499f7f 22 from os import listdir, remove, makedirs
screamer 0:66f3b5499f7f 23 from shutil import copyfile
screamer 29:1210849dba19 24 from os.path import isdir, join, exists, split, relpath, splitext, abspath
screamer 29:1210849dba19 25 from os.path import commonprefix, normpath
screamer 0:66f3b5499f7f 26 from subprocess import Popen, PIPE, STDOUT, call
screamer 7:5af61d55adbe 27 import json
screamer 7:5af61d55adbe 28 from collections import OrderedDict
screamer 19:3604ee113e2d 29 import logging
screamer 0:66f3b5499f7f 30
screamer 21:4fdf0dd04f6f 31 def compile_worker(job):
screamer 29:1210849dba19 32 """Standard task runner used for compiling
screamer 29:1210849dba19 33
screamer 29:1210849dba19 34 Positional argumets:
screamer 29:1210849dba19 35 job - a dict containing a list of commands and the remaining arguments
screamer 29:1210849dba19 36 to run_cmd
screamer 29:1210849dba19 37 """
screamer 21:4fdf0dd04f6f 38 results = []
screamer 21:4fdf0dd04f6f 39 for command in job['commands']:
screamer 21:4fdf0dd04f6f 40 try:
screamer 29:1210849dba19 41 _, _stderr, _rc = run_cmd(command, work_dir=job['work_dir'],
screamer 29:1210849dba19 42 chroot=job['chroot'])
screamer 29:1210849dba19 43 except KeyboardInterrupt:
screamer 21:4fdf0dd04f6f 44 raise ToolException
screamer 21:4fdf0dd04f6f 45
screamer 21:4fdf0dd04f6f 46 results.append({
screamer 21:4fdf0dd04f6f 47 'code': _rc,
screamer 21:4fdf0dd04f6f 48 'output': _stderr,
screamer 21:4fdf0dd04f6f 49 'command': command
screamer 21:4fdf0dd04f6f 50 })
screamer 21:4fdf0dd04f6f 51
screamer 21:4fdf0dd04f6f 52 return {
screamer 21:4fdf0dd04f6f 53 'source': job['source'],
screamer 21:4fdf0dd04f6f 54 'object': job['object'],
screamer 21:4fdf0dd04f6f 55 'commands': job['commands'],
screamer 21:4fdf0dd04f6f 56 'results': results
screamer 21:4fdf0dd04f6f 57 }
screamer 21:4fdf0dd04f6f 58
screamer 29:1210849dba19 59 def cmd(command, check=True, verbose=False, shell=False, cwd=None):
screamer 29:1210849dba19 60 """A wrapper to run a command as a blocking job"""
screamer 29:1210849dba19 61 text = command if shell else ' '.join(command)
screamer 0:66f3b5499f7f 62 if verbose:
screamer 0:66f3b5499f7f 63 print text
screamer 29:1210849dba19 64 return_code = call(command, shell=shell, cwd=cwd)
screamer 29:1210849dba19 65 if check and return_code != 0:
screamer 29:1210849dba19 66 raise Exception('ERROR %d: "%s"' % (return_code, text))
screamer 0:66f3b5499f7f 67
screamer 0:66f3b5499f7f 68
screamer 17:04753e1e329d 69 def run_cmd(command, work_dir=None, chroot=None, redirect=False):
screamer 29:1210849dba19 70 """Run a command in the forground
screamer 29:1210849dba19 71
screamer 29:1210849dba19 72 Positional arguments:
screamer 29:1210849dba19 73 command - the command to run
screamer 29:1210849dba19 74
screamer 29:1210849dba19 75 Keyword arguments:
screamer 29:1210849dba19 76 work_dir - the working directory to run the command in
screamer 29:1210849dba19 77 chroot - the chroot to run the command in
screamer 29:1210849dba19 78 redirect - redirect the stderr to a pipe to be used later
screamer 29:1210849dba19 79 """
screamer 17:04753e1e329d 80 if chroot:
screamer 17:04753e1e329d 81 # Conventions managed by the web team for the mbed.org build system
screamer 17:04753e1e329d 82 chroot_cmd = [
screamer 17:04753e1e329d 83 '/usr/sbin/chroot', '--userspec=33:33', chroot
screamer 17:04753e1e329d 84 ]
screamer 29:1210849dba19 85 for element in command:
screamer 29:1210849dba19 86 chroot_cmd += [element.replace(chroot, '')]
screamer 17:04753e1e329d 87
screamer 29:1210849dba19 88 logging.debug("Running command %s", ' '.join(chroot_cmd))
screamer 17:04753e1e329d 89 command = chroot_cmd
screamer 21:4fdf0dd04f6f 90 work_dir = None
screamer 17:04753e1e329d 91
screamer 0:66f3b5499f7f 92 try:
screamer 29:1210849dba19 93 process = Popen(command, stdout=PIPE,
screamer 29:1210849dba19 94 stderr=STDOUT if redirect else PIPE, cwd=work_dir)
screamer 29:1210849dba19 95 _stdout, _stderr = process.communicate()
screamer 29:1210849dba19 96 except OSError:
screamer 0:66f3b5499f7f 97 print "[OS ERROR] Command: "+(' '.join(command))
screamer 0:66f3b5499f7f 98 raise
screamer 13:ab47a20b66f0 99
screamer 29:1210849dba19 100 return _stdout, _stderr, process.returncode
screamer 0:66f3b5499f7f 101
screamer 0:66f3b5499f7f 102
screamer 0:66f3b5499f7f 103 def run_cmd_ext(command):
screamer 29:1210849dba19 104 """ A version of run command that checks if the command exists befor running
screamer 29:1210849dba19 105
screamer 29:1210849dba19 106 Positional arguments:
screamer 29:1210849dba19 107 command - the command line you are trying to invoke
screamer 29:1210849dba19 108 """
screamer 0:66f3b5499f7f 109 assert is_cmd_valid(command[0])
screamer 29:1210849dba19 110 process = Popen(command, stdout=PIPE, stderr=PIPE)
screamer 29:1210849dba19 111 _stdout, _stderr = process.communicate()
screamer 29:1210849dba19 112 return _stdout, _stderr, process.returncode
screamer 0:66f3b5499f7f 113
screamer 0:66f3b5499f7f 114
screamer 29:1210849dba19 115 def is_cmd_valid(command):
screamer 29:1210849dba19 116 """ Verify that a command exists and is executable
screamer 29:1210849dba19 117
screamer 29:1210849dba19 118 Positional arguments:
screamer 29:1210849dba19 119 command - the command to check
screamer 29:1210849dba19 120 """
screamer 0:66f3b5499f7f 121 caller = get_caller_name()
screamer 29:1210849dba19 122 cmd_path = find_cmd_abspath(command)
screamer 29:1210849dba19 123 if not cmd_path:
screamer 29:1210849dba19 124 error("%s: Command '%s' can't be found" % (caller, command))
screamer 29:1210849dba19 125 if not is_exec(cmd_path):
screamer 29:1210849dba19 126 error("%s: Command '%s' resolves to file '%s' which is not executable"
screamer 29:1210849dba19 127 % (caller, command, cmd_path))
screamer 0:66f3b5499f7f 128 return True
screamer 0:66f3b5499f7f 129
screamer 0:66f3b5499f7f 130
screamer 0:66f3b5499f7f 131 def is_exec(path):
screamer 29:1210849dba19 132 """A simple check to verify that a path to an executable exists
screamer 29:1210849dba19 133
screamer 29:1210849dba19 134 Positional arguments:
screamer 29:1210849dba19 135 path - the executable
screamer 29:1210849dba19 136 """
screamer 0:66f3b5499f7f 137 return os.access(path, os.X_OK) or os.access(path+'.exe', os.X_OK)
screamer 0:66f3b5499f7f 138
screamer 0:66f3b5499f7f 139
screamer 29:1210849dba19 140 def find_cmd_abspath(command):
screamer 0:66f3b5499f7f 141 """ Returns the absolute path to a command.
screamer 0:66f3b5499f7f 142 None is returned if no absolute path was found.
screamer 29:1210849dba19 143
screamer 29:1210849dba19 144 Positional arguhments:
screamer 29:1210849dba19 145 command - the command to find the path of
screamer 0:66f3b5499f7f 146 """
screamer 29:1210849dba19 147 if exists(command) or exists(command + '.exe'):
screamer 29:1210849dba19 148 return os.path.abspath(command)
screamer 0:66f3b5499f7f 149 if not 'PATH' in os.environ:
screamer 29:1210849dba19 150 raise Exception("Can't find command path for current platform ('%s')"
screamer 29:1210849dba19 151 % sys.platform)
screamer 29:1210849dba19 152 path_env = os.environ['PATH']
screamer 29:1210849dba19 153 for path in path_env.split(os.pathsep):
screamer 29:1210849dba19 154 cmd_path = '%s/%s' % (path, command)
screamer 29:1210849dba19 155 if exists(cmd_path) or exists(cmd_path + '.exe'):
screamer 29:1210849dba19 156 return cmd_path
screamer 0:66f3b5499f7f 157
screamer 0:66f3b5499f7f 158
screamer 0:66f3b5499f7f 159 def mkdir(path):
screamer 29:1210849dba19 160 """ a wrapped makedirs that only tries to create a directory if it does not
screamer 29:1210849dba19 161 exist already
screamer 29:1210849dba19 162
screamer 29:1210849dba19 163 Positional arguments:
screamer 29:1210849dba19 164 path - the path to maybe create
screamer 29:1210849dba19 165 """
screamer 0:66f3b5499f7f 166 if not exists(path):
screamer 0:66f3b5499f7f 167 makedirs(path)
screamer 0:66f3b5499f7f 168
screamer 0:66f3b5499f7f 169
screamer 0:66f3b5499f7f 170 def copy_file(src, dst):
screamer 0:66f3b5499f7f 171 """ Implement the behaviour of "shutil.copy(src, dst)" without copying the
screamer 29:1210849dba19 172 permissions (this was causing errors with directories mounted with samba)
screamer 29:1210849dba19 173
screamer 29:1210849dba19 174 Positional arguments:
screamer 29:1210849dba19 175 src - the source of the copy operation
screamer 29:1210849dba19 176 dst - the destination of the copy operation
screamer 0:66f3b5499f7f 177 """
screamer 0:66f3b5499f7f 178 if isdir(dst):
screamer 29:1210849dba19 179 _, base = split(src)
screamer 29:1210849dba19 180 dst = join(dst, base)
screamer 0:66f3b5499f7f 181 copyfile(src, dst)
screamer 0:66f3b5499f7f 182
screamer 0:66f3b5499f7f 183
screamer 29:1210849dba19 184 def delete_dir_files(directory):
screamer 29:1210849dba19 185 """ A function that does rm -rf
screamer 29:1210849dba19 186
screamer 29:1210849dba19 187 Positional arguments:
screamer 29:1210849dba19 188 directory - the directory to remove
screamer 29:1210849dba19 189 """
screamer 29:1210849dba19 190 if not exists(directory):
screamer 0:66f3b5499f7f 191 return
screamer 0:66f3b5499f7f 192
screamer 29:1210849dba19 193 for element in listdir(directory):
screamer 29:1210849dba19 194 to_remove = join(directory, element)
screamer 29:1210849dba19 195 if not isdir(to_remove):
screamer 0:66f3b5499f7f 196 remove(file)
screamer 0:66f3b5499f7f 197
screamer 0:66f3b5499f7f 198
screamer 0:66f3b5499f7f 199 def get_caller_name(steps=2):
screamer 0:66f3b5499f7f 200 """
screamer 0:66f3b5499f7f 201 When called inside a function, it returns the name
screamer 0:66f3b5499f7f 202 of the caller of that function.
screamer 29:1210849dba19 203
screamer 29:1210849dba19 204 Keyword arguments:
screamer 29:1210849dba19 205 steps - the number of steps up the stack the calling function is
screamer 0:66f3b5499f7f 206 """
screamer 0:66f3b5499f7f 207 return inspect.stack()[steps][3]
screamer 0:66f3b5499f7f 208
screamer 0:66f3b5499f7f 209
screamer 0:66f3b5499f7f 210 def error(msg):
screamer 29:1210849dba19 211 """Fatal error, abort hard
screamer 29:1210849dba19 212
screamer 29:1210849dba19 213 Positional arguments:
screamer 29:1210849dba19 214 msg - the message to print before crashing
screamer 29:1210849dba19 215 """
screamer 0:66f3b5499f7f 216 print("ERROR: %s" % msg)
screamer 0:66f3b5499f7f 217 sys.exit(1)
screamer 0:66f3b5499f7f 218
screamer 0:66f3b5499f7f 219
screamer 0:66f3b5499f7f 220 def rel_path(path, base, dot=False):
screamer 29:1210849dba19 221 """Relative path calculation that optionaly always starts with a dot
screamer 29:1210849dba19 222
screamer 29:1210849dba19 223 Positional arguments:
screamer 29:1210849dba19 224 path - the path to make relative
screamer 29:1210849dba19 225 base - what to make the path relative to
screamer 29:1210849dba19 226
screamer 29:1210849dba19 227 Keyword arguments:
screamer 29:1210849dba19 228 dot - if True, the path will always start with a './'
screamer 29:1210849dba19 229 """
screamer 29:1210849dba19 230 final_path = relpath(path, base)
screamer 29:1210849dba19 231 if dot and not final_path.startswith('.'):
screamer 29:1210849dba19 232 final_path = './' + final_path
screamer 29:1210849dba19 233 return final_path
screamer 0:66f3b5499f7f 234
screamer 0:66f3b5499f7f 235
screamer 0:66f3b5499f7f 236 class ToolException(Exception):
screamer 29:1210849dba19 237 """A class representing an exception throw by the tools"""
screamer 0:66f3b5499f7f 238 pass
screamer 0:66f3b5499f7f 239
screamer 0:66f3b5499f7f 240 class NotSupportedException(Exception):
screamer 29:1210849dba19 241 """A class a toolchain not supporting a particular target"""
screamer 0:66f3b5499f7f 242 pass
screamer 0:66f3b5499f7f 243
screamer 24:25bff2709c20 244 class InvalidReleaseTargetException(Exception):
screamer 24:25bff2709c20 245 pass
screamer 24:25bff2709c20 246
screamer 0:66f3b5499f7f 247 def split_path(path):
screamer 29:1210849dba19 248 """spilt a file name into it's directory name, base name, and extension
screamer 29:1210849dba19 249
screamer 29:1210849dba19 250 Positional arguments:
screamer 29:1210849dba19 251 path - the file name to split
screamer 29:1210849dba19 252 """
screamer 29:1210849dba19 253 base, has_ext = split(path)
screamer 29:1210849dba19 254 name, ext = splitext(has_ext)
screamer 0:66f3b5499f7f 255 return base, name, ext
screamer 0:66f3b5499f7f 256
screamer 0:66f3b5499f7f 257
screamer 24:25bff2709c20 258 def get_path_depth(path):
screamer 24:25bff2709c20 259 """ Given a path, return the number of directory levels present.
screamer 24:25bff2709c20 260 This roughly translates to the number of path separators (os.sep) + 1.
screamer 24:25bff2709c20 261 Ex. Given "path/to/dir", this would return 3
screamer 24:25bff2709c20 262 Special cases: "." and "/" return 0
screamer 29:1210849dba19 263
screamer 29:1210849dba19 264 Positional arguments:
screamer 29:1210849dba19 265 path - the path to calculate the depth of
screamer 24:25bff2709c20 266 """
screamer 24:25bff2709c20 267 normalized_path = normpath(path)
screamer 24:25bff2709c20 268 path_depth = 0
screamer 24:25bff2709c20 269 head, tail = split(normalized_path)
screamer 24:25bff2709c20 270
screamer 29:1210849dba19 271 while tail and tail != '.':
screamer 24:25bff2709c20 272 path_depth += 1
screamer 24:25bff2709c20 273 head, tail = split(head)
screamer 24:25bff2709c20 274
screamer 24:25bff2709c20 275 return path_depth
screamer 24:25bff2709c20 276
screamer 24:25bff2709c20 277
screamer 0:66f3b5499f7f 278 def args_error(parser, message):
screamer 29:1210849dba19 279 """Abort with an error that was generated by the arguments to a CLI program
screamer 29:1210849dba19 280
screamer 29:1210849dba19 281 Positional arguments:
screamer 29:1210849dba19 282 parser - the ArgumentParser object that parsed the command line
screamer 29:1210849dba19 283 message - what went wrong
screamer 29:1210849dba19 284 """
screamer 0:66f3b5499f7f 285 print "\n\n%s\n\n" % message
screamer 0:66f3b5499f7f 286 parser.print_help()
screamer 0:66f3b5499f7f 287 sys.exit()
screamer 0:66f3b5499f7f 288
screamer 0:66f3b5499f7f 289
screamer 0:66f3b5499f7f 290 def construct_enum(**enums):
screamer 29:1210849dba19 291 """ Create your own pseudo-enums
screamer 29:1210849dba19 292
screamer 29:1210849dba19 293 Keyword arguments:
screamer 29:1210849dba19 294 * - a member of the Enum you are creating and it's value
screamer 29:1210849dba19 295 """
screamer 0:66f3b5499f7f 296 return type('Enum', (), enums)
screamer 0:66f3b5499f7f 297
screamer 0:66f3b5499f7f 298
screamer 0:66f3b5499f7f 299 def check_required_modules(required_modules, verbose=True):
screamer 29:1210849dba19 300 """ Function checks for Python modules which should be "importable"
screamer 0:66f3b5499f7f 301 before test suite can be used.
screamer 0:66f3b5499f7f 302 @return returns True if all modules are installed already
screamer 0:66f3b5499f7f 303 """
screamer 0:66f3b5499f7f 304 import imp
screamer 0:66f3b5499f7f 305 not_installed_modules = []
screamer 0:66f3b5499f7f 306 for module_name in required_modules:
screamer 0:66f3b5499f7f 307 try:
screamer 0:66f3b5499f7f 308 imp.find_module(module_name)
screamer 29:1210849dba19 309 except ImportError:
screamer 0:66f3b5499f7f 310 # We also test against a rare case: module is an egg file
screamer 0:66f3b5499f7f 311 try:
screamer 0:66f3b5499f7f 312 __import__(module_name)
screamer 29:1210849dba19 313 except ImportError as exc:
screamer 0:66f3b5499f7f 314 not_installed_modules.append(module_name)
screamer 0:66f3b5499f7f 315 if verbose:
screamer 29:1210849dba19 316 print "Error: %s" % exc
screamer 0:66f3b5499f7f 317
screamer 0:66f3b5499f7f 318 if verbose:
screamer 0:66f3b5499f7f 319 if not_installed_modules:
screamer 29:1210849dba19 320 print ("Warning: Module(s) %s not installed. Please install " + \
screamer 29:1210849dba19 321 "required module(s) before using this script.")\
screamer 29:1210849dba19 322 % (', '.join(not_installed_modules))
screamer 0:66f3b5499f7f 323
screamer 0:66f3b5499f7f 324 if not_installed_modules:
screamer 0:66f3b5499f7f 325 return False
screamer 0:66f3b5499f7f 326 else:
screamer 0:66f3b5499f7f 327 return True
screamer 7:5af61d55adbe 328
screamer 29:1210849dba19 329 def dict_to_ascii(dictionary):
screamer 29:1210849dba19 330 """ Utility function: traverse a dictionary and change all the strings in
screamer 29:1210849dba19 331 the dictionary to ASCII from Unicode. Useful when reading ASCII JSON data,
screamer 29:1210849dba19 332 because the JSON decoder always returns Unicode string. Based on
screamer 29:1210849dba19 333 http://stackoverflow.com/a/13105359
screamer 29:1210849dba19 334
screamer 29:1210849dba19 335 Positional arguments:
screamer 29:1210849dba19 336 dictionary - The dict that contains some Unicode that should be ASCII
screamer 29:1210849dba19 337 """
screamer 29:1210849dba19 338 if isinstance(dictionary, dict):
screamer 29:1210849dba19 339 return OrderedDict([(dict_to_ascii(key), dict_to_ascii(value))
screamer 29:1210849dba19 340 for key, value in dictionary.iteritems()])
screamer 29:1210849dba19 341 elif isinstance(dictionary, list):
screamer 29:1210849dba19 342 return [dict_to_ascii(element) for element in dictionary]
screamer 29:1210849dba19 343 elif isinstance(dictionary, unicode):
screamer 29:1210849dba19 344 return dictionary.encode('ascii')
screamer 7:5af61d55adbe 345 else:
screamer 29:1210849dba19 346 return dictionary
screamer 29:1210849dba19 347
screamer 29:1210849dba19 348 def json_file_to_dict(fname):
screamer 29:1210849dba19 349 """ Read a JSON file and return its Python representation, transforming all
screamer 29:1210849dba19 350 the strings from Unicode to ASCII. The order of keys in the JSON file is
screamer 29:1210849dba19 351 preserved.
screamer 7:5af61d55adbe 352
screamer 29:1210849dba19 353 Positional arguments:
screamer 29:1210849dba19 354 fname - the name of the file to parse
screamer 29:1210849dba19 355 """
screamer 22:9e85236d8716 356 try:
screamer 29:1210849dba19 357 with open(fname, "r") as file_obj:
screamer 29:1210849dba19 358 return dict_to_ascii(json.load(file_obj,
screamer 29:1210849dba19 359 object_pairs_hook=OrderedDict))
screamer 22:9e85236d8716 360 except (ValueError, IOError):
screamer 22:9e85236d8716 361 sys.stderr.write("Error parsing '%s':\n" % fname)
screamer 22:9e85236d8716 362 raise
screamer 22:9e85236d8716 363
screamer 22:9e85236d8716 364 # Wowza, double closure
screamer 29:1210849dba19 365 def argparse_type(casedness, prefer_hyphen=False):
screamer 29:1210849dba19 366 def middle(lst, type_name):
screamer 22:9e85236d8716 367 def parse_type(string):
screamer 29:1210849dba19 368 """ validate that an argument passed in (as string) is a member of
screamer 29:1210849dba19 369 the list of possible arguments. Offer a suggestion if the case of
screamer 29:1210849dba19 370 the string, or the hyphens/underscores do not match the expected
screamer 29:1210849dba19 371 style of the argument.
screamer 29:1210849dba19 372 """
screamer 29:1210849dba19 373 if prefer_hyphen:
screamer 29:1210849dba19 374 newstring = casedness(string).replace("_", "-")
screamer 29:1210849dba19 375 else:
screamer 29:1210849dba19 376 newstring = casedness(string).replace("-", "_")
screamer 29:1210849dba19 377 if string in lst:
screamer 22:9e85236d8716 378 return string
screamer 29:1210849dba19 379 elif string not in lst and newstring in lst:
screamer 29:1210849dba19 380 raise argparse.ArgumentTypeError(
screamer 29:1210849dba19 381 "{0} is not a supported {1}. Did you mean {2}?".format(
screamer 29:1210849dba19 382 string, type_name, newstring))
screamer 22:9e85236d8716 383 else:
screamer 29:1210849dba19 384 raise argparse.ArgumentTypeError(
screamer 29:1210849dba19 385 "{0} is not a supported {1}. Supported {1}s are:\n{2}".
screamer 29:1210849dba19 386 format(string, type_name, columnate(lst)))
screamer 22:9e85236d8716 387 return parse_type
screamer 22:9e85236d8716 388 return middle
screamer 22:9e85236d8716 389
screamer 22:9e85236d8716 390 # short cuts for the argparse_type versions
screamer 22:9e85236d8716 391 argparse_uppercase_type = argparse_type(str.upper, False)
screamer 22:9e85236d8716 392 argparse_lowercase_type = argparse_type(str.lower, False)
screamer 22:9e85236d8716 393 argparse_uppercase_hyphen_type = argparse_type(str.upper, True)
screamer 22:9e85236d8716 394 argparse_lowercase_hyphen_type = argparse_type(str.lower, True)
screamer 22:9e85236d8716 395
screamer 22:9e85236d8716 396 def argparse_force_type(case):
screamer 29:1210849dba19 397 """ validate that an argument passed in (as string) is a member of the list
screamer 29:1210849dba19 398 of possible arguments after converting it's case.
screamer 29:1210849dba19 399 """
screamer 29:1210849dba19 400 def middle(lst, type_name):
screamer 29:1210849dba19 401 """ The parser type generator"""
screamer 22:9e85236d8716 402 def parse_type(string):
screamer 29:1210849dba19 403 """ The parser type"""
screamer 29:1210849dba19 404 for option in lst:
screamer 22:9e85236d8716 405 if case(string) == case(option):
screamer 22:9e85236d8716 406 return option
screamer 29:1210849dba19 407 raise argparse.ArgumentTypeError(
screamer 29:1210849dba19 408 "{0} is not a supported {1}. Supported {1}s are:\n{2}".
screamer 29:1210849dba19 409 format(string, type_name, columnate(lst)))
screamer 22:9e85236d8716 410 return parse_type
screamer 22:9e85236d8716 411 return middle
screamer 22:9e85236d8716 412
screamer 22:9e85236d8716 413 # these two types convert the case of their arguments _before_ validation
screamer 22:9e85236d8716 414 argparse_force_uppercase_type = argparse_force_type(str.upper)
screamer 22:9e85236d8716 415 argparse_force_lowercase_type = argparse_force_type(str.lower)
screamer 22:9e85236d8716 416
screamer 29:1210849dba19 417 def argparse_many(func):
screamer 29:1210849dba19 418 """ An argument parser combinator that takes in an argument parser and
screamer 29:1210849dba19 419 creates a new parser that accepts a comma separated list of the same thing.
screamer 29:1210849dba19 420 """
screamer 22:9e85236d8716 421 def wrap(string):
screamer 29:1210849dba19 422 """ The actual parser"""
screamer 29:1210849dba19 423 return [func(s) for s in string.split(",")]
screamer 22:9e85236d8716 424 return wrap
screamer 22:9e85236d8716 425
screamer 29:1210849dba19 426 def argparse_filestring_type(string):
screamer 29:1210849dba19 427 """ An argument parser that verifies that a string passed in corresponds
screamer 29:1210849dba19 428 to a file"""
screamer 29:1210849dba19 429 if exists(string):
screamer 22:9e85236d8716 430 return string
screamer 29:1210849dba19 431 else:
screamer 29:1210849dba19 432 raise argparse.ArgumentTypeError(
screamer 29:1210849dba19 433 "{0}"" does not exist in the filesystem.".format(string))
screamer 29:1210849dba19 434
screamer 29:1210849dba19 435 def columnate(strings, separator=", ", chars=80):
screamer 29:1210849dba19 436 """ render a list of strings as a in a bunch of columns
screamer 22:9e85236d8716 437
screamer 29:1210849dba19 438 Positional arguments:
screamer 29:1210849dba19 439 strings - the strings to columnate
screamer 29:1210849dba19 440
screamer 29:1210849dba19 441 Keyword arguments;
screamer 29:1210849dba19 442 separator - the separation between the columns
screamer 29:1210849dba19 443 chars - the maximum with of a row
screamer 29:1210849dba19 444 """
screamer 22:9e85236d8716 445 col_width = max(len(s) for s in strings)
screamer 29:1210849dba19 446 total_width = col_width + len(separator)
screamer 22:9e85236d8716 447 columns = math.floor(chars / total_width)
screamer 22:9e85236d8716 448 output = ""
screamer 29:1210849dba19 449 for i, string in zip(range(len(strings)), strings):
screamer 29:1210849dba19 450 append = string
screamer 22:9e85236d8716 451 if i != len(strings) - 1:
screamer 29:1210849dba19 452 append += separator
screamer 22:9e85236d8716 453 if i % columns == columns - 1:
screamer 22:9e85236d8716 454 append += "\n"
screamer 22:9e85236d8716 455 else:
screamer 22:9e85236d8716 456 append = append.ljust(total_width)
screamer 22:9e85236d8716 457 output += append
screamer 22:9e85236d8716 458 return output
screamer 24:25bff2709c20 459
screamer 24:25bff2709c20 460 def argparse_dir_not_parent(other):
screamer 29:1210849dba19 461 """fail if argument provided is a parent of the specified directory"""
screamer 24:25bff2709c20 462 def parse_type(not_parent):
screamer 29:1210849dba19 463 """The parser type"""
screamer 24:25bff2709c20 464 abs_other = abspath(other)
screamer 24:25bff2709c20 465 abs_not_parent = abspath(not_parent)
screamer 24:25bff2709c20 466 if abs_not_parent == commonprefix([abs_not_parent, abs_other]):
screamer 29:1210849dba19 467 raise argparse.ArgumentTypeError(
screamer 29:1210849dba19 468 "{0} may not be a parent directory of {1}".format(
screamer 29:1210849dba19 469 not_parent, other))
screamer 24:25bff2709c20 470 else:
screamer 24:25bff2709c20 471 return not_parent
screamer 24:25bff2709c20 472 return parse_type