packages feed

codex (empty) → 0.0.1.0

raw patch · 7 files changed

+303/−0 lines, 7 filesdep +Cabaldep +MissingHdep +basesetup-changed

Dependencies added: Cabal, MissingH, base, bytestring, codex, containers, directory, download-curl, filepath, hackage-db, monad-loops, mtl, process, tar, zlib

Files

+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ UNLICENSE view
@@ -0,0 +1,24 @@+This is free and unencumbered software released into the public domain.++Anyone is free to copy, modify, publish, use, compile, sell, or+distribute this software, either in source code form or as a compiled+binary, for any purpose, commercial or non-commercial, and by any+means.++In jurisdictions that recognize copyright laws, the author or authors+of this software dedicate any and all copyright interest in the+software to the public domain. We make this dedication for the benefit+of the public at large and to the detriment of our heirs and+successors. We intend this dedication to be an overt act of+relinquishment in perpetuity of all present and future rights to this+software under copyright law.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.++For more information, please refer to <http://unlicense.org/>
+ codex.cabal view
@@ -0,0 +1,55 @@+name:                codex+version:             0.0.1.0+synopsis:            Code Explorer for Cabal+description:         +  This tool download and cache the source code of packages in your local hackage,+  he can then use this cache to generate `tags` files aggregating the sources of all the dependencies of your cabal projects.+  . +  You basically do `codex update` in your cabal project directory and you'll get a `codex.tags` file+  that you can use in your favorite text editor (VIM/Emacs/Sublime).++homepage:            http://github.com/aloiscochard/codex+license:             OtherLicense+license-file:        UNLICENSE+author:              Alois Cochard+maintainer:          alois.cochard@gmail.com+category:            Development+build-type:          Simple+cabal-version:       >=1.10++library+  default-language:  Haskell2010+  hs-source-dirs:    src+  ghc-options:       -O2 -threaded -fwarn-incomplete-patterns+  exposed-modules:+    Codex+    Codex.Internal+    Distribution.Hackage.Utils+  build-depends:       +    base >=4.7 && <4.8+    , bytestring+    , Cabal >= 1.19+    , containers+    , directory+    , download-curl+    , filepath+    , hackage-db+    , mtl+    , process+    , tar+    , zlib++executable codex+  default-language:  Haskell2010+  main-is:           src/Main.hs+  ghc-options:       -O2 -threaded -fwarn-incomplete-patterns+  build-depends:       +      base+    , Cabal+    , directory+    , filepath+    , hackage-db+    , MissingH+    , mtl+    , monad-loops+    , codex 
+ src/Codex.hs view
@@ -0,0 +1,83 @@+module Codex (Codex(..), Verbosity, module Codex) where++import Control.Exception (try, SomeException)+import Control.Monad+import Control.Monad.Error+import Data.Traversable (sequenceA)+import Distribution.Package+import Distribution.PackageDescription+import Distribution.Verbosity+import Network.Curl.Download.Lazy+import System.Directory+import System.FilePath+import System.Process++import qualified Codec.Archive.Tar as Tar+import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as BS+import qualified Data.List as List++import Codex.Internal++-- TODO Replace the `Codex` context with a `Control.Reader.Monad `.++data Tagging = Tagged | Untagged+  deriving (Eq, Show)++fromBool :: Bool -> Tagging+fromBool True = Tagged+fromBool False = Untagged++data Status = Source Tagging | Archive | Remote+  deriving (Eq, Show)++type Action = ErrorT String IO++-- TODO It would be much better to work out which `Exception`s are thrown by which operations,+--      and store all of that in a ADT. For now, I'll just be lazy.+tryIO :: IO a -> Action a+tryIO io = do +  res <- liftIO $ (try :: IO a -> IO (Either SomeException a)) io+  either (throwError . show) return res++status :: Codex -> PackageIdentifier -> Action Status+status cx i = do+  sourcesExist <- tryIO . doesDirectoryExist $ packageSources cx i+  archiveExist <- tryIO . doesFileExist $ packageArchive cx i+  case (sourcesExist, archiveExist) of+    (True, _) -> fmap (Source . fromBool) (liftIO . doesFileExist $ packageTags cx i)+    (_, True) -> return Archive+    (_, _)    -> return Remote++fetch :: Codex -> PackageIdentifier -> Action FilePath+fetch cx i = do+  bs <- tryIO $ do +    createDirectoryIfMissing True (packagePath cx i)+    openLazyURI url+  either throwError write bs where+    write bs = fmap (const archivePath) $ tryIO $ BS.writeFile archivePath bs+    archivePath = packageArchive cx i+    url = packageUrl i++extract :: Codex -> PackageIdentifier -> Action FilePath+extract cx i = fmap (const path) . tryIO $ read path (packageArchive cx i) where+  read dir tar = Tar.unpack dir . Tar.read . GZip.decompress =<< BS.readFile tar+  path = packagePath cx i++tags :: Codex -> PackageIdentifier -> Action FilePath+tags cx i = do+  tryIO . createProcess $ shell command+  return tags where+    command = concat ["ctags --tag-relative=no --recurse -f '", tags, "' '", sources, "'"]+    sources = packageSources cx i+    tags = packageTags cx i++assembly :: Codex -> [PackageIdentifier] -> FilePath -> Action FilePath+assembly cx is o = tryIO . fmap (const o) $ mergeTags (fmap tags is) o where+  mergeTags files o = do+    contents <- sequence $ fmap readFile files+    let xs = List.sort . (List.filter (\x -> List.head x /= '!')) . concat $ fmap lines contents+    writeFile o $ unlines (concat [header, xs])+  tags i = packageTags cx i+  header = ["!_TAG_FILE_FORMAT 2", "!_TAG_FILE_SORTED 1"]+
+ src/Codex/Internal.hs view
@@ -0,0 +1,32 @@+module Codex.Internal where++import Control.Arrow+import Distribution.Package+import Distribution.Text+import Distribution.Verbosity+import System.FilePath++data Codex = Codex { hackagePath :: FilePath, verbosity :: Verbosity }++packagePath :: Codex -> PackageIdentifier -> FilePath+packagePath cx i = joinPath [hackagePath cx, relativePath i] where+  relativePath i = joinPath [name, version] where+    name = display $ pkgName i+    version = display $ pkgVersion i++packageArchive :: Codex -> PackageIdentifier -> FilePath+packageArchive cx i = joinPath [packagePath cx i, name] where+  name = concat [display $ pkgName i, "-", display $ pkgVersion i, ".tar.gz"]++packageSources :: Codex -> PackageIdentifier -> FilePath+packageSources cx i = joinPath [packagePath cx i, name] where+  name = concat [display $ pkgName i, "-", display $ pkgVersion i]++packageTags :: Codex -> PackageIdentifier -> FilePath+packageTags cx i = joinPath [packagePath cx i, "tags"]++packageUrl :: PackageIdentifier -> String+packageUrl i = concat ["http://hackage.haskell.org/package/", path] where+  path = concat [name, "/", name, ".tar.gz"]+  name = concat [display $ pkgName i, "-", display $ pkgVersion i]+
+ src/Distribution/Hackage/Utils.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE CPP #-}+module Distribution.Hackage.Utils where++import Data.Maybe+import Distribution.Hackage.DB (Hackage)+import Distribution.Package+import Distribution.PackageDescription+import Distribution.Version+import System.Directory+import System.FilePath++import qualified Data.List as List+import qualified Data.Map as Map++-- TODO Remove once path extracted in hackage-db+getHackagePath :: IO FilePath+getHackagePath = do+ homedir <- getHomeDirectory+ return (joinPath [homedir,+#ifdef IS_DARWIN+    "Library", "Haskell", "repo-cache"+#else+    ".cabal", "packages"+#endif+    , "hackage.haskell.org"])++allDependencies :: GenericPackageDescription -> [Dependency]+allDependencies pd = concat [lds, eds, tds] where+  lds = condTreeConstraints =<< (maybeToList $ condLibrary pd) +  eds = (condTreeConstraints . snd) =<< condExecutables pd +  tds = (condTreeConstraints . snd) =<< condTestSuites pd ++resolveDependency :: Hackage -> Dependency -> Maybe GenericPackageDescription+resolveDependency db (Dependency (PackageName name) versionRange) = do+  pdsByVersion <- Map.lookup name db+  latest <- List.find (\x -> withinRange x versionRange) $ List.reverse $ List.sort $ Map.keys pdsByVersion+  Map.lookup latest pdsByVersion++resolveDependencies :: Hackage -> GenericPackageDescription -> [GenericPackageDescription]+resolveDependencies db pd = maybeToList . resolveDependency db =<< allDependencies pd +
+ src/Main.hs view
@@ -0,0 +1,66 @@+import Control.Arrow+import Control.Monad.Error+import Data.Traversable (sequenceA)+import Data.String.Utils+import Distribution.Hackage.DB (readHackage)+import Distribution.Package -- TODO Remove+import Distribution.PackageDescription+import Distribution.PackageDescription.Parse+import Distribution.Text+import Distribution.Verbosity+import System.Directory+import System.Environment+import System.FilePath++import qualified Data.List as List++import Distribution.Hackage.Utils++import Codex++-- TODO Make Async+-- TODO Make verbosity configurable+-- TODO Make `ctags` command configurable+-- TODO Use `ctags --version` to check if ctags is installed++retrying :: Int -> IO (Either a b) -> IO (Either [a] b)+retrying n x = retrying' n $ fmap (left (:[])) x where+  retrying' 0 x = x+  retrying' n x = retrying' (n - 1) $ x >>= \res -> case res of+    Left ls -> fmap (left (++ ls)) x+    Right r -> return $ Right r++-- TODO Better error handling and fine grained retry+update :: Codex -> IO ()+update cx = resolve =<< getCurrentProject where+  resolve Nothing = putStrLn "No cabal file found."+  resolve (Just project) = do+    putStrLn $ concat ["Updating ", display . identifier $ project]+    dependencies <- fmap (\db -> resolveDependencies db project) readHackage+    founds <- retrying 4 $ runErrorT . sequence $ fmap (getTags . identifier) dependencies +    failOr (generate dependencies) founds where+      identifier = package . packageDescription+      -- TODO Add println of info (use verbosity)+      getTags i = status cx i >>= \x -> case x of+        (Source Tagged)   -> return ()+        (Source Untagged) -> tags cx i >>= (const $ getTags i)+        (Archive)         -> extract cx i >>= (const $ getTags i)+        (Remote)          -> fetch cx i >>= (const $ getTags i)+      generate xs = do +        res <- runErrorT $ assembly cx (fmap identifier xs) path+        failOr (return ()) res where+          path = joinPath [concat [display . pkgName . identifier $ project, ".tags"]]+      failOr y x = either (putStrLn . show) (const y) x++getCurrentProject :: IO (Maybe GenericPackageDescription)+getCurrentProject = do+  files <- getDirectoryContents $ joinPath ["."]+  sequenceA . fmap (readPackageDescription silent) $ List.find (endswith ".cabal") files++main :: IO ()+main = do+  hp    <- getHackagePath+  args  <- getArgs+  run (Codex hp silent) args where+    run cx ["update"] = update cx+    run cx _          = putStrLn "Usage: codex update"