Skip to content

Commit 984edb5

Browse files
committed
use new {split,join}Name for alterExtension and extention definitions
1 parent 6dc7471 commit 984edb5

File tree

4 files changed

+120
-31
lines changed

4 files changed

+120
-31
lines changed

src/Pathy.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module Pathy
88
) where
99

1010
import Pathy.Path (AbsDir, AbsFile, AbsPath, AnyPath, Path, RelDir, RelFile, RelPath, appendPath, canonicalize, currentDir, dir, dir', extendPath, file, file', fileName, foldPath, name, parentAppend, parentOf, peel, peelFile, refine, relativeTo, rename, renameTraverse, rootDir, setExtension, (<..>), (<.>), (</>))
11-
import Pathy.Name (Name(..), alterExtension, extension)
11+
import Pathy.Name (Name(..), joinName, splitName, alterExtension, extension)
1212
import Pathy.Printer (Escaper(..), Printer, debugPrintPath, posixPrinter, printPath, unsafePrintPath, windowsPrinter)
1313
import Pathy.Parser (Parser(..), parseAbsDir, parseAbsFile, parsePath, parseRelDir, parseRelFile, posixParser)
1414
import Pathy.Phantom (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, File, Rel)

src/Pathy/Gen.purs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module Pathy.Gen
55
, genRelDirPath
66
, genRelFilePath
77
, genRelAnyPath
8+
, genName
89
) where
910

1011
import Prelude
@@ -18,27 +19,27 @@ import Data.Foldable (foldr)
1819
import Data.List as L
1920
import Data.NonEmpty ((:|))
2021
import Data.String.Gen as SG
21-
import Data.String.NonEmpty (NonEmptyString, cons)
22-
import Pathy (AbsPath, AbsFile, AbsDir, RelDir, RelFile, RelPath, (</>))
22+
import Data.String.NonEmpty (cons)
23+
import Pathy (AbsDir, AbsFile, AbsPath, Dir, RelDir, RelFile, RelPath, (</>))
2324
import Pathy as P
2425

25-
genName m. MonadGen m MonadRec m m NonEmptyString
26-
genName = cons <$> genChar <*> SG.genString genChar
26+
genName m a. MonadGen m MonadRec m m (P.Name a)
27+
genName = map P.Name $ cons <$> genChar <*> SG.genString genChar
2728
where
2829
genChar = Gen.oneOf $ CG.genDigitChar :| [CG.genAlpha]
2930

3031
genAbsDirPath :: forall m. MonadGen m => MonadRec m => m AbsDir
3132
genAbsDirPath = Gen.sized \size → do
3233
newSize ← Gen.chooseInt 0 size
3334
Gen.resize (const newSize) do
34-
parts L.List NonEmptyStringGen.unfoldable genName
35-
pure $ foldr (flip P.appendPath <<< P.dir' <<< P.Name) P.rootDir parts
35+
parts L.List (P.Name Dir)Gen.unfoldable genName
36+
pure $ foldr (flip P.appendPath <<< P.dir') P.rootDir parts
3637

3738
genAbsFilePath :: forall m. MonadGen m => MonadRec m => m AbsFile
3839
genAbsFilePath = do
3940
dir ← genAbsDirPath
4041
file ← genName
41-
pure $ dir </> P.file' (P.Name file)
42+
pure $ dir </> P.file' file
4243

4344
genAbsAnyPath :: forall m. MonadGen m => MonadRec m => m AbsPath
4445
genAbsAnyPath = Gen.oneOf $ (Left <$> genAbsDirPath) :| [Right <$> genAbsFilePath]
@@ -47,14 +48,14 @@ genRelDirPath :: forall m. MonadGen m => MonadRec m => m RelDir
4748
genRelDirPath = Gen.sized \size → do
4849
newSize ← Gen.chooseInt 0 size
4950
Gen.resize (const newSize) do
50-
parts L.List NonEmptyStringGen.unfoldable genName
51-
pure $ foldr (flip P.appendPath <<< P.dir' <<< P.Name) P.currentDir parts
51+
parts L.List (P.Name Dir)Gen.unfoldable genName
52+
pure $ foldr (flip P.appendPath <<< P.dir') P.currentDir parts
5253

5354
genRelFilePath :: forall m. MonadGen m => MonadRec m => m RelFile
5455
genRelFilePath = do
5556
dir ← genRelDirPath
5657
file ← genName
57-
pure $ dir </> P.file' (P.Name file)
58+
pure $ dir </> P.file' file
5859

5960
genRelAnyPath :: forall m. MonadGen m => MonadRec m => m RelPath
6061
genRelAnyPath = Gen.oneOf $ (Left <$> genRelDirPath) :| [Right <$> genRelFilePath]

src/Pathy/Name.purs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Pathy.Name where
22

33
import Prelude
44

5-
import Data.Maybe (Maybe(..), maybe)
5+
import Data.Maybe (Maybe(..), fromMaybe)
66
import Data.Newtype (class Newtype)
77
import Data.String as S
88
import Data.String.NonEmpty (NonEmptyString)
@@ -24,32 +24,72 @@ derive newtype instance ordName :: Ord (Name a)
2424
instance showName :: Show (Name a) where
2525
show (Name name) = "(Name " <> show name <> ")"
2626

27-
-- | Retrieves the extension of a name.
27+
-- | Splits `Name` in name and extension part.
28+
-- |
29+
-- | ```purescript
30+
-- | splitName (Name ".foo") == { name: ".foo", extension: Nothing }
31+
-- | splitName (Name "foo.") == { name: "foo.", extension: Nothing }
32+
-- | splitName (Name "foo") == { name: "foo", extension: Nothing }
33+
-- | splitName (Name ".") == { name: ".", extension: Nothing }
34+
-- | splitName (Name "foo.baz") == { name: "foo", extension: Just "baz" }
35+
-- | ```
36+
-- | _Note, in real code all strings from this examples would be `NonEmptyString`._
37+
-- |
38+
-- | Also for any `Name` this property holds:
39+
-- | ```purescript
40+
-- | joinName <<< splitName = id
41+
-- | ````
42+
-- | see [`joinName`](#v:joinName).
43+
splitName :: forall n. Name n -> { name :: NonEmptyString, ext :: Maybe NonEmptyString }
44+
splitName (Name nameIn) =
45+
fromMaybe { name: nameIn, ext: Nothing } do
46+
idx <- NES.lastIndexOf (S.Pattern ".") nameIn
47+
name <- NES.take idx nameIn
48+
ext <- NES.drop (idx + 1) nameIn
49+
pure $ { name, ext: Just ext }
50+
51+
-- | Joins name and extension part into one `Name`.
52+
-- |
53+
-- | Also for any `Name` this property holds:
54+
-- | ```purescript
55+
-- | joinName <<< splitName = id
56+
-- | ````
57+
-- | see [`splitName`](#v:splitName).
58+
joinName :: forall n. { name :: NonEmptyString, ext :: Maybe NonEmptyString } -> Name n
59+
joinName { name, ext } = Name $ case ext of
60+
Nothing -> name
61+
Just ext -> name <> NES.singleton '.' <> ext
62+
63+
-- | Retrieves the extension of a name. also see [`splitName`](#v:splitName)
64+
-- |
65+
-- | ```purescript
66+
-- | extension (Name ".foo") == Nothing
67+
-- | extension (Name "foo.") == Nothing
68+
-- | extension (Name ".") == Nothing
69+
-- | extension (Name "foo.baz") == Just "baz"
70+
-- | ````
71+
-- | _Note, in real code all strings from this examples would be `NonEmptyString`._
2872
extension :: forall n. Name n -> Maybe NonEmptyString
29-
extension (Name name) =
30-
flip NES.drop name <<< (_ + 1) =<< NES.lastIndexOf (S.Pattern ".") name
73+
extension = splitName >>> _.ext
3174

3275
-- | Alters an extension of a name. This allows extensions to be added, removed,
33-
-- | or modified.
76+
-- | or modified. see [`splitName`](#v:splitName) and [`joinName`](#v:joinName)
77+
-- | for how a `Name` is split into name and extention part and joined back
78+
-- | into a `Name`.
79+
-- |
80+
-- | Also for any `Name` this property holds:
81+
-- | ```purescript
82+
-- | alterExtension id = id
83+
-- | ````
3484
alterExtension
3585
:: forall n
3686
. (Maybe NonEmptyString -> Maybe NonEmptyString)
3787
-> Name n
3888
-> Name n
39-
alterExtension f (Name name) =
40-
case NES.lastIndexOf (S.Pattern ".") name of
41-
Nothing -> extend name Nothing
42-
Just i ->
43-
case NES.splitAt i name of
44-
Just { before: Just n, after } -> extend n (NES.drop 1 =<< after)
45-
_ -> extend name Nothing
46-
where
47-
extend name' ext =
48-
maybe
49-
(Name name')
50-
(\ext' -> Name (name' <> NES.singleton '.' <> ext'))
51-
(f ext)
52-
89+
alterExtension f n =
90+
let spn = splitName n
91+
in joinName spn{ext = f spn.ext}
92+
5393
-- | A class for creating `Name` values from type-level strings. This allows us
5494
-- | to guarantee that a name is not empty at compile-time.
5595
class IsName sym where

test/Main.purs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@ import Control.Monad.Eff (Eff)
66
import Control.Monad.Eff.Console (CONSOLE, info)
77
import Control.Monad.Eff.Exception (EXCEPTION, throw)
88
import Data.Maybe (Maybe(..))
9+
import Data.Newtype (un)
10+
import Data.NonEmpty ((:|))
911
import Data.String as Str
1012
import Data.String.NonEmpty (NonEmptyString)
13+
import Data.String.NonEmpty as NES
1114
import Data.Symbol (SProxy(..))
12-
import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Path, Rel, alterExtension, canonicalize, currentDir, dir, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, debugPrintPath, unsandbox, (<..>), (<.>), (</>))
15+
import Pathy (class IsDirOrFile, class IsRelOrAbs, Abs, Dir, Name(..), Path, Rel, alterExtension, canonicalize, currentDir, debugPrintPath, dir, extension, file, parentOf, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, posixParser, posixPrinter, printPath, relativeTo, rename, rootDir, sandbox, unsandbox, (<..>), (<.>), (</>), joinName, splitName)
1316
import Pathy.Gen as PG
17+
import Pathy.Name (reflectName)
18+
import Test.QuickCheck ((===))
1419
import Test.QuickCheck as QC
1520
import Test.QuickCheck.Gen as Gen
1621
import Unsafe.Coerce (unsafeCoerce)
@@ -65,6 +70,31 @@ parsePrintRelFilePath :: Gen.Gen QC.Result
6570
parsePrintRelFilePath = PG.genRelFilePath <#> \path ->
6671
parsePrintCheck path (parseRelFile posixParser $ printTestPath path)
6772

73+
genAmbigiousName :: forall a. Gen.Gen (Name a)
74+
genAmbigiousName =
75+
let
76+
genNES = PG.genName <#> un Name
77+
in
78+
map Name $ Gen.oneOf $ genNES :|
79+
[ genNES <#> \a -> a <> (NES.singleton '.')
80+
, genNES <#> \a -> (NES.singleton '.') <> a
81+
, pure (NES.singleton '.')
82+
, do
83+
a <- genNES
84+
b <- genNES
85+
pure $ a <> (NES.singleton '.') <> b
86+
]
87+
88+
checkAlterExtensionId :: Gen.Gen QC.Result
89+
checkAlterExtensionId = do
90+
n <- genAmbigiousName
91+
pure $ alterExtension id n === id n
92+
93+
checkJoinSplitNameId :: Gen.Gen QC.Result
94+
checkJoinSplitNameId = do
95+
n <- genAmbigiousName
96+
pure $ joinName (splitName n) === id n
97+
6898
checkRelative :: forall b. IsDirOrFile b => Gen.Gen (Path Abs b) -> Gen.Gen QC.Result
6999
checkRelative gen = do
70100
p1 <- gen
@@ -92,6 +122,8 @@ main = do
92122
info "checking `parse <<< print` for `RelFile`" *> QC.quickCheck parsePrintRelFilePath
93123
info "checking `relativeTo` for `AbsDir`" *> QC.quickCheck (checkRelative PG.genAbsDirPath)
94124
info "checking `relativeTo` for `AbsFile`" *> QC.quickCheck (checkRelative PG.genAbsFilePath)
125+
info "checking `joinName <<< splitName === id`" *> QC.quickCheck 100 checkJoinSplitNameId
126+
info "checking `alterExtension id === id`" *> QC.quickCheck 100 checkAlterExtensionId
95127

96128
-- Should not compile:
97129
-- test
@@ -231,6 +263,22 @@ main = do
231263
(rename (alterExtension (const Nothing)) (file (SProxy :: SProxy "image.png")))
232264
(file $ SProxy :: SProxy "image")
233265

266+
test """extension (Name ".foo") == Nothing"""
267+
(extension (reflectName $ SProxy :: SProxy ".foo"))
268+
(Nothing)
269+
test """extension (Name "foo.") == Nothing"""
270+
(extension (reflectName $ SProxy :: SProxy "foo."))
271+
(Nothing)
272+
test """extension (Name "foo") == Nothing"""
273+
(extension (reflectName $ SProxy :: SProxy "foo"))
274+
(Nothing)
275+
test """extension (Name ".") == Nothing"""
276+
(extension (reflectName $ SProxy :: SProxy "."))
277+
(Nothing)
278+
test """extension (Name "foo.baz") == (Just "baz")"""
279+
(extension (reflectName $ SProxy :: SProxy "foo.baz"))
280+
(NES.fromString "baz")
281+
234282
test "sandbox - fail when relative path lies outside sandbox (above)"
235283
(sandbox (rootDir </> dirBar) (parentOf currentDir))
236284
Nothing

0 commit comments

Comments
 (0)