-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathEverythings.hs
155 lines (140 loc) · 6.69 KB
/
Everythings.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import System.Environment ( getArgs )
import System.Directory ( getDirectoryContents )
import System.Exit ( exitFailure )
import Control.Monad ( forM, forM_ )
import Data.Maybe ( listToMaybe, maybeToList, mapMaybe )
import Data.List ( (\\), delete, find, intercalate, sort, stripPrefix, isSuffixOf )
stripSuffix :: (Eq a) => [a] -> [a] -> Maybe [a]
stripSuffix sfx = fmap reverse . stripPrefix (reverse sfx) . reverse
splitOn :: Char -> String -> [String]
splitOn d = foldr go []
where go :: Char -> [String] -> [String]
go x acc | d == x = []:acc
go x (a:acc) = (x:a):acc
go x [] = [[x]]
type SplitFilePath = [String]
showFP :: Char -> SplitFilePath -> String
showFP c fp = intercalate [c] (reverse fp)
addToFP :: SplitFilePath -> String -> SplitFilePath
addToFP fp dir = dir : fp
-- Given a path to a directory, returns a pair containing the list of all its
-- subdirectories and the list of all agda files it contains
getSubDirsFiles :: SplitFilePath -> IO ([String], [String])
getSubDirsFiles fp = do
ls <- getDirectoryContents ("./" ++ showFP '/' fp)
let sub_dirs = filter ('.' `notElem`) ls
files = mapMaybe (stripSuffix ".agda") ls
pure (sub_dirs, files)
-- Given a path to an agda file, returns the list of all files it imports
getImported :: SplitFilePath -> IO [SplitFilePath]
getImported fp = do
ls <- fmap words . lines <$> readFile ("./" ++ showFP '/' fp ++ ".agda")
pure $ fmap (reverse . splitOn '.') (mapMaybe f ls)
where f :: [String] -> Maybe String
f ("open":"import":x:_) = Just x
f ("import":x:_) = Just x
f _ = Nothing
-- Given a path to a directory $fp and a path to an agda file $fileToCheck.agda,
-- returns the list of all files in* $fp not imported in $fileToCheck.agda
-- * recursively
getMissingFiles :: SplitFilePath -> Maybe SplitFilePath -> IO [SplitFilePath]
getMissingFiles fp fpToCheck = do
(sub_dirs, sub_files) <- getSubDirsFiles fp
-- recursively get all files in $fp/X not imported in $fp/X.agda (if it exists)
missing_files <- concat <$> forM sub_dirs (\sub_dir ->
getMissingFiles
(addToFP fp sub_dir)
(addToFP fp <$> find (== sub_dir) sub_files))
-- return all of these files, plus all agda files in the current directory,
-- except for those which are imported in $fpToCheck.agda (if it exists) or
-- which are $fpToCheck.agda itself
imported <- maybe (pure []) getImported fpToCheck
pure $ ((addToFP fp <$> sub_files) ++ missing_files)
\\ (maybeToList fpToCheck ++ imported)
checkEverythings :: [SplitFilePath] -> [String] -> IO ()
checkEverythings excludes dirs = do
missing_files <- concat <$> forM dirs (\dir -> do
let ex = map reverse excludes
miss <- getMissingFiles [dir] (Just ["Everything",dir])
pure $ filter (\l -> not $ any (`isSuffixOf` l) ex) miss
)
if null missing_files then pure ()
else do putStrLn "Found some files which are not imported in any Everything.agda:"
forM_ missing_files (putStrLn . (" " ++) . showFP '.')
exitFailure
checkRoot :: IO ()
checkRoot = do
(sub_dirs', _) <- getSubDirsFiles ["."]
let sub_dirs = filter (\d -> d `notElem` ["Everything.hs",
"_build",
"README.org",
".gitignore",
".github",
"env",
"LICENSE",
"Notes",
"check-line-lengths.sh",
"cubical-categorical-logic.agda-lib",
"fix-whitespace.yaml",
"Makefile"]) sub_dirs'
imported <- getImported ["TestEverything"]
let missing_files = fmap (\dir -> ["Everything",dir]) sub_dirs \\ imported
if null missing_files then pure ()
else do putStrLn "Found some Everything.agda's which are not imported in README.agda:"
forM_ missing_files (putStrLn . (" " ++) . showFP '.')
exitFailure
genEverythings :: [SplitFilePath] -> [String] -> IO ()
genEverythings excludes =
mapM_ $ \ dir -> do
let fp = [dir]
files' <- getMissingFiles fp Nothing
let ex = map reverse excludes
let files = filter (\l -> not $ any (`isSuffixOf` l) ex) files'
let ls = ["module " ++ showFP '.' (addToFP fp "Everything") ++ " where",[]]
++ sort (fmap (\file -> do
"import " ++ showFP '.' file
)
(delete (addToFP fp "Everything") files))
writeFile ("./" ++ showFP '/' (addToFP fp "Everything") ++ ".agda")
(unlines ls)
helpText :: String
helpText = unlines [
"Accepted arguments: ",
" check d1 d2 ... dn checks imports in the Everything files in the",
" given directories",
" check-except d1 d2 ... dn checks imports in all Everything files except those",
" in the given directories",
" gen d1 d2 ... dn generates Everything files in the given directories",
" gen-except d1 d2 ... dn generates Everything files in all directories",
" except in those given",
" check-Root checks all Everything files are imported in README"]
main :: IO ()
main = do
all_dirs' <- filter ('.' `notElem`) <$> getDirectoryContents "."
let all_dirs = filter (\d -> d `notElem` ["Everything.hs",
"_build",
"README.org",
".gitignore",
".github",
"env",
"LICENSE",
"Notes",
"check-line-lengths.sh",
"cubical-categorical-logic.agda-lib",
"fix-whitespace.yaml",
"Makefile"]) all_dirs'
args <- getArgs
case args of
"check":dirs -> checkEverythings [] dirs
"gen" :dirs -> genEverythings [] dirs
"check-except":ex_dirs ->
checkEverythings
(map (\f -> splitOn '/' f) ex_dirs)
(all_dirs \\ ex_dirs)
"gen-except" :ex_dirs ->
genEverythings
(map (\f -> splitOn '/' f) ex_dirs)
(all_dirs \\ ex_dirs)
["check-Root"] -> checkRoot
"help":_ -> putStrLn helpText
_ -> putStrLn "argument(s) not recognized, try 'help'"