5
5
-- | Create a package.dhall from files and directory contents.
6
6
7
7
module Dhall.Package
8
- ( writePackage
8
+ ( Options
9
+ , defaultOptions
10
+ , characterSet
11
+ , packageFileName
12
+ , packagingMode
13
+ , PackagingMode (.. )
14
+ , writePackage
9
15
, getPackagePathAndContent
10
16
, PackageError (.. )
11
17
) where
12
18
13
19
import Control.Exception (Exception , throwIO )
14
20
import Control.Monad
15
21
import Data.List.NonEmpty (NonEmpty (.. ))
16
- import Data.Maybe (fromMaybe )
17
22
import Data.Text (Text )
18
23
import qualified Data.Text as Text
19
24
import Data.Traversable (for )
@@ -34,16 +39,60 @@ import qualified Dhall.Map as Map
34
39
import Dhall.Pretty (CharacterSet (.. ))
35
40
import qualified Dhall.Pretty
36
41
import Dhall.Util (_ERROR , renderExpression )
42
+ import Lens.Family (LensLike' )
37
43
import System.Directory
38
44
import System.FilePath
39
45
46
+ -- | Options for package creation.
47
+ data Options = Options
48
+ { optionsCharacterSet :: CharacterSet
49
+ , optionsPackageFileName :: String
50
+ , optionsPackagingMode :: PackagingMode
51
+ }
52
+
53
+ -- | The default options used for packaging.
54
+ --
55
+ -- The default values for the different settings are:
56
+ --
57
+ -- * The character set used is the one given by 'Dhall.Pretty.defaultCharacterSet'.
58
+ -- * The package file name is @package.dhall@.
59
+ -- * The packaging mode is 'OnlyThisPackage'.
60
+ defaultOptions :: Options
61
+ defaultOptions = Options
62
+ { optionsCharacterSet = Dhall.Pretty. defaultCharacterSet
63
+ , optionsPackageFileName = " package.dhall"
64
+ , optionsPackagingMode = OnlyThisPackage
65
+ }
66
+
67
+ -- | Access the character set used to render the package content.
68
+ characterSet :: Functor f => LensLike' f Options CharacterSet
69
+ characterSet k s =
70
+ fmap (\ x -> s { optionsCharacterSet = x }) (k (optionsCharacterSet s))
71
+
72
+ -- | Access the file name used for the package file.
73
+ packageFileName :: Functor f => LensLike' f Options String
74
+ packageFileName k s =
75
+ fmap (\ x -> s { optionsPackageFileName = x }) (k (optionsPackageFileName s))
76
+
77
+ -- | Access the packaging mode.
78
+ -- See the documentation of 'getPackagePathAndContent'.
79
+ packagingMode :: Functor f => LensLike' f Options PackagingMode
80
+ packagingMode k s =
81
+ fmap (\ x -> s { optionsPackagingMode = x }) (k (optionsPackagingMode s))
82
+
83
+ -- | Whether to recursively create a package for each subdirectory or not.
84
+ -- See the documentation of 'getPackagePathAndContent'.
85
+ data PackagingMode
86
+ = OnlyThisPackage
87
+ | RecursiveSubpackages
88
+
40
89
-- | Create a package.dhall from files and directory contents.
41
90
-- For a description of how the package file is constructed see
42
91
-- 'getPackagePathAndContent'.
43
- writePackage :: CharacterSet -> Maybe String -> NonEmpty FilePath -> IO ()
44
- writePackage characterSet outputFn inputs = do
45
- (outputPath, expr) <- getPackagePathAndContent outputFn inputs
46
- renderExpression characterSet True (Just outputPath) expr
92
+ writePackage :: Options -> NonEmpty FilePath -> IO ()
93
+ writePackage options inputs = do
94
+ (outputPath, expr) <- getPackagePathAndContent options inputs
95
+ renderExpression (optionsCharacterSet options) True (Just outputPath) expr
47
96
48
97
-- | Get the path and the Dhall expression for a package file.
49
98
--
@@ -62,11 +111,18 @@ writePackage characterSet outputFn inputs = do
62
111
-- * If the path points to a directory, all files with a @.dhall@ extensions
63
112
-- in that directory are included in the package.
64
113
--
114
+ -- If you passed `Recurse` to the this function, then in addition to these
115
+ -- files all subdirectories are traversed and a sub-package created for each
116
+ -- one. That sub-package will be included in the package too.
117
+ --
65
118
-- * If the path points to a regular file, it is included in the package
66
119
-- unless it is the path of the package file itself.
67
120
--
68
- getPackagePathAndContent :: Maybe String -> NonEmpty FilePath -> IO (FilePath , Expr s Import )
69
- getPackagePathAndContent outputFn (path :| paths) = do
121
+ getPackagePathAndContent
122
+ :: Options
123
+ -> NonEmpty FilePath
124
+ -> IO (FilePath , Expr s Import )
125
+ getPackagePathAndContent options (path :| paths) = do
70
126
outputDir <- do
71
127
isDirectory <- doesDirectoryExist path
72
128
return $ if isDirectory then path else takeDirectory path
@@ -82,32 +138,55 @@ getPackagePathAndContent outputFn (path :| paths) = do
82
138
return relativeDir
83
139
84
140
resultMap <- go Map. empty checkOutputDir (path: paths)
85
- return (outputDir </> outputFn' , RecordLit $ Map. sort resultMap)
141
+ return (outputDir </> outputFn, RecordLit $ Map. sort resultMap)
86
142
where
87
- go :: Map Text (RecordField s Import ) -> (FilePath -> IO FilePath ) -> [FilePath ] -> IO (Map Text (RecordField s Import ))
143
+ go
144
+ :: Map Text (RecordField s Import )
145
+ -> (FilePath -> IO FilePath )
146
+ -> [FilePath ]
147
+ -> IO (Map Text (RecordField s Import ))
88
148
go ! acc _checkOutputDir [] = return acc
89
149
go ! acc checkOutputDir (p: ps) = do
90
150
isDirectory <- doesDirectoryExist p
91
151
isFile <- doesFileExist p
92
152
if | isDirectory -> do
93
153
void $ checkOutputDir p
94
154
entries <- listDirectory p
95
- let entries' = filter (\ entry -> takeExtension entry == " .dhall" ) entries
96
- go acc checkOutputDir (map (p </> ) entries' <> ps)
155
+ (dhallFiles, subdirectories) <- foldMap
156
+ ( \ entry -> do
157
+ let entry' = p </> entry
158
+ isDirectoryEntry <- doesDirectoryExist entry'
159
+ return $ if isDirectoryEntry
160
+ then (mempty , [entry'])
161
+ else if hasDhallExtension entry
162
+ then ([entry'], mempty )
163
+ else mempty
164
+ ) entries
165
+ subpackages <- case optionsPackagingMode options of
166
+ RecursiveSubpackages ->
167
+ for subdirectories $ \ subdirectory -> do
168
+ writePackage options (subdirectory :| [] )
169
+ return (subdirectory </> outputFn)
170
+ OnlyThisPackage -> return []
171
+ go acc checkOutputDir (dhallFiles <> subpackages <> ps)
97
172
| isFile -> do
98
173
dir <- checkOutputDir $ takeDirectory p
99
174
100
175
let p' = normalise $ dir </> takeFileName p
101
176
102
- let resultMap = if p' == outputFn'
177
+ let resultMap = if p' == outputFn
103
178
then Map. empty
104
- else filepathToMap outputFn' p'
179
+ else filepathToMap outputFn p'
105
180
106
181
acc' <- mergeMaps acc resultMap
107
182
go acc' checkOutputDir ps
108
183
| otherwise -> throwIO $ InvalidPath p
109
184
110
- outputFn' = fromMaybe " package.dhall" outputFn
185
+ hasDhallExtension :: FilePath -> Bool
186
+ hasDhallExtension entry = takeExtension entry == " .dhall"
187
+
188
+ outputFn :: String
189
+ outputFn = optionsPackageFileName options
111
190
112
191
-- | Construct a nested 'Map' from a 'FilePath'.
113
192
-- For example, the filepath @some/file/path.dhall@ will result in something
0 commit comments