Skip to content

Commit 24f57e6

Browse files
authored
Merge pull request #33 from safareli/refactor
Next version
2 parents e8f5581 + 4b1e07f commit 24f57e6

File tree

13 files changed

+1339
-783
lines changed

13 files changed

+1339
-783
lines changed

README.md

Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,14 @@
66

77
A type-safe abstraction for platform-independent file system paths.
88

9-
# Example
10-
11-
```purescript
12-
fullPath = rootDir </> dir "baz" </> file "foo.png"
13-
```
14-
15-
See the [tests file](/test/Main.purs) for various example usages more.
16-
17-
# Getting Started
18-
199
## Installation
2010

2111
```bash
2212
bower install purescript-pathy
2313
```
2414

2515
```purescript
26-
import Data.Path.Pathy
16+
import Pathy
2717
```
2818

2919
## Introduction
@@ -34,9 +24,8 @@ Many path libraries provide a single abstraction to deal with file system paths.
3424

3525
* The distinction between relative and absolute paths.
3626
* The distinction between paths denoting file resources and paths denoting directories.
37-
* The distinction between paths that are secure (sandboxed to some location in the file system) and those that are insecure.
3827

39-
*Pathy* also uses a single abstraction for file system paths, called `Path`, but uses *phantom types* to keep track of the above distinctions.
28+
Pathy also uses a single abstraction for file system paths, called `Path`, but uses *phantom types* to keep track of the above distinctions.
4029

4130
This approach lets you write code that performs type-safe composition of relative, absolute, file, and directory paths, and makes sure you never use paths in an unsafe fashion. Bogus and insecure operations simply aren't allowed by the type system!
4231

@@ -46,48 +35,50 @@ Many paths come from user-input or configuration data. Pathy can parse such stri
4635

4736
Building path liberals is easy. You will typically build path literals from the following components:
4837

49-
* `rootDir` &mdash; The root directory of an absolute path.
50-
* `currentDir` &mdash; The current directory (AKA the "working directory"), useful for building relative paths.
51-
* `file` &mdash; A file (in the current directory).
52-
* `dir` &mdash; A directory (in the current directory).
53-
* `(</>)` &mdash; Adds a relative path to the end of a (relative or absolute) path.
54-
* `(<.>)` &mdash; Sets the extension of a file path.
55-
* `(<..>)` &mdash; Ascends one level in a directory, then descends into the specified relative path.
38+
* `rootDir` – The root directory of an absolute path.
39+
* `currentDir` – The current directory (AKA the "working directory"), useful for building relative paths.
40+
* `file` – A file (in the current directory).
41+
* `dir` – A directory (in the current directory).
42+
* `(</>)` – Adds a relative path to the end of a (relative or absolute) path.
43+
* `(<.>)` – Sets the extension of a file path.
44+
* `(<..>)` – Ascends one level in a directory, then descends into the specified relative path.
45+
46+
All path segments (`file` / `dir`) names are required to be non-empty. This is enforced by `Name` being constructed from a `NonEmptyString`. At compile time, we can have provably non-empty strings by using `Symbol`s and a bit of type class trickery:
5647

57-
For example:
48+
``` purescript
49+
dirFoo :: Name Dir
50+
dirFoo = dir (SProxy :: SProxy "foo")
51+
```
52+
53+
Here we're using a symbol proxy (`SProxy`) and then typing it to explicitly carry the name that we want to use for our path at runtime. There is also a `dir'` and `file'` variation on the function that accepts normal `Name` values, so if you are not constructing a path at compile-time, you'd be using these instead.
54+
55+
Some example compile-time path constructions:
5856

5957
```purescript
60-
let
61-
path1 = rootDir </> dir "foo" </> dir "bar" </> file "baz.boo"
62-
path2 = currentDir </> dir "foo"
63-
in do
64-
trace $ show $ printPath path1
65-
trace $ show $ printPath path2
58+
path1 = rootDir </> dir (SProxy :: SProxy "foo") </> dir (SProxy :: SProxy "bar") </> file (SProxy :: SProxy "baz.boo")
59+
path2 = currentDir </> dir (SProxy :: SProxy "foo")
6660
```
6761

68-
Pathy doesn't let you create combinators that don't make sense, such as:
62+
Thanks to the phantom type parameters, Pathy doesn't let you create path combinations that don't make sense. The following examples will be rejected at compile time:
6963

7064
```purescript
71-
rootDir </> rootDir
65+
rootDir </> rootDir
7266
currentDir </> rootDir
73-
file "foo" </> file "bar"
74-
file "foo" </> dir "bar"
67+
file (SProxy :: SProxy "foo") </> file (SProxy :: SProxy "bar")
68+
file (SProxy :: SProxy "foo") </> dir (SProxy :: SProxy "bar")
7569
```
7670

77-
All these combinations will be disallowed at compile time!
78-
7971
### The Path Type
8072

81-
The `Path a b s` type has three type parameters:
73+
The `Path a b` type has two type parameters:
8274

83-
* `a` &mdash; This may be `Abs` or `Rel`, indicating whether the path is absolute or relative.
84-
* `b` &mdash; This may be `Dir` or `File`, indicating whether the path is a file or directory.
85-
* `s` &mdash; This may be `Sandboxed` or `Unsandboxed`, indicating whether the path has been sandboxed yet or not.
75+
* `a` – This may be `Abs` or `Rel`, indicating whether the path is absolute or relative.
76+
* `b` – This may be `Dir` or `File`, indicating whether the path is a file or directory.
8677

8778
You should try to make the `Path` functions that you write as generic as possible. If you have a function that only cares if a path refers to a file, then you can write it like this:
8879

8980
```purescript
90-
myFunction :: forall a s. Path a File s -> ...
81+
myFunction :: forall a. Path a File -> ...
9182
myFunction p = ...
9283
```
9384

@@ -97,38 +88,34 @@ By universally quantifying over the type parameters you don't care about, you en
9788

9889
To parse a string into a `Path`, you can use the `parsePath` function, which expects you to handle four cases:
9990

100-
* `Path Rel File Unsandboxed`
101-
* `Path Abs File Unsandboxed`
102-
* `Path Rel Dir Unsandboxed`
103-
* `Path Abs Dir Unsandboxed`
91+
* `Path Rel File`
92+
* `Path Abs File`
93+
* `Path Rel Dir`
94+
* `Path Abs Dir`
10495

10596
If you need a specific case, you can use helper functions such as `parseRelFile`, which return a `Maybe`.
10697

107-
### Print Paths to Strings
108-
109-
You can print any path as a `String` by calling the `printPath` function.
110-
111-
For security reasons, you can only perform this operation if you have *sandboxed* the path. Sandboxing a path ensures that users cannot escape a sandbox directory that you specify; it's the right thing to do!
98+
The `parsePath` function also expects a `Parser` argument so that different path formats can be parsed into the common `Path` type.
11299

113100
### Sandboxing
114101

115-
Pathy makes it easy to create relative paths, even paths that ascend into parent directories of relative paths.
116-
117-
With this power comes danger: if you parse a user string, the user may be able to escape any arbitrary directory.
102+
Pathy makes it easy to create relative paths, even paths that ascend into parent directories of relative paths. With this power comes danger: if you parse a user string, the user may be able to escape any arbitrary directory.
118103

119104
Pathy solves this security problem by *disallowing* conversion from a `Path` to a `String` until the `Path` has been *sandboxed*.
120105

121106
To sandbox a path, you just call `sandbox` and provide the sandbox directory, as well as the path to sandbox:
122107

123108
```purescript
124-
sandbox (rootDir </> dir "foo") (rootDir </> dir "foo" </> dir "bar")
109+
sandbox
110+
(rootDir </> dir (SProxy :: SProxy "foo")) -- sandbox root
111+
(rootDir </> dir (SProxy :: SProxy "foo") </> dir (SProxy :: SProxy "bar")) -- path to sandbox
125112
```
126113

127-
This returns a `Maybe`, which is either equal to `Nothing` if the tainted path escapes the sandbox, or `Just p`, where `p` is the tainted path, relative to the sandbox path.
114+
This returns a `Maybe`, which is `Nothing` if the tainted path escapes the sandbox.
128115

129-
After you have sandboxed a foreign path, you may call `printPath` on it. There's no need to remember this rule because it's enforced at compile-time by phantom types!
116+
After you have sandboxed a foreign path, you may call `printPath` on it, which will print the path absolutely.
130117

131-
All the path literals you build by hand are automatically sandboxed, unless you call `parentDir'` on them.
118+
There is also the option to `unsafePrintPath`. This is labelled as being unsafe as it may be depending on how it is used - for example, if a path was sandboxed against some path other than the current working directory, but then used when launching a command in the current working directory, it may still refer to a location that it should not have access to.
132119

133120
### Renaming, Transforming, Etc.
134121

bower.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
"purescript-lists": "^4.0.0",
2323
"purescript-partial": "^1.2.0",
2424
"purescript-profunctor": "^3.0.0",
25-
"purescript-strings": "^3.0.0",
25+
"purescript-strings": "^3.5.0",
2626
"purescript-transformers": "^3.0.0",
27-
"purescript-unsafe-coerce": "^3.0.0"
27+
"purescript-unsafe-coerce": "^3.0.0",
28+
"purescript-typelevel-prelude": "^2.6.0"
2829
},
2930
"devDependencies": {
30-
"purescript-quickcheck": "^4.0.0",
31-
"purescript-quickcheck-laws": "^3.0.0"
31+
"purescript-quickcheck": "^4.0.0"
3232
}
3333
}

0 commit comments

Comments
 (0)