-
Notifications
You must be signed in to change notification settings - Fork 1
/
GenerateEverythingIlc.hs
152 lines (120 loc) · 4.64 KB
/
GenerateEverythingIlc.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
{-# LANGUAGE PatternGuards #-}
-- TODO: this should actually be reusable by being a package.
-- And the parser should be an actual parser (ReadP? Whatever).
--
-- Could be combined with Toxaris/filter-agda-dependency-graph, since it needs
-- the output of this tool.
import qualified Data.List as List
import Control.Applicative
import System.Environment
import System.IO
import System.Exit
import System.FilePath
import System.FilePath.Find
--------------------------------------------------------------------------------
-- Configuration parameters
--------------------------------------------------------------------------------
marker = "INCREMENTAL λ-CALCULUS"
projectName = "Incremental λ-calculus"
binaryNameSuffix = "Ilc"
binaryName = "GenerateEverything" ++ binaryNameSuffix
headerFile = "EverythingHeader.agda.inc"
outputFile = "Everything.agda"
-- This could be "src", as it was in the Agda standard library. But that change
-- might cause conflicts with other Agda work.
srcDir = "."
--------------------------------------------------------------------------------
-- Logic to choose files to list - project-dependent
--------------------------------------------------------------------------------
-- Should we descend into this dir?
descendIntoDir = fileName /=? "bugs"
-- Do we want to exclude this source file
wantedSourceFile =
fileName /=? "README.agda" &&?
liftOp ((not .) . flip List.isInfixOf) filePath (".stack-work" ++ [pathSeparator])
--------------------------------------------------------------------------------
-- Logic to choose files to list - should be project-independent
--------------------------------------------------------------------------------
isSource =
fileName /=? outputFile &&?
(extension ==? ".agda" ||? extension ==? ".lagda") &&?
wantedSourceFile
sources = find descendIntoDir isSource srcDir
----------------------------------------
-- Reusable implementation
----------------------------------------
main = do
args <- getArgs
case args of
[] -> return ()
_ -> hPutStr stderr usage >> exitFailure
header <- readFileUTF8 headerFile
modules <- filter isLibraryModule . List.sort <$> sources
headers <- mapM extractHeader modules
writeFileUTF8 outputFile $
header ++ format (zip modules headers)
-- | Usage info.
usage :: String
usage = unlines
[ binaryName ++ ": A utility program for Agda libraries (specialized for "
++ projectName ++ ")."
, ""
, "Usage: " ++ binaryName
, ""
, "This program should be run in the base directory of a clean checkout of"
, "the library."
, ""
, "The program generates documentation for the library by extracting"
, "headers from library modules. The output is written to " ++ outputFile
, "with the file " ++ headerFile ++ " inserted verbatim at the beginning."
]
-- | Returns 'True' for all Agda files except for core modules.
isLibraryModule :: FilePath -> Bool
isLibraryModule f =
takeExtension f `elem` [".agda", ".lagda"] &&
dropExtension (takeFileName f) /= "Core"
trim toTrim list = core
where
(prefix, rest) = span toTrim list
(revSuffix, revCore) = span toTrim (reverse rest)
core = reverse revCore
-- | Reads a module and extracts the header.
extractHeader :: FilePath -> IO [String]
extractHeader mod = fmap (extract . lines) $ readFileUTF8 mod
where
delimiter line = length line /= 0 && all (== '-') line
extract (d1 : expectedMarker : "--" : ss)
| delimiter d1
, expectedMarker == "-- " ++ marker
, (info, rest) <- span ("--" `List.isPrefixOf`) ss
, let d2 = last info
, delimiter d2
= trim delimiter info
extract _ = []
-- | Formats the extracted module information.
format :: [(FilePath, [String])]
-- ^ Pairs of module names and headers. All lines in the
-- headers are already prefixed with \"-- \".
-> String
format = unlines . concat . map fmt
where
fmt (mod, header) = "" : header ++ ["import " ++ fileToMod mod]
-- | Translates a file name to the corresponding module name. It is
-- assumed that the file name corresponds to an Agda module under
-- 'srcDir'.
fileToMod :: FilePath -> String
fileToMod = map slashToDot . dropExtension . makeRelative srcDir
where
slashToDot c | isPathSeparator c = '.'
| otherwise = c
-- | A variant of 'readFile' which uses the 'utf8' encoding.
readFileUTF8 :: FilePath -> IO String
readFileUTF8 f = do
h <- openFile f ReadMode
hSetEncoding h utf8
hGetContents h
-- | A variant of 'writeFile' which uses the 'utf8' encoding.
writeFileUTF8 :: FilePath -> String -> IO ()
writeFileUTF8 f s = withFile f WriteMode $ \h -> do
hSetEncoding h utf8
hPutStr h s