packages feed

stacker-0.0.1.0: src/PathUtil.hs

-- | Path utilities for cross-platform compatibility
--
-- This module provides path utilities that use forward slash (/) as the
-- path separator on all platforms, including Windows. While Windows primarily
-- uses backslash (\), it also accepts forward slash for relative paths,
-- which is what this tool deals with.
--
-- This approach ensures consistent path handling and output across all platforms,
-- avoiding the path separator issues that can occur when using System.FilePath's
-- platform-specific path combinators.
module PathUtil
  ( (</>)
  , normalizeFilePath
  , splitPath
  , joinPath
  , collapseDots
  , takeDirectory
  , takeFileName
  , toForwardSlashes
  ) where

import Data.List (stripPrefix)

-- | Cross-platform path combination using forward slash
-- This ensures consistent behavior across Linux, macOS, and Windows
-- for relative paths (which is what this tool processes).
--
-- Note: This is specifically designed for combining relative path components.
-- For absolute paths or complex path manipulations, System.FilePath should be used.
(</>) :: FilePath -> FilePath -> FilePath
"" </> b = b
a </> "" = a
a </> b = a ++ "/" ++ b

-- | Normalize a file path by removing "./" prefix
-- This is used to ensure consistent output format where paths
-- don't have a leading "./" component.
normalizeFilePath :: FilePath -> FilePath
normalizeFilePath path =
  case stripPrefix "./" path of
    Just rest -> rest
    Nothing -> path

-- | Split a path into components using forward slash as separator
-- Works consistently across all platforms
splitPath :: FilePath -> [String]
splitPath "" = []
splitPath path = split path
  where
    split "" = []
    split s = case break (== '/') s of
      (comp, "") -> [comp]
      (comp, _:rest) -> comp : split rest

-- | Join path components using forward slash
-- Works consistently across all platforms
joinPath :: [String] -> FilePath
joinPath [] = ""
joinPath [x] = x
joinPath (x:xs) = x ++ "/" ++ joinPath xs

-- | Collapse ".." and "." components in a path
-- This normalizes paths like "a/b/../c" to "a/c"
collapseDots :: FilePath -> FilePath
collapseDots path = joinPath $ reverse $ foldl collapseDir [] (splitPath path)
  where
    collapseDir :: [String] -> String -> [String]
    collapseDir acc "." = acc
    collapseDir (prev:rest) ".." | prev /= ".." = rest
    collapseDir acc ".." = ".." : acc
    collapseDir acc dir = dir : acc

-- | Get the directory part of a file path
-- Uses forward slash as separator on all platforms
-- Examples: takeDirectory "a/b/c" = "a/b", takeDirectory "file.txt" = "."
takeDirectory :: FilePath -> FilePath
takeDirectory "" = "."
takeDirectory path =
  case reverse (filter (not . null) (splitPath path)) of
    [] -> "."
    [_] -> "."
    (_:dirs) -> joinPath (reverse dirs)

-- | Get the file name part of a file path
-- Uses forward slash as separator on all platforms
-- Examples: takeFileName "a/b/c.txt" = "c.txt", takeFileName "file.txt" = "file.txt"
takeFileName :: FilePath -> FilePath
takeFileName "" = ""
takeFileName path =
  case reverse (filter (not . null) (splitPath path)) of
    [] -> ""
    (name:_) -> name

-- | Convert backslashes to forward slashes
-- This is needed to normalize paths returned by Windows system calls
-- to use forward slashes consistently
toForwardSlashes :: FilePath -> FilePath
toForwardSlashes = map (\c -> if c == '\\' then '/' else c)