Skip to content

Commit

Permalink
Allow @ in range (#107)
Browse files Browse the repository at this point in the history
* allow @ sign in range. Fix failing git parse unit test. restructure.

* update README

* remove trace statement

* % -> @
  • Loading branch information
adnelson authored Feb 18, 2018
1 parent d142496 commit 819e2f7
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 130 deletions.
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ it immediately with `nix-build`.

```bash
$ git clone https://github.com/adnelson/nix-node-packages
$ nixfromnpm -o nix-node-packages -p 'the-package-I-need%the-version-I-need'
$ nixfromnpm -o nix-node-packages -p 'the-package-I-need@the-version-I-need'
$ nix-build nix-node-packages -A nodePackages.the-package-I-need_the-version-I-need
```

Expand All @@ -105,22 +105,20 @@ output path does exist, a package will only be fetched if a nix
expression for it doesn't already exist.

You can also specify a version bound on the packages you are fetching,
using `%`:
using `@`:

```bash
$ nixfromnpm -p package_name%version_bound -o /some/path
$ nixfromnpm -p package_name@version_bound -o /some/path
```

Any NPM version bound is valid; so for example:

```bash
$ nixfromnpm -p foo%0.8.6 -o /some/path
$ nixfromnpm -p 'foo%>=0.8 <0.9' -o /some/path
$ nixfromnpm -p 'foo%~1.0.0' -o /some/path
$ nixfromnpm -p foo@0.8.6 -o /some/path
$ nixfromnpm -p 'foo@>=0.8 <0.9' -o /some/path
$ nixfromnpm -p 'foo@~1.0.0' -o /some/path
```

(Note that we're using a `%` instead of a `@` to indicate a version range).

#### Generating an expression from a package.json file

You can also generate an expression for a project on the local disk by
Expand Down Expand Up @@ -249,7 +247,7 @@ the most common ones are:
* See what version range `nixfromnpm` failed to resolve. E.g. `foo@>=1.2.3-bar <2.3.4-baz.qux`.
* Use `npm` to manually build the package at the given version bounds. E g. `npm install foo@>=1.2.3-bar <2.3.4-baz.qux`.
* See what version it ends up building. E.g. `[email protected]`.
* Call `nixfromnpm` on that version. E.g. `nixfromnpm -o /path/to/nix-node-packages -p 'foo%1.2.3-xyz'`.
* Call `nixfromnpm` on that version. E.g. `nixfromnpm -o /path/to/nix-node-packages -p 'foo@1.2.3-xyz'`.
* Replace the call to `brokenPackage` with `foo_1-2-3-xyz`.
* The build fails with `npm` complaining about HTTP errors. This is usually caused by a dependency that wasn't satified, likely because `nixfromnpm` calculated the wrong dependency. In this case, use steps similar to the above to find out what the actual dependency should be, and modify the package definition to include the correct one.
* A package build script is attempting to do some hacky bullshit like modifying its dependencies. This, of course, is not kosher in the `nix` view of things. In this case, you'll probably want to `nix-shell` into the package and see what it's trying to do. Figure out how to stop it from doing these things, and supply `prePatch` or `postPatch` steps to apply those changes.
Expand Down
13 changes: 6 additions & 7 deletions nixfromnpm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,23 @@ source-repository head
location: git://github.com/adnelson/nixfromnpm.git

library
default-language: Haskell2010
default-language: Haskell2010
hs-source-dirs: src
exposed-modules: NixFromNpm
, NixFromNpm.Cli
, NixFromNpm.Common
, NixFromNpm.Options
, NixFromNpm.Conversion.ToDisk
, NixFromNpm.Git.Types
, NixFromNpm.Npm.Version
, NixFromNpm.Merge
, NixFromNpm.Npm.PackageMap
other-modules: Filesystem.Path.Wrappers
, NixFromNpm.Parsers.Common
, NixFromNpm.HttpTools
, NixFromNpm.Conversion.ToNix
, NixFromNpm.Npm.Resolve
, NixFromNpm.Npm.Types
, NixFromNpm.Npm.PackageMap
, Paths_nixfromnpm
other-extensions: LambdaCase
, NoImplicitPrelude
other-extensions: NoImplicitPrelude
build-depends: base >=4.8 && < 5.0
, classy-prelude
, text
Expand Down Expand Up @@ -83,7 +81,8 @@ library
default-language: Haskell2010

executable nixfromnpm
default-language: Haskell2010
default-language: Haskell2010
other-extensions: NoImplicitPrelude
main-is: src/Main.hs
build-depends: base >=4.8 && < 5.0
, optparse-applicative
Expand Down
1 change: 1 addition & 0 deletions release.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ let
shellHook = builtins.trace src ((oldDerivation.shellHook or "") + ''
export SRC=${src}
export CURL_CA_BUNDLE=${newPkgs.cacert}/etc/ssl/certs/ca-bundle.crt
export NIX_LIBS_DIR=$PWD/nix-libs
'');
});
};
Expand Down
34 changes: 3 additions & 31 deletions src/Main.hs
Original file line number Diff line number Diff line change
@@ -1,37 +1,9 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where

import qualified Options.Applicative as O
import System.Environment (getArgs)
import System.Exit
import System.Exit (exitWith)

import NixFromNpm.Common hiding (getArgs)
import NixFromNpm.Options (NixFromNpmOptions, parseOptions,
validateOptions)
import NixFromNpm.Conversion.ToDisk (dumpPkgFromOptions)
import NixFromNpm.Merge (mergeInto, MergeType(..), Source(..), Dest(..))

customExecParser_ :: O.ParserInfo a -> [String] -> IO a
customExecParser_ pinfo args = do
let result = O.execParserPure O.defaultPrefs pinfo args
O.handleParseResult result

mainFromArgs :: [String] -> IO a
mainFromArgs args = do
let pInfo = O.info (O.helper <*> parseOptions)
(O.fullDesc <> O.progDesc description <> O.header headerText)

parsedOpts <- customExecParser_ pInfo args
validatedOpts <- validateOptions parsedOpts
exitWith =<< dumpPkgFromOptions validatedOpts
where
description = concat ["nixfromnpm allows you to generate nix expressions ",
"automatically from npm packages. It provides ",
"features such as de-duplication of shared ",
"dependencies and advanced customization."]
headerText = "nixfromnpm - Create nix expressions from NPM"
import NixFromNpm.Cli (runWithArgs)

main :: IO ()
main = mainFromArgs =<< getArgs
main = exitWith =<< runWithArgs =<< getArgs
35 changes: 35 additions & 0 deletions src/NixFromNpm/Cli.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- | The nixfromnpm command-line interface
{-# LANGUAGE NoImplicitPrelude #-}
module NixFromNpm.Cli (runWithArgs) where

import qualified Options.Applicative as O
import System.Environment (getArgs)
import System.Exit (ExitCode)

import NixFromNpm.Common hiding (getArgs)
import NixFromNpm.Options (NixFromNpmOptions, parseOptions,
validateOptions)
import NixFromNpm.Conversion.ToDisk (dumpPkgFromOptions)
import NixFromNpm.Merge (mergeInto, MergeType(..), Source(..), Dest(..))

-- | Execute an argument parser with a list of arguments.
customExecParser_ :: O.ParserInfo a -> [String] -> IO a
customExecParser_ pinfo args = do
let result = O.execParserPure O.defaultPrefs pinfo args
O.handleParseResult result

-- | Execute the CLI with an argument list, returning an exit code.
runWithArgs :: [String] -> IO ExitCode
runWithArgs args = do
let pInfo = O.info (O.helper <*> parseOptions)
(O.fullDesc <> O.progDesc description <> O.header headerText)

parsedOpts <- customExecParser_ pInfo args
validatedOpts <- validateOptions parsedOpts
dumpPkgFromOptions validatedOpts
where
description = concat ["nixfromnpm allows you to generate nix expressions ",
"automatically from npm packages. It provides ",
"features such as de-duplication of shared ",
"dependencies and advanced customization."]
headerText = "nixfromnpm - Create nix expressions from NPM"
3 changes: 1 addition & 2 deletions src/NixFromNpm/Git/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ instance Show GitIdentifier where
proto = toLower (show source) <> "://"
proto <> unpack (account <> "/" <> repo <> ref')


data GithubError
= GithubUnreachable
| InvalidJsonFromGithub Text
Expand Down Expand Up @@ -129,7 +128,7 @@ sourceFromServer regname

-- | Get the repo owner and repo name from a URI path.
ownerRepoFromPath :: String -> Maybe (Name, Name)
ownerRepoFromPath path = case scan [re|^/(\w+)/(\w+)$|] $ pack path of
ownerRepoFromPath path = case scan [re|^/([\w_-]+)/([\w_-]+)$|] $ pack path of
[(_, [owner, repo])] -> Just (owner, repo)
_ -> Nothing

Expand Down
36 changes: 35 additions & 1 deletion src/NixFromNpm/Npm/Version.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module NixFromNpm.Npm.Version where

import qualified Data.Text as T
Expand All @@ -9,7 +10,6 @@ import Data.SemVer
import Data.Aeson
import qualified Data.Aeson.Types as Aeson


import NixFromNpm.Common
import NixFromNpm.Npm.PackageMap
import NixFromNpm.Git.Types hiding (Tag)
Expand Down Expand Up @@ -86,3 +86,37 @@ instance FromJSON NpmVersionRange where
Nothing -> return $ InvalidVersion s
Just range -> return range
_ -> Aeson.typeMismatch "string" v

-- | A package name can be passed in directly, or a version range can be
-- specified with a @.
parseNameAndRange :: MonadIO m => Text -> m (PackageName, NpmVersionRange)
parseNameAndRange name = do
let badFormat err = UnrecognizedVersionFormat (name <> " (" <> err <> ")")

let parseName n = case parsePackageName n of
Left err -> throw $ badFormat err
Right pkgName -> pure pkgName

let parseRange r = case parseNpmVersionRange r of
Nothing -> throw $ VersionSyntaxError r
Just range -> pure range

case T.split (== '@') name of
-- No namespace, no version range
[_] -> (, SemVerRange anyVersion) <$> parseName name

-- Namespace but no version range
["", _] -> (, SemVerRange anyVersion) <$> parseName name

-- Namespace and range
"" : name' : ranges ->
-- In case a '@' appears in the range, treat the range as a list
-- and join on '@'
(,) <$> parseName ("@" <> name') <*> parseRange (joinBy "@" ranges)

-- No namespace, but with range
name' : ranges ->
(,) <$> parseName name' <*> parseRange (joinBy "@" ranges)

-- Anything else is invalid.
_ -> throw $ badFormat "Not in format <name> or <name>@<range>"
35 changes: 8 additions & 27 deletions src/NixFromNpm/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
module NixFromNpm.Options where
-- RawOptions(..), NixFromNpmOptions(..),
-- parseOptions, validateOptions
-- ) where

import qualified Prelude as P
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.HashMap.Strict as H

import Data.SemVer (anyVersion)
import Options.Applicative
import Options.Applicative (Mod, OptionFields, Parser, value, switch)
import Options.Applicative (help, long, metavar, short, auto, option, strOption)

import NixFromNpm.Npm.Resolve (getNpmTokens, parseNpmTokens)
import NixFromNpm.Npm.PackageMap (PackageName(..), parsePackageName)
import NixFromNpm.Npm.Version
import NixFromNpm.Conversion.ToNix (nodePackagesDir)
import NixFromNpm.Common
import NixFromNpm.Conversion.ToNix (nodePackagesDir)
import NixFromNpm.Npm.PackageMap (PackageName(..))
import NixFromNpm.Npm.Resolve (getNpmTokens, parseNpmTokens)
import NixFromNpm.Npm.Version (NpmVersionError, NpmVersionRange)
import NixFromNpm.Npm.Version (parseNameAndRange)

-- | Errors about node libraries
data InvalidNodeLib
Expand Down Expand Up @@ -150,25 +150,6 @@ validateJsPkg = absPath >=> \path -> doesDirectoryExist path >>= \case
assert (doesFileExist path) (NoPackageJsonFoundAt path)
return (parent path)

-- | A package name can be passed in directly, or a version range can be
-- specified with a %.
parseNameAndRange :: MonadIO m => Text -> m (PackageName, NpmVersionRange)
parseNameAndRange name = do
let badFormat err = UnrecognizedVersionFormat (name <> " (" <> err <> ")")
case T.split (== '%') name of
-- Just a name, no version range
[name'] -> case parsePackageName name' of
Left err -> throw $ NpmVersionError $ badFormat err
Right pkgName -> return (pkgName, SemVerRange anyVersion)
-- If a @ occurs in the middle, treat it as a name and range identifier.
[name', range] -> case parseNpmVersionRange range of
Nothing -> throw $ NpmVersionError (VersionSyntaxError range)
Just nrange -> case parsePackageName name' of
Left err -> throw $ NpmVersionError $ badFormat err
Right pkgName -> return (pkgName, nrange)
-- Anything else is invalid.
_ -> throw $ NpmVersionError $ badFormat "?"

-- | Get a list of the top n packages. If n is negative, or too large, we'll
-- return all of the packages we're aware of. If it's too large,
getTopN :: MonadIO io => Maybe Int -> io [(PackageName, NpmVersionRange)]
Expand Down
52 changes: 0 additions & 52 deletions src/NixFromNpm/Parsers/Common.hs

This file was deleted.

26 changes: 25 additions & 1 deletion tests/Unit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import qualified Data.Text as T

import NixFromNpm
import NixFromNpm.Git.Types as Git
import NixFromNpm.Npm.PackageMap (PackageName(..))
import NixFromNpm.Npm.Version as Npm

shouldBeR :: (Eq a, Eq b, Show a, Show b) => Either a b -> b -> Expectation
Expand Down Expand Up @@ -82,7 +83,30 @@ npmVersionParserSpec = describe "npm version parser" $ do
parseNpmVersionRange "../foo/bar" `shouldBeJ` LocalPath "../foo/bar"
parseNpmVersionRange "~/foo/bar" `shouldBeJ` LocalPath "~/foo/bar"

npmNameAndVersionParserSpec :: Spec
npmNameAndVersionParserSpec = describe "npm name@version parser" $ do
it "should parse a name with no version range" $ do
(name, range) <- parseNameAndRange "foo"
name `shouldBe` "foo"
range `shouldBe` SemVerRange anyVersion

it "should parse a namespaced name with no version range" $ do
(name, range) <- parseNameAndRange "@foo/bar"
name `shouldBe` PackageName "bar" (Just "foo")
range `shouldBe` SemVerRange anyVersion

it "should parse a name and a version range" $ do
(name, range) <- parseNameAndRange "[email protected]"
name `shouldBe` "foo"
range `shouldBe` SemVerRange (Eq $ semver 1 2 3)

it "should parse a namespaced name and a version range" $ do
(name, range) <- parseNameAndRange "@foo/[email protected]"
name `shouldBe` PackageName "bar" (Just "foo")
range `shouldBe` SemVerRange (Eq $ semver 1 2 3)

main :: IO ()
main = hspec $ do
-- npmVersionParserSpec
npmVersionParserSpec
npmNameAndVersionParserSpec
gitIdParsingSpec

0 comments on commit 819e2f7

Please sign in to comment.