def archive_zip(directory, archive): """Archive the directory to the given tar/gz archive filename. Args: directory: A string, the name of the directory to archive. archive: A string, the name of the file to output. """ # Figure out optimal level of compression among the supported ones in this # installation. for spec, compression in [('lzma', zipfile.ZIP_LZMA), ('bz2', zipfile.ZIP_BZIP2), ('zlib', zipfile.ZIP_DEFLATED)]: if importlib.util.find_spec(spec): zip_compression = compression break else: # Default is no compression. zip_compression = zipfile.ZIP_STORED with file_utils.chdir(directory), zipfile.ZipFile( archive, 'w', compression=zip_compression) as archfile: for root, dirs, files in os.walk(directory): for filename in files: relpath = path.relpath(path.join(root, filename), directory) archfile.write(relpath)
def _parse_recursive(sources, log_timings, encoding=None): """Parse Beancount input, run its transformations and validate it. Recursively parse a list of files or strings and their include files and return an aggregate of parsed directives, errors, and the top-level options-map. If the same file is being parsed twice, ignore it and issue an error. Args: sources: A list of (filename-or-string, is-filename) where the first element is a string, with either a filename or a string to be parsed directly, and the second argument is a boolean that is true if the first is a filename. You may provide a list of such arguments to be parsed. Filenames must be absolute paths. log_timings: A function to write timings to, or None, if it should remain quiet. encoding: A string or None, the encoding to decode the input filename with. Returns: A tuple of (entries, parse_errors, options_map). """ assert isinstance(sources, list) and all( isinstance(el, tuple) for el in sources) # Current parse state. entries, parse_errors = [], [] options_map = None # A stack of sources to be parsed. source_stack = list(sources) # A list of absolute filenames that have been parsed in the past, used to # detect and avoid duplicates (cycles). filenames_seen = set() with misc_utils.log_time('beancount.parser.parser', log_timings, indent=1): while source_stack: source, is_file = source_stack.pop(0) is_top_level = options_map is None # If the file is encrypted, read it in and process it as a string. if is_file: cwd = path.dirname(source) source_filename = source if encryption.is_encrypted_file(source): source = encryption.read_encrypted_file(source) is_file = False else: # If we're parsing a string, the CWD is the current process # working directory. cwd = os.getcwd() source_filename = None if is_file: # All filenames here must be absolute. assert path.isabs(source) filename = path.normpath(source) # Check for file previously parsed... detect duplicates. if filename in filenames_seen: parse_errors.append( LoadError( data.new_metadata("<load>", 0), 'Duplicate filename parsed: "{}"'.format(filename), None)) continue # Check for a file that does not exist. if not path.exists(filename): parse_errors.append( LoadError(data.new_metadata("<load>", 0), 'File "{}" does not exist'.format(filename), None)) continue # Parse a file from disk directly. filenames_seen.add(filename) with misc_utils.log_time('beancount.parser.parser.parse_file', log_timings, indent=2): (src_entries, src_errors, src_options_map) = parser.parse_file(filename, encoding=encoding) cwd = path.dirname(filename) else: # Encode the contents if necessary. if encoding: if isinstance(source, bytes): source = source.decode(encoding) source = source.encode('ascii', 'replace') # Parse a string buffer from memory. with misc_utils.log_time( 'beancount.parser.parser.parse_string', log_timings, indent=2): (src_entries, src_errors, src_options_map) = parser.parse_string( source, source_filename) # Merge the entries resulting from the parsed file. entries.extend(src_entries) parse_errors.extend(src_errors) # We need the options from the very top file only (the very # first file being processed). No merging of options should # occur. if is_top_level: options_map = src_options_map else: aggregate_options_map(options_map, src_options_map) # Add includes to the list of sources to process. chdir() for glob, # which uses it indirectly. include_expanded = [] with file_utils.chdir(cwd): for include_filename in src_options_map['include']: matched_filenames = glob.glob(include_filename, recursive=True) if matched_filenames: include_expanded.extend(matched_filenames) else: parse_errors.append( LoadError( data.new_metadata("<load>", 0), 'File glob "{}" does not match any files'. format(include_filename), None)) for include_filename in include_expanded: if not path.isabs(include_filename): include_filename = path.join(cwd, include_filename) include_filename = path.normpath(include_filename) # Add the include filenames to be processed later. source_stack.append((include_filename, True)) # Make sure we have at least a dict of valid options. if options_map is None: options_map = options.OPTIONS_DEFAULTS.copy() # Save the set of parsed filenames in options_map. options_map['include'] = sorted(filenames_seen) return entries, parse_errors, options_map
def test_chdir_contextmanager(self): with file_utils.chdir(tempfile.gettempdir()) as tmpdir: self.assertIsInstance(tmpdir, str) self.assertEqual(path.realpath(tempfile.gettempdir()), os.getcwd())