Module relax_io
[hide private]
[frames] | no frames]

Source Code for Module relax_io

   1  ############################################################################### 
   2  #                                                                             # 
   3  # Copyright (C) 2003-2012 Edward d'Auvergne                                   # 
   4  #                                                                             # 
   5  # This file is part of the program relax (http://www.nmr-relax.com).          # 
   6  #                                                                             # 
   7  # This program is free software: you can redistribute it and/or modify        # 
   8  # it under the terms of the GNU General Public License as published by        # 
   9  # the Free Software Foundation, either version 3 of the License, or           # 
  10  # (at your option) any later version.                                         # 
  11  #                                                                             # 
  12  # This program is distributed in the hope that it will be useful,             # 
  13  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
  14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
  15  # GNU General Public License for more details.                                # 
  16  #                                                                             # 
  17  # You should have received a copy of the GNU General Public License           # 
  18  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
  19  #                                                                             # 
  20  ############################################################################### 
  21   
  22  # Module docstring. 
  23  """Module containing advanced IO functions for relax. 
  24   
  25  This includes IO redirection, automatic loading and writing of compressed files (both Gzip and BZ2 
  26  compression), reading and writing of files, processing of the contents of files, etc. 
  27  """ 
  28   
  29  # Dependency check module. 
  30  import dep_check 
  31   
  32  # Python module imports. 
  33  from os import devnull 
  34  from os import F_OK, X_OK, access, altsep, getenv, makedirs, pathsep, remove, sep 
  35  from os.path import expanduser, basename, splitext 
  36  from re import match, search 
  37  import sys 
  38  from sys import stdin, stdout, stderr 
  39  from warnings import warn 
  40   
  41  # relax module imports. 
  42  from check_types import is_filetype, is_float 
  43  from compat import bz2_open, gz_open 
  44  import generic_fns 
  45  from relax_errors import RelaxError, RelaxFileError, RelaxFileOverwriteError, RelaxInvalidSeqError, RelaxMissingBinaryError, RelaxNoInPathError, RelaxNonExecError 
  46  from relax_warnings import RelaxWarning, RelaxFileEmptyWarning 
  47   
  48   
  49   
50 -def delete(file_name, dir=None, fail=True):
51 """Deleting the given file, taking into account missing compression extensions. 52 53 @param file_name: The name of the file to delete. 54 @type file_name: str 55 @keyword dir: The directory containing the file. 56 @type dir: None or str 57 @keyword fail: A flag which if True will cause RelaxFileError to be raised. 58 @type fail: bool 59 @raises RelaxFileError: If the file does not exist, and fail is set to true. 60 """ 61 62 # File path. 63 file_path = get_file_path(file_name, dir) 64 65 # Test if the file exists and determine the compression type. 66 if access(file_path, F_OK): 67 pass 68 elif access(file_path + '.bz2', F_OK): 69 file_path = file_path + '.bz2' 70 elif access(file_path + '.gz', F_OK): 71 file_path = file_path + '.gz' 72 elif fail: 73 raise RelaxFileError(file_path) 74 else: 75 return 76 77 # Remove the file. 78 remove(file_path)
79 80
81 -def determine_compression(file_path):
82 """Function for determining the compression type, and for also testing if the file exists. 83 84 @param file_path: The full file path of the file. 85 @type file_path: str 86 @return: A tuple of the compression type and full path of the file (including its 87 extension). A value of 0 corresponds to no compression. Bzip2 compression 88 corresponds to a value of 1. Gzip compression corresponds to a value of 2. 89 @rtype: (int, str) 90 """ 91 92 # The file has been supplied without its compression extension. 93 if access(file_path, F_OK): 94 compress_type = 0 95 if search('.bz2$', file_path): 96 compress_type = 1 97 elif search('.gz$', file_path): 98 compress_type = 2 99 100 # The file has been supplied with the '.bz2' extension. 101 elif access(file_path + '.bz2', F_OK): 102 file_path = file_path + '.bz2' 103 compress_type = 1 104 105 # The file has been supplied with the '.gz' extension. 106 elif access(file_path + '.gz', F_OK): 107 file_path = file_path + '.gz' 108 compress_type = 2 109 110 # The file doesn't exist. 111 else: 112 raise RelaxFileError(file_path) 113 114 # Return the compression type. 115 return compress_type, file_path
116 117
118 -def extract_data(file=None, dir=None, file_data=None, sep=None):
119 """Return all data in the file as a list of lines where each line is a list of line elements. 120 121 @param file: The file to extract the data from. 122 @type file: str or file object 123 @param dir: The path where the file is located. If None and the file argument is a 124 string, then the current directory is assumed. 125 @type dir: str or None 126 @param file_data: If the file data has already been extracted from the file, it can be 127 passed into this function using this argument. If data is supplied 128 here, then the file_name and dir args are ignored. 129 @type file_data: list of str 130 @param sep: The character separating the columns in the file data. If None, then 131 whitespace is assumed. 132 @type sep: str 133 @return: The file data. 134 @rtype: list of lists of str 135 """ 136 137 # Data not already extracted from the file. 138 if not file_data: 139 # Open the file. 140 if isinstance(file, str): 141 file = open_read_file(file_name=file, dir=dir) 142 143 # Read lines. 144 file_data = file.readlines() 145 146 # Create a data structure from the contents of the file split by either whitespace or the separator, sep. 147 data = [] 148 for i in range(len(file_data)): 149 if sep: 150 row = file_data[i].split(sep) 151 else: 152 row = file_data[i].split() 153 data.append(row) 154 155 # Close the file. 156 if not file_data: 157 file.close() 158 159 # Return the data. 160 return data
161 162
163 -def file_root(file_path):
164 """Return the root file name, striped of path and extension details. 165 166 @param file_path: The full path to the file. 167 @type file_path: str 168 @return: The file root (with all directories and the extension stripped away). 169 @rtype: str 170 """ 171 172 root, ext = splitext(file_path) 173 return basename(root)
174 175
176 -def get_file_path(file_name=None, dir=None):
177 """Generate and expand the full file path. 178 179 @param file_name: The name of the file to extract the data from. 180 @type file_name: str 181 @param dir: The path where the file is located. If None, then the current directory is 182 assumed. 183 @type dir: str 184 @return: The full file path. 185 @rtype: str 186 """ 187 188 # File name. 189 file_path = file_name 190 191 # Add the directory. 192 if dir: 193 file_path = dir + sep + file_path 194 195 # Expand any ~ characters. 196 if file_path: # Catch a file path of None, as expanduser can't handle this. 197 file_path = expanduser(file_path) 198 199 # Return the file path. 200 return file_path
201 202
203 -def io_streams_restore(verbosity=1):
204 """Restore all IO streams to the Python defaults. 205 206 @param verbosity: The verbosity level. 207 @type verbosity: int 208 """ 209 210 # Print out. 211 if verbosity: 212 print("Restoring the sys.stdin IO stream to the Python STDIN IO stream.") 213 print("Restoring the sys.stdout IO stream to the Python STDOUT IO stream.") 214 print("Restoring the sys.stderr IO stream to the Python STDERR IO stream.") 215 216 # Restore streams. 217 sys.stdin = sys.__stdin__ 218 sys.stdout = sys.__stdout__ 219 sys.stderr = sys.__stderr__
220 221
222 -def io_streams_log(file_name=None, dir=None, verbosity=1):
223 """Turn on logging, sending both STDOUT and STDERR streams to a file. 224 225 @param file_name: The name of the file. 226 @type file_name: str 227 @param dir: The path where the file is located. If None, then the current directory is 228 assumed. 229 @type dir: str 230 @param verbosity: The verbosity level. 231 @type verbosity: int 232 """ 233 234 # Log file. 235 log_file, file_path = open_write_file(file_name=file_name, dir=dir, force=True, verbosity=verbosity, return_path=True) 236 237 # Logging IO streams. 238 log_stdin = stdin 239 log_stdout = None 240 log_stderr = SplitIO() 241 242 # Print out. 243 if verbosity: 244 print("Redirecting the sys.stdin IO stream to the python stdin IO stream.") 245 print("Redirecting the sys.stdout IO stream to the log file '%s'." % file_path) 246 print("Redirecting the sys.stderr IO stream to both the python stderr IO stream and the log file '%s'." % file_path) 247 248 # Set the logging IO streams. 249 log_stdout = log_file 250 log_stderr.split(stderr, log_file) 251 252 # IO stream redirection. 253 sys.stdin = log_stdin 254 sys.stdout = log_stdout 255 sys.stderr = log_stderr
256 257
258 -def io_streams_tee(file_name=None, dir=None, compress_type=0, verbosity=1):
259 """Turn on teeing to split both STDOUT and STDERR streams and sending second part to a file. 260 261 @param file_name: The name of the file. 262 @type file_name: str 263 @param dir: The path where the file is located. If None, then the current directory 264 is assumed. 265 @type dir: str 266 @param compress_type: The compression type. The integer values correspond to the compression 267 type: 0, no compression; 1, Bzip2 compression; 2, Gzip compression. 268 @type compress_type: int 269 @param verbosity: The verbosity level. 270 @type verbosity: int 271 """ 272 273 # Tee file. 274 tee_file, file_path = open_write_file(file_name=file_name, dir=dir, force=True, compress_type=compress_type, verbosity=verbosity, return_path=1) 275 276 # Tee IO streams. 277 tee_stdin = stdin 278 tee_stdout = SplitIO() 279 tee_stderr = SplitIO() 280 281 # Print out. 282 if verbosity: 283 print("Redirecting the sys.stdin IO stream to the python stdin IO stream.") 284 print("Redirecting the sys.stdout IO stream to both the python stdout IO stream and the log file '%s'." % file_path) 285 print("Redirecting the sys.stderr IO stream to both the python stderr IO stream and the log file '%s'." % file_path) 286 287 # Set the tee IO streams. 288 tee_stdout.split(stdout, tee_file) 289 tee_stderr.split(stderr, tee_file) 290 291 # IO stream redirection. 292 sys.stdin = tee_stdin 293 sys.stdout = tee_stdout 294 sys.stderr = tee_stderr
295 296
297 -def mkdir_nofail(dir=None, verbosity=1):
298 """Create the given directory, or exit without raising an error if the directory exists. 299 300 @param dir: The directory to create. 301 @type dir: str 302 @param verbosity: The verbosity level. 303 @type verbosity: int 304 """ 305 306 # No directory given. 307 if dir == None: 308 return 309 310 # Make the directory. 311 try: 312 makedirs(dir) 313 except OSError: 314 if verbosity: 315 print("Directory ." + sep + dir + " already exists.\n")
316 317
318 -def open_read_file(file_name=None, dir=None, verbosity=1):
319 """Open the file 'file' and return all the data. 320 321 @param file_name: The name of the file to extract the data from. 322 @type file_name: str 323 @param dir: The path where the file is located. If None, then the current directory is 324 assumed. 325 @type dir: str 326 @param verbosity: The verbosity level. 327 @type verbosity: int 328 @return: The open file object. 329 @rtype: file object 330 """ 331 332 # A file descriptor object. 333 if is_filetype(file_name): 334 # Nothing to do here! 335 return file_name 336 337 # Invalid file name. 338 if not file_name and not isinstance(file_name, str): 339 raise RelaxError("The file name " + repr(file_name) + " " + repr(type(file_name)) + " is invalid and cannot be opened.") 340 341 # File path. 342 file_path = get_file_path(file_name, dir) 343 344 # Test if the file exists and determine the compression type. 345 compress_type, file_path = determine_compression(file_path) 346 347 # Open the file for reading. 348 try: 349 # Print out. 350 if verbosity: 351 print("Opening the file " + repr(file_path) + " for reading.") 352 353 # Uncompressed text. 354 if compress_type == 0: 355 file_obj = open(file_path, 'r') 356 357 # Bzip2 compressed text. 358 elif compress_type == 1: 359 file_obj = bz2_open(file=file_path, mode='r') 360 361 # Gzipped compressed text. 362 elif compress_type == 2: 363 file_obj = gz_open(file=file_path, mode='r') 364 365 # Cannot open. 366 except IOError: 367 message = sys.exc_info()[1] 368 raise RelaxError("Cannot open the file " + repr(file_path) + ". " + message.args[1] + ".") 369 370 # Return the opened file. 371 return file_obj
372 373
374 -def open_write_file(file_name=None, dir=None, force=False, compress_type=0, verbosity=1, return_path=False):
375 """Function for opening a file for writing and creating directories if necessary. 376 377 @param file_name: The name of the file to extract the data from. 378 @type file_name: str 379 @param dir: The path where the file is located. If None, then the current directory 380 is assumed. 381 @type dir: str 382 @param force: Boolean argument which if True causes the file to be overwritten if it 383 already exists. 384 @type force: bool 385 @param compress_type: The compression type. The integer values correspond to the compression 386 type: 0, no compression; 1, Bzip2 compression; 2, Gzip compression. 387 @type compress_type: int 388 @param verbosity: The verbosity level. 389 @type verbosity: int 390 @param return_path: If True, the function will return a tuple of the file object and the 391 full file path. 392 @type return_path: bool 393 @return: The open, writable file object and, if the return_path is True, then the 394 full file path is returned as well. 395 @rtype: writable file object (if return_path, then a tuple of the writable file 396 and the full file path) 397 """ 398 399 # A file descriptor object. 400 if is_filetype(file_name): 401 # Nothing to do here! 402 return file_name 403 404 # Something pretending to be a file object. 405 if hasattr(file_name, 'write'): 406 # Nothing to do here! 407 return file_name 408 409 # The null device. 410 if search('devnull', file_name): 411 # Print out. 412 if verbosity: 413 print("Opening the null device file for writing.") 414 415 # Open the null device. 416 file_obj = open(devnull, 'w') 417 418 # Return the file. 419 if return_path: 420 return file_obj, None 421 else: 422 return file_obj 423 424 # Create the directories. 425 mkdir_nofail(dir, verbosity=0) 426 427 # File path. 428 file_path = get_file_path(file_name, dir) 429 430 # Bzip2 compression. 431 if compress_type == 1 and not search('.bz2$', file_path): 432 # Bz2 module exists. 433 if dep_check.bz2_module: 434 file_path = file_path + '.bz2' 435 436 # Switch to gzip compression. 437 else: 438 warn(RelaxWarning("Cannot use Bzip2 compression, using gzip compression instead. " + dep_check.bz2_module_message + ".")) 439 compress_type = 2 440 441 # Gzip compression. 442 if compress_type == 2 and not search('.gz$', file_path): 443 file_path = file_path + '.gz' 444 445 # Fail if the file already exists and the force flag is set to 0. 446 if access(file_path, F_OK) and not force: 447 raise RelaxFileOverwriteError(file_path, 'force flag') 448 449 # Open the file for writing. 450 try: 451 # Print out. 452 if verbosity: 453 print("Opening the file " + repr(file_path) + " for writing.") 454 455 # Uncompressed text. 456 if compress_type == 0: 457 file_obj = open(file_path, 'w') 458 459 # Bzip2 compressed text. 460 elif compress_type == 1: 461 file_obj = bz2_open(file=file_path, mode='w') 462 463 # Gzipped compressed text. 464 elif compress_type == 2: 465 file_obj = gz_open(file=file_path, mode='w') 466 467 # Cannot open. 468 except IOError: 469 message = sys.exc_info()[1] 470 raise RelaxError("Cannot open the file " + repr(file_path) + ". " + message.args[1] + ".") 471 472 # Return the opened file. 473 if return_path: 474 return file_obj, file_path 475 else: 476 return file_obj
477 478
479 -def read_spin_data(file=None, dir=None, file_data=None, spin_id_col=None, mol_name_col=None, res_num_col=None, res_name_col=None, spin_num_col=None, spin_name_col=None, data_col=None, error_col=None, sep=None, spin_id=None):
480 """Generator function for reading the spin specific data from file. 481 482 Description 483 =========== 484 485 This function reads a columnar formatted file where each line corresponds to a spin system. Spin identification is either through a spin ID string or through columns containing the molecule name, residue name and number, and/or spin name and number. 486 487 488 @keyword file: The name of the file to open. 489 @type file: str 490 @keyword dir: The directory containing the file (defaults to the current directory if None). 491 @type dir: str or None 492 @keyword file_data: An alternative to opening a file, if the data already exists in the correct format. The format is a list of lists where the first index corresponds to the row and the second the column. 493 @type file_data: list of lists 494 @keyword spin_id_col: The column containing the spin ID strings. If supplied, the mol_name_col, res_name_col, res_num_col, spin_name_col, and spin_num_col arguments must be none. 495 @type spin_id_col: int or None 496 @keyword mol_name_col: The column containing the molecule name information. If supplied, spin_id_col must be None. 497 @type mol_name_col: int or None 498 @keyword res_name_col: The column containing the residue name information. If supplied, spin_id_col must be None. 499 @type res_name_col: int or None 500 @keyword res_num_col: The column containing the residue number information. If supplied, spin_id_col must be None. 501 @type res_num_col: int or None 502 @keyword spin_name_col: The column containing the spin name information. If supplied, spin_id_col must be None. 503 @type spin_name_col: int or None 504 @keyword spin_num_col: The column containing the spin number information. If supplied, spin_id_col must be None. 505 @type spin_num_col: int or None 506 @keyword data_col: The column containing the data. 507 @type data_col: int or None 508 @keyword error_col: The column containing the errors. 509 @type error_col: int or None 510 @keyword sep: The column separator which, if None, defaults to whitespace. 511 @type sep: str or None 512 @keyword spin_id: The spin ID string used to restrict data loading to a subset of all spins. 513 @type spin_id: None or str 514 @return: A list of the spin specific data is yielded. The format is a list consisting of the spin ID string, the data value (if data_col is give), and the error value (if error_col is given). If both data_col and error_col are None, then the spin ID string is simply yielded. 515 @rtype: str, list of [str, float], or list of [str, float, float] 516 """ 517 518 # Argument tests. 519 col_args = [spin_id_col, mol_name_col, res_name_col, res_num_col, spin_name_col, spin_num_col, data_col, error_col] 520 col_arg_names = ['spin_id_col', 'mol_name_col', 'res_name_col', 'res_num_col', 'spin_name_col', 'spin_num_col', 'data_col', 'error_col'] 521 for i in range(len(col_args)): 522 if col_args[i] == 0: 523 raise RelaxError("The '%s' argument cannot be zero, column numbering starts at one." % col_arg_names[i]) 524 if spin_id_col and (mol_name_col or res_name_col or res_num_col or spin_name_col or spin_num_col): 525 raise RelaxError("If the 'spin_id_col' argument has been supplied, then the mol_name_col, res_name_col, res_num_col, spin_name_col, and spin_num_col must all be set to None.") 526 527 # Minimum number of columns. 528 min_col_num = max(filter(None, [spin_id_col, mol_name_col, res_num_col, res_name_col, spin_num_col, spin_name_col, data_col, error_col])) 529 530 # Extract the data from the file. 531 if not file_data: 532 # Extract. 533 file_data = extract_data(file, dir) 534 535 # Strip the data of all comments and empty lines. 536 if spin_id_col != None: 537 file_data = strip(file_data, comments=False) 538 else: 539 file_data = strip(file_data) 540 541 # No data! 542 if not file_data: 543 warn(RelaxFileEmptyWarning(file)) 544 return 545 546 # Yield the data, spin by spin. 547 missing_data = True 548 for line in file_data: 549 # Convert the spin IDs. 550 if spin_id_col != None and line[spin_id_col-1][0] in ["\"", "\'"]: 551 line[spin_id_col-1] = eval(line[spin_id_col-1]) 552 553 # Convert. 554 # Validate the sequence. 555 try: 556 generic_fns.sequence.validate_sequence(line, spin_id_col=spin_id_col, mol_name_col=mol_name_col, res_num_col=res_num_col, res_name_col=res_name_col, spin_num_col=spin_num_col, spin_name_col=spin_name_col, data_col=data_col, error_col=error_col) 557 except RelaxInvalidSeqError: 558 # Extract the message string, without the RelaxError bit. 559 msg = sys.exc_info()[1] 560 string = msg.__str__()[12:-1] 561 562 # Give a warning. 563 warn(RelaxWarning(string)) 564 565 # Skip the line. 566 continue 567 568 # Get the spin data from the ID. 569 if spin_id_col: 570 # Invalid spin ID. 571 if line[spin_id_col-1] == '#': 572 warn(RelaxWarning("Invalid spin ID, skipping the line %s" % line)) 573 continue 574 575 mol_name, res_num, res_name, spin_num, spin_name = generic_fns.mol_res_spin.spin_id_to_data_list(line[spin_id_col-1]) 576 577 # Convert the spin data. 578 else: 579 # The molecule. 580 mol_name = None 581 if mol_name_col != None and line[mol_name_col-1] != 'None': 582 mol_name = line[mol_name_col-1] 583 584 # The residue number, catching bad values. 585 res_num = None 586 if res_num_col != None: 587 try: 588 if line[res_num_col-1] == 'None': 589 res_num = None 590 else: 591 res_num = int(line[res_num_col-1]) 592 except ValueError: 593 warn(RelaxWarning("Invalid residue number, skipping the line %s" % line)) 594 continue 595 596 # The residue name. 597 res_name = None 598 if res_name_col != None and line[res_name_col-1] != 'None': 599 res_name = line[res_name_col-1] 600 601 # The spin number, catching bad values. 602 spin_num = None 603 if spin_num_col != None: 604 try: 605 if line[spin_num_col-1] == 'None': 606 spin_num = None 607 else: 608 spin_num = int(line[spin_num_col-1]) 609 except ValueError: 610 warn(RelaxWarning("Invalid spin number, skipping the line %s" % line)) 611 continue 612 613 # The spin name. 614 spin_name = None 615 if spin_name_col != None and line[spin_name_col-1] != 'None': 616 spin_name = line[spin_name_col-1] 617 618 # Convert the data. 619 value = None 620 if data_col != None: 621 try: 622 # None. 623 if line[data_col-1] == 'None': 624 value = None 625 626 # A float. 627 else: 628 value = float(line[data_col-1]) 629 630 # Bad data. 631 except ValueError: 632 warn(RelaxWarning("Invalid data, skipping the line %s" % line)) 633 continue 634 635 # Convert the errors. 636 error = None 637 if error_col != None: 638 try: 639 # None. 640 if line[error_col-1] == 'None': 641 error = None 642 643 # A float. 644 else: 645 error = float(line[error_col-1]) 646 647 # Bad data. 648 except ValueError: 649 warn(RelaxWarning("Invalid errors, skipping the line %s" % line)) 650 continue 651 652 # Right, data is OK and exists. 653 missing_data = False 654 655 # Yield the data. 656 if data_col and error_col: 657 yield mol_name, res_num, res_name, spin_num, spin_name, value, error 658 elif data_col: 659 yield mol_name, res_num, res_name, spin_num, spin_name, value 660 elif error_col: 661 yield mol_name, res_num, res_name, spin_num, spin_name, error 662 else: 663 yield mol_name, res_num, res_name, spin_num, spin_name 664 665 # Hmmm, no data! 666 if missing_data: 667 raise RelaxError("No corresponding data could be found within the file.")
668 669
670 -def strip(data, comments=True):
671 """Remove all comment and empty lines from the file data structure. 672 673 @param data: The file data to clean up. 674 @type data: list of lists of str 675 @keyword comments: A flag which if True will cause comments to be deleted. 676 @type comments: bool 677 @return: The input data with the empty and comment lines removed. 678 @rtype: list of lists of str 679 """ 680 681 # Initialise the new data array. 682 new = [] 683 684 # Loop over the data. 685 for i in range(len(data)): 686 # Empty lines. 687 if len(data[i]) == 0: 688 continue 689 690 # Comment lines. 691 if comments and search("^#", data[i][0]): 692 continue 693 694 # Data lines. 695 new.append(data[i]) 696 697 # Return the new data structure. 698 return new
699 700
701 -def test_binary(binary):
702 """Function for testing that the binary string corresponds to a valid executable file. 703 704 @param binary: The name or path of the binary executable file. 705 @type binary: str 706 """ 707 708 # Path separator RE string. 709 if altsep: 710 path_sep = '[' + sep + altsep + ']' 711 else: 712 path_sep = sep 713 714 # The full path of the program has been given (if a directory separatory has been supplied). 715 if search(path_sep, binary): 716 # Test that the binary exists. 717 if not access(binary, F_OK): 718 raise RelaxMissingBinaryError(binary) 719 720 # Test that if the binary is executable. 721 if not access(binary, X_OK): 722 raise RelaxNonExecError(binary) 723 724 # The path to the binary has not been given. 725 else: 726 # Get the PATH environmental variable. 727 path = getenv('PATH') 728 729 # Split PATH by the path separator. 730 path_list = path.split(pathsep) 731 732 # Test that the binary exists within the system path (and exit this function instantly once it has been found). 733 for path in path_list: 734 if access(path + sep + binary, F_OK): 735 return 736 737 # The binary is not located in the system path! 738 raise RelaxNoInPathError(binary)
739 740
741 -def write_data(out=None, headings=None, data=None, sep=None):
742 """Write out a table of the data to the given file handle. 743 744 @keyword out: The file handle to write to. 745 @type out: file handle 746 @keyword headings: The optional headings to print out. 747 @type headings: list of str or None 748 @keyword data: The data to print out. 749 @type data: list of list of str 750 @keyword sep: The column separator which, if None, defaults to whitespace. 751 @type sep: str or None 752 """ 753 754 # No data to print out. 755 if data in [None, []]: 756 return 757 758 # The number of rows and columns. 759 num_rows = len(data) 760 num_cols = len(data[0]) 761 762 # Pretty whitespace formatting. 763 if sep == None: 764 # Determine the widths for the headings. 765 widths = [] 766 for j in range(num_cols): 767 if headings != None: 768 if j == 0: 769 widths.append(len(headings[j]) + 2) 770 else: 771 widths.append(len(headings[j])) 772 773 # No headings. 774 else: 775 widths.append(0) 776 777 # Determine the maximum column widths for nice whitespace formatting. 778 for i in range(num_rows): 779 for j in range(num_cols): 780 size = len(data[i][j]) 781 if size > widths[j]: 782 widths[j] = size 783 784 # Convert to format strings. 785 formats = [] 786 for j in range(num_cols): 787 formats.append("%%-%ss" % (widths[j] + 4)) 788 789 # The headings. 790 if headings != None: 791 out.write(formats[0] % ("# " + headings[0])) 792 for j in range(1, num_cols): 793 out.write(formats[j] % headings[j]) 794 out.write('\n') 795 796 # The data. 797 for i in range(num_rows): 798 # The row. 799 for j in range(num_cols): 800 out.write(formats[j] % data[i][j]) 801 out.write('\n') 802 803 # Non-whitespace formatting. 804 else: 805 # The headings. 806 if headings != None: 807 out.write('#') 808 for j in range(num_cols): 809 # The column separator. 810 if j > 0: 811 out.write(sep) 812 813 # The heading. 814 out.write(headings[j]) 815 out.write('\n') 816 817 # The data. 818 for i in range(num_rows): 819 # The row. 820 for j in range(num_cols): 821 # The column separator. 822 if j > 0: 823 out.write(sep) 824 825 # The heading. 826 out.write(data[i][j]) 827 out.write('\n')
828 829
830 -def write_spin_data(file, dir=None, sep=None, spin_ids=None, mol_names=None, res_nums=None, res_names=None, spin_nums=None, spin_names=None, force=False, data=None, data_name=None, error=None, error_name=None, float_format="%20.15g"):
831 """Generator function for reading the spin specific data from file. 832 833 Description 834 =========== 835 836 This function writes a columnar formatted file where each line corresponds to a spin system. Spin identification is either through a spin ID string or through columns containing the molecule name, residue name and number, and/or spin name and number. 837 838 839 @param file: The name of the file to write the data to (or alternatively an already opened file object). 840 @type file: str or file object 841 @keyword dir: The directory to place the file into (defaults to the current directory if None and the file argument is not a file object). 842 @type dir: str or None 843 @keyword sep: The column separator which, if None, defaults to whitespace. 844 @type sep: str or None 845 @keyword spin_ids: The list of spin ID strings. 846 @type spin_ids: None or list of str 847 @keyword mol_names: The list of molecule names. 848 @type mol_names: None or list of str 849 @keyword res_nums: The list of residue numbers. 850 @type res_nums: None or list of int 851 @keyword res_names: The list of residue names. 852 @type res_names: None or list of str 853 @keyword spin_nums: The list of spin numbers. 854 @type spin_nums: None or list of int 855 @keyword spin_names: The list of spin names. 856 @type spin_names: None or list of str 857 @keyword force: A flag which if True will cause an existing file to be overwritten. 858 @type force: bool 859 @keyword data: A list of the data to write out. The first dimension corresponds to the spins. A second dimension can also be given if multiple data sets across multiple columns are desired. 860 @type data: list or list of lists 861 @keyword data_name: A name corresponding to the data argument. If the data argument is a list of lists, then this must also be a list with the same length as the second dimension of the data arg. 862 @type data_name: str or list of str 863 @keyword error: A list of the errors to write out. The first dimension corresponds to the spins. A second dimension can also be given if multiple data sets across multiple columns are desired. These will be inter-dispersed between the data columns, if the data is given. If the data arg is not None, then this must have the same dimensions as that object. 864 @type error: list or list of lists 865 @keyword error_name: A name corresponding to the error argument. If the error argument is a list of lists, then this must also be a list with the same length at the second dimension of the error arg. 866 @type error_name: str or list of str 867 @keyword float_format: A float formatting string to use for the data and error whenever a float is found. 868 @type float_format: str 869 """ 870 871 # Data argument tests. 872 if data: 873 # Data is a list of lists. 874 if isinstance(data[0], list): 875 # Data and data_name don't match. 876 if not isinstance(data_name, list): 877 raise RelaxError("The data_name arg '%s' must be a list as the data argument is a list of lists." % data_name) 878 879 # Error doesn't match. 880 if error and (len(data) != len(error) or len(data[0]) != len(error[0])): 881 raise RelaxError("The data arg:\n%s\n\ndoes not have the same dimensions as the error arg:\n%s." % (data, error)) 882 883 # Data is a simple list. 884 else: 885 # Data and data_name don't match. 886 if not isinstance(data_name, str): 887 raise RelaxError("The data_name arg '%s' must be a string as the data argument is a simple list." % data_name) 888 889 # Error doesn't match. 890 if error and len(data) != len(error): 891 raise RelaxError("The data arg:\n%s\n\ndoes not have the same dimensions as the error arg:\n%s." % (data, error)) 892 893 # Error argument tests. 894 if error: 895 # Error is a list of lists. 896 if isinstance(error[0], list): 897 # Error and error_name don't match. 898 if not isinstance(error_name, list): 899 raise RelaxError("The error_name arg '%s' must be a list as the error argument is a list of lists." % error_name) 900 901 # Error is a simple list. 902 else: 903 # Error and error_name don't match. 904 if not isinstance(error_name, str): 905 raise RelaxError("The error_name arg '%s' must be a string as the error argument is a simple list." % error_name) 906 907 # Number of spins check. 908 args = [spin_ids, mol_names, res_nums, res_names, spin_nums, spin_names] 909 arg_names = ['spin_ids', 'mol_names', 'res_nums', 'res_names', 'spin_nums', 'spin_names'] 910 N = None 911 first_arg = None 912 first_arg_name = None 913 for i in range(len(args)): 914 if isinstance(args[i], list): 915 # First list match. 916 if N == None: 917 N = len(args[i]) 918 first_arg = args[i] 919 first_arg_name = arg_names[i] 920 921 # Length check. 922 if len(args[i]) != N: 923 raise RelaxError("The %s and %s arguments do not have the same number of spins ('%s' vs. '%s' respectively)." % (first_arg_name, arg_names[i], len(first_arg), len(args[i]))) 924 925 # Nothing?!? 926 if N == None: 927 raise RelaxError("No spin ID data is present.") 928 929 # Data and error length check. 930 if data and len(data) != N: 931 raise RelaxError("The %s and data arguments do not have the same number of spins ('%s' vs. '%s' respectively)." % (first_arg_name, len(first_arg), len(data))) 932 if error and len(error) != N: 933 raise RelaxError("The %s and error arguments do not have the same number of spins ('%s' vs. '%s' respectively)." % (first_arg_name, len(first_arg), len(error))) 934 935 # The spin arguments. 936 args = [spin_ids, mol_names, res_nums, res_names, spin_nums, spin_names] 937 arg_names = ['spin_id', 'mol_name', 'res_num', 'res_name', 'spin_num', 'spin_name'] 938 939 940 # Init. 941 headings = [] 942 file_data = [] 943 944 # Headers - the spin ID info. 945 for i in range(len(args)): 946 if args[i]: 947 headings.append(arg_names[i]) 948 949 # Headers - the data. 950 if data: 951 # List of lists. 952 if isinstance(data[0], list): 953 # Loop over the list. 954 for i in range(len(data[0])): 955 # The data. 956 headings.append(data_name[i]) 957 958 # The error. 959 if error: 960 headings.append(error_name[i]) 961 962 # Simple list. 963 else: 964 # The data. 965 headings.append(data_name) 966 967 # The error. 968 if error: 969 headings.append(error_name) 970 971 # Headers - only errors. 972 elif error: 973 # List of lists. 974 if isinstance(error[0], list): 975 for i in range(len(error[0])): 976 headings.append(error_name[i]) 977 978 # Simple list. 979 else: 980 headings.append(error_name) 981 982 # No headings. 983 if headings == []: 984 headings = None 985 986 # Spin specific data. 987 for spin_index in range(N): 988 # Append a new data row. 989 file_data.append([]) 990 991 # The spin ID info. 992 for i in range(len(args)): 993 if args[i]: 994 value = args[i][spin_index] 995 if not isinstance(value, str): 996 value = repr(value) 997 file_data[-1].append(value) 998 999 # The data. 1000 if data: 1001 # List of lists. 1002 if isinstance(data[0], list): 1003 # Loop over the list. 1004 for i in range(len(data[0])): 1005 # The data. 1006 if is_float(data[spin_index][i]): 1007 file_data[-1].append(float_format % data[spin_index][i]) 1008 else: 1009 file_data[-1].append(repr(data[spin_index][i])) 1010 1011 # The error. 1012 if error: 1013 if is_float(error[spin_index][i]): 1014 file_data[-1].append(float_format % error[spin_index][i]) 1015 else: 1016 file_data[-1].append(repr(error[spin_index][i])) 1017 1018 # Simple list. 1019 else: 1020 # The data. 1021 if is_float(data[spin_index]): 1022 file_data[-1].append(float_format % data[spin_index]) 1023 else: 1024 file_data[-1].append(repr(data[spin_index])) 1025 1026 # The error. 1027 if error: 1028 if is_float(error[spin_index]): 1029 file_data[-1].append(float_format % error[spin_index]) 1030 else: 1031 file_data[-1].append(repr(error[spin_index])) 1032 1033 # Only errors. 1034 elif error: 1035 # List of lists. 1036 if isinstance(error[0], list): 1037 for i in range(len(error[0])): 1038 file_data[-1].append(repr(error[spin_index][i])) 1039 1040 # Simple list. 1041 else: 1042 file_data[-1].append(repr(error[spin_index])) 1043 1044 # No data to write, so do nothing! 1045 if file_data == [] or file_data == [[]]: 1046 return 1047 1048 # Open the file for writing. 1049 file = open_write_file(file_name=file, dir=dir, force=force) 1050 1051 # Write out the file data. 1052 write_data(out=file, headings=headings, data=file_data, sep=sep)
1053 1054 1055
1056 -class DummyFileObject:
1057 - def __init__(self):
1058 """Set up the dummy object to act as a file object.""" 1059 1060 # Initialise an object for adding the string from all write calls to. 1061 self.data = '' 1062 1063 # Set the closed flag. 1064 self.closed = False
1065 1066
1067 - def close(self):
1068 """A method for 'closing' this object.""" 1069 1070 # Set the closed flag. 1071 self.closed = True
1072 1073
1074 - def write(self, str):
1075 """Mimic the file object write() method so that this class can be used as a file object. 1076 1077 @param str: The string to be written. 1078 @type str: str 1079 """ 1080 1081 # Check if the file is closed. 1082 if self.closed: 1083 raise ValueError('I/O operation on closed file') 1084 1085 # Append the string to the data object. 1086 self.data = self.data + str
1087 1088
1089 - def readlines(self):
1090 """Mimic the file object readlines() method. 1091 1092 This method works even if this dummy file object is closed! 1093 1094 1095 @return: The contents of the file object separated by newline characters. 1096 @rtype: list of str 1097 """ 1098 1099 # Split up the string. 1100 lines = self.data.split('\n') 1101 1102 # Remove the last line if empty. 1103 if lines[-1] == '': 1104 lines.pop() 1105 1106 # Loop over the lines, re-adding the newline character to match the file object readlines() method. 1107 for i in range(len(lines)): 1108 lines[i] = lines[i] + '\n' 1109 1110 # Return the file lines. 1111 return lines
1112 1113 1114
1115 -class SplitIO:
1116 - def __init__(self):
1117 """Class for splitting an IO stream to two outputs."""
1118 1119
1120 - def flush(self):
1121 """Flush all streams.""" 1122 1123 # Call the streams' methods. 1124 self.stream1.flush() 1125 self.stream2.flush()
1126 1127
1128 - def isatty(self):
1129 """Check that both streams are TTYs. 1130 1131 @return: True, only if both streams are TTYs. 1132 @rtype: bool 1133 """ 1134 1135 # Check both streams. 1136 return self.stream1.isatty() & self.stream2.isatty()
1137 1138
1139 - def split(self, stream1, stream2):
1140 """Function for setting the streams.""" 1141 1142 # Arguments. 1143 self.stream1 = stream1 1144 self.stream2 = stream2
1145 1146
1147 - def write(self, text):
1148 """Replacement write function.""" 1149 1150 # Write to stream1. 1151 self.stream1.write(text) 1152 1153 # Write to stream2. 1154 self.stream2.write(text)
1155