source: branches/numpy/anuga/utilities/system_tools.py @ 6702

Last change on this file since 6702 was 6689, checked in by rwilson, 16 years ago

Back-merge from Numeric trunk to numpy branch.

File size: 16.2 KB
RevLine 
[3937]1"""Implementation of tools to do with system administration made as platform independent as possible.
2
3
4"""
5
6import sys
7import os
[6415]8import string
[6689]9import urllib
10import urllib2
11import getpass
12import tarfile
13import md5
[3937]14
[6689]15
[5586]16def log_to_file(filename, s, verbose=False):
17    """Log string to file name
[5436]18    """
19
20    fid = open(filename, 'a')
21    if verbose: print s
22    fid.write(s + '\n')
23    fid.close()
24
25
[3937]26def get_user_name():
27    """Get user name provide by operating system
28    """
29
30    if sys.platform == 'win32':
31        #user = os.getenv('USERPROFILE')
32        user = os.getenv('USERNAME')
33    else:
34        user = os.getenv('LOGNAME')
35
36
37    return user   
38
39def get_host_name():
40    """Get host name provide by operating system
41    """
42
43    if sys.platform == 'win32':
44        host = os.getenv('COMPUTERNAME')
45    else:
46        host = os.uname()[1]
47
48
49    return host   
[4787]50
51def get_revision_number():
52    """Get the version number of the SVN
53    NOTE: This requires that the command svn is on the system PATH
54    (simply aliasing svn to the binary will not work)
55    """
56
[6689]57    # FIXME (Ole): Change this so that svn info is attempted first.
58    # If that fails, try to read a stored file with that same info (this would
59    # be created by e.g. the release script). Failing that, throw an exception.
60    # FIXME (Ole): Move this and store_version_info to utilities
61    try:
62        from anuga.stored_version_info import version_info
63    except:
64        msg = '''
65No version info stored and command 'svn' is not recognised on the system PATH.
[4787]66
[6689]67If ANUGA has been installed from a distribution e.g. as obtained from
68SourceForge, the version info should be available in the automatically
69generated file 'stored_version_info.py' in the anuga root directory.
[4787]70
[6689]71If run from a Subversion sandpit, ANUGA will try to obtain the version info
72by using the command 'svn info'.  In this case, make sure 'svn' is accessible
73on the system PATH.  Simply aliasing 'svn' to the binary will not work.
[4787]74
[6689]75If you are using Windows, you have to install the file svn.exe which can be
76obtained from http://www.collab.net/downloads/subversion.
[4787]77
[6689]78Good luck!
79'''
[4787]80
81        # No file available - try using Subversion
82        try:
83            # The null stuff is so this section fails quitly.
84            # This could cause the svn info command to fail due to
85            # the redirection being bad on some platforms.
86            # If that occurs then change this code.
87            if sys.platform[0:3] == 'win':
88                fid = os.popen('svn info 2> null')
89            else:
90                fid = os.popen('svn info 2>/dev/null')
91        except:
92            raise Exception(msg)
93        else:
94            version_info = fid.read()
[6689]95
96        if version_info == '':
97            raise Exception, msg
[4787]98    else:
99        pass
100        #print 'Got version from file'
101           
102    for line in version_info.split('\n'):
103        if line.startswith('Revision:'):
104            break
105
106    fields = line.split(':')
[6689]107    msg = 'Keyword "Revision" was not found anywhere in text: %s' % version_info
[4787]108    assert fields[0].startswith('Revision'), msg           
109
110    try:
111        revision_number = int(fields[1])
112    except:
113        msg = 'Revision number must be an integer. I got %s' %fields[1]
114        msg += 'Check that the command svn is on the system path' 
[6689]115        raise Exception, msg
116
[4787]117    return revision_number
118
119
120def store_version_info(destination_path='.', verbose=False):
121    """Obtain current version from Subversion and store it.
122   
123    Title: store_version_info()
124
125    Author: Ole Nielsen (Ole.Nielsen@ga.gov.au)
126
127    CreationDate: January 2006
128
129    Description:
130        This function obtains current version from Subversion and stores it
131        is a Python file named 'stored_version_info.py' for use with
132        get_version_info()
133
134        If svn is not available on the system PATH, an Exception is thrown
135    """
136
137    # Note (Ole): This function should not be unit tested as it will only
138    # work when running out of the sandpit. End users downloading the
139    # ANUGA distribution would see a failure.
140    #
141    # FIXME: This function should really only be used by developers (
142    # (e.g. for creating new ANUGA releases), so maybe it should move
143    # to somewhere else.
144   
145    import config
146
147    try:
148        fid = os.popen('svn info')
149    except:
150        msg = 'Command "svn" is not recognised on the system PATH'
151        raise Exception(msg)
152    else:   
153        txt = fid.read()
154        fid.close()
155
156
157        # Determine absolute filename
158        if destination_path[-1] != os.sep:
159            destination_path += os.sep
160           
161        filename = destination_path + config.version_filename
162
163        fid = open(filename, 'w')
164
165        docstring = 'Stored version info.\n\n'
166        docstring += 'This file provides the version for distributions '
167        docstring += 'that are not accessing Subversion directly.\n'
168        docstring += 'The file is automatically generated and should not '
169        docstring += 'be modified manually.\n'
170        fid.write('"""%s"""\n\n' %docstring)
171       
172        fid.write('version_info = """\n%s"""' %txt)
173        fid.close()
174
175
176        if verbose is True:
177            print 'Version info stored to %s' %filename
[4952]178
[6689]179
[5048]180def safe_crc(string):
181    """64 bit safe crc computation.
182
183       See Guido's 64 bit fix at http://bugs.python.org/issue1202           
184    """
185
186    from zlib import crc32
187    import os
188
189    x = crc32(string)
190       
[5921]191    if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
[5048]192        crcval = x - ((x & 0x80000000) << 1)
193    else:
194        crcval = x
195       
196    return crcval
197
198
[4963]199def compute_checksum(filename, max_length=2**20):
[4952]200    """Compute the CRC32 checksum for specified file
[4963]201
202    Optional parameter max_length sets the maximum number
203    of bytes used to limit time used with large files.
204    Default = 2**20 (1MB)
[4952]205    """
206
[5042]207    fid = open(filename, 'rb') # Use binary for portability
[5048]208    crcval = safe_crc(fid.read(max_length))
[4977]209    fid.close()
[5045]210
[4952]211    return crcval
[5072]212
213def get_pathname_from_package(package):
214    """Get pathname of given package (provided as string)
215
216    This is useful for reading files residing in the same directory as
217    a particular module. Typically, this is required in unit tests depending
218    on external files.
219
220    The given module must start from a directory on the pythonpath
221    and be importable using the import statement.
222
223    Example
224    path = get_pathname_from_package('anuga.utilities')
225
226    """
227
228    exec('import %s as x' %package)
229
230    path = x.__path__[0]
231   
232    return path
233
234    # Alternative approach that has been used at times
235    #try:
236    #    # When unit test is run from current dir
237    #    p1 = read_polygon('mainland_only.csv')
238    #except:
239    #    # When unit test is run from ANUGA root dir
240    #    from os.path import join, split
241    #    dir, tail = split(__file__)
242    #    path = join(dir, 'mainland_only.csv')
243    #    p1 = read_polygon(path)
244       
[6410]245   
[6360]246##
247# @brief Split a string into 'clean' fields.
248# @param str The string to process.
249# @param delimiter The delimiter string to split 'line' with.
250# @return A list of 'cleaned' field strings.
251# @note Any fields that were initially zero length will be removed.
252# @note If a field contains '\n' it isn't zero length.
253def clean_line(str, delimiter):
254    """Split string on given delimiter, remove whitespace from each field."""
[5072]255
[6410]256    return [x.strip() for x in str.strip().split(delimiter) if x != '']
[5072]257
[6360]258
[6415]259################################################################################
260# The following two functions are used to get around a problem with numpy and
261# NetCDF files.  Previously, using Numeric, we could take a list of strings and
262# convert to a Numeric array resulting in this:
263#     Numeric.array(['abc', 'xy']) -> [['a', 'b', 'c'],
264#                                      ['x', 'y', ' ']]
265#
266# However, under numpy we get:
267#     numpy.array(['abc', 'xy']) -> ['abc',
268#                                    'xy']
269#
270# And writing *strings* to a NetCDF file is problematic.
271#
272# The solution is to use these two routines to convert a 1-D list of strings
273# to the 2-D list of chars form and back.  The 2-D form can be written to a
274# NetCDF file as before.
275#
276# The other option, of inverting a list of tag strings into a dictionary with
277# keys being the unique tag strings and the key value a list of indices of where
278# the tag string was in the original list was rejected because:
279#    1. It's a lot of work
280#    2. We'd have to rewite the I/O code a bit (extra variables instead of one)
281#    3. The code below is fast enough in an I/O scenario
282################################################################################
283
284##
285# @brief Convert 1-D list of strings to 2-D list of chars.
286# @param l 1-dimensional list of strings.
287# @return A 2-D list of 'characters' (1 char strings).
288# @note No checking that we supply a 1-D list.
289def string_to_char(l):
290    '''Convert 1-D list of strings to 2-D list of chars.'''
291
[6428]292    if not l:
293        return []
294
295    if l == ['']:
296        l = [' ']
297
[6415]298    maxlen = reduce(max, map(len, l))
299    ll = [x.ljust(maxlen) for x in l]
300    result = []
301    for s in ll:
302        result.append([x for x in s])
303    return result
304
305
306##
307# @brief Convert 2-D list of chars to 1-D list of strings.
308# @param ll 2-dimensional list of 'characters' (1 char strings).
309# @return A 1-dimensional list of strings.
310# @note Each string has had right-end spaces removed.
311def char_to_string(ll):
312    '''Convert 2-D list of chars to 1-D list of strings.'''
313
314    return map(string.rstrip, [''.join(x) for x in ll])
315
316
[6689]317##
318# @brief Get list of variable names in a python expression string.
319# @param source A string containing a python expression.
320# @return A list of variable name strings.
321# @note Throws SyntaxError exception if not a valid expression.
322def get_vars_in_expression(source):
323    '''Get list of variable names in a python expression.'''
324
325    import compiler
326    from compiler.ast import Node
327
328    ##
329    # @brief Internal recursive function.
330    # @param node An AST parse Node.
331    # @param var_list Input list of variables.
332    # @return An updated list of variables.
333    def get_vars_body(node, var_list=[]):
334        if isinstance(node, Node):
335            if node.__class__.__name__ == 'Name':
336                for child in node.getChildren():
337                    if child not in var_list:
338                        var_list.append(child)
339            for child in node.getChildren():
340                if isinstance(child, Node):
341                    for child in node.getChildren():
342                        var_list = get_vars_body(child, var_list)
343                    break
344
345        return var_list
346
347    return get_vars_body(compiler.parse(source))
348
349
350##
351# @brief Get a file from the web.
352# @param file_url URL of the file to fetch.
353# @param file_name Path to file to create in the filesystem.
354# @param auth Auth tuple (httpproxy, proxyuser, proxypass).
355# @param blocksize Read file in this block size.
356# @return 'auth' tuple for subsequent calls, if successful.
357# @note If 'auth' not supplied, will prompt user.
358# @note Will try using environment variable HTTP_PROXY for proxy server.
359# @note Will try using environment variable PROXY_USERNAME for proxy username.
360# @note Will try using environment variable PROXY_PASSWORD for proxy password.
361def get_web_file(file_url, file_name, auth=None, blocksize=1024*1024):
362    '''Get a file from the web.
363
364    file_url:  The URL of the file to get
365    file_name: Local path to save loaded file in
366    auth:      A tuple (httpproxy, proxyuser, proxypass)
367    blocksize: Block size of file reads
368   
369    Will try simple load through urllib first.  Drop down to urllib2
370    if there is a proxy and it requires authentication.
371
372    Environment variable HTTP_PROXY can be used to supply proxy information.
373    PROXY_USERNAME is used to supply the authentication username.
374    PROXY_PASSWORD supplies the password, if you dare!
375    '''
376
377    # Simple fetch, if fails, check for proxy error
378    try:
379        urllib.urlretrieve(file_url, file_name)
380        return None     # no proxy, no auth required
381    except IOError, e:
382        if e[1] != 407:
383            raise       # raise error if *not* proxy auth error
384
385    # We get here if there was a proxy error, get file through the proxy
386    # unpack auth info
387    try:
388        (httpproxy, proxyuser, proxypass) = auth
389    except:
390        (httpproxy, proxyuser, proxypass) = (None, None, None)
391
392    # fill in any gaps from the environment
393    if httpproxy is None:
394        httpproxy = os.getenv('HTTP_PROXY')
395    if proxyuser is None:
396        proxyuser = os.getenv('PROXY_USERNAME')
397    if proxypass is None:
398        proxypass = os.getenv('PROXY_PASSWORD')
399
400    # Get auth info from user if still not supplied
401    if httpproxy is None or proxyuser is None or proxypass is None:
402        print '-'*80
403        print ('You need to supply proxy authentication information.  '
404               'Use environment variables\n'
405               'HTTP_PROXY, PROXY_USERNAME and PROXY_PASSWORD to bypass '
406               'entry here:')
407        if httpproxy is None:
408            httpproxy = raw_input('  proxy server: ')
409        if proxyuser is None:
410            proxyuser = raw_input('proxy username: ') 
411        if proxypass is None:
412            proxypass = getpass.getpass('proxy password: ')
413        print '-'*80
414
415    # the proxy URL cannot start with 'http://', we add that later
416    httpproxy = httpproxy.lower()
417    if httpproxy.startswith('http://'):
418        httpproxy = httpproxy.replace('http://', '', 1)
419
420    # open remote file
421    proxy = urllib2.ProxyHandler({'http': 'http://' + proxyuser
422                                              + ':' + proxypass
423                                              + '@' + httpproxy})
424    authinfo = urllib2.HTTPBasicAuthHandler()
425    opener = urllib2.build_opener(proxy, authinfo, urllib2.HTTPHandler)
426    urllib2.install_opener(opener)
427    webget = urllib2.urlopen(file_url)
428
429    # transfer file to local filesystem
430    fd = open(file_name, 'wb')
431    while True:
432        data = webget.read(blocksize)
433        if len(data) == 0:
434            break
435        fd.write(data)
436    fd.close
437    webget.close()
438
439    # return successful auth info
440    return (httpproxy, proxyuser, proxypass)
441
442
443##
444# @brief Tar a file (or directory) into a tarfile.
445# @param files A list of files (or directories) to tar.
446# @param tarfile The created tarfile name.
447# @note We use gzip compression.
448def tar_file(files, tarname):
449    '''Compress a file or directory into a tar file.'''
450
451    o = tarfile.open(tarname, 'w:gz')
452    for file in files:
453        o.add(file)
454    o.close()
455
456
457##
458# @brief Untar a file into an optional target directory.
459# @param tarname Name of the file to untar.
460# @param target_dir Directory to untar into.
461def untar_file(tarname, target_dir='.'):
462    '''Uncompress a tar file.'''
463
464    o = tarfile.open(tarname, 'r:gz')
465    members = o.getmembers()
466    for member in members:
467        o.extract(member, target_dir)
468    o.close()
469
470
471##
472# @brief Return a hex digest string for a given file.
473# @param filename Path to the file of interest.
474# @param blocksize Size of data blocks to read.
475# @return A hex digest string (16 bytes).
476# @note Uses MD5 digest.
477def get_file_hexdigest(filename, blocksize=1024*1024*10):
478    '''Get a hex digest of a file.'''
479   
480    m = md5.new()
481    fd = open(filename, 'r')
482           
483    while True:
484        data = fd.read(blocksize)
485        if len(data) == 0:
486            break
487        m.update(data)
488                                                               
489    fd.close()
490    return m.hexdigest()
491
492    fd = open(filename, 'r')
493
494
495##
496# @brief Create a file containing a hexdigest string of a data file.
497# @param data_file Path to the file to get the hexdigest from.
498# @param digest_file Path to hexdigest file to create.
499# @note Uses MD5 digest.
500def make_digest_file(data_file, digest_file):
501    '''Create a file containing the hex digest string of a data file.'''
502   
503    hexdigest = get_file_hexdigest(data_file)
504    fd = open(digest_file, 'w')
505    fd.write(hexdigest)
506    fd.close()
507
508
509##
510# @brief Function to return the length of a file.
511# @param in_file Path to file to get length of.
512# @return Number of lines in file.
513# @note Doesn't count '\n' characters.
514# @note Zero byte file, returns 0.
515# @note No \n in file at all, but >0 chars, returns 1.
516def file_length(in_file):
517    '''Function to return the length of a file.'''
518
519    fid = open(in_file)
520    data = fid.readlines()
521    fid.close()
522    return len(data)
523
524
Note: See TracBrowser for help on using the repository browser.