Skip to content

Commit d059b81

Browse files
authored
Ch7 - Add anchors (#239)
* Ch7 replace code snippets with anchors * Add removeAnchors.sh script * remove unused unV code
1 parent ab9f371 commit d059b81

File tree

5 files changed

+124
-112
lines changed

5 files changed

+124
-112
lines changed

exercises/chapter7/src/Data/AddressBook.purs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ type Address
88
, state :: String
99
}
1010

11+
-- ANCHOR: address_anno
1112
address :: String -> String -> String -> Address
13+
-- ANCHOR_END: address_anno
1214
address street city state = { street, city, state }
1315

16+
-- ANCHOR: PhoneType
1417
data PhoneType
1518
= HomePhone
1619
| WorkPhone
1720
| CellPhone
1821
| OtherPhone
22+
-- ANCHOR_END: PhoneType
1923

2024
{-| derive has not been discussed yet but will be
2125
covered in Ch 10. Here it is needed by the unit
@@ -35,7 +39,9 @@ type PhoneNumber
3539
, number :: String
3640
}
3741

42+
-- ANCHOR: phoneNumber_anno
3843
phoneNumber :: PhoneType -> String -> PhoneNumber
44+
-- ANCHOR_END: phoneNumber_anno
3945
phoneNumber ty number =
4046
{ "type": ty
4147
, number: number
@@ -48,13 +54,17 @@ type Person
4854
, phones :: Array PhoneNumber
4955
}
5056

57+
-- ANCHOR: person_anno
5158
person :: String -> String -> Address -> Array PhoneNumber -> Person
59+
-- ANCHOR_END: person_anno
5260
person firstName lastName homeAddress phones = { firstName, lastName, homeAddress, phones }
5361

62+
-- ANCHOR: examplePerson
5463
examplePerson :: Person
5564
examplePerson =
5665
person "John" "Smith"
5766
(address "123 Fake St." "FakeTown" "CA")
5867
[ phoneNumber HomePhone "555-555-5555"
5968
, phoneNumber CellPhone "555-555-0000"
6069
]
70+
-- ANCHOR_END: examplePerson

exercises/chapter7/src/Data/AddressBook/Validation.purs

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,74 +10,124 @@ import Data.Traversable (traverse)
1010
import Data.Validation.Semigroup (V, unV, invalid)
1111
import Partial.Unsafe (unsafePartial)
1212

13+
-----------------
14+
-- Some simple early examples returning `Either` instead of `V`:
15+
16+
-- ANCHOR: nonEmpty1
17+
nonEmpty1 :: String -> Either String Unit
18+
nonEmpty1 "" = Left "Field cannot be empty"
19+
nonEmpty1 _ = Right unit
20+
-- ANCHOR_END: nonEmpty1
21+
22+
-- ANCHOR: validatePerson1
23+
validatePerson1 :: Person -> Either String Person
24+
validatePerson1 p =
25+
person <$> (nonEmpty1 p.firstName *> pure p.firstName)
26+
<*> (nonEmpty1 p.lastName *> pure p.lastName)
27+
<*> pure p.homeAddress
28+
<*> pure p.phones
29+
-- ANCHOR_END: validatePerson1
30+
31+
-- ANCHOR: validatePerson1Ado
32+
validatePerson1Ado :: Person -> Either String Person
33+
validatePerson1Ado p = ado
34+
f <- nonEmpty1 p.firstName *> pure p.firstName
35+
l <- nonEmpty1 p.lastName *> pure p.firstName
36+
in person f l p.homeAddress p.phones
37+
-- ANCHOR_END: validatePerson1Ado
38+
39+
-----------------
40+
41+
-- ANCHOR: Errors
1342
type Errors
1443
= Array String
44+
-- ANCHOR_END: Errors
1545

46+
-- ANCHOR: nonEmpty
1647
nonEmpty :: String -> String -> V Errors Unit
1748
nonEmpty field "" = invalid [ "Field '" <> field <> "' cannot be empty" ]
49+
nonEmpty _ _ = pure unit
50+
-- ANCHOR_END: nonEmpty
1851

19-
nonEmpty _ _ = pure unit
20-
52+
-- ANCHOR: arrayNonEmpty
2153
arrayNonEmpty :: forall a. String -> Array a -> V Errors Unit
22-
arrayNonEmpty field [] = invalid [ "Field '" <> field <> "' must contain at least one value" ]
23-
24-
arrayNonEmpty _ _ = pure unit
54+
arrayNonEmpty field [] =
55+
invalid [ "Field '" <> field <> "' must contain at least one value" ]
56+
arrayNonEmpty _ _ =
57+
pure unit
58+
-- ANCHOR_END: arrayNonEmpty
2559

60+
-- ANCHOR: lengthIs
2661
lengthIs :: String -> Int -> String -> V Errors Unit
27-
lengthIs field len value
28-
| length value /= len = invalid [ "Field '" <> field <> "' must have length " <> show len ]
29-
30-
lengthIs _ _ _ = pure unit
62+
lengthIs field len value | length value /= len =
63+
invalid [ "Field '" <> field <> "' must have length " <> show len ]
64+
lengthIs _ _ _ = pure unit
65+
-- ANCHOR_END: lengthIs
3166

67+
-- ANCHOR: phoneNumberRegex
3268
phoneNumberRegex :: Regex
3369
phoneNumberRegex =
3470
unsafePartial case regex "^\\d{3}-\\d{3}-\\d{4}$" noFlags of
3571
Right r -> r
72+
-- ANCHOR_END: phoneNumberRegex
3673

74+
-- ANCHOR: matches
3775
matches :: String -> Regex -> String -> V Errors Unit
38-
matches _ regex value
39-
| test regex value = pure unit
40-
41-
matches field _ _ = invalid [ "Field '" <> field <> "' did not match the required format" ]
76+
matches _ regex value | test regex value =
77+
pure unit
78+
matches field _ _ =
79+
invalid [ "Field '" <> field <> "' did not match the required format" ]
80+
-- ANCHOR_END: matches
4281

82+
-- ANCHOR: validateAddress
4383
validateAddress :: Address -> V Errors Address
4484
validateAddress a =
45-
address <$> (nonEmpty "Street" a.street *> pure a.street)
46-
<*> (nonEmpty "City" a.city *> pure a.city)
47-
<*> (lengthIs "State" 2 a.state *> pure a.state)
85+
address <$> (nonEmpty "Street" a.street *> pure a.street)
86+
<*> (nonEmpty "City" a.city *> pure a.city)
87+
<*> (lengthIs "State" 2 a.state *> pure a.state)
88+
-- ANCHOR_END: validateAddress
4889

90+
-- ANCHOR: validateAddressAdo
4991
validateAddressAdo :: Address -> V Errors Address
5092
validateAddressAdo a = ado
51-
street <- (nonEmpty "Street" a.street *> pure a.street)
52-
city <- (nonEmpty "City" a.city *> pure a.city)
53-
state <- (lengthIs "State" 2 a.state *> pure a.state)
93+
street <- (nonEmpty "Street" a.street *> pure a.street)
94+
city <- (nonEmpty "City" a.city *> pure a.city)
95+
state <- (lengthIs "State" 2 a.state *> pure a.state)
5496
in address street city state
97+
-- ANCHOR_END: validateAddressAdo
5598

99+
-- ANCHOR: validatePhoneNumber
56100
validatePhoneNumber :: PhoneNumber -> V Errors PhoneNumber
57101
validatePhoneNumber pn =
58102
phoneNumber <$> pure pn."type"
59-
<*> (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
103+
<*> (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
104+
-- ANCHOR_END: validatePhoneNumber
60105

106+
-- ANCHOR: validatePhoneNumberAdo
61107
validatePhoneNumberAdo :: PhoneNumber -> V Errors PhoneNumber
62108
validatePhoneNumberAdo pn = ado
63-
tpe <- pure pn."type"
109+
tpe <- pure pn."type"
64110
number <- (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
65111
in phoneNumber tpe number
112+
-- ANCHOR_END: validatePhoneNumberAdo
66113

114+
-- ANCHOR: validatePerson
67115
validatePerson :: Person -> V Errors Person
68116
validatePerson p =
69117
person <$> (nonEmpty "First Name" p.firstName *> pure p.firstName)
70-
<*> (nonEmpty "Last Name" p.lastName *> pure p.lastName)
71-
<*> validateAddress p.homeAddress
72-
<*> (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones)
118+
<*> (nonEmpty "Last Name" p.lastName *> pure p.lastName)
119+
<*> validateAddress p.homeAddress
120+
<*> (arrayNonEmpty "Phone Numbers" p.phones *>
121+
traverse validatePhoneNumber p.phones)
122+
-- ANCHOR_END: validatePerson
73123

124+
-- ANCHOR: validatePersonAdo
74125
validatePersonAdo :: Person -> V Errors Person
75126
validatePersonAdo p = ado
76127
firstName <- (nonEmpty "First Name" p.firstName *> pure p.firstName)
77-
lastName <- (nonEmpty "Last Name" p.lastName *> pure p.lastName)
78-
address <- validateAddress p.homeAddress
79-
numbers <- (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones)
128+
lastName <- (nonEmpty "Last Name" p.lastName *> pure p.lastName)
129+
address <- validateAddress p.homeAddress
130+
numbers <- (arrayNonEmpty "Phone Numbers" p.phones *>
131+
traverse validatePhoneNumber p.phones)
80132
in person firstName lastName address numbers
81-
82-
validatePerson' :: Person -> Either Errors Person
83-
validatePerson' p = unV Left Right $ validatePerson p
133+
-- ANCHOR_END: validatePersonAdo

scripts/removeAnchors.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
3+
# This script removes all code anchors to improve readability
4+
5+
# All .purs files in the exercises directories (excluding hidden files)
6+
ALL_PURS=$(find exercises \( ! -regex '.*/\..*' \) -type f -name '*.purs')
7+
8+
for f in $ALL_PURS; do
9+
# Delete lines starting with an '-- ANCHOR' comment
10+
perl -ni -e 'print if !/^-- ANCHOR/' $f
11+
done

text/chapter2.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ Now that you've installed the necessary development tools, clone this book's rep
1515
git clone https://github.com/purescript-contrib/purescript-book.git
1616
```
1717

18-
The book repo contains PureScript example code and unit tests for the exercises that accompany each chapter. There's some initial setup required to reset the exercise solutions so they are ready to be solved by you. Use the `resetSolutions.sh` script to simplify this process:
18+
The book repo contains PureScript example code and unit tests for the exercises that accompany each chapter. There's some initial setup required to reset the exercise solutions so they are ready to be solved by you. Use the `resetSolutions.sh` script to simplify this process. While you're at it, you should also strip out all the anchor comments with the `removeAnchors.sh` script (these anchors are used for copying code snippets into the book's rendered markdown, and you probably don't need this clutter in your local repo):
1919
```
2020
cd purescript-book
2121
./scripts/resetSolutions.sh
22+
./scripts/removeAnchors.sh
2223
git commit --all --message "Exercises ready to be solved"
2324
```
2425

0 commit comments

Comments
 (0)