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 +2/−0
- UNLICENSE +24/−0
- codex.cabal +55/−0
- src/Codex.hs +83/−0
- src/Codex/Internal.hs +32/−0
- src/Distribution/Hackage/Utils.hs +41/−0
- src/Main.hs +66/−0
+ 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"