mirror of
https://github.com/anoma/juvix.git
synced 2024-12-25 16:45:20 +03:00
1ab3aa06da
This PR adds `juvix format` that can be used to format either a single Juvix file or all files in a Juvix project. ## Usage ``` $ juvix format --help Usage: juvix format JUVIX_FILE_OR_PROJECT [--check] [--in-place] Format a Juvix file or Juvix project When the command is run with an unformatted file it prints the reformatted source to standard output. When the command is run with a project directory it prints a list of unformatted files in the project. Available options: JUVIX_FILE_OR_PROJECT Path to a .juvix file or to a directory containing a Juvix project. --check Do not print reformatted sources or unformatted file paths to standard output. --in-place Do not print reformatted sources to standard output. Overwrite the target's contents with the formatted version if the formatted version differs from the original content. -h,--help Show this help text ``` ## Location of main implementation The implementation is split into two components: * The src API: `format` and `formatProject`73952ba15c/src/Juvix/Formatter.hs
* The CLI interface:73952ba15c/app/Commands/Format.hs
## in-place uses polysemy Resource effect The `--in-place` option makes a backup of the target file and restores it if there's an error during processing to avoid data loss. The implementation of this uses the polysemy [Resource effect](https://hackage.haskell.org/package/polysemy-1.9.0.0/docs/Polysemy-Resource.html). The recommended way to interpret the resource effect is to use `resourceToIOFinal` which makes it necessary to change the effects interpretation in main to use `Final IO`:73952ba15c/app/Main.hs (L15)
## Format input is `FilePath` The format options uses `FilePath` instead of `AppFile f` for the input file/directory used by other commands. This is because we cannot determine if the input string is a file or directory in the CLI parser (we require IO). I discussed some ideas with @janmasrovira on how to improve this in a way that would also solve other issues with CLI input file/parsing but I want to defer this to a separate PR as this one is already quite large. One consequence of Format using `FilePath` as the input option is that the code that changes the working directory to the root of the project containing the CLI input file is changed to work with `FilePath`:f715ef6a53/app/TopCommand/Options.hs (L33)
## New dependencies This PR adds new dependencies on `temporary` and `polysemy-zoo`. `temporary` is used for `emptySystemTempFile` in the implementation of the TempFile interpreter for IO:73952ba15c/src/Juvix/Data/Effect/Files/IO.hs (L49)
`polysemy-zoo` is used for the `Fresh` effect and `absorbMonadThrow` in the implementation of the pure TempFile interpreter:73952ba15c/src/Juvix/Data/Effect/Files/Pure.hs (L91)
NB: The pure TempFile interpreter is not used, but it seemed a good idea to include it while it's fresh in my mind. * Closes https://github.com/anoma/juvix/issues/1777 --------- Co-authored-by: Jonathan Cubides <jonathan.cubides@uib.no>
62 lines
2.2 KiB
Haskell
62 lines
2.2 KiB
Haskell
module Commands.Format where
|
|
|
|
import Commands.Base
|
|
import Commands.Format.Options
|
|
import Juvix.Formatter
|
|
import Juvix.Prelude.Pretty
|
|
|
|
data FormatNoEditRenderMode
|
|
= ReformattedFile (NonEmpty AnsiText)
|
|
| InputPath (Path Abs File)
|
|
| Silent
|
|
|
|
data FormatRenderMode
|
|
= EditInPlace FormattedFileInfo
|
|
| NoEdit FormatNoEditRenderMode
|
|
|
|
data FormatTarget
|
|
= TargetFile
|
|
| TargetDir
|
|
|
|
runCommand :: forall r. Members '[Embed IO, App, Resource, Files] r => FormatOptions -> Sem r ()
|
|
runCommand opts = do
|
|
f <- filePathToAbs (opts ^. formatInput)
|
|
let target = case f of
|
|
Left {} -> TargetFile
|
|
Right {} -> TargetDir
|
|
runOutputSem (renderFormattedOutput target opts) $ runScopeFileApp $ do
|
|
res <- case f of
|
|
Left p -> format p
|
|
Right p -> formatProject p
|
|
when (res == FormatResultFail) (embed (exitWith (ExitFailure 1)))
|
|
|
|
renderModeFromOptions :: FormatTarget -> FormatOptions -> FormattedFileInfo -> FormatRenderMode
|
|
renderModeFromOptions target opts formattedInfo
|
|
| opts ^. formatInPlace = EditInPlace formattedInfo
|
|
| opts ^. formatCheck = NoEdit Silent
|
|
| otherwise = case target of
|
|
TargetFile -> NoEdit (ReformattedFile (formattedInfo ^. formattedFileInfoContentsAnsi))
|
|
TargetDir -> NoEdit (InputPath (formattedInfo ^. formattedFileInfoPath))
|
|
|
|
renderFormattedOutput :: forall r. Members '[Embed IO, App, Resource, Files] r => FormatTarget -> FormatOptions -> FormattedFileInfo -> Sem r ()
|
|
renderFormattedOutput target opts fInfo = do
|
|
let renderMode = renderModeFromOptions target opts fInfo
|
|
outputResult renderMode
|
|
where
|
|
outputResult :: FormatRenderMode -> Sem r ()
|
|
outputResult = \case
|
|
EditInPlace i@(FormattedFileInfo {..}) ->
|
|
runTempFileIO $
|
|
restoreFileOnError _formattedFileInfoPath $
|
|
writeFile' _formattedFileInfoPath (i ^. formattedFileInfoContentsText)
|
|
NoEdit m -> case m of
|
|
ReformattedFile ts -> forM_ ts renderStdOut
|
|
InputPath p -> say (pack (toFilePath p))
|
|
Silent -> return ()
|
|
|
|
runScopeFileApp :: Member App r => Sem (ScopeEff ': r) a -> Sem r a
|
|
runScopeFileApp = interpret $ \case
|
|
ScopeFile p -> do
|
|
let appFile = AppPath (Abs p) False
|
|
runPipeline appFile upToScoping
|