diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Mateusz Kowalczyk (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Mateusz Kowalczyk nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# jenkinsPlugins2nix
+
+```
+jenkinsPlugins2nix: PLUGIN_NAME{:PLUGIN_VERSION} [PLUGIN_NAME{:PLUGIN_VERSION} ...]
+```
+
+Along with recent nixpkgs, you can then do the following.
+
+```
+jenkinsPlugins2nix github-api > plugins.nix
+
+```
+
+and in your `configuration.nix`:
+
+```
+services.jenkins.plugins = pkgs.callPackage plugins.nix {};
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module    : Nix.JenkinsPlugins2Nix.Types
+-- Copyright : (c) 2017 Mateusz Kowalczyk
+-- License   : BSD3
+--
+-- @jenkinsPlugins2nix@ entry point.
+module Main (main) where
+
+import           Data.Monoid ((<>))
+import qualified Data.Text as Text
+import           Nix.JenkinsPlugins2Nix
+import           Nix.JenkinsPlugins2Nix.Types
+import           System.Environment
+import           System.Exit
+import           System.IO
+import qualified Text.PrettyPrint.ANSI.Leijen as Pretty
+
+usage :: Handle -> IO ()
+usage h = do
+  pName <- getProgName
+  hPutStrLn h $
+    pName <> ": PLUGIN_NAME{:PLUGIN_VERSION} [PLUGIN_NAME{:PLUGIN_VERSION} ...]"
+
+main :: IO ()
+main = do
+  getArgs >>= \case
+    [] -> do
+      usage stderr
+      exitFailure
+    [v] | v == "-h" || v == "--help" -> do
+      usage stdout
+      exitSuccess
+    plugins -> do
+      mkExprsFor (map parseRequestedPlugin plugins) >>= \case
+        Left err -> do
+          hPutStrLn stderr err
+          exitFailure
+        Right p -> do
+          Pretty.putDoc p
+          exitSuccess
+  where
+    parseRequestedPlugin :: String -> RequestedPlugin
+    parseRequestedPlugin p = case break (== ':') p of
+      (n, ':' : ver) -> RequestedPlugin
+        { requested_name = Text.pack n
+        , requested_version = Just (Text.pack ver)
+        }
+      _ -> RequestedPlugin
+        { requested_name = Text.pack p
+        , requested_version = Nothing
+        }
diff --git a/jenkinsPlugins2nix.cabal b/jenkinsPlugins2nix.cabal
new file mode 100644
--- /dev/null
+++ b/jenkinsPlugins2nix.cabal
@@ -0,0 +1,61 @@
+name:                jenkinsPlugins2nix
+version:             0.1.0.0
+synopsis:            Generate nix for Jenkins plugins.
+description:         Generate nix for Jenkins plugins.
+homepage:            https://github.com/Fuuzetsu/jenkinsPlugins2nix#readme
+license:             BSD3
+license-file:        LICENSE
+author:              Mateusz Kowalczyk
+maintainer:          fuuzetsu@fuuzetsu.com
+copyright:           2017 Mateusz Kowalczyk
+category:            Nix
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Nix.JenkinsPlugins2Nix
+                       Nix.JenkinsPlugins2Nix.Parser
+                       Nix.JenkinsPlugins2Nix.Types
+  build-depends:       base >= 4.7 && < 5
+                     , ansi-wl-pprint
+                     , attoparsec
+                     , bytestring
+                     , containers
+                     , cryptohash
+                     , data-fix
+                     , hnix
+                     , http-conduit
+                     , mtl
+                     , text
+                     , zip-archive
+  default-language:    Haskell2010
+  ghc-options:        -Wall
+
+executable jenkinsPlugins2nix
+  hs-source-dirs:      app
+  main-is:             Main.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall
+  build-depends:       base
+                     , ansi-wl-pprint
+                     , jenkinsPlugins2nix
+                     , text
+  default-language:    Haskell2010
+
+test-suite jenkinsPlugins2nix-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  other-modules:       Nix.JenkinsPlugins2Nix.Tests.Parser
+  build-depends:       base
+                     , containers
+                     , jenkinsPlugins2nix
+                     , tasty-hspec
+                     , text
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/Fuuzetsu/jenkinsPlugins2nix
diff --git a/src/Nix/JenkinsPlugins2Nix.hs b/src/Nix/JenkinsPlugins2Nix.hs
new file mode 100644
--- /dev/null
+++ b/src/Nix/JenkinsPlugins2Nix.hs
@@ -0,0 +1,131 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module    : Nix.JenkinsPlugins2Nix.Types
+-- Copyright : (c) 2017 Mateusz Kowalczyk
+-- License   : BSD3
+--
+-- Main library entry point.
+module Nix.JenkinsPlugins2Nix where
+
+import qualified Codec.Archive.Zip as Zip
+import           Control.Monad (foldM)
+import qualified Control.Monad.Except as MTL
+import qualified Crypto.Hash as Hash
+import qualified Data.ByteString.Lazy as BSL
+import           Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+import           Data.Monoid ((<>))
+import           Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text.IO as Text
+import qualified Network.HTTP.Simple as HTTP
+import qualified Nix.Expr as Nix
+import qualified Nix.JenkinsPlugins2Nix.Parser as Parser
+import           Nix.JenkinsPlugins2Nix.Types
+import qualified Nix.Pretty as Nix
+import           System.IO (stderr)
+import qualified Text.PrettyPrint.ANSI.Leijen as Pretty
+import           Text.Printf (printf)
+
+-- | Get the download URL of the plugin we're looking for.
+getPluginUrl :: RequestedPlugin -> Text
+getPluginUrl (RequestedPlugin { requested_name = n, requested_version = Just v })
+  = Text.pack
+  $ printf "https://updates.jenkins-ci.org/download/plugins/%s/%s/%s.hpi"
+           (Text.unpack n) (Text.unpack v) (Text.unpack n)
+getPluginUrl (RequestedPlugin { requested_name = n, requested_version = Nothing })
+  = Text.pack
+  $ printf "https://updates.jenkins-ci.org/latest/%s.hpi"
+           (Text.unpack n)
+
+-- | Download a plugin from 'getPluginUrl'.
+downloadPlugin :: RequestedPlugin -> IO (Either String Plugin)
+downloadPlugin p = do
+  let fullUrl = getPluginUrl p
+  Text.hPutStrLn stderr $ "Downloading " <> fullUrl
+  req <- HTTP.parseRequest $ Text.unpack fullUrl
+  archiveLBS <- HTTP.getResponseBody <$> HTTP.httpLBS req
+  let manifestFileText = fmap (Text.decodeUtf8 . BSL.toStrict . Zip.fromEntry)
+                       $ Zip.findEntryByPath "META-INF/MANIFEST.MF"
+                       $ Zip.toArchive archiveLBS
+  case manifestFileText of
+    Nothing -> return $ Left "Could not find manifest file in the archive."
+    Just t -> return $! case Parser.runParseManifest t of
+      Left err -> Left err
+      Right manifest' -> Right $! Plugin
+           -- We have to account for user not specifying the version.
+           -- If they haven't, we downloaded the latest version and do
+           -- not have a URL pointing at static resource. We do
+           -- however have the version of the package now so we can
+           -- reconstruct the URL.
+        { download_url = getPluginUrl $
+            p { requested_version = Just $! plugin_version manifest' }
+        , sha256 = Hash.hashlazy archiveLBS
+        , manifest = manifest'
+        }
+
+-- | Download the given plugin as well as recursively download its dependencies.
+downloadPluginsRecursive :: Map Text Plugin -- ^ Already downloaded plugins.
+                         -> RequestedPlugin -- ^ Plugins we're asked to download.
+                         -> MTL.ExceptT String IO (Map Text Plugin)
+downloadPluginsRecursive m p = if Map.member (requested_name p) m
+  then return m
+  else do
+    plugin <- MTL.ExceptT $ downloadPlugin p
+    foldM (\m' p' -> downloadPluginsRecursive m' $
+              RequestedPlugin { requested_name = plugin_dependency_name p'
+                              , requested_version = Just $ plugin_dependency_version p'
+                              })
+      (Map.insert (requested_name p) plugin m)
+      (plugin_dependencies $ manifest plugin)
+
+-- | Pretty-print nix expression for all the given plugins and their
+-- dependencies that the user asked for.
+mkExprsFor :: [RequestedPlugin]
+           -> IO (Either String Pretty.Doc)
+mkExprsFor ps = do
+  eplugins <- MTL.runExceptT $ do
+    plugins <- foldM downloadPluginsRecursive Map.empty ps
+    return $ Map.elems plugins
+  return $! case eplugins of
+    Left err -> Left err
+    Right plugins ->
+      let args = Nix.mkParamset exprs
+          res = Nix.mkNonRecSet $ map formatPlugin plugins
+          mkJenkinsPlugin = Nix.bindTo "mkJenkinsPlugin" $
+            Nix.mkFunction (Nix.mkParamset
+                              [ ("name", Nothing)
+                              , ("src", Nothing)
+                              ]) $
+              Nix.mkApp (Nix.mkSym "stdenv.mkDerivation") $ Nix.mkNonRecSet
+                [ Nix.inherit [ Nix.StaticKey "name"
+                              , Nix.StaticKey "src" ]
+                , "phases" Nix.$= Nix.mkStr "installPhase"
+                , "installPhase" Nix.$= Nix.mkStr "cp $src $out"
+                ]
+      in return $ Nix.prettyNix
+                $ Nix.mkFunction args
+                $ Nix.mkLets [mkJenkinsPlugin] res
+
+  where
+    fetchurl :: Plugin -> Nix.NExpr
+    fetchurl p = Nix.mkApp (Nix.mkSym "fetchurl") $
+      Nix.mkNonRecSet [ "url" Nix.$= Nix.mkUri (download_url p)
+                      , "sha256" Nix.$= Nix.mkStr (Text.pack . show $ sha256 p)
+                      ]
+
+    mkBody :: Plugin -> Nix.NExpr
+    mkBody p = Nix.mkApp (Nix.mkSym "mkJenkinsPlugin") $
+      Nix.mkNonRecSet $ [ "name" Nix.$= Nix.mkStr (short_name $ manifest p)
+                        , "src" Nix.$= fetchurl p
+                        ]
+
+    formatPlugin :: Plugin -> Nix.Binding Nix.NExpr
+    formatPlugin p = short_name (manifest p) Nix.$= mkBody p
+
+    exprs :: [(Text, Maybe Nix.NExpr)]
+    exprs =
+      [ ("stdenv", Nothing)
+      , ("fetchurl", Nothing)
+      ]
diff --git a/src/Nix/JenkinsPlugins2Nix/Parser.hs b/src/Nix/JenkinsPlugins2Nix/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Nix/JenkinsPlugins2Nix/Parser.hs
@@ -0,0 +1,145 @@
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module    : Nix.JenkinsPlugins2Nix.Types
+-- Copyright : (c) 2017 Mateusz Kowalczyk
+-- License   : BSD3
+--
+-- Parsers.
+module Nix.JenkinsPlugins2Nix.Parser
+  ( parseManifest
+  , runParseManifest
+  ) where
+
+import           Control.Applicative
+import           Control.Monad (void)
+import qualified Data.Attoparsec.Text as A
+import           Data.Either (either)
+import           Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+import           Data.Monoid ((<>))
+import           Data.Set (Set)
+import qualified Data.Set as Set
+import           Data.Text (Text)
+import qualified Data.Text as Text
+import           Nix.JenkinsPlugins2Nix.Types
+
+-- | Run parser on manifest file contents.
+runParseManifest :: Text -> Either String Manifest
+runParseManifest = A.parseOnly parseManifest
+
+-- | 'Manifest' parser.
+parseManifest :: A.Parser Manifest
+parseManifest = do
+  kvs <- kvMap
+  let getKey :: Text -> Either String Text
+      getKey k = case Map.lookup k kvs of
+        Nothing -> Left $ "Could not find " <> Text.unpack k <> " in " <> show kvs
+        Just v -> Right v
+
+      getKeyParsing :: Text -> A.Parser a -> Either String a
+      getKeyParsing k p = getKey k >>= A.parseOnly p
+
+      eManifest :: Either String Manifest
+      eManifest = do
+        manifest_version' <- getKey "Manifest-Version"
+        archiver_version' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Archiver-Version"
+        created_by' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Created-By"
+        built_by' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Built-By"
+        build_jdk' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Build-Jdk"
+        extension_name' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Extension-Name"
+        specification_title' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Specification-Title"
+        implementation_title' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Implementation-Title"
+        implementation_version' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Implementation-Version"
+        group_id' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Group-Id"
+        short_name' <- getKey "Short-Name"
+        long_name' <- getKey "Long-Name"
+        url' <- getKey "Url"
+        plugin_version' <- getKey "Plugin-Version"
+        hudson_version' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Hudson-Version"
+        jenkins_version' <- either (\_ -> Right Nothing) (return . Just) $
+          getKey "Jenkins-Version"
+        plugin_dependencies' <- either (\_ -> Right Set.empty) return $
+          getKeyParsing "Plugin-Dependencies" parsePluginDependencies
+        plugin_developers' <- either (\_ -> Right Set.empty) return $
+          getKeyParsing "Plugin-Developers" parsePluginDevelopers
+        return $! Manifest
+          { manifest_version = manifest_version'
+          , archiver_version = archiver_version'
+          , created_by = created_by'
+          , built_by = built_by'
+          , build_jdk = build_jdk'
+          , extension_name = extension_name'
+          , specification_title = specification_title'
+          , implementation_title = implementation_title'
+          , implementation_version = implementation_version'
+          , group_id = group_id'
+          , short_name = short_name'
+          , long_name = long_name'
+          , url = url'
+          , plugin_version = plugin_version'
+          , hudson_version = hudson_version'
+          , jenkins_version = jenkins_version'
+          , plugin_dependencies = plugin_dependencies'
+          , plugin_developers = plugin_developers'
+          }
+  case eManifest of
+    Left err -> fail err
+    Right m -> return m
+  where
+    parsePluginDevelopers :: A.Parser (Set Text)
+    parsePluginDevelopers = Set.fromList . Text.splitOn "," <$> A.takeText
+
+    parsePluginDependencies :: A.Parser (Set PluginDependency)
+    parsePluginDependencies =
+      let plugin = do
+            name <- A.takeWhile1 (/= ':') <* A.char ':'
+            version <- A.takeWhile1 (\c -> c /= ',' && c /= ';')
+            resolution <- A.peekChar >>= \case
+              -- end of input
+              Nothing -> return Mandatory
+              -- next dependency
+              Just ',' -> A.char ',' *> return Mandatory
+              -- specifier
+              Just ';' -> do
+                _ <- A.string ";resolution:="
+                A.takeWhile1 (/= ',') >>= \case
+                  "optional" -> return Optional
+                  res' -> fail $ "Don't know how to parse resolution: " <> Text.unpack res'
+              Just c -> fail $ "plugin: expected , or ; but got: " <> [c]
+
+            -- Consume trailing comma if any
+            void (A.char ',') <|> return ()
+            return $! PluginDependency
+              { plugin_dependency_name = name
+              , plugin_dependency_version = version
+              , plugin_dependency_resolution = resolution
+              }
+      in Set.fromList <$> many plugin
+
+    -- Lines in manifest can span multiple file lines as long as they
+    -- are indented with a space on next physical line.
+    kvEntry :: A.Parser (Text, Text)
+    kvEntry = do
+      key <- A.takeWhile1 (/= ':')
+      A.anyChar *> A.skipSpace
+      let restOfLine = A.takeTill A.isEndOfLine <* (A.endOfLine <|> return ())
+          indentedLine = A.peekChar' >>= \case
+            ' ' -> A.skipSpace *> restOfLine
+            _ -> fail "indentedLine doesn't start with a space"
+
+      valueLines <- (:) <$> restOfLine <*> many indentedLine
+      return $! (key, Text.concat valueLines)
+
+    kvMap :: A.Parser (Map Text Text)
+    kvMap = Map.fromList <$> many kvEntry
diff --git a/src/Nix/JenkinsPlugins2Nix/Types.hs b/src/Nix/JenkinsPlugins2Nix/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Nix/JenkinsPlugins2Nix/Types.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE StrictData #-}
+-- |
+-- Module    : Nix.JenkinsPlugins2Nix.Types
+-- Copyright : (c) 2017 Mateusz Kowalczyk
+-- License   : BSD3
+--
+-- Types used through-out jenkinsPlugins2nix
+module Nix.JenkinsPlugins2Nix.Types
+  ( Manifest(..)
+  , Plugin(..)
+  , PluginDependency(..)
+  , PluginResolution(..)
+  , RequestedPlugin(..)
+  ) where
+
+import qualified Crypto.Hash as Hash
+import           Data.Set (Set)
+import           Data.Text (Text)
+
+-- | Plugin that user requested on the command line.
+data RequestedPlugin = RequestedPlugin
+  { -- | Name of the plugin.
+    requested_name :: !Text
+    -- | Possibly a specified version. If version not present, latest
+    -- version is downloaded then pinned.
+  , requested_version :: !(Maybe Text)
+  } deriving (Show, Eq, Ord)
+
+-- | Plugin resolution. Determines optional plugins.
+data PluginResolution = Mandatory | Optional
+  deriving (Show, Eq, Ord)
+
+-- | A dependency on another plugin.
+data PluginDependency = PluginDependency
+  { -- | Is the dependency optional?
+    plugin_dependency_resolution :: !PluginResolution
+    -- | Name of the dependency.
+  , plugin_dependency_name :: !Text
+    -- | Version of the dependency.
+  , plugin_dependency_version :: !Text
+  } deriving (Show, Eq, Ord)
+
+-- | All the information we need about the plugin to generate a nix
+-- expression.
+data Plugin = Plugin
+  { -- | Download location of the plugin.
+    download_url :: !Text
+    -- | Checksum.
+  , sha256 :: !(Hash.Digest Hash.SHA256)
+    -- | Manifest information of the plugin.
+  , manifest :: !Manifest
+  } deriving (Show, Eq, Ord)
+
+-- | Plugin meta-data.
+data Manifest = Manifest
+  { -- | @Manifest-Version@.
+    manifest_version :: !Text
+    -- | @Archiver-Version@.
+  , archiver_version :: !(Maybe Text)
+    -- | @Created-By@.
+  , created_by :: !(Maybe Text)
+    -- | @Built-By@.
+  , built_by :: !(Maybe Text)
+    -- | @Build-Jdk@.
+  , build_jdk :: !(Maybe Text)
+    -- | @Extension-Name@.
+  , extension_name :: !(Maybe Text)
+    -- | @Specification-Title@.
+  , specification_title :: !(Maybe Text)
+    -- | @Implementation-Title@.
+  , implementation_title :: !(Maybe Text)
+    -- | @Implementation-Version@.
+  , implementation_version :: !(Maybe Text)
+    -- | @Group-Id@.
+  , group_id :: !(Maybe Text)
+    -- | @Short-Name@.
+  , short_name :: !Text
+    -- | @Long-Name@.
+  , long_name :: !Text
+    -- | @Url@.
+  , url :: !Text
+    -- | @Plugin-Version@.
+  , plugin_version :: !Text
+    -- | @Hudson-Version@.
+  , hudson_version :: !(Maybe Text)
+    -- | @Jenkins-Version@.
+  , jenkins_version :: !(Maybe Text)
+    -- | @Plugin-Dependencies@.
+  , plugin_dependencies :: !(Set PluginDependency)
+    -- | @Plugin-Developers@.
+  , plugin_developers :: !(Set Text)
+  } deriving (Show, Eq, Ord)
diff --git a/test/Nix/JenkinsPlugins2Nix/Tests/Parser.hs b/test/Nix/JenkinsPlugins2Nix/Tests/Parser.hs
new file mode 100644
--- /dev/null
+++ b/test/Nix/JenkinsPlugins2Nix/Tests/Parser.hs
@@ -0,0 +1,101 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module    : Nix.JenkinsPlugins2Nix.Types
+-- Copyright : (c) 2017 Mateusz Kowalczyk
+-- License   : BSD3
+--
+-- Parser tests.
+module Nix.JenkinsPlugins2Nix.Tests.Parser (spec) where
+
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+import           Nix.JenkinsPlugins2Nix.Parser (runParseManifest)
+import           Nix.JenkinsPlugins2Nix.Types
+import qualified Test.Tasty.Hspec as Hspec
+
+spec :: Hspec.Spec
+spec = Hspec.describe "parser spec" $ do
+  Hspec.it "parser github-pullrequest manifest" githubPr
+
+githubPr :: Hspec.Expectation
+githubPr = Right expected `Hspec.shouldBe` runParseManifest githubPrManifestText
+  where
+    pluginDependencies :: Set.Set PluginDependency
+    pluginDependencies = Set.fromList
+      [ PluginDependency { plugin_dependency_resolution = Mandatory
+                         , plugin_dependency_name = "github"
+                         , plugin_dependency_version  = "1.22.1" }
+      , PluginDependency { plugin_dependency_resolution = Mandatory
+                         , plugin_dependency_name = "workflow-step-api"
+                         , plugin_dependency_version = "1.14" }
+      , PluginDependency { plugin_dependency_resolution = Optional
+                         , plugin_dependency_name = "block-queued-job"
+                         , plugin_dependency_version = "0.2.0" }
+      , PluginDependency { plugin_dependency_resolution = Optional
+                         , plugin_dependency_name = "email-ext"
+                         , plugin_dependency_version = "2.38.2" }
+      , PluginDependency { plugin_dependency_resolution = Mandatory
+                         , plugin_dependency_name = "github-api"
+                         , plugin_dependency_version = "1.80" }
+      , PluginDependency { plugin_dependency_resolution = Optional
+                         , plugin_dependency_name = "job-dsl"
+                         , plugin_dependency_version = "1.38" }
+      , PluginDependency { plugin_dependency_resolution = Optional
+                         , plugin_dependency_name = "jucies"
+                         , plugin_dependency_version = "0.2.1" }
+      , PluginDependency { plugin_dependency_resolution = Mandatory
+                         , plugin_dependency_name = "matrix-project"
+                         , plugin_dependency_version = "1.6" }
+      , PluginDependency { plugin_dependency_resolution = Optional
+                         , plugin_dependency_name = "token-macro"
+                         , plugin_dependency_version = "1.10" }
+      ]
+
+    expected :: Manifest
+    expected = Manifest
+      { manifest_version = "1.0"
+      , archiver_version = Just "Plexus Archiver"
+      , created_by = Just "Apache Maven"
+      , built_by = Just "integer"
+      , build_jdk = Just "1.8.0_112"
+      , extension_name = Just "github-pullrequest"
+      , specification_title = Just "GitHub Integration Plugin for Jenkins"
+      , implementation_title = Just "github-pullrequest"
+      , implementation_version = Just "0.1.0-rc24"
+      , group_id = Just "org.jenkins-ci.plugins"
+      , short_name = "github-pullrequest"
+      , long_name = "GitHub Integration Plugin"
+      , url = "https://wiki.jenkins-ci.org/display/JENKINS/GitHub+Integration+Plugin"
+      , plugin_version = "0.1.0-rc24"
+      , hudson_version = Just "1.625.3"
+      , jenkins_version = Just "1.625.3"
+      , plugin_dependencies = pluginDependencies
+      , plugin_developers = Set.fromList ["Kanstantsin Shautsou:KostyaSha:"]
+      }
+
+githubPrManifestText :: Text.Text
+githubPrManifestText = Text.intercalate "\r\n"
+  [ "Manifest-Version: 1.0"
+  , "Archiver-Version: Plexus Archiver"
+  , "Created-By: Apache Maven"
+  , "Built-By: integer"
+  , "Build-Jdk: 1.8.0_112"
+  , "Extension-Name: github-pullrequest"
+  , "Specification-Title: GitHub Integration Plugin for Jenkins"
+  , "Implementation-Title: github-pullrequest"
+  , "Implementation-Version: 0.1.0-rc24"
+  , "Group-Id: org.jenkins-ci.plugins"
+  , "Short-Name: github-pullrequest"
+  , "Long-Name: GitHub Integration Plugin"
+  , "Url: https://wiki.jenkins-ci.org/display/JENKINS/GitHub+Integration+Pl"
+  , " ugin"
+  , "Plugin-Version: 0.1.0-rc24"
+  , "Hudson-Version: 1.625.3"
+  , "Jenkins-Version: 1.625.3"
+  , "Plugin-Dependencies: github:1.22.1,workflow-step-api:1.14,block-queued"
+  , " -job:0.2.0;resolution:=optional,email-ext:2.38.2;resolution:=optional"
+  , " ,github-api:1.80,job-dsl:1.38;resolution:=optional,jucies:0.2.1;resol"
+  , " ution:=optional,matrix-project:1.6,token-macro:1.10;resolution:=optio"
+  , " nal"
+  , "Plugin-Developers: Kanstantsin Shautsou:KostyaSha:"
+  ]
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,8 @@
+module Main where
+
+import qualified Nix.JenkinsPlugins2Nix.Tests.Parser as Parser
+import qualified Test.Tasty.Hspec as Hspec
+
+main :: IO ()
+main = Hspec.hspec $ do
+  Parser.spec
