fs: add methods as_posix, is_absolute

fs: make exception specify method name

fs: actually raise exceptions

fs: resolve path e.g.  /opt/foo/.. => /opt/foo

fs: correct behavior of is_symlink
pull/6595/head
Michael Hirsch, Ph.D 5 years ago committed by Michael Hirsch, Ph.D
parent 9e365e4d0a
commit 4556343d95
  1. 41
      docs/markdown/Fs-module.md
  2. 50
      mesonbuild/modules/fs.py
  3. 22
      test cases/common/227 fs module/meson.build

@ -10,6 +10,8 @@ current `meson.build` file is.
If specified, a leading `~` is expanded to the user home directory.
Where possible, symlinks and parent directory notation are resolved to an absolute path.
### exists
Takes a single string argument and returns true if an entity with that
@ -19,13 +21,12 @@ special entry such as a device node.
### is_dir
Takes a single string argument and returns true if a directory with
that name exists on the file system. This method follows symbolic
links.
that name exists on the file system.
### is_file
Takes a single string argument and returns true if an file with that
name exists on the file system. This method follows symbolic links.
name exists on the file system.
### is_symlink
@ -34,6 +35,23 @@ by the string is a symbolic link.
## File Parameters
### is_absolute
Return a boolean indicating if the path string specified is absolute for this computer, WITHOUT expanding `~`.
Examples:
```meson
fs.is_absolute('~') # false unless you literally have a path with string name `~`
home = fs.expanduser('~')
fs.is_absolute(home) # true
fs.is_absolute(home / 'foo') # true, even if ~/foo doesn't exist
fs.is_absolute('foo/bar') # false, even if ./foo/bar exists
```
### hash
The `fs.hash(filename, hash_algorithm)` method returns a string containing
@ -44,7 +62,6 @@ md5, sha1, sha224, sha256, sha384, sha512.
### size
The `fs.size(filename)` method returns the size of the file in integer bytes.
Symlinks will be resolved if possible.
### is_samepath
@ -79,7 +96,21 @@ fs.is_samepath(p, s) # false
## Filename modification
The files need not actually exist yet for this method, as it's just string manipulation.
The files need not actually exist yet for these methods, as they are just string manipulation.
### as_posix
`fs.as_posix(path)` assumes a Windows path, even if on a Unix-like system.
Thus, all `'\'` or `'\\'` are turned to '/', even if you meant to escape a character.
Examples
```meson
fs.as_posix('\\') == '/' # true
fs.as_posix('\\\\') == '/' # true
fs.as_posix('foo\\bar/baz') == 'foo/bar/baz' # true
```
### replace_suffix

@ -31,18 +31,52 @@ class FSModule(ExtensionModule):
super().__init__(interpreter)
self.snippets.add('generate_dub_file')
def _absolute_dir(self, state: 'ModuleState', arg: str) -> Path:
"""
make an absolute path from a relative path, WITHOUT resolving symlinks
"""
return Path(state.source_root) / state.subdir / Path(arg).expanduser()
def _resolve_dir(self, state: 'ModuleState', arg: str) -> Path:
"""
resolves (makes absolute) a directory relative to calling meson.build,
resolves symlinks and makes absolute a directory relative to calling meson.build,
if not already absolute
"""
return Path(state.source_root) / state.subdir / Path(arg).expanduser()
path = self._absolute_dir(state, arg)
try:
# accomodate unresolvable paths e.g. symlink loops
path = path.resolve()
except Exception:
# return the best we could do
pass
return path
def _check(self, check: str, state: 'ModuleState', args: T.Sequence[str]) -> ModuleReturnValue:
if len(args) != 1:
raise MesonException('fs.{} takes exactly one argument.'.format(check))
test_file = self._resolve_dir(state, args[0])
return ModuleReturnValue(getattr(test_file, check)(), [])
val = getattr(test_file, check)()
if isinstance(val, Path):
val = str(val)
return ModuleReturnValue(val, [])
@stringArgs
@noKwargs
def is_absolute(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 1:
raise MesonException('fs.is_absolute takes exactly one argument.')
return ModuleReturnValue(PurePath(args[0]).is_absolute(), [])
@stringArgs
@noKwargs
def as_posix(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
"""
this function assumes you are passing a Windows path, even if on a Unix-like system
and so ALL '\' are turned to '/', even if you meant to escape a character
"""
if len(args) != 1:
raise MesonException('fs.as_posix takes exactly one argument.')
return ModuleReturnValue(PureWindowsPath(args[0]).as_posix(), [])
@stringArgs
@noKwargs
@ -52,7 +86,9 @@ class FSModule(ExtensionModule):
@stringArgs
@noKwargs
def is_symlink(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
return self._check('is_symlink', state, args)
if len(args) != 1:
raise MesonException('fs.is_symlink takes exactly one argument.')
return ModuleReturnValue(self._absolute_dir(state, args[0]).is_symlink(), [])
@stringArgs
@noKwargs
@ -68,7 +104,7 @@ class FSModule(ExtensionModule):
@noKwargs
def hash(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 2:
raise MesonException('method takes exactly two arguments.')
raise MesonException('fs.hash takes exactly two arguments.')
file = self._resolve_dir(state, args[0])
if not file.is_file():
raise MesonException('{} is not a file and therefore cannot be hashed'.format(file))
@ -84,7 +120,7 @@ class FSModule(ExtensionModule):
@noKwargs
def size(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 1:
raise MesonException('method takes exactly one argument.')
raise MesonException('fs.size takes exactly one argument.')
file = self._resolve_dir(state, args[0])
if not file.is_file():
raise MesonException('{} is not a file and therefore cannot be sized'.format(file))
@ -113,7 +149,7 @@ class FSModule(ExtensionModule):
@noKwargs
def replace_suffix(self, state: 'ModuleState', args: T.Sequence[str], kwargs: dict) -> ModuleReturnValue:
if len(args) != 2:
raise MesonException('method takes exactly two arguments.')
raise MesonException('fs.replace_suffix takes exactly two arguments.')
original = PurePath(args[0])
new = original.with_suffix(args[1])
return ModuleReturnValue(str(new), [])

@ -33,6 +33,28 @@ assert(not fs.is_dir('nonexisting'), 'Bad path detected as a dir.')
assert(fs.is_dir('~'), 'expanduser not working')
assert(not fs.is_file('~'), 'expanduser not working')
# -- as_posix
assert(fs.as_posix('/') == '/', 'as_posix idempotent')
assert(fs.as_posix('\\') == '/', 'as_posix simple')
assert(fs.as_posix('\\\\') == '/', 'as_posix simple')
assert(fs.as_posix('foo\\bar/baz') == 'foo/bar/baz', 'as_posix mixed slash')
# -- is_absolute
winabs = 'q:/foo'
unixabs = '/foo'
if is_windows
assert(fs.is_absolute(winabs), 'is_absolute windows not detected')
assert(not fs.is_absolute(unixabs), 'is_absolute unix false positive')
else
assert(fs.is_absolute(unixabs), 'is_absolute unix not detected')
assert(not fs.is_absolute(winabs), 'is_absolute windows false positive')
endif
# -- replace_suffix
original = 'foo'
assert(fs.replace_suffix(original, '') == original, 'replace_suffix idempotent')
original = 'foo.txt'
new = fs.replace_suffix(original, '.ini')
assert(new == 'foo.ini', 'replace_suffix failed')

Loading…
Cancel
Save