haskell / filepath

Haskell FilePath core library
BSD 3-Clause "New" or "Revised" License
66 stars 32 forks source link

Add function `stripPrefix` #73

Open fendor opened 4 years ago

fendor commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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:

mb720 commented 4 years ago

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 commented 4 years ago

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

mb720 commented 4 years ago

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

ndmitchell commented 4 years ago

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 commented 4 years ago

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

ndmitchell commented 4 years ago

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 commented 4 years ago

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)