latest-npm-version 0.2.1 → 0.3.0
raw patch · 4 files changed
+65/−26 lines, 4 filesdep +http-clientdep +http-types
Dependencies added: http-client, http-types
Files
- Main.hs +35/−11
- Npm/Latest.hs +6/−4
- Tests/Main.hs +14/−7
- latest-npm-version.cabal +10/−4
Main.hs view
@@ -1,30 +1,54 @@-{-# LANGUAGE OverloadedStrings, DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings, DeriveDataTypeable, ExistentialQuantification #-} +import Paths_latest_npm_version (version)+import Control.Exception (fromException, Exception, SomeException)+import Network.HTTP.Client (HttpException(StatusCodeException))+import Network.HTTP.Types.Status (statusCode) import Data.Data (Data)+import Data.Version (showVersion) import Data.Typeable (Typeable)-import Npm.Latest (fetchLatestVersion)+import Npm.Latest (fetchLatestVersion, GenericNpmException(..)) import System.Console.CmdArgs.Implicit (cmdArgsRun, (&=))+import System.Console.CmdArgs.Explicit (HelpFormat(..), helpText)+import System.IO (stderr) import qualified Data.Text as T import qualified Data.Text.IO as TIO import qualified System.Console.CmdArgs.Implicit as CA +data ErrHandler a = forall e . Exception e => ErrHandler (e -> a)++matchErr :: SomeException -> b -> [ErrHandler b] -> b+matchErr e = foldr (\(ErrHandler f) r -> maybe r f $ fromException e)+ data LatestNpmVersion = LatestNpmVersion {name :: String} deriving (Show, Data, Typeable) +programName :: String+programName = "latest-npm-version"+ args :: CA.Mode (CA.CmdArgs LatestNpmVersion) args = CA.cmdArgsMode $ LatestNpmVersion{name = CA.def &= CA.args}- &= CA.summary "latest-npm-version v0.1.0"+ &= CA.summary (unwords [programName, showVersion version])+ &= CA.program programName main :: IO () main = do mainArgs <- cmdArgsRun args- let moduleName = (name mainArgs)- if length moduleName == 0- then TIO.putStrLn "Missing module name. Consult --help."- else do- version <- fetchLatestVersion moduleName- case version of- Nothing -> TIO.putStrLn "Error: fetching/parsing JSON failed"- Just v -> TIO.putStrLn $ T.unwords [T.pack moduleName, v]+ let moduleName = name mainArgs+ if null moduleName+ then print $ helpText [] HelpFormatDefault args+ else fetchLatestVersion moduleName >>= either handleErr TIO.putStrLn++handleErr :: SomeException -> IO ()+handleErr err = TIO.hPutStrLn stderr $ matchErr err handleOther+ [ErrHandler handleA, ErrHandler handleB] where+ handleA GenericNpmException = "Invalid JSON data received."+ handleB (StatusCodeException status _ _) =+ if statusCode status == 404+ then "Package not found."+ else T.unwords+ ["Invalid status code ", T.pack $ show $ statusCode status]+ handleB s@_ = T.unwords ["HTTP error: ", T.pack $ show s]+ handleOther = "Unknown error."
Npm/Latest.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-|-Module : Npm.Latestj+Module : Npm.Latest Description : Fetch the latest version of an npm module. Copyright : (c) Pascal Hartig, 2014 License : MIT@@ -13,18 +13,20 @@ -} module Npm.Latest ( fetchLatestVersion,- extractVersion+ extractVersion,+ GenericNpmException(..) ) where -import Npm.Latest.Internal (buildRequest, makeVersionRequest, extractVersion)+import Control.Exception (SomeException) import Data.Text.Format (Format)+import Npm.Latest.Internal (buildRequest, makeVersionRequest, extractVersion, GenericNpmException(..)) import qualified Data.Text as T latestUrl :: Format latestUrl = "https://registry.npmjs.org/{}/latest" -- |Fetch the latest version for the given module name.-fetchLatestVersion :: String -> IO (Maybe T.Text)+fetchLatestVersion :: String -> IO (Either SomeException T.Text) fetchLatestVersion name = do req <- buildRequest name latestUrl resp <- makeVersionRequest req
Tests/Main.hs view
@@ -2,21 +2,28 @@ module Main where import Test.Hspec-import Npm.Latest (extractVersion)+import Control.Exception (SomeException(..), toException)+import Npm.Latest (extractVersion, GenericNpmException(..)) import qualified Data.Text as T +instance Eq SomeException where+ -- Terrible hack, but this is for tests, so it's fine, right?+ a == b = show a == show b+ main :: IO () main = hspec $ do describe "extractVersion" $ do- it "returns Just for a valid JSON object" $ do+ it "is Right for a valid JSON object" $ do let json = T.unpack "{\"version\": \"1.0\"}"- extractVersion (Just $ Right $ json) `shouldBe` Just "1.0"+ extractVersion (Right json) `shouldBe` (Right "1.0") - it "returns Nothing for a malformed JSON object" $ do+ it "is Left for a malformed JSON object" $ do let json = T.unpack "{\"description\": \"not what we expect\"}"- extractVersion (Just $ Right $ json) `shouldBe` Nothing+ extractVersion (Right $ json) `shouldBe`+ (Left $ toException GenericNpmException) - it "returns Nothing for a malformed JSON object" $ do+ it "is Left for a malformed JSON object" $ do let json = T.unpack "not even $ JSON"- extractVersion (Just $ Right $ json) `shouldBe` Nothing+ extractVersion (Right $ json) `shouldBe`+ (Left $ toException GenericNpmException)
latest-npm-version.cabal view
@@ -1,5 +1,5 @@ name: latest-npm-version-version: 0.2.1+version: 0.3.0 synopsis: Find the latest version of a package on npm homepage: https://github.com/passy/latest-npm-version license: MIT@@ -29,7 +29,9 @@ aeson >= 0.7 && < 0.8, pipes-attoparsec >= 0.5 && < 0.6, transformers >= 0.3 && < 0.4,- lens >= 4.1 && < 4.2+ lens >= 4.1 && < 4.2,+ http-client >= 0.3 && < 0.4,+ http-types == 0.8.* default-language: Haskell2010 executable latest-npm-version@@ -47,7 +49,9 @@ pipes-attoparsec >= 0.5 && < 0.6, transformers >= 0.3 && < 0.4, lens >= 4.1 && < 4.2,- cmdargs >= 0.10 && < 0.11+ cmdargs >= 0.10 && < 0.11,+ http-client >= 0.3 && < 0.4,+ http-types == 0.8.* default-language: Haskell2010 @@ -65,4 +69,6 @@ aeson >= 0.7 && < 0.8, pipes-attoparsec >= 0.5 && < 0.6, transformers >= 0.3 && < 0.4,- lens >= 4.1 && < 4.2+ lens >= 4.1 && < 4.2,+ http-client >= 0.3 && < 0.4,+ http-types == 0.8.*