1{-# LANGUAGE TupleSections #-}
5import Control.Applicative
6import Control.Monad.Trans.Except (runExceptT)
7import Control.Arrow (first, second)
8import Control.Category ((>>>))
9import Control.Monad.Writer
10import Data.Text (Text)
11import qualified Data.Text as T
12import Data.Function (on)
14import Data.Maybe (fromMaybe)
15import Data.Tuple (swap)
16import Data.Version (showVersion)
18import Options.Applicative
19import qualified Text.PrettyPrint.ANSI.Leijen as PP
21import qualified Language.PureScript as P
22import qualified Paths_purescript as Paths
23import System.Exit (exitFailure)
24import System.IO (hPutStrLn, hPrint, hSetEncoding, stderr, stdout, utf8)
25import System.IO.UTF8 (readUTF8FileT)
26import System.Directory (createDirectoryIfMissing)
27import System.FilePath (takeDirectory)
28import System.FilePath.Glob (glob)
32import qualified Language.PureScript.Docs as D
33import qualified Language.PureScript.Docs.AsMarkdown as D
35-- Available output formats
36data Format = Markdown -- Output documentation in Markdown format
37 | Ctags -- Output ctags symbol index suitable for use with vi
38 | Etags -- Output etags symbol index suitable for use with emacs
39 deriving (Show, Eq, Ord)
41-- | Available methods of outputting Markdown documentation
44 | ToStdOut [P.ModuleName]
45 | ToFiles [(P.ModuleName, FilePath)]
48data PSCDocsOptions = PSCDocsOptions
49 { pscdFormat :: Format
50 , pscdInputFiles :: [FilePath]
51 , pscdDocgen :: DocgenOutput
55docgen :: PSCDocsOptions -> IO ()
56docgen (PSCDocsOptions fmt inputGlob output) = do
57 input <- concat <$> mapM glob inputGlob
59 Etags -> dumpTags input dumpEtags
60 Ctags -> dumpTags input dumpCtags
62 ms <- runExceptT (D.parseAndBookmark input []
63 >>= (fst >>> D.convertModulesInPackage))
68 putStrLn (D.runDocs (D.modulesAsMarkdown ms))
70 let (ms', missing) = takeByName ms names
72 putStrLn (D.runDocs (D.modulesAsMarkdown ms'))
74 let (ms', missing) = takeByName' ms names
77 let ms'' = groupBy ((==) `on` fst) . sortBy (compare `on` fst) $ map swap ms'
78 forM_ ms'' $ \grp -> do
79 let fp = fst (head grp)
80 createDirectoryIfMissing True (takeDirectory fp)
81 writeFile fp (D.runDocs (D.modulesAsMarkdown (map snd grp)))
84 guardMissing [] = return ()
85 guardMissing [mn] = do
86 hPutStrLn stderr ("psc-docs: error: unknown module \"" ++ T.unpack (P.runModuleName mn) ++ "\"")
89 hPutStrLn stderr "psc-docs: error: unknown modules:"
91 hPutStrLn stderr (" * " ++ T.unpack (P.runModuleName mn))
94 successOrExit :: Either P.MultipleErrors a -> IO a
100 hPutStrLn stderr $ P.prettyPrintMultipleErrors P.defaultPPEOptions err
103 takeByName = takeModulesByName D.modName
104 takeByName' = takeModulesByName' D.modName
107-- Given a list of module names and a list of modules, return a list of modules
108-- whose names appeared in the given name list, together with a list of names
109-- for which no module could be found in the module list.
111takeModulesByName :: (Eq n) => (m -> n) -> [m] -> [n] -> ([m], [n])
112takeModulesByName getModuleName modules names =
113 first (map fst) (takeModulesByName' getModuleName modules (map (,()) names))
116-- Like takeModulesByName, but also keeps some extra information with each
119takeModulesByName' :: (Eq n) => (m -> n) -> [m] -> [(n, a)] -> ([(m, a)], [n])
120takeModulesByName' getModuleName modules = foldl go ([], [])
122 go (ms, missing) (name, x) =
123 case find ((== name) . getModuleName) modules of
124 Just m -> ((m, x) : ms, missing)
125 Nothing -> (ms, name : missing)
127dumpTags :: [FilePath] -> ([(String, P.Module)] -> [String]) -> IO ()
128dumpTags input renderTags = do
129 e <- P.parseModulesFromFiles (fromMaybe "") <$> mapM (fmap (first Just) . parseFile) (nub input)
135 ldump (renderTags (pairs ms))
138 pairs :: [(Maybe String, m)] -> [(String, m)]
139 pairs = map (first (fromMaybe ""))
141 ldump :: [String] -> IO ()
142 ldump = mapM_ putStrLn
144parseFile :: FilePath -> IO (FilePath, Text)
145parseFile input = (,) input <$> readUTF8FileT input
147inputFile :: Parser FilePath
148inputFile = strArgument $
150 <> help "The input .purs file(s)"
152instance Read Format where
153 readsPrec _ "etags" = [(Etags, "")]
154 readsPrec _ "ctags" = [(Ctags, "")]
155 readsPrec _ "markdown" = [(Markdown, "")]
158format :: Parser Format
159format = option auto $ value Markdown
162 <> help "Set output FORMAT (markdown | etags | ctags)"
164docgenModule :: Parser String
165docgenModule = strOption $
167 <> help "A list of module names which should appear in the output. This can optionally include file paths to write individual modules to, by separating with a colon ':'. For example, Prelude:docs/Prelude.md. This option may be specified multiple times."
169pscDocsOptions :: Parser (Format, [FilePath], [String])
170pscDocsOptions = (,,) <$> format <*> many inputFile <*> many docgenModule
172parseDocgen :: [String] -> Either String DocgenOutput
173parseDocgen [] = Right EverythingToStdOut
174parseDocgen xs = go xs
183 = IToStdOut P.ModuleName
184 | IToFile (P.ModuleName, FilePath)
186parseItem :: String -> DocgenOutputItem
187parseItem s = case elemIndex ':' s of
190 >>> first (P.moduleNameFromString . T.pack)
194 IToStdOut (P.moduleNameFromString (T.pack s))
200combine :: [DocgenOutputItem] -> Either String DocgenOutput
201combine [] = Right EverythingToStdOut
202combine (x:xs) = foldM go (initial x) xs
204 initial (IToStdOut m) = ToStdOut [m]
205 initial (IToFile m) = ToFiles [m]
207 go (ToStdOut ms) (IToStdOut m) = Right (ToStdOut (m:ms))
208 go (ToFiles ms) (IToFile m) = Right (ToFiles (m:ms))
209 go _ _ = Left "Can't mix module names and module name/file path pairs in the same invocation."
211buildOptions :: (Format, [FilePath], [String]) -> IO PSCDocsOptions
212buildOptions (fmt, input, mapping) =
213 case parseDocgen mapping of
214 Right mapping' -> return (PSCDocsOptions fmt input mapping')
216 hPutStrLn stderr "psc-docs: error in --docgen option:"
217 hPutStrLn stderr (" " ++ err)
222 hSetEncoding stdout utf8
223 hSetEncoding stderr utf8
224 execParser opts >>= buildOptions >>= docgen
226 opts = info (version <*> helper <*> pscDocsOptions) infoModList
227 infoModList = fullDesc <> headerInfo <> footerInfo
228 headerInfo = header "psc-docs - Generate Markdown documentation from PureScript source files"
229 footerInfo = footerDoc $ Just $ PP.vcat
230 [ examples, PP.empty, PP.text ("psc-docs " ++ showVersion Paths.version) ]
232 version :: Parser (a -> a)
233 version = abortOption (InfoMsg (showVersion Paths.version)) $ long "version" <> help "Show the version number" <> hidden
237 PP.vcat $ map PP.text
239 , " print documentation for Data.List to stdout:"
240 , " psc-docs \"src/**/*.purs\" \".psc-package/*/*/*/src/**/*.purs\" \\"
241 , " --docgen Data.List"
243 , " write documentation for Data.List to docs/Data.List.md:"
244 , " psc-docs \"src/**/*.purs\" \".psc-package/*/*/*/src/**/*.purs\" \\"
245 , " --docgen Data.List:docs/Data.List.md"
247 , " write documentation for Data.List to docs/Data.List.md, and"
248 , " documentation for Data.List.Lazy to docs/Data.List.Lazy.md:"
249 , " psc-docs \"src/**/*.purs\" \".psc-package/*/*/*/src/**/*.purs\" \\"
250 , " --docgen Data.List:docs/Data.List.md \\"
251 , " --docgen Data.List.Lazy:docs/Data.List.Lazy.md"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list