|
| 1 | +{-# LANGUAGE OverlappingInstances #-} |
| 2 | +{-# LANGUAGE FlexibleInstances #-} |
| 3 | +{-# LANGUAGE DeriveGeneric #-} |
| 4 | +{-# LANGUAGE DerivingVia, TypeOperators #-} |
| 5 | +{-# LANGUAGE ScopedTypeVariables #-} |
| 6 | +{-# LANGUAGE UndecidableInstances #-} |
| 7 | +{-# LANGUAGE DeriveAnyClass #-} |
| 8 | +{-# LANGUAGE DataKinds #-} |
| 9 | + |
| 10 | +module Gen where |
| 11 | + |
| 12 | +import System.FilePath |
| 13 | +import Data.List.NonEmpty (NonEmpty(..)) |
| 14 | +import GHC.Generics |
| 15 | +import Generic.Random |
| 16 | +import Generics.Deriving.Show |
| 17 | +import Prelude as P |
| 18 | +import Test.Tasty.QuickCheck hiding ((==>)) |
| 19 | + |
| 20 | +import qualified Data.List.NonEmpty as NE |
| 21 | + |
| 22 | + |
| 23 | +class AltShow a where |
| 24 | + altShow :: a -> String |
| 25 | + |
| 26 | +instance {-# OVERLAPPABLE #-} Show a => AltShow a where |
| 27 | + altShow = show |
| 28 | + |
| 29 | +instance {-# OVERLAPS #-} AltShow String where |
| 30 | + altShow = id |
| 31 | + |
| 32 | +instance {-# OVERLAPPABLE #-} AltShow a => AltShow (Maybe a) where |
| 33 | + altShow Nothing = "" |
| 34 | + altShow (Just a) = altShow a |
| 35 | + |
| 36 | + |
| 37 | +newtype WindowsFilePaths = WindowsFilePaths { unWindowsFilePaths :: [WindowsFilePath] } |
| 38 | + deriving (Show, Eq, Ord, Generic) |
| 39 | + |
| 40 | +-- filepath = namespace *"\" namespace-tail |
| 41 | +-- / UNC |
| 42 | +-- / [ disk ] *"\" relative-path |
| 43 | +-- / disk *"\" |
| 44 | +data WindowsFilePath = NS NameSpace [Separator] NSTail |
| 45 | + | UNC UNCShare |
| 46 | + | N (Maybe Char) [Separator] (Maybe RelFilePath) |
| 47 | + -- ^ This differs from the grammar, because we allow |
| 48 | + -- empty paths |
| 49 | + | PotentiallyInvalid FilePath |
| 50 | + -- ^ this branch is added purely for the tests |
| 51 | + deriving (GShow, Eq, Ord, Generic) |
| 52 | + deriving Arbitrary via (GenericArbitraryRec '[6, 2, 2, 1] `AndShrinking` WindowsFilePath) |
| 53 | + |
| 54 | +instance Show WindowsFilePath where |
| 55 | + show wf = gshow wf ++ " (" ++ altShow wf ++ ")" |
| 56 | + |
| 57 | +instance AltShow WindowsFilePath where |
| 58 | + altShow (NS ns seps nstail) = altShow ns ++ altShow seps ++ altShow nstail |
| 59 | + altShow (UNC unc) = altShow unc |
| 60 | + altShow (N mdisk seps mfrp) = maybe [] (:[]) mdisk ++ (altShow seps ++ altShow mfrp) |
| 61 | + altShow (PotentiallyInvalid fp) = fp |
| 62 | + |
| 63 | + |
| 64 | +-- namespace-tail = ( disk 1*"\" relative-path ; C:foo\bar is not valid |
| 65 | +-- ; namespaced paths are all absolute |
| 66 | +-- / disk *"\" |
| 67 | +-- / relative-path |
| 68 | +-- ) |
| 69 | +data NSTail = NST1 Char (NonEmpty Separator) RelFilePath |
| 70 | + | NST2 Char [Separator] |
| 71 | + | NST3 RelFilePath |
| 72 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 73 | + deriving Arbitrary via (GenericArbitraryRec '[1, 1, 1] `AndShrinking` NSTail) |
| 74 | + |
| 75 | +instance AltShow NSTail where |
| 76 | + altShow (NST1 disk seps relfp) = disk:':':(altShow seps ++ altShow relfp) |
| 77 | + altShow (NST2 disk seps) = disk:':':altShow seps |
| 78 | + altShow (NST3 relfp) = altShow relfp |
| 79 | + |
| 80 | + |
| 81 | +-- UNC = "\\" 1*pchar "\" 1*pchar [ 1*"\" [ relative-path ] ] |
| 82 | +data UNCShare = UNCShare Separator Separator |
| 83 | + NonEmptyString |
| 84 | + (NonEmpty Separator) |
| 85 | + NonEmptyString |
| 86 | + (Maybe (NonEmpty Separator, Maybe RelFilePath)) |
| 87 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 88 | + deriving Arbitrary via (GenericArbitraryRec '[1] `AndShrinking` UNCShare) |
| 89 | + |
| 90 | +instance AltShow UNCShare where |
| 91 | + altShow (UNCShare sep1 sep2 fp1 seps fp2 mrfp) = altShow sep1 ++ altShow sep2 ++ altShow fp1 ++ altShow seps ++ altShow fp2 ++ maybe "" (\(a, b) -> altShow a ++ maybe "" altShow b) mrfp |
| 92 | + |
| 93 | +newtype NonEmptyString = NonEmptyString (NonEmpty Char) |
| 94 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 95 | + deriving Arbitrary via (GenericArbitraryRec '[1] `AndShrinking` NonEmptyString) |
| 96 | + |
| 97 | +instance Semigroup NonEmptyString where |
| 98 | + (<>) (NonEmptyString ne) (NonEmptyString ne') = NonEmptyString (ne <> ne') |
| 99 | + |
| 100 | +instance AltShow NonEmptyString where |
| 101 | + altShow (NonEmptyString ns) = NE.toList ns |
| 102 | + |
| 103 | + |
| 104 | +-- | Windows API Namespaces |
| 105 | +-- |
| 106 | +-- https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#namespaces |
| 107 | +-- https://support.microsoft.com/en-us/topic/70b92942-a643-2f2d-2ac6-aad8acad49fb |
| 108 | +-- https://superuser.com/a/1096784/854039 |
| 109 | +-- https://reverseengineering.stackexchange.com/a/15178 |
| 110 | +-- https://stackoverflow.com/a/25099634 |
| 111 | +-- |
| 112 | +-- namespace = file-namespace / device-namespace / nt-namespace |
| 113 | +-- file-namespace = "\" "\" "?" "\" |
| 114 | +-- device-namespace = "\" "\" "." "\" |
| 115 | +-- nt-namespace = "\" "?" "?" "\" |
| 116 | +data NameSpace = FileNameSpace |
| 117 | + | DeviceNameSpace |
| 118 | + | NTNameSpace |
| 119 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 120 | + deriving Arbitrary via (GenericArbitraryRec '[3, 1, 1] `AndShrinking` NameSpace) |
| 121 | + |
| 122 | +instance AltShow NameSpace where |
| 123 | + altShow FileNameSpace = "\\\\?\\" |
| 124 | + altShow DeviceNameSpace = "\\\\.\\" |
| 125 | + altShow NTNameSpace = "\\??\\" |
| 126 | + |
| 127 | + |
| 128 | +data Separator = UnixSep |
| 129 | + | WindowsSep |
| 130 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 131 | + deriving Arbitrary via (GenericArbitraryRec '[1, 1] `AndShrinking` Separator) |
| 132 | + |
| 133 | +instance AltShow Separator where |
| 134 | + altShow UnixSep = "/" |
| 135 | + altShow WindowsSep = "\\" |
| 136 | + |
| 137 | +instance {-# OVERLAPS #-} AltShow (NonEmpty Separator) where |
| 138 | + altShow ne = mconcat $ NE.toList (altShow <$> ne) |
| 139 | + |
| 140 | +instance {-# OVERLAPS #-} AltShow [Separator] where |
| 141 | + altShow [] = "" |
| 142 | + altShow ne = altShow (NE.fromList ne) |
| 143 | + |
| 144 | +-- relative-path = 1*(path-name 1*"\") [ file-name ] / file-name |
| 145 | +data RelFilePath = Rel1 (NonEmpty (NonEmptyString, NonEmpty Separator)) (Maybe FileName) |
| 146 | + | Rel2 FileName |
| 147 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 148 | + deriving Arbitrary via (GenericArbitraryRec '[2, 1] `AndShrinking` RelFilePath) |
| 149 | + |
| 150 | +instance AltShow RelFilePath where |
| 151 | + altShow (Rel1 ns mf) = (mconcat $ NE.toList $ fmap (\(a, b) -> altShow a ++ altShow b) ns) ++ altShow mf |
| 152 | + altShow (Rel2 fn) = altShow fn |
| 153 | + |
| 154 | +-- file-name = 1*pchar [ stream ] |
| 155 | +data FileName = FileName NonEmptyString (Maybe DataStream) |
| 156 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 157 | + |
| 158 | +instance Arbitrary FileName where |
| 159 | + arbitrary = do |
| 160 | + ns <- arbitrary |
| 161 | + ds <- arbitrary |
| 162 | + i <- chooseInt (0, 100) |
| 163 | + if i >= 50 |
| 164 | + then do |
| 165 | + ns' <- arbitrary |
| 166 | + pure $ FileName (ns <> NonEmptyString ('.':|[]) <> ns') ds |
| 167 | + else pure $ FileName ns ds |
| 168 | + shrink = genericShrink |
| 169 | + |
| 170 | + |
| 171 | +instance Arbitrary (Maybe DataStream) where |
| 172 | + arbitrary = genericArbitraryRec (1 % 1 % ()) |
| 173 | + shrink = genericShrink |
| 174 | + |
| 175 | +instance AltShow FileName where |
| 176 | + altShow (FileName ns ds) = altShow ns ++ altShow ds |
| 177 | + |
| 178 | +-- stream = ":" 1*schar [ ":" 1*schar ] / ":" ":" 1*schar |
| 179 | +data DataStream = DS1 NonEmptyString (Maybe NonEmptyString) |
| 180 | + | DS2 NonEmptyString -- ::datatype |
| 181 | + deriving (GShow, Show, Eq, Ord, Generic) |
| 182 | + deriving Arbitrary via (GenericArbitraryRec '[1, 1] `AndShrinking` DataStream) |
| 183 | + |
| 184 | +instance AltShow DataStream where |
| 185 | + altShow (DS1 ns Nothing) = ":" ++ altShow ns |
| 186 | + altShow (DS1 ns (Just ns2)) = ":" ++ altShow ns ++ ":" ++ altShow ns2 |
| 187 | + altShow (DS2 ns) = "::" ++ altShow ns |
| 188 | + |
| 189 | +instance Arbitrary WindowsFilePaths where |
| 190 | + arbitrary = WindowsFilePaths <$> listOf' arbitrary |
| 191 | + shrink = genericShrink |
| 192 | + |
| 193 | +instance Arbitrary [Separator] where |
| 194 | + arbitrary = listOf' arbitrary |
| 195 | + shrink = genericShrink |
| 196 | + |
| 197 | +instance Arbitrary a => Arbitrary (NonEmpty a) where |
| 198 | + arbitrary = NE.fromList <$> listOf1' arbitrary |
| 199 | + shrink = genericShrink |
| 200 | + |
0 commit comments