Skip to content

Commit 83509eb

Browse files
committed
Locate .ormolu file independently of .cabal files
1 parent 35647fb commit 83509eb

File tree

8 files changed

+158
-117
lines changed

8 files changed

+158
-117
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
syntax), or on the command line with the `--reexport`/`-r` option. [Issue
1111
1017](https://github.com/tweag/ormolu/issues/1017).
1212

13+
* Ormolu now looks for `.ormolu` files independently of `.cabal` files. This
14+
means that it is now possible to have one `.ormolu` file for multiple
15+
Cabal packages. [Issue 1019](https://github.com/tweag/ormolu/issues/1019).
16+
1317
* Consistently format `do` blocks/`case`s/`MultiWayIf`s with 4 spaces if and
1418
only if they occur as the applicand. [Issue
1519
1002](https://github.com/tweag/ormolu/issues/1002) and [issue

README.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@ project is formatted with Ormolu.
162162
### Language extensions, dependencies, and fixities
163163

164164
Ormolu automatically locates the Cabal file that corresponds to a given
165-
source code file. When input comes from stdin, one can pass
166-
`--stdin-input-file` which will give Ormolu the location of the Haskell
167-
source file that should be used as the starting point for searching for a
168-
suitable Cabal file. Cabal files are used to extract both default extensions
165+
source code file. Cabal files are used to extract both default extensions
169166
and dependencies. Default extensions directly affect behavior of the GHC
170167
parser, while dependencies are used to figure out fixities of operators that
171-
appear in the source code. Fixities can also be overridden if `.ormolu` file
172-
is found next to the corresponding Cabal file, i.e. they should be siblings
173-
in the same directory.
168+
appear in the source code. Fixities can also be overridden via an `.ormolu`
169+
file which should be located at a higher level in the file system hierarchy
170+
than the source file that is being formatted. When the input comes from
171+
stdin, one can pass `--stdin-input-file` which will give Ormolu the location
172+
that should be used as the starting point for searching for `.cabal` and
173+
`.ormolu` files.
174174

175175
Here is an example of `.ormolu` file:
176176

@@ -214,8 +214,8 @@ command line:
214214
* Fixities can be specified with the `-f` or `--fixity` flag.
215215
* Re-exports can be specified with the `-r` or `--reexport` flag.
216216

217-
Searching for both `.cabal` and `.ormolu` files can be disabled by passing
218-
`--no-cabal`.
217+
Searching for `.cabal` and `.ormolu` files can be disabled by passing
218+
`--no-cabal` and `--no-dot-ormolu` respectively.
219219

220220
### Magic comments
221221

app/Main.hs

+46-36
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ main = do
4040
Opts {..} <- execParser optsParserInfo
4141
let formatOne' =
4242
formatOne
43-
optCabal
43+
optConfigFileOpts
4444
optMode
4545
optSourceType
4646
optConfig
@@ -67,7 +67,7 @@ main = do
6767
-- | Format a single input.
6868
formatOne ::
6969
-- | How to use .cabal files
70-
CabalOpts ->
70+
ConfigFileOpts ->
7171
-- | Mode of operation
7272
Mode ->
7373
-- | The 'SourceType' requested by the user
@@ -77,7 +77,7 @@ formatOne ::
7777
-- | File to format or stdin as 'Nothing'
7878
Maybe FilePath ->
7979
IO ExitCode
80-
formatOne CabalOpts {..} mode reqSourceType rawConfig mpath =
80+
formatOne ConfigFileOpts {..} mode reqSourceType rawConfig mpath =
8181
withPrettyOrmoluExceptions (cfgColorMode rawConfig) $ do
8282
let getCabalInfoForSourceFile' sourceFile = do
8383
cabalSearchResult <- getCabalInfoForSourceFile sourceFile
@@ -99,21 +99,24 @@ formatOne CabalOpts {..} mode reqSourceType rawConfig mpath =
9999
<> sourceFile
100100
return (Just cabalInfo)
101101
CabalFound cabalInfo -> return (Just cabalInfo)
102+
getDotOrmoluForSourceFile' sourceFile = do
103+
if optDoNotUseDotOrmolu
104+
then return Nothing
105+
else Just <$> getDotOrmoluForSourceFile sourceFile
102106
case FP.normalise <$> mpath of
103107
-- input source = STDIN
104108
Nothing -> do
105-
resultConfig <-
106-
( if optDoNotUseCabal
107-
then pure Nothing
108-
else case optStdinInputFile of
109-
Just stdinInputFile ->
110-
getCabalInfoForSourceFile' stdinInputFile
111-
Nothing -> throwIO OrmoluMissingStdinInputFile
112-
)
113-
>>= patchConfig Nothing
109+
mcabalInfo <- case (optStdinInputFile, optDoNotUseCabal) of
110+
(_, True) -> return Nothing
111+
(Nothing, False) -> throwIO OrmoluMissingStdinInputFile
112+
(Just inputFile, False) -> getCabalInfoForSourceFile' inputFile
113+
mdotOrmolu <- case optStdinInputFile of
114+
Nothing -> return Nothing
115+
Just inputFile -> getDotOrmoluForSourceFile' inputFile
116+
config <- patchConfig Nothing mcabalInfo mdotOrmolu
114117
case mode of
115118
Stdout -> do
116-
ormoluStdin resultConfig >>= TIO.putStr
119+
ormoluStdin config >>= TIO.putStr
117120
return ExitSuccess
118121
InPlace -> do
119122
hPutStrLn
@@ -126,41 +129,44 @@ formatOne CabalOpts {..} mode reqSourceType rawConfig mpath =
126129
originalInput <- getContentsUtf8
127130
let stdinRepr = "<stdin>"
128131
formattedInput <-
129-
ormolu resultConfig stdinRepr originalInput
132+
ormolu config stdinRepr originalInput
130133
handleDiff originalInput formattedInput stdinRepr
131134
-- input source = a file
132135
Just inputFile -> do
133-
resultConfig <-
134-
( if optDoNotUseCabal
135-
then pure Nothing
136-
else getCabalInfoForSourceFile' inputFile
137-
)
138-
>>= patchConfig (Just (detectSourceType inputFile))
136+
mcabalInfo <-
137+
if optDoNotUseCabal
138+
then return Nothing
139+
else getCabalInfoForSourceFile' inputFile
140+
mdotOrmolu <- getDotOrmoluForSourceFile' inputFile
141+
config <-
142+
patchConfig
143+
(Just (detectSourceType inputFile))
144+
mcabalInfo
145+
mdotOrmolu
139146
case mode of
140147
Stdout -> do
141-
ormoluFile resultConfig inputFile >>= TIO.putStr
148+
ormoluFile config inputFile >>= TIO.putStr
142149
return ExitSuccess
143150
InPlace -> do
144151
-- ormoluFile is not used because we need originalInput
145152
originalInput <- readFileUtf8 inputFile
146153
formattedInput <-
147-
ormolu resultConfig inputFile originalInput
154+
ormolu config inputFile originalInput
148155
when (formattedInput /= originalInput) $
149156
writeFileUtf8 inputFile formattedInput
150157
return ExitSuccess
151158
Check -> do
152159
-- ormoluFile is not used because we need originalInput
153160
originalInput <- readFileUtf8 inputFile
154161
formattedInput <-
155-
ormolu resultConfig inputFile originalInput
162+
ormolu config inputFile originalInput
156163
handleDiff originalInput formattedInput inputFile
157164
where
158-
patchConfig mdetectedSourceType mcabalInfo = do
165+
patchConfig mdetectedSourceType mcabalInfo mdotOrmolu = do
159166
let sourceType =
160167
fromMaybe
161168
ModuleSource
162169
(reqSourceType <|> mdetectedSourceType)
163-
mdotOrmolu <- traverse parseDotOrmoluForSourceFile mcabalInfo
164170
let mfixityOverrides = fst <$> mdotOrmolu
165171
mmoduleReexports = snd <$> mdotOrmolu
166172
return $
@@ -189,8 +195,8 @@ data Opts = Opts
189195
optMode :: !Mode,
190196
-- | Ormolu 'Config'
191197
optConfig :: !(Config RegionIndices),
192-
-- | Options related to info extracted from .cabal files
193-
optCabal :: CabalOpts,
198+
-- | Options related to info extracted from files
199+
optConfigFileOpts :: ConfigFileOpts,
194200
-- | Source type option, where 'Nothing' means autodetection
195201
optSourceType :: !(Maybe SourceType),
196202
-- | Haskell source files to format or stdin (when the list is empty)
@@ -208,10 +214,12 @@ data Mode
208214
Check
209215
deriving (Eq, Show)
210216

211-
-- | Configuration related to .cabal files.
212-
data CabalOpts = CabalOpts
217+
-- | Options related to configuration stored in the file system.
218+
data ConfigFileOpts = ConfigFileOpts
213219
{ -- | DO NOT extract default-extensions and dependencies from .cabal files
214220
optDoNotUseCabal :: Bool,
221+
-- | DO NOT look for @.ormolu@ files
222+
optDoNotUseDotOrmolu :: Bool,
215223
-- | Optional path to a file which will be used to find a .cabal file
216224
-- when using input from stdin
217225
optStdinInputFile :: Maybe FilePath
@@ -262,21 +270,23 @@ optsParser =
262270
]
263271
)
264272
<*> configParser
265-
<*> cabalOptsParser
273+
<*> configFileOptsParser
266274
<*> sourceTypeParser
267275
<*> (many . strArgument . mconcat)
268276
[ metavar "FILE",
269277
help "Haskell source files to format or stdin (the default)"
270278
]
271279

272-
cabalOptsParser :: Parser CabalOpts
273-
cabalOptsParser =
274-
CabalOpts
280+
configFileOptsParser :: Parser ConfigFileOpts
281+
configFileOptsParser =
282+
ConfigFileOpts
275283
<$> (switch . mconcat)
276284
[ long "no-cabal",
277-
help $
278-
"Do not extract default-extensions and dependencies from .cabal files"
279-
++ ", do not look for .ormolu files"
285+
help "Do not extract default-extensions and dependencies from .cabal files"
286+
]
287+
<*> (switch . mconcat)
288+
[ long "no-dot-ormolu",
289+
help "Do not look for .ormolu files"
280290
]
281291
<*> (optional . strOption . mconcat)
282292
[ long "stdin-input-file",

fixity-tests/default.nix

+9-9
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,25 @@
1111
doCheck = true;
1212
buildPhase = ''
1313
cp test-0-input.hs test-0-no-extra-info.hs
14-
ormolu --check-idempotence --mode inplace --no-cabal test-0-no-extra-info.hs
14+
ormolu --check-idempotence --mode inplace --no-cabal --no-dot-ormolu test-0-no-extra-info.hs
1515
cp test-0-input.hs test-0-with-fixity-info-manual.hs
16-
ormolu --check-idempotence --mode inplace --no-cabal --fixity 'infixr 8 .=' --fixity 'infixr 5 :>' test-0-with-fixity-info-manual.hs
16+
ormolu --check-idempotence --mode inplace --no-cabal --no-dot-ormolu --fixity 'infixr 8 .=' --fixity 'infixr 5 :>' test-0-with-fixity-info-manual.hs
1717
cp test-0-input.hs test-0-with-fixity-info-dotormolu.hs
18-
ormolu --check-idempotence --mode inplace -p base test-0-with-fixity-info-dotormolu.hs
18+
ormolu --check-idempotence --mode inplace --no-cabal -p base test-0-with-fixity-info-dotormolu.hs
1919
cp test-1-input.hs test-1-no-extra-info.hs
20-
ormolu --check-idempotence --mode inplace --no-cabal test-1-no-extra-info.hs
20+
ormolu --check-idempotence --mode inplace --no-cabal --no-dot-ormolu test-1-no-extra-info.hs
2121
cp test-1-input.hs test-1-with-fixity-info-manual.hs
22-
ormolu --check-idempotence --mode inplace --no-cabal --fixity 'infixr 8 .=' --fixity 'infixr 5 #' test-1-with-fixity-info-manual.hs
22+
ormolu --check-idempotence --mode inplace --no-cabal --no-dot-ormolu --fixity 'infixr 8 .=' --fixity 'infixr 5 #' test-1-with-fixity-info-manual.hs
2323
cp test-1-input.hs test-1-with-fixity-info-dotormolu.hs
24-
ormolu --check-idempotence --mode inplace -p base test-1-with-fixity-info-dotormolu.hs
24+
ormolu --check-idempotence --mode inplace --no-cabal -p base test-1-with-fixity-info-dotormolu.hs
2525
cp test-1-input.hs test-1-with-fixity-info-weird-overwrite.hs
2626
ormolu --check-idempotence --mode inplace -p base --fixity "infixr 5 $" test-1-with-fixity-info-weird-overwrite.hs
2727
cp test-2-input.hs test-2-no-extra-info.hs
28-
ormolu --check-idempotence --mode inplace --no-cabal -p base -p lens test-2-no-extra-info.hs
28+
ormolu --check-idempotence --mode inplace --no-cabal --no-dot-ormolu -p base -p lens test-2-no-extra-info.hs
2929
cp test-2-input.hs test-2-reexports-manual.hs
30-
ormolu --check-idempotence --mode inplace --no-cabal -p base -p lens --reexport 'module Foo exports Control.Lens' test-2-reexports-manual.hs
30+
ormolu --check-idempotence --mode inplace --no-cabal --no-dot-ormolu -p base -p lens --reexport 'module Foo exports Control.Lens' test-2-reexports-manual.hs
3131
cp test-2-input.hs test-2-reexports-dotormolu.hs
32-
ormolu --check-idempotence --mode inplace -p base -p lens test-2-reexports-dotormolu.hs
32+
ormolu --check-idempotence --mode inplace --no-cabal -p base -p lens test-2-reexports-dotormolu.hs
3333
'';
3434
checkPhase = ''
3535
echo test-0-no-extra-info.hs

src/Ormolu.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module Ormolu
2929
defaultFixityOverrides,
3030
ModuleReexports,
3131
defaultModuleReexports,
32-
parseDotOrmoluForSourceFile,
32+
getDotOrmoluForSourceFile,
3333

3434
-- * Working with exceptions
3535
OrmoluException (..),
@@ -59,7 +59,7 @@ import Ormolu.Parser.Result
5959
import Ormolu.Printer
6060
import Ormolu.Utils (showOutputable)
6161
import Ormolu.Utils.Cabal qualified as CabalUtils
62-
import Ormolu.Utils.Fixity (parseDotOrmoluForSourceFile)
62+
import Ormolu.Utils.Fixity (getDotOrmoluForSourceFile)
6363
import Ormolu.Utils.IO
6464
import System.FilePath
6565

src/Ormolu/Utils/Cabal.hs

+16-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{-# LANGUAGE LambdaCase #-}
22
{-# LANGUAGE RecordWildCards #-}
3-
{-# LANGUAGE ViewPatterns #-}
43

54
module Ormolu.Utils.Cabal
65
( CabalSearchResult (..),
@@ -29,9 +28,9 @@ import Distribution.Utils.Path (getSymbolicPath)
2928
import Language.Haskell.Extension
3029
import Ormolu.Config
3130
import Ormolu.Exception
31+
import Ormolu.Utils.IO (findClosestFileSatisfying)
3232
import System.Directory
3333
import System.FilePath
34-
import System.IO.Error (isDoesNotExistError)
3534
import System.IO.Unsafe (unsafePerformIO)
3635

3736
-- | The result of searching for a @.cabal@ file.
@@ -68,8 +67,8 @@ getCabalInfoForSourceFile ::
6867
FilePath ->
6968
-- | Extracted cabal info, if any
7069
m CabalSearchResult
71-
getCabalInfoForSourceFile sourceFile = liftIO $ do
72-
findCabalFile sourceFile >>= \case
70+
getCabalInfoForSourceFile sourceFile =
71+
liftIO (findCabalFile sourceFile) >>= \case
7372
Just cabalFile -> do
7473
(mentioned, cabalInfo) <- parseCabalInfo cabalFile sourceFile
7574
return
@@ -79,34 +78,16 @@ getCabalInfoForSourceFile sourceFile = liftIO $ do
7978
)
8079
Nothing -> return CabalNotFound
8180

82-
-- | Find the path to an appropriate .cabal file for a Haskell source file,
83-
-- if available.
81+
-- | Find the path to an appropriate @.cabal@ file for a Haskell source
82+
-- file, if available.
8483
findCabalFile ::
8584
(MonadIO m) =>
86-
-- | Path to a Haskell source file in a project with a .cabal file
85+
-- | Path to a Haskell source file in a project with a @.cabal@ file
8786
FilePath ->
88-
-- | Absolute path to the .cabal file if available
87+
-- | Absolute path to the @.cabal@ file, if available
8988
m (Maybe FilePath)
90-
findCabalFile sourceFile = liftIO $ do
91-
parentDir <- takeDirectory <$> makeAbsolute sourceFile
92-
dirEntries <-
93-
listDirectory parentDir `catch` \case
94-
(isDoesNotExistError -> True) -> pure []
95-
e -> throwIO e
96-
let findDotCabal = \case
97-
[] -> pure Nothing
98-
e : es
99-
| takeExtension e == ".cabal" ->
100-
doesFileExist (parentDir </> e) >>= \case
101-
True -> pure $ Just e
102-
False -> findDotCabal es
103-
_ : es -> findDotCabal es
104-
findDotCabal dirEntries >>= \case
105-
Just cabalFile -> pure . Just $ parentDir </> cabalFile
106-
Nothing ->
107-
if isDrive parentDir
108-
then pure Nothing
109-
else findCabalFile parentDir
89+
findCabalFile = findClosestFileSatisfying $ \x ->
90+
takeExtension x == ".cabal"
11091

11192
-- | Parsed cabal file information to be shared across multiple source files.
11293
data CachedCabalFile = CachedCabalFile
@@ -118,12 +99,12 @@ data CachedCabalFile = CachedCabalFile
11899
}
119100
deriving (Show)
120101

121-
-- | Cache ref that stores 'CachedCabalFile' per cabal file.
122-
cabalCacheRef :: IORef (Map FilePath CachedCabalFile)
123-
cabalCacheRef = unsafePerformIO $ newIORef M.empty
124-
{-# NOINLINE cabalCacheRef #-}
102+
-- | Cache ref that stores 'CachedCabalFile' per Cabal file.
103+
cacheRef :: IORef (Map FilePath CachedCabalFile)
104+
cacheRef = unsafePerformIO $ newIORef M.empty
105+
{-# NOINLINE cacheRef #-}
125106

126-
-- | Parse 'CabalInfo' from a .cabal file at the given 'FilePath'.
107+
-- | Parse 'CabalInfo' from a @.cabal@ file at the given 'FilePath'.
127108
parseCabalInfo ::
128109
(MonadIO m) =>
129110
-- | Location of the .cabal file
@@ -136,7 +117,7 @@ parseCabalInfo ::
136117
parseCabalInfo cabalFileAsGiven sourceFileAsGiven = liftIO $ do
137118
cabalFile <- makeAbsolute cabalFileAsGiven
138119
sourceFileAbs <- makeAbsolute sourceFileAsGiven
139-
cabalCache <- readIORef cabalCacheRef
120+
cabalCache <- readIORef cacheRef
140121
CachedCabalFile {..} <- whenNothing (M.lookup cabalFile cabalCache) $ do
141122
cabalFileBs <- B.readFile cabalFile
142123
genericPackageDescription <-
@@ -145,7 +126,7 @@ parseCabalInfo cabalFileAsGiven sourceFileAsGiven = liftIO $ do
145126
let extensionsAndDeps =
146127
getExtensionAndDepsMap cabalFile genericPackageDescription
147128
cachedCabalFile = CachedCabalFile {..}
148-
atomicModifyIORef cabalCacheRef $
129+
atomicModifyIORef cacheRef $
149130
(,cachedCabalFile) . M.insert cabalFile cachedCabalFile
150131
let (dynOpts, dependencies, mentioned) =
151132
case M.lookup (dropExtensions sourceFileAbs) extensionsAndDeps of

0 commit comments

Comments
 (0)