1#!/usr/bin/python 2# encoding: UTF-8 3 4'''An easy access to pygnulib constants.''' 5 6from __future__ import unicode_literals 7#=============================================================================== 8# Define global imports 9#=============================================================================== 10import re 11import os 12import sys 13import platform 14import tempfile 15import subprocess as sp 16 17 18#=============================================================================== 19# Define module information 20#=============================================================================== 21__all__ = list() 22__author__ = \ 23 [ 24 'Bruno Haible', 25 'Paul Eggert', 26 'Simon Josefsson', 27 'Dmitriy Selyutin', 28 ] 29__license__ = 'GNU GPLv3+' 30__copyright__ = '2002-2017 Free Software Foundation, Inc.' 31 32 33#=============================================================================== 34# Backward compatibility 35#=============================================================================== 36# Check for Python version 37if sys.version_info.major == 2: 38 PYTHON3 = False 39else: 40 PYTHON3 = True 41 42# Create string compatibility 43if not PYTHON3: 44 string = unicode 45else: # if PYTHON3 46 string = str 47 48# Current working directory 49if not PYTHON3: 50 os.getcwdb = os.getcwd 51 os.getcwd = os.getcwdu 52 53 54#=============================================================================== 55# Define global constants 56#=============================================================================== 57# Declare necessary variables 58APP = dict() # Application 59DIRS = dict() # Directories 60UTILS = dict() # Utilities 61ENCS = dict() # Encodings 62MODES = dict() # Modes 63TESTS = dict() # Tests 64NL = ''' 65''' # Newline character 66ALPHANUMERIC = 'abcdefghijklmnopqrstuvwxyz\ 67ABCDEFGHIJKLMNOPQRSTUVWXYZ\ 680123456789' # Alphanumeric characters 69 70# Set ENCS dictionary 71import __main__ as interpreter 72if not hasattr(interpreter, '__file__'): 73 if sys.stdout.encoding != None: 74 ENCS['default'] = sys.stdout.encoding 75 else: # sys.stdout.encoding == None 76 ENCS['default'] = 'UTF-8' 77else: # if hasattr(interpreter, '__file__'): 78 ENCS['default'] = 'UTF-8' 79ENCS['system'] = sys.getfilesystemencoding() 80ENCS['shell'] = sys.stdout.encoding 81if ENCS['shell'] == None: 82 ENCS['shell'] = 'UTF-8' 83 84# Set APP dictionary 85APP['name'] = sys.argv[0] 86if not APP['name']: 87 APP['name'] = 'gnulib-tool.py' 88APP['path'] = os.path.realpath(sys.argv[0]) 89if type(APP['name']) is bytes: 90 APP['name'] = string(APP['name'], ENCS['system']) 91if type(APP['path']) is bytes: 92 APP['path'] = string(APP['path'], ENCS['system']) 93 94# Set DIRS dictionary 95DIRS['root'] = os.path.dirname(APP['path']) 96DIRS['cwd'] = os.getcwd() 97DIRS['build-aux'] = os.path.join(DIRS['root'], 'build-aux') 98DIRS['config'] = os.path.join(DIRS['root'], 'config') 99DIRS['doc'] = os.path.join(DIRS['root'], 'doc') 100DIRS['lib'] = os.path.join(DIRS['root'], 'lib') 101DIRS['m4'] = os.path.join(DIRS['root'], 'm4') 102DIRS['modules'] = os.path.join(DIRS['root'], 'modules') 103DIRS['tests'] = os.path.join(DIRS['root'], 'tests') 104DIRS['git'] = os.path.join(DIRS['root'], '.git') 105DIRS['cvs'] = os.path.join(DIRS['root'], 'CVS') 106 107# Set MODES dictionary 108MODES = \ 109 { 110 'import': 0, 111 'add-import': 1, 112 'remove-import': 2, 113 'update': 3, 114 } 115MODES['verbose-min'] = -2 116MODES['verbose-default'] = 0 117MODES['verbose-max'] = 2 118 119# Set TESTS dictionary 120TESTS = \ 121 { 122 'tests': 0, 123 'obsolete': 1, 124 'c++-test': 2, 125 'cxx-test': 2, 126 'c++-tests': 2, 127 'cxx-tests': 2, 128 'longrunning-test': 3, 129 'longrunning-tests': 3, 130 'privileged-test': 4, 131 'privileged-tests': 4, 132 'unportable-test': 5, 133 'unportable-tests': 5, 134 'all-test': 6, 135 'all-tests': 6, 136 } 137 138# Define AUTOCONF minimum version 139DEFAULT_AUTOCONF_MINVERSION = 2.59 140# You can set AUTOCONFPATH to empty if autoconf 2.57 is already in your PATH 141AUTOCONFPATH = '' 142# You can set AUTOMAKEPATH to empty if automake 1.9.x is already in your PATH 143AUTOMAKEPATH = '' 144# You can set GETTEXTPATH to empty if autopoint 0.15 is already in your PATH 145GETTEXTPATH = '' 146# You can set LIBTOOLPATH to empty if libtoolize 2.x is already in your PATH 147LIBTOOLPATH = '' 148 149# You can also set the variable AUTOCONF individually 150if AUTOCONFPATH: 151 UTILS['autoconf'] = AUTOCONFPATH + 'autoconf' 152else: 153 if os.getenv('AUTOCONF'): 154 UTILS['autoconf'] = os.getenv('AUTOCONF') 155 else: 156 UTILS['autoconf'] = 'autoconf' 157 158# You can also set the variable AUTORECONF individually 159if AUTOCONFPATH: 160 UTILS['autoreconf'] = AUTOCONFPATH + 'autoreconf' 161else: 162 if os.getenv('AUTORECONF'): 163 UTILS['autoreconf'] = os.getenv('AUTORECONF') 164 else: 165 UTILS['autoreconf'] = 'autoreconf' 166 167# You can also set the variable AUTOHEADER individually 168if AUTOCONFPATH: 169 UTILS['autoheader'] = AUTOCONFPATH + 'autoheader' 170else: 171 if os.getenv('AUTOHEADER'): 172 UTILS['autoheader'] = os.getenv('AUTOHEADER') 173 else: 174 UTILS['autoheader'] = 'autoheader' 175 176# You can also set the variable AUTOMAKE individually 177if AUTOMAKEPATH: 178 UTILS['automake'] = AUTOMAKEPATH + 'automake' 179else: 180 if os.getenv('AUTOMAKE'): 181 UTILS['automake'] = os.getenv('AUTOMAKE') 182 else: 183 UTILS['automake'] = 'automake' 184 185# You can also set the variable ACLOCAL individually 186if AUTOMAKEPATH: 187 UTILS['aclocal'] = AUTOMAKEPATH + 'aclocal' 188else: 189 if os.getenv('ACLOCAL'): 190 UTILS['aclocal'] = os.getenv('ACLOCAL') 191 else: 192 UTILS['aclocal'] = 'aclocal' 193 194# You can also set the variable AUTOPOINT individually 195if GETTEXTPATH: 196 UTILS['autopoint'] = GETTEXTPATH + 'autopoint' 197else: 198 if os.getenv('AUTOPOINT'): 199 UTILS['autopoint'] = os.getenv('AUTOPOINT') 200 else: 201 UTILS['autopoint'] = 'autopoint' 202 203# You can also set the variable LIBTOOLIZE individually 204if LIBTOOLPATH: 205 UTILS['libtoolize'] = LIBTOOLPATH + 'libtoolize' 206else: 207 if os.getenv('LIBTOOLIZE'): 208 UTILS['libtoolize'] = os.getenv('LIBTOOLIZE') 209 else: 210 UTILS['libtoolize'] = 'libtoolize' 211 212# You can also set the variable MAKE 213if os.getenv('MAKE'): 214 UTILS['make'] = os.getenv('MAKE') 215else: 216 UTILS['make'] = 'make' 217 218 219#=============================================================================== 220# Define global functions 221#=============================================================================== 222def execute(args, verbose): 223 '''Execute the given shell command.''' 224 if verbose >= 0: 225 print("executing %s" % ' '.join(args)) 226 try: # Try to run 227 retcode = sp.call(args) 228 except Exception as error: 229 print(error) 230 sys.exit(1) 231 else: 232 # Commands like automake produce output to stderr even when they succeed. 233 # Turn this output off if the command succeeds. 234 temp = tempfile.mktemp() 235 if type(temp) is bytes: 236 temp = temp.decode(ENCS['system']) 237 xargs = '%s > %s 2>&1' % (' '.join(args), temp) 238 try: # Try to run 239 retcode = sp.call(xargs, shell=True) 240 except Exception as error: 241 print(error) 242 sys.exit(1) 243 if retcode == 0: 244 os.remove(temp) 245 else: 246 print("executing %s" % ' '.join(args)) 247 with codecs.open(temp, 'rb') as file: 248 cmdout = file.read() 249 print(cmdout) 250 os.remove(temp) 251 sys.exit(retcode) 252 253 254def compiler(pattern, flags=0): 255 '''Compile regex pattern depending on version of Python.''' 256 if not PYTHON3: 257 pattern = re.compile(pattern, re.UNICODE | flags) 258 else: # if PYTHON3 259 pattern = re.compile(pattern, flags) 260 return(pattern) 261 262 263def cleaner(sequence): 264 '''Clean string or list of strings after using regex.''' 265 if type(sequence) is string: 266 sequence = sequence.replace('[', '') 267 sequence = sequence.replace(']', '') 268 elif type(sequence) is list: 269 sequence = [value.replace('[', '').replace(']', '') 270 for value in sequence] 271 sequence = [value.replace('(', '').replace(')', '') 272 for value in sequence] 273 sequence = [False if value == 'false' else value for value in sequence] 274 sequence = [True if value == 'true' else value for value in sequence] 275 sequence = [value.strip() for value in sequence] 276 return(sequence) 277 278 279def joinpath(head, *tail): 280 '''joinpath(head, *tail) -> string 281 282 Join two or more pathname components, inserting '/' as needed. If any 283 component is an absolute path, all previous path components will be 284 discarded. The second argument may be string or list of strings.''' 285 newtail = list() 286 if type(head) is bytes: 287 head = head.decode(ENCS['default']) 288 for item in tail: 289 if type(item) is bytes: 290 item = item.decode(ENCS['default']) 291 newtail += [item] 292 result = os.path.normpath(os.path.join(head, *tail)) 293 if type(result) is bytes: 294 result = result.decode(ENCS['default']) 295 return(result) 296 297 298def relativize(dir1, dir2): 299 '''Compute a relative pathname reldir such that dir1/reldir = dir2.''' 300 dir0 = os.getcwd() 301 if type(dir1) is bytes: 302 dir1 = dir1.decode(ENCS['default']) 303 if type(dir2) is bytes: 304 dir2 = dir2.decode(ENCS['default']) 305 while dir1: 306 dir1 = '%s%s' % (os.path.normpath(dir1), os.path.sep) 307 dir2 = '%s%s' % (os.path.normpath(dir2), os.path.sep) 308 if dir1.startswith(os.path.sep): 309 first = dir1[:dir1.find(os.path.sep, 1)] 310 else: # if not dir1.startswith('/') 311 first = dir1[:dir1.find(os.path.sep)] 312 if first != '.': 313 if first == '..': 314 dir2 = os.path.basename(joinpath(dir0, dir2)) 315 dir0 = os.path.dirname(dir0) 316 else: # if first != '..' 317 # Get first component of dir2 318 if dir2.startswith(os.path.sep): 319 first2 = dir2[:dir2.find(os.path.sep, 1)] 320 else: # if not dir1.startswith('/') 321 first2 = dir2[:dir2.find(os.path.sep)] 322 if first == first2: 323 dir2 = dir2[dir2.find(os.path.sep) + 1:] 324 else: # if first != first2 325 dir2 = joinpath('..', dir2) 326 dir0 = joinpath(dir0, first) 327 dir1 = dir1[dir1.find(os.path.sep) + 1:] 328 result = os.path.normpath(dir2) 329 return(result) 330 331 332def link_relative(src, dest): 333 '''Like ln -s, except that src is given relative to the current directory 334 (or absolute), not given relative to the directory of dest.''' 335 if type(src) is bytes or type(src) is string: 336 if type(src) is bytes: 337 src = src.decode(ENCS['default']) 338 else: # if src has not bytes or string type 339 raise(TypeError( 340 'src must be a string, not %s' % (type(src).__name__))) 341 if type(dest) is bytes or type(dest) is string: 342 if type(dest) is bytes: 343 dest = dest.decode(ENCS['default']) 344 else: # if dest has not bytes or string type 345 raise(TypeError( 346 'dest must be a string, not %s' % (type(dest).__name__))) 347 if src.startswith('/') or (len(src) >= 2 and src[1] == ':'): 348 os.symlink(src, dest) 349 else: # if src is not absolute 350 if dest.startswith('/') or (len(dest) >= 2 and dest[1] == ':'): 351 if not constants.PYTHON3: 352 cwd = os.getcwdu() 353 else: # if constants.PYTHON3 354 cwd = os.getcwd() 355 os.symlink(joinpath(cwd, src), dest) 356 else: # if dest is not absolute 357 destdir = os.path.dirname(dest) 358 if not destdir: 359 destdir = '.' 360 if type(destdir) is bytes: 361 destdir = destdir.decode(ENCS['default']) 362 src = relativize(destdir, src) 363 os.symlink(src, dest) 364 365 366def link_if_changed(src, dest): 367 '''Create a symlink, but avoids munging timestamps if the link is correct.''' 368 if type(src) is bytes: 369 src = src.decode(ENCS['default']) 370 if type(dest) is bytes: 371 dest = dest.decode(ENCS['default']) 372 ln_target = os.path.realpath(src) 373 if not (os.path.islink(dest) and src == ln_target): 374 os.remove(dest) 375 link_relative(src, dest) 376 377 378def filter_filelist(separator, filelist, 379 prefix, suffix, removed_prefix, removed_suffix, 380 added_prefix=string(), added_suffix=string()): 381 '''filter_filelist(*args) -> list 382 383 Filter the given list of files. Filtering: Only the elements starting with 384 prefix and ending with suffix are considered. Processing: removed_prefix 385 and removed_suffix are removed from each element, added_prefix and 386 added_suffix are added to each element.''' 387 listing = list() 388 for filename in filelist: 389 if filename.startswith(prefix) and filename.endswith(suffix): 390 pattern = compiler('^%s(.*?)%s$' % 391 (removed_prefix, removed_suffix)) 392 result = pattern.sub('%s\\1%s' % 393 (added_prefix, added_suffix), filename) 394 listing += [result] 395 result = separator.join(listing) 396 return(result) 397 398 399def substart(orig, repl, data): 400 '''Replaces the start portion of a string. 401 402 Returns data with orig replaced by repl, but only at the beginning of data. 403 Like data.replace(orig,repl), except only at the beginning of data.''' 404 result = data 405 if data.startswith(orig): 406 result = repl + data[len(orig):] 407 return(result) 408 409 410def subend(orig, repl, data): 411 '''Replaces the end portion of a string. 412 413 Returns data with orig replaced by repl, but only at the end of data. 414 Like data.replace(orig,repl), except only at the end of data.''' 415 result = data 416 if data.endswith(orig): 417 result = data[:-len(orig)] + repl 418 return(result) 419 420 421def nlconvert(text): 422 '''Convert line-endings to specific for this platform.''' 423 system = platform.system().lower() 424 text = text.replace('\r\n', '\n') 425 if system == 'windows': 426 text = text.replace('\n', '\r\n') 427 return(text) 428 429 430def nlremove(text): 431 '''Remove empty lines from the source text.''' 432 text = nlconvert(text) 433 text = text.replace('\r\n', '\n') 434 lines = [line for line in text.split('\n') if line != ''] 435 text = '\n'.join(lines) 436 text = nlconvert(text) 437 return(text) 438 439 440def remove_backslash_newline(text): 441 '''Given a multiline string text, join lines: 442 When a line ends in a backslash, remove the backslash and join the next 443 line to it.''' 444 return text.replace('\\\n', '') 445 446def combine_lines(text): 447 '''Given a multiline string text, join lines by spaces: 448 When a line ends in a backslash, remove the backslash and join the next 449 line to it, inserting a space between them.''' 450 return text.replace('\\\n', ' ') 451 452def combine_lines_matching(pattern, text): 453 '''Given a multiline string text, join lines by spaces, when the first 454 such line matches a given RegexObject pattern. 455 When a line that matches the pattern ends in a backslash, remove the 456 backslash and join the next line to it, inserting a space between them. 457 When a line that is the result of such a join ends in a backslash, 458 proceed likewise.''' 459 outerpos = 0 460 match = pattern.search(text, outerpos) 461 while match: 462 (startpos, pos) = match.span() 463 # Look how far the continuation lines extend. 464 pos = text.find('\n',pos) 465 while pos > 0 and text[pos-1] == '\\': 466 pos = text.find('\n',pos+1) 467 if pos < 0: 468 pos = len(text) 469 # Perform a combine_lines throughout the continuation lines. 470 partdone = text[:startpos] + combine_lines(text[startpos:pos]) 471 outerpos = len(partdone) 472 text = partdone + text[pos:] 473 # Next round. 474 match = pattern.search(text, outerpos) 475 return text 476 477 478__all__ += ['APP', 'DIRS', 'MODES', 'UTILS'] 479