git.iko.soy/typeable/cabal2nix
on 9d40586 help sign in
86 lines 2.7 kB history raw
1{-# LANGUAGE DeriveGeneric #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4module Language.Nix.Identifier ( Identifier(..), ident, quote, needsQuoting ) where
6import Control.DeepSeq.Generics
7import Control.Lens
8import Data.Char
9import Data.Function ( on )
10import Data.String
11import Distribution.Nixpkgs.Util.PrettyPrinting ( Pretty(..), text )
12import Text.Regex.Posix
13import GHC.Generics ( Generic )
15-- | Identifiers in Nix are essentially strings. Reasonable people restrict
16-- themselves to identifiers of the form @[a-zA-Z\_][a-zA-Z0-9\_\'\-]*@,
17-- because these don't need quoting. The @Identifier@ type is an instance of
18-- the 'IsString' class for convenience. The methods of the 'Pretty' class can
19-- be used to pretty-print an identifier with proper quoting.
20--
21-- >>> let i = Identifier "test" in (i, pPrint i)
22-- (Identifier "test",test)
23-- >>> let i = Identifier "foo.bar" in (i, pPrint i)
24-- (Identifier "foo.bar","foo.bar")
25--
26-- The 'Ord' instance for identifiers is respects character case in less-than
27-- comparisons, etc.
28--
29-- >>> Identifier "abc" == Identifier "ABC"
30-- False
31-- >>> Identifier "abc" < Identifier "ABC"
32-- False
33-- >>> Identifier "abc" > Identifier "ABC"
34-- True
35-- >>> Identifier "X" > Identifier "a"
36-- True
37-- >>> Identifier "x" > Identifier "A"
38-- True
39--
40-- prop> \str -> Identifier str == Identifier str
41-- prop> \str -> any (`elem` ['a'..'z']) str ==> Identifier (map toLower str) /= Identifier (map toUpper str)
43newtype Identifier = Identifier String
44 deriving (Show, Eq, IsString, Generic)
46instance Pretty Identifier where
47 pPrint i = text (i ^. ident . to quote)
49instance Ord Identifier where
50 compare (Identifier a) (Identifier b) =
51 case (compare `on` map toLower) a b of
52 EQ -> compare a b
53 r -> r
55instance NFData Identifier where rnf = genericRnf
57-- | Checks whether a given string would need quoting when interpreted as an
58-- intentifier.
60needsQuoting :: String -> Bool
61needsQuoting str = not (str =~ grammar)
62 where grammar :: String -- TODO: should be a compiled regular expression
63 grammar = "^[a-zA-Z\\_][a-zA-Z0-9\\_\\'\\-]*$"
65-- | Lens that allows conversion from/to the standard 'String' type. The setter
66-- does not evaluate its input, so it's safe to use with 'undefined'.
67--
68-- >>> putStrLn $ Identifier "abc.def" ^. ident
69-- abc.def
70--
71-- >>> pPrint $ undefined & ident .~ "abcdef"
72-- abcdef
74ident :: Lens' Identifier String
75ident f (Identifier str) = Identifier `fmap` f str
77-- | Help function that quotes a given identifier string (if necessary).
78--
79-- >>> putStrLn (quote "abc")
80-- abc
81--
82-- >>> putStrLn (quote "abc.def")
83-- "abc.def"
85quote :: String -> String
86quote s = if needsQuoting s then show s else s