Skip to content

Commit 1ba8994

Browse files
committed
fix: move to typed paths
This is technically a chore, but we'll call it a fix to get a release out and not let the refactor (and possible regression) sit unreleased.
1 parent 88c388a commit 1ba8994

27 files changed

+485
-280
lines changed

package.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ library:
6262
- network-uri
6363
- opt-env-conf >= 0.6.0.2
6464
- path
65+
- path-io
6566
- relude
6667
- retry
6768
- ronn
@@ -100,15 +101,13 @@ tests:
100101
- autodocodec
101102
- autodocodec-yaml
102103
- containers
103-
- directory
104-
- extra
105-
- filepath
106104
- hspec
107105
- hspec-core
108106
- hspec-expectations-lifted
109107
- lens-aeson
110108
- opt-env-conf
111109
- path
110+
- path-io
112111
- restyler
113112
- safe-coloured-text
114113
- text

restyler.cabal

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ library
4949
Restyler.Monad.Git
5050
Restyler.Monad.ReadFile
5151
Restyler.Monad.WriteFile
52+
Restyler.OrphanInstances
53+
Restyler.Path
5254
Restyler.Prelude
5355
Restyler.ReadP
5456
Restyler.Restyle
@@ -100,6 +102,7 @@ library
100102
, network-uri
101103
, opt-env-conf >=0.6.0.2
102104
, path
105+
, path-io
103106
, relude
104107
, retry
105108
, ronn
@@ -157,6 +160,7 @@ test-suite test
157160
Restyler.ConfigSpec
158161
Restyler.DelimitedSpec
159162
Restyler.IgnoreSpec
163+
Restyler.PathSpec
160164
Restyler.RestrictionsSpec
161165
Restyler.Restyler.RunSpec
162166
Restyler.RestylerSpec
@@ -191,15 +195,13 @@ test-suite test
191195
, autodocodec-yaml
192196
, base
193197
, containers
194-
, directory
195-
, extra
196-
, filepath
197198
, hspec
198199
, hspec-core
199200
, hspec-expectations-lifted
200201
, lens-aeson
201202
, opt-env-conf
202203
, path
204+
, path-io
203205
, restyler
204206
, safe-coloured-text
205207
, text

src/Restyler/Config.hs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import Restyler.Config.NoPull as X
3939
import Restyler.Config.RemoteFile as X
4040
import Restyler.Config.Restrictions as X
4141
import Restyler.Config.Restyler as X
42+
import Restyler.Path
4243

4344
data Config = Config
4445
{ enabled :: Bool
@@ -59,7 +60,7 @@ data Config = Config
5960
, logSettings :: LogSettingsOption
6061
, githubActions :: Bool
6162
, pullRequestJson :: Maybe (Path Abs File)
62-
, paths :: NonEmpty FilePath
63+
, paths :: NonEmpty SomePath
6364
}
6465

6566
parseConfig :: IO Config
@@ -108,14 +109,7 @@ configParser sources =
108109
, option
109110
, long "pull-request-json"
110111
]
111-
paths <-
112-
someNonEmpty
113-
$ setting
114-
[ help "Path to restyle"
115-
, argument
116-
, reader str
117-
, metavar "PATH"
118-
]
112+
paths <- someNonEmpty somePathParser
119113
pure Config {..}
120114

121115
-- | Use 'filePathSetting' to handle creating the @'Path' 'Abs' 'File'@ we need

src/Restyler/Config/AutoEnable.hs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
-- |
2+
--
3+
-- Module : Restyler.Config.AutoEnable
4+
-- Copyright : (c) 2026 Patrick Brisbin
5+
-- License : AGPL-3
6+
-- Maintainer : pbrisbin@gmail.com
7+
-- Stability : experimental
8+
-- Portability : POSIX
19
module Restyler.Config.AutoEnable
210
( -- * Configuration
311
AutoEnable (..)

src/Restyler/Config/Glob.hs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Restyler.Config.Glob
1212
( Glob (..)
1313
, GlobTarget (..)
1414
, match
15+
, matchPath
1516
, matchAny
1617
, matchFirst
1718
, matchAnyInCurrentDirectory
@@ -22,7 +23,6 @@ import Restyler.Prelude
2223
import Autodocodec (Autodocodec (..), HasCodec)
2324
import Data.Aeson
2425
import Restyler.Monad.Directory
25-
import System.FilePath ((</>))
2626
import System.FilePath.Glob hiding (match)
2727

2828
newtype Glob a = Glob {unwrap :: String}
@@ -54,19 +54,21 @@ match (Glob p) =
5454
(compileWith (getCompOptions @a) p)
5555
. forMatch
5656

57+
matchPath :: Glob FilePath -> Path b t -> Bool
58+
matchPath (Glob p) =
59+
matchWith
60+
(matchDefault {matchDotsImplicitly = True})
61+
(compileWith (getCompOptions @FilePath) p)
62+
. forMatch
63+
. toFilePath
64+
5765
matchAny :: (Foldable t, GlobTarget a) => [Glob a] -> t a -> Bool
5866
matchAny globs = any $ \x -> any (`match` x) globs
5967

6068
matchFirst :: (Foldable t, GlobTarget a) => [Glob a] -> t a -> Maybe a
6169
matchFirst globs = find $ \x -> any (`match` x) globs
6270

6371
matchAnyInCurrentDirectory :: MonadDirectory m => [Glob FilePath] -> m Bool
64-
matchAnyInCurrentDirectory gs = go id =<< getCurrentDirectory
65-
where
66-
go :: MonadDirectory m => (FilePath -> FilePath) -> FilePath -> m Bool
67-
go prefix d = do
68-
contents <- map prefix <$> listDirectory d
69-
70-
if matchAny gs contents
71-
then pure True
72-
else anyM (go (d </>)) =<< filterM doesDirectoryExist contents
72+
matchAnyInCurrentDirectory gs = do
73+
files <- listDirectoryRecur =<< getCurrentDirectory
74+
pure $ matchAny gs $ map toFilePath files

src/Restyler/Config/RemoteFile.hs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import Autodocodec
1818
import Network.URI (parseAbsoluteURI)
1919
import Network.URI qualified as URI
2020
import OptEnvConf
21+
import Path (parseRelFile)
2122

2223
class HasRemoteFiles env where
2324
getRemoteFiles :: env -> [RemoteFile]
2425

2526
data RemoteFile = RemoteFile
2627
{ url :: String
27-
, path :: FilePath
28+
, path :: Path Rel File
2829
}
2930
deriving stock (Eq, Show)
3031

@@ -39,12 +40,13 @@ codecObject =
3940
<$> (requiredField "url" "URL to download" .= fst)
4041
<*> (optionalField "path" "Path to download to" .= snd)
4142

42-
remoteFileFromPair :: (String, Maybe String) -> Either String RemoteFile
43+
remoteFileFromPair
44+
:: (String, Maybe (Path Rel File)) -> Either String RemoteFile
4345
remoteFileFromPair (url, mPath) = case mPath of
4446
Nothing -> remoteFileFromUrl url
4547
Just path -> Right $ RemoteFile {url, path}
4648

47-
remoteFileToPair :: RemoteFile -> (String, Maybe String)
49+
remoteFileToPair :: RemoteFile -> (String, Maybe (Path Rel File))
4850
remoteFileToPair rf = (rf.url, Just rf.path)
4951

5052
codecUrl :: JSONCodec RemoteFile
@@ -53,9 +55,13 @@ codecUrl = bimapCodec remoteFileFromUrl remoteFileToUrl stringCodec <?> "URL wit
5355
remoteFileFromUrl :: String -> Either String RemoteFile
5456
remoteFileFromUrl url = do
5557
uri <- note "" $ parseAbsoluteURI url
56-
case nonEmpty $ URI.pathSegments uri of
57-
Nothing -> Left "RemoteFile's URL has no path, and one is not configured"
58-
Just ps -> Right $ RemoteFile {url, path = last ps}
58+
segs <-
59+
note "RemoteFile's URL has no path, and one is not configured"
60+
$ nonEmpty
61+
$ URI.pathSegments uri
62+
path <-
63+
note "RemoteFile's URL path is not a valid file name" $ parseRelFile $ last segs
64+
pure $ RemoteFile {url, path}
5965

6066
-- | Invalid, but not the codec is never used to render
6167
remoteFileToUrl :: RemoteFile -> String

src/Restyler/Delimited.hs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Restyler.Prelude hiding ((.=))
2626
import Autodocodec
2727
import Data.Aeson (FromJSON, ToJSON)
2828
import Data.Text qualified as T
29+
import Path (addExtension)
2930
import Restyler.Monad.Directory
3031
import Restyler.Monad.ReadFile
3132
import Restyler.Monad.WriteFile
@@ -46,14 +47,14 @@ instance HasCodec Delimiters where
4647
<*> (requiredField' "end" .= dEnd)
4748

4849
data DelimitedPath = DelimitedPath
49-
{ dpSource :: FilePath
50+
{ dpSource :: Path Rel File
5051
, dpParts :: [DelimitedPathPart]
5152
}
5253
deriving stock (Eq, Show)
5354

5455
data DelimitedPathPart = DelimitedPathPart
5556
{ dppIn :: Bool
56-
, dppPath :: FilePath
57+
, dppPath :: Path Rel File
5758
, dppMeta :: Maybe DelimitedMeta
5859
}
5960
deriving stock (Eq, Show)
@@ -73,9 +74,9 @@ restyleDelimited
7374
, MonadWriteFile m
7475
)
7576
=> Delimiters
76-
-> ([FilePath] -> m result)
77+
-> ([Path Rel File] -> m result)
7778
-- ^ Restyle files inplace
78-
-> [FilePath]
79+
-> [Path Rel File]
7980
-> m result
8081
restyleDelimited delimiters restyle paths =
8182
bracket
@@ -85,7 +86,7 @@ restyleDelimited delimiters restyle paths =
8586
result <- restyle $ concatMap delimitedInPaths delimited
8687
result <$ traverse_ (undelimit delimiters) delimited
8788

88-
delimitedInPaths :: DelimitedPath -> [FilePath]
89+
delimitedInPaths :: DelimitedPath -> [Path Rel File]
8990
delimitedInPaths = map dppPath . filter dppIn . dpParts
9091

9192
-- | Split a File into separate files of the content between delimiters
@@ -101,7 +102,8 @@ delimitedInPaths = map dppPath . filter dppIn . dpParts
101102
-- delimiters (repeatedly). The returned value tracks which paths hold content
102103
-- that was delimited /in/ or /out/.
103104
delimit
104-
:: (MonadReadFile m, MonadWriteFile m) => Delimiters -> FilePath -> m DelimitedPath
105+
:: (MonadReadFile m, MonadWriteFile m)
106+
=> Delimiters -> Path Rel File -> m DelimitedPath
105107
delimit Delimiters {..} path = do
106108
content <- readFile path
107109
parts <-
@@ -115,12 +117,16 @@ delimit Delimiters {..} path = do
115117

116118
writePart
117119
:: MonadWriteFile m
118-
=> FilePath
120+
=> Path Rel File
119121
-> Int
120122
-> Either Text Text
121123
-> m DelimitedPathPart
122124
writePart path n part = do
123-
let path' = path <> "." <> show @String @Int n
125+
let
126+
-- NB. addExtension only throws if the extension is invalid, we know
127+
-- ".{number}" is valid so, the error here is safe.
128+
ext = "." <> show @String @Int n
129+
path' = either (error . show) id $ addExtension ext path
124130

125131
case part of
126132
Left content -> do

src/Restyler/Monad/Directory.hs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,21 @@ module Restyler.Monad.Directory
1818

1919
import Restyler.Prelude
2020

21+
import Path.IO qualified as PathIO
2122
import UnliftIO.Directory (Permissions (..))
2223
import UnliftIO.Directory qualified as Directory
2324

2425
class Monad m => MonadDirectory m where
25-
getCurrentDirectory :: m FilePath
26-
setCurrentDirectory :: FilePath -> m ()
27-
doesFileExist :: FilePath -> m Bool
28-
doesDirectoryExist :: FilePath -> m Bool
29-
getPermissions :: FilePath -> m Permissions
30-
setPermissions :: FilePath -> Permissions -> m ()
31-
createFileLink :: FilePath -> FilePath -> m ()
32-
pathIsSymbolicLink :: FilePath -> m Bool
33-
listDirectory :: FilePath -> m [FilePath]
34-
removeFile :: FilePath -> m ()
26+
getCurrentDirectory :: m (Path Abs Dir)
27+
setCurrentDirectory :: Path Abs Dir -> m ()
28+
doesFileExist :: forall b. Path b File -> m Bool
29+
doesDirectoryExist :: forall b. Path b Dir -> m Bool
30+
getPermissions :: forall b. Path b File -> m Permissions
31+
setPermissions :: forall b. Path b File -> Permissions -> m ()
32+
createFileLink :: forall b0 b1. Path b0 File -> Path b1 File -> m ()
33+
pathIsSymbolicLink :: forall b. Path b File -> m Bool
34+
listDirectoryRecur :: forall b. Path b Dir -> m [Path Rel File]
35+
removeFile :: forall b. Path b File -> m ()
3536

3637
newtype ActualDirectory m a = ActualDirectory
3738
{ unwrap :: m a
@@ -48,49 +49,49 @@ newtype ActualDirectory m a = ActualDirectory
4849
instance (MonadLogger m, MonadUnliftIO m) => MonadDirectory (ActualDirectory m) where
4950
getCurrentDirectory = do
5051
logTrace "getCurrentDirectory"
51-
liftIO Directory.getCurrentDirectory
52+
liftIO PathIO.getCurrentDir
5253

5354
setCurrentDirectory path = do
5455
logTrace $ "setCurrentDirectory" :# ["path" .= path]
55-
liftIO $ Directory.setCurrentDirectory path
56+
liftIO $ PathIO.setCurrentDir path
5657

5758
doesFileExist path = do
5859
logTrace $ "doesFileExist" :# ["path" .= path]
59-
liftIO $ Directory.doesFileExist path
60+
liftIO $ PathIO.doesFileExist path
6061

6162
doesDirectoryExist path = do
6263
logTrace $ "doesDirectoryExist" :# ["path" .= path]
63-
liftIO $ Directory.doesDirectoryExist path
64+
liftIO $ PathIO.doesDirExist path
6465

6566
getPermissions path = do
6667
logTrace $ "getPermissions" :# ["path" .= path]
67-
liftIO $ Directory.getPermissions path
68+
liftIO $ PathIO.getPermissions path
6869

6970
setPermissions path p = do
7071
logTrace $ "setPermissions" :# ["path" .= path]
71-
liftIO $ Directory.setPermissions path p
72+
liftIO $ PathIO.setPermissions path p
7273

7374
createFileLink path t = do
7475
logTrace $ "createFileLink" :# ["path" .= path]
75-
liftIO $ Directory.createFileLink path t
76+
liftIO $ PathIO.createFileLink path t
7677

7778
pathIsSymbolicLink path = do
7879
logTrace $ "pathIsSymbolicLink" :# ["path" .= path]
79-
liftIO $ Directory.pathIsSymbolicLink path
80+
liftIO $ PathIO.isSymlink path
8081

81-
listDirectory path = do
82-
logTrace $ "listDirectory" :# ["path" .= path]
83-
liftIO $ Directory.listDirectory path
82+
listDirectoryRecur path = do
83+
logTrace $ "listDirectoryRecur" :# ["path" .= path]
84+
liftIO $ snd <$> PathIO.listDirRecurRel path
8485

8586
removeFile path = do
8687
logTrace $ "removeFile" :# ["path" .= path]
87-
liftIO $ Directory.removeFile path
88+
liftIO $ PathIO.removeFile path
8889

89-
isFileExecutable :: MonadDirectory m => FilePath -> m Bool
90+
isFileExecutable :: MonadDirectory m => Path b File -> m Bool
9091
isFileExecutable = fmap Directory.executable . getPermissions
9192

9293
modifyPermissions
93-
:: MonadDirectory m => FilePath -> (Permissions -> Permissions) -> m ()
94+
:: MonadDirectory m => Path b File -> (Permissions -> Permissions) -> m ()
9495
modifyPermissions path f = do
9596
p <- getPermissions path
9697
setPermissions path $ f p

src/Restyler/Monad/DownloadFile.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Conduit (runResourceT, sinkFile)
2020
import Network.HTTP.Simple hiding (Request)
2121

2222
class Monad m => MonadDownloadFile m where
23-
downloadFile :: String -> FilePath -> m ()
23+
downloadFile :: String -> Path b File -> m ()
2424

2525
newtype ActualDownloadFile m a = ActualDownloadFile
2626
{ unwrap :: m a
@@ -42,7 +42,7 @@ instance
4242
logDebug $ "downloadFile" :# ["url" .= url]
4343
liftIO $ do
4444
request <- parseRequestThrow url
45-
runResourceT $ httpSink request $ \_ -> sinkFile path
45+
runResourceT $ httpSink request $ \_ -> sinkFile (toFilePath path)
4646

4747
newtype NullDownloadFile m a = NullDownloadFile
4848
{ unwrap :: m a

0 commit comments

Comments
 (0)