2014-02-22 17:35:50 +00:00
|
|
|
{- filenames (not paths) used in views
|
|
|
|
-
|
2023-03-24 17:53:51 +00:00
|
|
|
- Copyright 2014-2023 Joey Hess <id@joeyh.name>
|
2014-02-22 17:35:50 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2014-02-22 17:35:50 +00:00
|
|
|
-}
|
|
|
|
|
2014-12-30 21:48:04 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
2014-02-22 18:54:53 +00:00
|
|
|
module Annex.View.ViewedFile (
|
|
|
|
ViewedFile,
|
|
|
|
MkViewedFile,
|
|
|
|
viewedFileFromReference,
|
2023-03-24 17:53:51 +00:00
|
|
|
viewedFileFromReference',
|
2014-02-22 18:54:53 +00:00
|
|
|
viewedFileReuse,
|
|
|
|
dirFromViewedFile,
|
|
|
|
prop_viewedFile_roundtrips,
|
|
|
|
) where
|
2014-02-22 17:35:50 +00:00
|
|
|
|
2016-01-20 20:36:33 +00:00
|
|
|
import Annex.Common
|
2020-11-10 00:07:31 +00:00
|
|
|
import Utility.QuickCheck
|
2014-02-22 17:35:50 +00:00
|
|
|
|
2020-10-28 21:25:59 +00:00
|
|
|
import qualified Data.ByteString as S
|
|
|
|
|
2014-02-22 17:35:50 +00:00
|
|
|
type FileName = String
|
|
|
|
type ViewedFile = FileName
|
|
|
|
|
|
|
|
type MkViewedFile = FilePath -> ViewedFile
|
|
|
|
|
|
|
|
{- Converts a filepath used in a reference branch to the
|
|
|
|
- filename that will be used in the view.
|
|
|
|
-
|
2023-03-14 02:39:16 +00:00
|
|
|
- No two filepaths from the same branch should yield the same result,
|
2014-02-22 18:54:53 +00:00
|
|
|
- so all directory structure needs to be included in the output filename
|
|
|
|
- in some way.
|
2014-02-22 17:35:50 +00:00
|
|
|
-
|
2014-02-22 18:54:53 +00:00
|
|
|
- So, from dir/subdir/file.foo, generate file_%dir%subdir%.foo
|
2014-02-22 17:35:50 +00:00
|
|
|
-}
|
2023-03-24 17:53:51 +00:00
|
|
|
viewedFileFromReference :: GitConfig -> MkViewedFile
|
|
|
|
viewedFileFromReference g = viewedFileFromReference' (annexMaxExtensionLength g)
|
|
|
|
|
|
|
|
viewedFileFromReference' :: Maybe Int -> MkViewedFile
|
|
|
|
viewedFileFromReference' maxextlen f = concat $
|
|
|
|
[ escape (fromRawFilePath base')
|
2014-02-22 18:54:53 +00:00
|
|
|
, if null dirs then "" else "_%" ++ intercalate "%" (map escape dirs) ++ "%"
|
2023-03-24 17:53:51 +00:00
|
|
|
, escape $ fromRawFilePath $ S.concat extensions'
|
2014-02-22 17:35:50 +00:00
|
|
|
]
|
|
|
|
where
|
|
|
|
(path, basefile) = splitFileName f
|
|
|
|
dirs = filter (/= ".") $ map dropTrailingPathSeparator (splitPath path)
|
2023-03-24 17:53:51 +00:00
|
|
|
(base, extensions) = case maxextlen of
|
|
|
|
Nothing -> splitShortExtensions (toRawFilePath basefile')
|
|
|
|
Just n -> splitShortExtensions' (n+1) (toRawFilePath basefile')
|
|
|
|
{- Limit to two extensions maximum. -}
|
|
|
|
(base', extensions')
|
|
|
|
| length extensions <= 2 = (base, extensions)
|
|
|
|
| otherwise =
|
|
|
|
let (es,more) = splitAt 2 (reverse extensions)
|
|
|
|
in (base <> mconcat (reverse more), reverse es)
|
2021-08-24 18:03:29 +00:00
|
|
|
{- On Windows, if the filename looked like "dir/c:foo" then
|
|
|
|
- basefile would look like it contains a drive letter, which will
|
|
|
|
- not work. There cannot really be a filename like that, probably,
|
|
|
|
- but it prevents the test suite failing. -}
|
|
|
|
(_basedrive, basefile') = splitDrive basefile
|
2014-02-22 17:35:50 +00:00
|
|
|
|
2014-02-22 18:54:53 +00:00
|
|
|
{- To avoid collisions with filenames or directories that contain
|
|
|
|
- '%', and to allow the original directories to be extracted
|
2014-12-30 21:48:04 +00:00
|
|
|
- from the ViewedFile, '%' is escaped. )
|
2014-02-22 18:54:53 +00:00
|
|
|
-}
|
|
|
|
escape :: String -> String
|
2014-12-30 21:48:04 +00:00
|
|
|
escape = replace "%" (escchar:'%':[]) . replace [escchar] [escchar, escchar]
|
|
|
|
|
|
|
|
escchar :: Char
|
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
escchar = '\\'
|
|
|
|
#else
|
|
|
|
-- \ is path separator on Windows, so instead use !
|
|
|
|
escchar = '!'
|
|
|
|
#endif
|
2014-02-22 17:35:50 +00:00
|
|
|
|
2014-02-22 20:09:00 +00:00
|
|
|
{- For use when operating already within a view, so whatever filepath
|
|
|
|
- is present in the work tree is already a ViewedFile. -}
|
2014-02-22 17:35:50 +00:00
|
|
|
viewedFileReuse :: MkViewedFile
|
|
|
|
viewedFileReuse = takeFileName
|
2014-02-22 18:54:53 +00:00
|
|
|
|
|
|
|
{- Extracts from a ViewedFile the directory where the file is located on
|
|
|
|
- in the reference branch. -}
|
|
|
|
dirFromViewedFile :: ViewedFile -> FilePath
|
|
|
|
dirFromViewedFile = joinPath . drop 1 . sep [] ""
|
|
|
|
where
|
2014-10-09 18:53:13 +00:00
|
|
|
sep l _ [] = reverse l
|
2014-02-22 18:54:53 +00:00
|
|
|
sep l curr (c:cs)
|
|
|
|
| c == '%' = sep (reverse curr:l) "" cs
|
2014-12-30 21:48:04 +00:00
|
|
|
| c == escchar = case cs of
|
2014-02-22 18:54:53 +00:00
|
|
|
(c':cs') -> sep l (c':curr) cs'
|
|
|
|
[] -> sep l curr cs
|
|
|
|
| otherwise = sep l (c:curr) cs
|
|
|
|
|
2020-11-10 00:07:31 +00:00
|
|
|
prop_viewedFile_roundtrips :: TestableFilePath -> Bool
|
|
|
|
prop_viewedFile_roundtrips tf
|
2014-02-25 22:09:45 +00:00
|
|
|
-- Relative filenames wanted, not directories.
|
|
|
|
| any (isPathSeparator) (end f ++ beginning f) = True
|
2020-11-26 15:48:52 +00:00
|
|
|
| isAbsolute f || isDrive f = True
|
2023-03-24 17:53:51 +00:00
|
|
|
| otherwise = dir == dirFromViewedFile (viewedFileFromReference' Nothing f)
|
2014-02-22 18:54:53 +00:00
|
|
|
where
|
2020-11-10 00:07:31 +00:00
|
|
|
f = fromTestableFilePath tf
|
2014-02-22 18:54:53 +00:00
|
|
|
dir = joinPath $ beginning $ splitDirectories f
|