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