Skip to content

Commit 59c31e6

Browse files
author
Allen Nelson
committed
initial commit, handling semvers
0 parents  commit 59c31e6

File tree

7 files changed

+218
-0
lines changed

7 files changed

+218
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

LICENSE

Whitespace-only changes.

Setup.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Distribution.Simple
2+
main = defaultMain

npm2nixhs.cabal

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- Initial npm2nixhs.cabal generated by cabal init. For further
2+
-- documentation, see http://haskell.org/cabal/users-guide/
3+
4+
name: npm2nixhs
5+
version: 0.1.0.0
6+
-- synopsis:
7+
-- description:
8+
-- license:
9+
license-file: LICENSE
10+
author: Allen Nelson
11+
maintainer: [email protected]
12+
-- copyright:
13+
-- category:
14+
build-type: Simple
15+
-- extra-source-files:
16+
cabal-version: >=1.10
17+
18+
executable npm2nixhs
19+
main-is: Main.hs
20+
-- other-modules:
21+
other-extensions: NoImplicitPrelude, OverloadedStrings
22+
build-depends: base >=4.7 && <4.8, classy-prelude, text, unordered-containers, parsec
23+
hs-source-dirs: src
24+
default-language: Haskell2010

src/Main.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
5+
import SemVer
6+
import ParseSemVer
7+
import ClassyPrelude
8+
9+
--type DependencySet = HashMap Name Version
10+
main = putStrLn "hey"

src/ParseSemVer.hs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
module ParseSemVer where
5+
6+
import ClassyPrelude hiding (try)
7+
import Control.Applicative
8+
import qualified Prelude as P
9+
import Prelude (String)
10+
import Text.Parsec hiding (many, (<|>), spaces, parse)
11+
import qualified Text.Parsec as Parsec
12+
import SemVer
13+
14+
type Parser = ParsecT String () Identity
15+
16+
-- | Parse a string as a version range, or return an error.
17+
parseSemVerRange :: String -> Either ParseError SemVerRange
18+
parseSemVerRange = parse (pSemVerRange <* eof)
19+
20+
parse :: Parser a -> String -> Either ParseError a
21+
parse p = Parsec.parse p ""
22+
23+
spaces :: Parser String
24+
spaces = many $ char ' '
25+
26+
spaces1 :: Parser String
27+
spaces1 = many1 $ char ' '
28+
29+
sym :: String -> Parser String
30+
sym s = string s <* spaces
31+
32+
cmp :: Parser String
33+
cmp = choice $ fmap (try . sym) [">=", "<=", ">", "<", "="]
34+
35+
pNum :: Parser Int
36+
pNum = (P.read <$> many1 digit) <* spaces
37+
38+
-- | Parses a semantic version.
39+
pSemVer :: Parser SemVer
40+
pSemVer = (,,) <$> pNum <*> (sym "." *> pNum) <*> (sym "." *> pNum)
41+
42+
-- | Parses versions with an explicit range qualifier (gt, lt, etc).
43+
pSemVerRangeSingle :: Parser SemVerRange
44+
pSemVerRangeSingle = choice [wildcardToRange <$> pWildCard,
45+
tildeToRange <$> pTildeRange,
46+
caratToRange <$> pCaratRange,
47+
Eq <$> (sym "=" *> pSemVer),
48+
Gt <$> (sym ">" *> pSemVer),
49+
Lt <$> (sym "<" *> pSemVer),
50+
Geq <$> (sym ">=" *> pSemVer),
51+
Leq <$> (sym "<=" *> pSemVer)
52+
]
53+
54+
-- | Joins semantic version ranges with Ands and Ors.
55+
pJoinedSemVerRange :: Parser SemVerRange
56+
pJoinedSemVerRange = do
57+
first <- pSemVerRangeSingle
58+
option first $ do
59+
lookAhead (sym "||" <|> cmp) >>= \case
60+
"||" -> Or first <$> (sym "||" *> pJoinedSemVerRange)
61+
_ -> And first <$> pJoinedSemVerRange
62+
63+
-- | Translates a hyphenated range to an actual range.
64+
-- Ex: 1.2.3 - 2.3.4 := >=1.2.3 <=2.3.4
65+
-- Ex: 1.2 - 2.3.4 := >=1.2.0 <=2.3.4
66+
-- Ex: 1.2.3 - 2 := >=1.2.3 <3.0.0
67+
pHyphen :: Parser SemVerRange
68+
pHyphen = do
69+
wc1 <- pWildCard
70+
sym "-"
71+
wc2 <- pWildCard
72+
let sv1 = case wc1 of
73+
Any -> Geq (0, 0, 0)
74+
One n -> Geq (n, 0, 0)
75+
Two n m -> Geq (n, m, 0)
76+
Three n m o -> Geq (n, m, o)
77+
let sv2 = case wc2 of
78+
Any -> Geq (0, 0, 0) -- Refers to "any version"
79+
One n -> Lt (n+1, 0, 0)
80+
Two n m -> Lt (n, m + 1, 0)
81+
Three n m o -> Leq (n, m, o)
82+
return $ And sv1 sv2
83+
84+
85+
-- | Parses a "wildcard" (which is a possibly partial semantic version).
86+
pWildCard :: Parser Wildcard
87+
pWildCard = try $ do
88+
let bound = choice [sym "x" *> pure Nothing, Just <$> pNum]
89+
sepBy1 bound (sym ".") >>= \case
90+
[Nothing] -> return Any
91+
[Just n] -> return $ One n
92+
[Just n, Nothing] -> return $ One n
93+
[Just n, Just m] -> return $ Two n m
94+
[Just n, Just m, Nothing] -> return $ Two n m
95+
[Just n, Just m, Just o] -> return $ Three n m o
96+
w -> unexpected ("Invalid version " ++ show w)
97+
98+
pTildeRange :: Parser Wildcard
99+
pTildeRange = sym "~" *> pWildCard
100+
101+
pCaratRange :: Parser Wildcard
102+
pCaratRange = sym "^" *> pWildCard
103+
104+
pSemVerRange :: Parser SemVerRange
105+
pSemVerRange = pJoinedSemVerRange <|> pHyphen

src/SemVer.hs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
module SemVer where
5+
6+
import ClassyPrelude hiding (try)
7+
8+
type SemVer = (Int, Int, Int)
9+
10+
-- | A partially specified semantic version. Implicitly defines
11+
-- a range of acceptable versions, as seen in @wildcardToRange@.
12+
data Wildcard = Any
13+
| One Int
14+
| Two Int Int
15+
| Three Int Int Int
16+
deriving (Show, Eq)
17+
18+
data SemVerRange
19+
= Eq SemVer
20+
| Gt SemVer
21+
| Lt SemVer
22+
| Geq SemVer
23+
| Leq SemVer
24+
| And SemVerRange SemVerRange
25+
| Or SemVerRange SemVerRange
26+
deriving (Show, Eq)
27+
28+
29+
-- | Returns whether a given semantic version matches a range.
30+
matches :: SemVerRange -> SemVer -> Bool
31+
matches range ver = case range of
32+
Eq sv -> ver == sv
33+
Gt sv -> ver > sv
34+
Lt sv -> ver < sv
35+
Geq sv -> ver >= sv
36+
Leq sv -> ver <= sv
37+
And sv1 sv2 -> matches sv1 ver && matches sv2 ver
38+
Or sv1 sv2 -> matches sv1 ver || matches sv2 ver
39+
40+
-- | Fills in zeros in a wildcard.
41+
wildcardToSemver :: Wildcard -> SemVer
42+
wildcardToSemver Any = (0, 0, 0)
43+
wildcardToSemver (One n) = (n, 0, 0)
44+
wildcardToSemver (Two n m) = (n, m, 0)
45+
wildcardToSemver (Three n m o) = (n, m, o)
46+
47+
48+
-- | Translates a wildcard (partially specified version) to a range.
49+
-- Ex: 2 := >=2.0.0 <3.0.0
50+
-- Ex: 1.2.x := 1.2 := >=1.2.0 <1.3.0
51+
wildcardToRange :: Wildcard -> SemVerRange
52+
wildcardToRange = \case
53+
Any -> Geq (0, 0, 0)
54+
One n -> Geq (n, 0, 0) `And` Lt (n+1, 0, 0)
55+
Two n m -> Geq (n, m, 0) `And` Lt (n, m + 1, 0)
56+
Three n m o -> Eq (n, m, o)
57+
58+
-- | Translates a ~wildcard to a range.
59+
-- Ex: ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0
60+
tildeToRange :: Wildcard -> SemVerRange
61+
tildeToRange = \case
62+
Any -> tildeToRange (Three 0 0 0)
63+
One n -> tildeToRange (Three n 0 0)
64+
Two n m -> tildeToRange (Three n m 0)
65+
Three n m o -> And (Geq (n, m, o)) (Lt (n, m + 1, 0))
66+
67+
68+
-- | Translates a ^wildcard to a range.
69+
-- Ex: ^1.2.x := >=1.2.0 <2.0.0
70+
caratToRange :: Wildcard -> SemVerRange
71+
caratToRange = \case
72+
One n -> And (Geq (n, 0, 0)) (Lt (n+1, 0, 0))
73+
Two n m -> And (Geq (n, m, 0)) (Lt (n+1, 0, 0))
74+
Three 0 0 n -> Eq (0, 0, n)
75+
Three 0 n m -> And (Geq (0, n, m)) (Lt (0, n + 1, 0))
76+
Three n m o -> And (Geq (n, m, o)) (Lt (n+1, 0, 0))

0 commit comments

Comments
 (0)