1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE TupleSections #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6module Language.PureScript.Publish where
8import Prelude hiding (userError)
11import Data.Char (isSpace)
12import Data.String (fromString)
13import Data.List (stripPrefix, isSuffixOf, (\\), nubBy)
14import Data.List.Split (splitOn)
15import Data.List.NonEmpty (NonEmpty(..))
17import Data.Function (on)
19import Data.Aeson.BetterErrors
20import qualified Data.Text as T
22import Control.Applicative
23import Control.Category ((>>>))
24import Control.Arrow ((***))
25import Control.Exception (catch, try)
26import Control.Monad.Trans.Except
27import Control.Monad.Error.Class (MonadError(..))
28import Control.Monad.Writer
30import System.Directory (doesFileExist)
31import System.Process (readProcess)
32import System.Exit (exitFailure)
33import qualified System.FilePath.Glob as Glob
35import Web.Bower.PackageMeta (PackageMeta(..), BowerError(..), PackageName,
36 runPackageName, parsePackageName, Repository(..))
37import qualified Web.Bower.PackageMeta as Bower
39import qualified Language.PureScript as P (version)
40import qualified Language.PureScript.Docs as D
41import Language.PureScript.Publish.Utils
42import Language.PureScript.Publish.ErrorsWarnings
44-- | Attempt to retrieve package metadata from the current directory.
45-- Calls exitFailure if no package metadata could be retrieved.
46preparePackage :: IO D.UploadedPackage
48 runPrepareM preparePackage'
49 >>= either (\e -> printError e >> exitFailure)
52 handleWarnings (result, warns) = do
57 PrepareM { unPrepareM :: WriterT [PackageWarning] (ExceptT PackageError IO) a }
58 deriving (Functor, Applicative, Monad,
59 MonadWriter [PackageWarning],
60 MonadError PackageError)
62-- This MonadIO instance ensures that IO errors don't crash the program.
63instance MonadIO PrepareM where
65 lift' (try act) >>= either (otherError . IOExceptionThrown) return
67 lift' :: IO a -> PrepareM a
68 lift' = PrepareM . lift . lift
70runPrepareM :: PrepareM a -> IO (Either PackageError (a, [PackageWarning]))
71runPrepareM = runExceptT . runWriterT . unPrepareM
73warn :: PackageWarning -> PrepareM ()
76userError :: UserError -> PrepareM a
77userError = throwError . UserError
79internalError :: InternalError -> PrepareM a
80internalError = throwError . InternalError
82otherError :: OtherError -> PrepareM a
83otherError = throwError . OtherError
85catchLeft :: Applicative f => Either a b -> (a -> f b) -> f b
86catchLeft a f = either f pure a
88preparePackage' :: PrepareM D.UploadedPackage
90 exists <- liftIO (doesFileExist "bower.json")
91 unless exists (userError BowerJSONNotFound)
93 pkgMeta <- liftIO (Bower.decodeFile "bower.json")
94 >>= flip catchLeft (userError . CouldntParseBowerJSON)
95 (pkgVersionTag, pkgVersion) <- getVersionFromGitTag
96 pkgGithub <- getBowerInfo pkgMeta
97 (pkgBookmarks, pkgModules) <- getModulesAndBookmarks
99 let declaredDeps = map fst (bowerDependencies pkgMeta ++
100 bowerDevDependencies pkgMeta)
101 pkgResolvedDependencies <- getResolvedDependencies declaredDeps
103 let pkgUploader = D.NotYetKnown
104 let pkgCompilerVersion = P.version
108getModulesAndBookmarks :: PrepareM ([D.Bookmark], [D.Module])
109getModulesAndBookmarks = do
110 (inputFiles, depsFiles) <- liftIO getInputAndDepsFiles
111 liftIO (D.parseAndDesugar inputFiles depsFiles renderModules)
112 >>= either (userError . ParseAndDesugarError) return
114 renderModules bookmarks modules =
115 return (bookmarks, map D.convertModule modules)
117getVersionFromGitTag :: PrepareM (String, Version)
118getVersionFromGitTag = do
119 out <- readProcess' "git" ["tag", "--list", "--points-at", "HEAD"] ""
120 let vs = map trimWhitespace (lines out)
121 case mapMaybe parseMay vs of
122 [] -> userError TagMustBeCheckedOut
124 xs -> userError (AmbiguousVersions (map snd xs))
127 dropWhile isSpace >>> reverse >>> dropWhile isSpace >>> reverse
129 (str,) <$> D.parseVersion' (dropPrefix "v" str)
130 dropPrefix prefix str =
131 fromMaybe str (stripPrefix prefix str)
133getBowerInfo :: PackageMeta -> PrepareM (D.GithubUser, D.GithubRepo)
134getBowerInfo = either (userError . BadRepositoryField) return . tryExtract
137 case bowerRepository pkgMeta of
138 Nothing -> Left RepositoryFieldMissing
139 Just Repository{..} -> do
140 unless (repositoryType == "git")
141 (Left (BadRepositoryType repositoryType))
142 maybe (Left NotOnGithub) Right (extractGithub repositoryUrl)
144extractGithub :: String -> Maybe (D.GithubUser, D.GithubRepo)
146 stripPrefix "git://github.com/"
147 >>> fmap (splitOn "/")
149 >>> fmap (D.GithubUser *** (D.GithubRepo . dropDotGit))
152 takeTwo :: [a] -> Maybe (a, a)
153 takeTwo [x, y] = Just (x, y)
156 dropDotGit :: String -> String
158 | ".git" `isSuffixOf` str = take (length str - 4) str
161readProcess' :: String -> [String] -> String -> PrepareM String
162readProcess' prog args stdin = do
163 out <- liftIO (catch (Right <$> readProcess prog args stdin)
165 either (otherError . ProcessFailed prog args) return out
169 -- ^ Listed in bower.json, but not installed.
171 -- ^ In the output of `bower list --json --offline`, there was no
172 -- _resolution key. This can be caused by adding the dependency using
173 -- `bower link`, or simply copying it into bower_components instead of
174 -- installing it normally.
175 | ResolvedOther String
176 -- ^ Resolved, but to something other than a version. The String argument
177 -- is the resolution type. The values it can take that I'm aware of are
178 -- "commit" and "branch".
179 | ResolvedVersion String
180 -- ^ Resolved to a version. The String argument is the resolution tag (eg,
184-- Go through all bower dependencies which contain purescript code, and
185-- extract their versions.
187-- In the case where a bower dependency is taken from a particular version,
188-- that's easy; take that version. In any other case (eg, a branch, or a commit
189-- sha) we print a warning that documentation links will not work, and avoid
190-- linking to documentation for any types from that package.
192-- The rationale for this is: people will prefer to use a released version
193-- where possible. If they are not using a released version, then this is
194-- probably for a reason. However, docs are only ever available for released
195-- versions. Therefore there will probably be no version of the docs which is
196-- appropriate to link to, and we should omit links.
197getResolvedDependencies :: [PackageName] -> PrepareM [(PackageName, Version)]
198getResolvedDependencies declaredDeps = do
199 depsBS <- fromString <$> readProcess' "bower" ["list", "--json", "--offline"] ""
201 -- Check for undeclared dependencies
202 toplevels <- catchJSON (parse asToplevelDependencies depsBS)
203 warnUndeclared declaredDeps toplevels
205 deps <- catchJSON (parse asResolvedDependencies depsBS)
209 catchJSON = flip catchLeft (internalError . JSONError FromBowerList)
211-- | Extracts all dependencies and their versions from
212-- `bower list --json --offline`
213asResolvedDependencies :: Parse BowerError [(PackageName, DependencyStatus)]
214asResolvedDependencies = nubBy ((==) `on` fst) <$> go
217 fmap (fromMaybe []) $
218 keyMay "dependencies" $
219 (++) <$> eachInObjectWithKey (parsePackageName . T.unpack)
221 <*> (concatMap snd <$> eachInObject asResolvedDependencies)
223-- | Extracts only the top level dependency names from the output of
224-- `bower list --json --offline`
225asToplevelDependencies :: Parse BowerError [PackageName]
226asToplevelDependencies =
229 eachInObjectWithKey (parsePackageName . T.unpack) (return ())
231asDependencyStatus :: Parse e DependencyStatus
232asDependencyStatus = do
233 isMissing <- keyOrDefault "missing" False asBool
239 keyOrDefault "_resolution" NoResolution $ do
240 type_ <- key "type" asString
242 "version" -> ResolvedVersion <$> key "tag" asString
243 other -> return (ResolvedOther other)
245warnUndeclared :: [PackageName] -> [PackageName] -> PrepareM ()
246warnUndeclared declared actual =
247 mapM_ (warn . UndeclaredDependency) (actual \\ declared)
250 [(PackageName, DependencyStatus)] -> PrepareM [(PackageName, Version)]
252 let (missing, noVersion, installed) = partitionDeps deps
255 userError (MissingDependencies (x :| xs))
257 mapM_ (warn . NoResolvedVersion) noVersion
258 withVersions <- catMaybes <$> mapM tryExtractVersion' installed
259 filterM (liftIO . isPureScript . bowerDir . fst) withVersions
262 partitionDeps = foldr go ([], [], [])
263 go (pkgName, d) (ms, os, is) =
265 Missing -> (pkgName : ms, os, is)
266 NoResolution -> (ms, pkgName : os, is)
267 ResolvedOther _ -> (ms, pkgName : os, is)
268 ResolvedVersion v -> (ms, os, (pkgName, v) : is)
270 bowerDir pkgName = "bower_components/" ++ runPackageName pkgName
272 -- Try to extract a version, and warn if unsuccessful.
273 tryExtractVersion' pair =
274 maybe (warn (UnacceptableVersion pair) >> return Nothing)
276 (tryExtractVersion pair)
278tryExtractVersion :: (PackageName, String) -> Maybe (PackageName, Version)
279tryExtractVersion (pkgName, tag) =
280 let tag' = fromMaybe tag (stripPrefix "v" tag)
281 in (pkgName,) <$> D.parseVersion' tag'
283-- | Returns whether it looks like there is a purescript package checked out
284-- in the given directory.
285isPureScript :: FilePath -> IO Bool
287 files <- Glob.globDir1 purescriptSourceFiles dir
288 return (not (null files))
290getInputAndDepsFiles :: IO ([FilePath], [(PackageName, FilePath)])
291getInputAndDepsFiles = do
292 inputFiles <- globRelative purescriptSourceFiles
293 depsFiles' <- globRelative purescriptDepsFiles
294 return (inputFiles, mapMaybe withPackageName depsFiles')
296withPackageName :: FilePath -> Maybe (PackageName, FilePath)
297withPackageName fp = (,fp) <$> getPackageName fp
299getPackageName :: FilePath -> Maybe PackageName
300getPackageName fp = do
301 let xs = splitOn "/" fp
302 ys <- stripPrefix ["bower_components"] xs
304 case Bower.mkPackageName y of
305 Right name -> Just name
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