Source code for eye.pathutils

# this project is licensed under the WTFPLv2, see COPYING.txt for details

"""Path manipulation utilities
"""

import glob
import os
import re

__all__ = (
	'vim_filename_arg', 'parse_filename',
	'find_ancestor_containing', 'find_in_ancestors',
	'get_common_prefix', 'get_relative_path_in', 'is_in',
	'get_config_path', 'get_config_file_path', 'data_path',
)


_VIM_JUMP = re.compile(r"\+\d+")


[docs] def vim_filename_arg(args): """Parse `+line filename` args Vim and other editors are sometimes called with a filename and a line argument. Returns (filename, lineno) """ if len(args) != 2 or not _VIM_JUMP.fullmatch(args[0]): return path = args[1] try: row = int(args[0][1:]) except ValueError: return return path, row
[docs] def parse_filename(filepath): """Parse a `filename:line:col` string Parse a string containing a file path, a line number and column number, in the format `filepath:line:col`. Line and column are optional. If only one is present, it's taken as the line number. Returns a tuple This function can be useful for command-line arguments. >>> parseFilename('/foo/bar:1') ('/foo/bar', 1, None) """ row, col = None, None mtc = re.search(r'(:\d+)?(:\d+)?$', filepath) if mtc.group(1): row = int(mtc.group(1)[1:]) if mtc.group(2): col = int(mtc.group(2)[1:]) filepath = filepath[:mtc.start()] return (filepath, row, col)
[docs] def find_ancestor_containing(path, patterns): """Find an ancestor containing any of `patterns` Like :any:`find_in_ancestors`, but returns the directory containing the matched file. """ found = find_in_ancestors(path, patterns) if found: return os.path.dirname(found)
[docs] def find_in_ancestors(path, patterns): """Find file matching any of `patterns` in ancestors of `path` `patterns` should be a list of globbing patterns (see standard `glob` module). Returns the absolute path of the first matching file. Patterns are searched in order given. `path` is searched first, then its parent, then ancestors in ascending order. """ path = os.path.abspath(path) while True: for pattern in patterns: matches = glob.glob(os.path.join(path, pattern)) if matches: return matches[0] if path == '/': return path = os.path.dirname(path)
[docs] def get_common_prefix(a, b): """Return common path prefix between path `a` and path `b` Paths are normalized with `os.path.normpath`. Will not cut in the middle of a path component. >>> get_common_prefix('/foo/bar', '/foo/baz') '/foo' """ aparts, bparts = (os.path.normpath(x).split(os.path.sep) for x in (a, b)) n = 0 for aelem, belem in zip(aparts, bparts): if aelem != belem: break n += 1 return os.path.sep.join(aparts[:n]) or os.path.sep
[docs] def is_in(a, b): """Return True if path `a` is contained path `b` Does not check existence of paths. Paths are normalized with `os.path.normpath`. >>> is_in('/foo, '/bar') False >>> is_in('/bar/foo', '/bar') True """ r = get_relative_path_in(a, b) return r is not None
[docs] def get_relative_path_in(a, b): """Return the relative path of `a` inside `b` If `a` is not contained inside `b`, returns `None`. Paths are normalized with `os.path.normpath`. Does not check existence of paths. >>> get_relative_path_in('/bar/foo/qux', '/bar') 'foo/qux' >>> get_relative_path_in('/foo', '/bar') is None True """ aparts, bparts = (os.path.normpath(x).split(os.path.sep) for x in (a, b)) if len(aparts) < len(bparts): return for n, bpart in enumerate(bparts): if aparts[n] != bpart: return return '/'.join(aparts[n + 1:])
[docs] def get_config_path(*args): try: import xdg.BaseDirectory return xdg.BaseDirectory.save_config_path('eyeditor', *args) except ImportError: path = os.path.join(os.path.expanduser('~/.config/eyeditor'), *args) if not os.path.isdir(path): os.makedirs(os.path.normpath(path)) return path
[docs] def get_config_file_path(*args): subpath = os.path.join(*args) dir = get_config_path(os.path.dirname(subpath)) file = os.path.basename(subpath) return os.path.join(dir, file)
[docs] def data_path(*args): dest = os.path.join(os.path.dirname(__file__), '..', 'data', *args) if os.path.exists(dest): return os.path.abspath(dest) try: import xdg.BaseDirectory for path in xdg.BaseDirectory.load_data_paths('eye', *args): return path except ImportError: pass return os.path.join('/usr/share/eye', *args)