def _edit(args): """Edit the mapping directly using an editor.""" if args: abort(USAGE + 'dtags: too many arguments') try: with TempFile( mode='w+t', delete=False, prefix='mapping.', dir=CFG_DIR ) as tfile: with io.open(MAPPING_FILE, 'rt') as mapping_file: tfile.write(EDIT_HELP_COMMENTS + mapping_file.read()) tfile.flush() except (OSError, IOError) as err: abort('dtags: failed to edit mapping: {}'.format(err), err.errno) else: editor = shlex.split(os.environ.get('EDITOR')) if not editor: abort('dtags: undefined environment variable: ' + style.bad('EDITOR')) try: sp.check_call(editor + [tfile.name]) except sp.CalledProcessError as err: abort('dtags: failed to edit mapping: {}'.format(err.message)) else: mapping, excluded = parse_mapping(tfile.name) save_mapping(mapping) rm_files(tfile.name) if excluded: print('Cleaned the following entries:\n' + excluded + '\n') finish('New entries saved successfully')
def save_mapping(mapping): """Save the mapping (and tags for autocomplete) back to the disk. This function assumes that the mapping is already validated. :param mapping: the mapping object to save """ # Create a reverse mapping (this is what gets saved to the disk) reverse_mapping = defaultdict(set) for tag, paths in mapping.items(): for path in paths: reverse_mapping[path].add(tag) # Save the reverse mapping to a temporary file temp_mapping_file = NamedTemporaryFile( mode='w', dir=CFG_DIR, prefix='mapping.', delete=False ) temp_mapping_file.close() try: with io.open(temp_mapping_file.name, 'wt') as temp_mapping_file: temp_mapping_file.write('\n'.join( path + ',' + ','.join(sorted(reverse_mapping[path])) + ',' for path in sorted(reverse_mapping) )) temp_mapping_file.flush() os.fsync(temp_mapping_file.fileno()) except (IOError, OSError) as temp_mapping_write_error: rm_files(temp_mapping_file.name) abort( message='Failed to write to {name}: {msg}\n'.format( name=temp_mapping_file.name, msg=temp_mapping_write_error.strerror ), exit_code=temp_mapping_write_error.errno ) # Save the tags (for tab-completion) to a temporary file temp_tags_file = NamedTemporaryFile( mode='w', prefix='tags.', dir=CFG_DIR, delete=False ) temp_tags_file.close() try: with io.open(temp_tags_file.name, 'wt') as temp_tags_file: temp_tags_file.write('\n'.join(sorted(mapping))) temp_tags_file.flush() os.fsync(temp_tags_file.fileno()) except (IOError, OSError) as temp_tags_write_error: rm_files(temp_mapping_file.name, temp_tags_file.name) abort( message='Failed to write to {name}: {msg}\n'.format( name=temp_tags_file.name, msg=temp_tags_write_error.strerror ), exit_code=temp_tags_write_error.errno ) try: # Overwrite the mapping file with the temporary one shutil.move(temp_mapping_file.name, MAPPING_FILE) except (IOError, OSError) as mapping_rename_error: rm_files(temp_mapping_file.name, temp_tags_file.name) abort( message='Failed to move {src} to {dest}: {msg}\n'.format( src=temp_mapping_file.name, dest=MAPPING_FILE, msg=mapping_rename_error.strerror ), exit_code=mapping_rename_error.errno ) try: # Overwrite the tags file with the temporary one shutil.move(temp_tags_file.name, TAGS_FILE) except (IOError, OSError) as tags_rename_error: rm_files(temp_tags_file.name) abort( message='Failed to move {src} to {dest}: {msg}\n'.format( src=temp_tags_file.name, dest=TAGS_FILE, msg=tags_rename_error.strerror ), exit_code=tags_rename_error.errno )