Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function stripPrefix #73

Open
fendor opened this issue Oct 28, 2019 · 11 comments · May be fixed by #75
Open

Add function stripPrefix #73

fendor opened this issue Oct 28, 2019 · 11 comments · May be fixed by #75
Milestone

Comments

@fendor
Copy link

fendor commented Oct 28, 2019

The intention is to strip away a common prefix and return the relative FilePath, if and only if there is a common prefix.
Possible function signature:

stripPrefix :: 
  -- | Root directory. May be relative or absolute.
  FilePath -> 
  -- | FilePath to strip away from the root directory.
  FilePath ->
  -- | FilePath relative to the root directory or Nothing.
  Maybe FilePath

Some example calls:

>>> stripPrefix "app" "app/File.hs"
Just "File.hs"

>>> stripPrefix "src" "app/File.hs"
Nothing

>>> stripPrefix "src" "src-dir/File.hs"
Nothing

>>> stripPrefix "." "src/File.hs"
Just "src/File.hs"

>>> stripPrefix "app/" "./app/Lib/File.hs"
Just "Lib/File.hs"

>>> stripPrefix "/app/" "./app/Lib/File.hs"
Nothing -- Nothing since '/app/' is absolute

>>> stripPrefix "/app" "/app/Lib/File.hs"
Just "Lib/File.hs"

Motivation: I often want to strip away some directory from another FilePath if and only if the given FilePath is part of the directory. This is some combination of makeRelative and checking, whether the given FilePath is in the given directory.

Other programming languages, such as Rust (https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix) also implement a similar function in the standard library for Path manipulation.

Is such a function useful to others as well?
Or maybe, this function is trivial to express with the given API already?

@ndmitchell
Copy link
Contributor

ndmitchell commented Nov 9, 2019

I agree this is a useful addition. I think people usually do some super unsafe drop stuff which is very unpleasant.

I note the name stripPrefix clashes with List, and nothing else in this module does, so we need a different name.

I also note the closely related operation of take/drop directories from the front of a path. I wonder if they should be added at the same time? Or even if they replace this as a general case of it? In Shake I have takeDirectory1 (terrible name) for that purpose.

@fendor
Copy link
Author

fendor commented Nov 11, 2019

I agree that it makes sense not to clash with Data.List.

I dont think that taking and dropping directories form the front of a path are a generalization of this. And even if they are, a well-named, well-documented function would make it easier to discover the functionality and understand it.

Name suggestions, mainly for brainstorming:

  • stripCommonPrefix
  • stripPathPrefix
  • takeRelative
  • stripAncestor
  • removeAncestor

@fendor fendor linked a pull request Nov 12, 2019 that will close this issue
@mb720
Copy link

mb720 commented Dec 16, 2019

I'd appreciate this function. 👍

I just tried to implement a function that checks whether a file is inside a directory or inside its subdirectories using this flawed approach:

isInside :: FilePath -> FilePath -> Bool
isInside dir file =
  let dir'   = show dir
      file'  = show file
  in Data.List.isPrefixOf dir' file'

This returns false, also for files that were indeed inside the directory.

After a bit of head scratching I realized that show puts quotes around the file paths. This meant checking if "/home/me/dir" is the prefix of "/home/me/dir/file" which it is not because of the trailing ".

@ndmitchell
Copy link
Contributor

@mb720 I would have thought Data.List.isPrefixOf (addTrailingPathSeparator dir) file would do what you are after more easily than the hypothetical stripPrefix.

@mb720
Copy link

mb720 commented Dec 19, 2019

Thanks @ndmitchell! I forgot that FilePath is a type alias for String. Your suggestion makes for a simple solution.

@ndmitchell
Copy link
Contributor

So I think what we want is:

splitDirectoryStart :: FilePath -> (FilePath, FilePath)

Which turns foo/bar/baz into (foo, bar/baz). Then everything else, probably a take, drop, and strip are built off that. Does that make sense? Not a massive fan of the names, but split/take/drop/strip seems to be consistent with what else is in the filepath library.

@fendor
Copy link
Author

fendor commented Jan 12, 2020

I think, this is a reasonable primitive.
To be clear, the function proposed in this issue would still be added to the library?

@ndmitchell
Copy link
Contributor

For other additions to the library, there are the split/drop/take functions all in the library - I see no reason to not do that here too. The main question I have open is coming up with a name that is clear.

@ndmitchell
Copy link
Contributor

In #77 I offered to change maintainership of this library. Given this decision is the big one outstanding, I'll wait for a resolution on that matter before figuring out this patch. (And sorry for the delay, I've got weighed down with lots of other stuff)

@Bodigrim
Copy link
Contributor

A dual to stripExtension to strip a few segments from the start of the path would be helpful indeed. A low-level stripPrefix from os-string isn't quite there, because it will strip segments even in the middle of a folder name.

@hasufell
Copy link
Member

hasufell commented Dec 11, 2024

There are some rather peculiar edge cases on windows:

stripPrefix "C:foo" "C:foo/bar"

This is a relative path that has a drive letter and means "relative to the current directory on drive C".

Semantically it should probably produce "C:bar".

Similarly stripPrefix "/foo" "/foo/bar" should produce "/bar", since the leading slash means "apply the drive from the current directory".

UNC paths are difficult. Stripping can produce an "invalid path". But that problem already exists in other functions to some degree.

The question is what properties do we really expect. If it's "the entire prefix should be gone from the output", then that won't hold on windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants