Compare commits
3 Commits
760f5ef4a6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d1d94c31d | ||
|
|
172b59c404 | ||
|
|
589a3b886a |
24
newsletter/.exercism/config.json
Normal file
24
newsletter/.exercism/config.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"lpil"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"src/newsletter.gleam"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/newsletter_test.gleam"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/example.gleam"
|
||||||
|
],
|
||||||
|
"invalidator": [
|
||||||
|
"gleam.toml",
|
||||||
|
"manifest.toml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"forked_from": [
|
||||||
|
"elixir/newsletter"
|
||||||
|
],
|
||||||
|
"blurb": "Learn about IO by sending out a newsletter."
|
||||||
|
}
|
||||||
1
newsletter/.exercism/metadata.json
Normal file
1
newsletter/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"gleam","exercise":"newsletter","id":"ec3e4622cebd4906aff5b7b7f8a3e90b","url":"https://exercism.org/tracks/gleam/exercises/newsletter","handle":"fw353qwgs","is_requester":true,"auto_approve":false}
|
||||||
6
newsletter/.gitignore
vendored
Normal file
6
newsletter/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
build
|
||||||
|
erl_crash.dump
|
||||||
|
log.txt
|
||||||
|
emails.txt
|
||||||
32
newsletter/HELP.md
Normal file
32
newsletter/HELP.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run the command `gleam test` from within the exercise directory.
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit src/newsletter.gleam` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam)
|
||||||
|
- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam)
|
||||||
|
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
To get help if you're having trouble, you can use one of the following resources:
|
||||||
|
|
||||||
|
- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation.
|
||||||
|
- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel.
|
||||||
|
- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
|
||||||
24
newsletter/HINTS.md
Normal file
24
newsletter/HINTS.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Hints
|
||||||
|
|
||||||
|
## 1. Read email addresses from a file
|
||||||
|
|
||||||
|
- The [`simplifile.read` function][file-read] can be used to read the contents of a file.
|
||||||
|
|
||||||
|
## 2. Create a log file for writing
|
||||||
|
|
||||||
|
- The [`simplifile.create_file` function][file-create] can be used to create an empty file.
|
||||||
|
|
||||||
|
## 3. Log a sent email
|
||||||
|
|
||||||
|
- The [`simplifile.append` function][file-append] can be used to append text to a file.
|
||||||
|
|
||||||
|
## 5. Send the newsletter
|
||||||
|
|
||||||
|
- All the necessary operations on files were already implemented in the previous steps.
|
||||||
|
- The [`result.try` function][result-try] and [`list.try_each` function][list-try-each] can be used to work with the `Result` type.
|
||||||
|
|
||||||
|
[file-read]: https://hexdocs.pm/simplifile/simplifile.html#read
|
||||||
|
[file-create]: https://hexdocs.pm/simplifile/simplifile.html#create_file
|
||||||
|
[file-append]: https://hexdocs.pm/simplifile/simplifile.html#append
|
||||||
|
[result-try]: https://hexdocs.pm/gleam_stdlib/gleam/result.html#try
|
||||||
|
[list-try-each]: https://hexdocs.pm/gleam_stdlib/gleam/list.html#try_each
|
||||||
98
newsletter/README.md
Normal file
98
newsletter/README.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Newsletter
|
||||||
|
|
||||||
|
Welcome to Newsletter on Exercism's Gleam Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Nil
|
||||||
|
|
||||||
|
`Nil` in Gleam is a type with a single value, also called `Nil`. It is similar to `void` in other languages in that it is used when a function does not have any more suitable value to return.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
io.println("Hello, Joe!")
|
||||||
|
// -> Nil
|
||||||
|
```
|
||||||
|
|
||||||
|
Values in Gleam are not "nil-able" or "nullable" like in some other languages. A value can only be `Nil` if it's type is `Nil`, and a value of any other type can never be `Nil`.
|
||||||
|
|
||||||
|
## IO
|
||||||
|
|
||||||
|
Like most programming languages Gleam has "side effects", so functions can read and change the state of the world, as well as returning a value.
|
||||||
|
|
||||||
|
The `gleam/io` module in the Gleam standard library provides functions for printing strings to the console.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
io.println("Hello, Joe!")
|
||||||
|
// Hello, Joe!
|
||||||
|
// -> Nil
|
||||||
|
```
|
||||||
|
|
||||||
|
Other packages may provide other IO functions, such as `simplifile`, a package which provides functions for reading and writing files.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
simplifile.read("favourite-colour.txt")
|
||||||
|
// -> Ok("Pink\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Boris is a big model train enthusiast and has decided to share their passion with the world by starting a newsletter. They'll start by sending the first issue of the newsletter to friends and acquaintances that share the hobby, their email addresses are stored in a text file.
|
||||||
|
|
||||||
|
Hint: Use the [simplifile](https://hexdocs.pm/simplifile/simplifile.html) module for file operations.
|
||||||
|
|
||||||
|
## 1. Read email addresses from a file
|
||||||
|
|
||||||
|
Implement the `read_emails` function. It takes a path string to a text file that contains email addresses separated by newlines, and returns a list of the email addresses from the file.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
read_emails("/home/my_user/documents/model_train_friends_emails.txt")
|
||||||
|
// -> Ok(["rick@example.com", "choochoo42@example.com", "anna@example.com"])
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Create a log file for writing
|
||||||
|
|
||||||
|
Sending an email is a task that might fail for many unpredictable reasons, like a typo in the email address or temporary network issues. To ensure that you can retry sending the emails to all your friends without sending duplicates, you need to log the email addresses that already received the email. For this, you'll need a log file.
|
||||||
|
|
||||||
|
Implement the `create_log_file` function. It takes a file path and creates a new empty file at that location.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
create_log_file("/home/my_user/documents/newsletter_issue1_log.txt")
|
||||||
|
// -> Ok(Nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Log a sent email
|
||||||
|
|
||||||
|
Implement the `log_sent_email` function. It takes a path to a log file and a string with the email address. It writes the email address to the file, followed by a newline.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
log_sent_email(
|
||||||
|
"/home/my_user/documents/newsletter_issue1_log.txt",
|
||||||
|
"joe@example.com",
|
||||||
|
)
|
||||||
|
// -> Ok(Nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Send the newsletter
|
||||||
|
|
||||||
|
Now that you have all of the building blocks of the email sending procedure, you need to combine them together in a single function.
|
||||||
|
|
||||||
|
Implement the `send_newsletter` function. It takes a path of the file with email addresses, a path of a log file, and an anonymous function that sends an email to a given email address.
|
||||||
|
|
||||||
|
It should read all the email addresses from the given file and attempt to send an email to every one of them. If the anonymous function that sends the email returns an `Ok` value, write the email address to the log file. Make sure to do it as soon as the email is sent.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
send_newsletter(
|
||||||
|
"model_train_friends_emails.txt",
|
||||||
|
"newsletter_issue1_log.txt",
|
||||||
|
send_email,
|
||||||
|
)
|
||||||
|
// -> Ok(Nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @lpil
|
||||||
14
newsletter/gleam.toml
Normal file
14
newsletter/gleam.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name = "newsletter"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_otp = "~> 0.7 or ~> 1.0"
|
||||||
|
gleam_stdlib = ">= 0.54.0 or ~> 1.0"
|
||||||
|
simplifile = "~> 1.0"
|
||||||
|
gleam_erlang = ">= 0.25.0 and < 1.0.0"
|
||||||
|
gleam_yielder = ">= 1.1.0 and < 2.0.0"
|
||||||
|
gleam_regexp = ">= 1.1.0 and < 2.0.0"
|
||||||
|
gleam_deque = ">= 1.0.0 and < 2.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
exercism_test_runner = "~> 1.9"
|
||||||
31
newsletter/manifest.toml
Normal file
31
newsletter/manifest.toml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
|
||||||
|
{ name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" },
|
||||||
|
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
|
||||||
|
{ name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" },
|
||||||
|
{ name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" },
|
||||||
|
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
|
||||||
|
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
|
||||||
|
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
|
||||||
|
{ name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" },
|
||||||
|
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
|
||||||
|
{ name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" },
|
||||||
|
{ name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" },
|
||||||
|
{ name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" },
|
||||||
|
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
|
||||||
|
{ name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" },
|
||||||
|
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
exercism_test_runner = { version = "~> 1.9" }
|
||||||
|
gleam_deque = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
|
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
|
||||||
|
gleam_otp = { version = "~> 0.7 or ~> 1.0" }
|
||||||
|
gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" }
|
||||||
|
gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" }
|
||||||
|
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
|
||||||
|
simplifile = { version = "~> 1.0" }
|
||||||
55
newsletter/src/newsletter.gleam
Normal file
55
newsletter/src/newsletter.gleam
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import gleam/string
|
||||||
|
import simplifile
|
||||||
|
|
||||||
|
pub fn read_emails(path: String) -> Result(List(String), Nil) {
|
||||||
|
case simplifile.read(path) {
|
||||||
|
Error(_) -> Error(Nil)
|
||||||
|
Ok(content) ->
|
||||||
|
string.trim(content)
|
||||||
|
|> string.split("\n")
|
||||||
|
|> Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_log_file(path: String) -> Result(Nil, Nil) {
|
||||||
|
case simplifile.create_file(path) {
|
||||||
|
Error(_) -> Error(Nil)
|
||||||
|
Ok(_) -> Ok(Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_sent_email(path: String, email: String) -> Result(Nil, Nil) {
|
||||||
|
case simplifile.append(path, email <> "\n") {
|
||||||
|
Error(_) -> Error(Nil)
|
||||||
|
Ok(_) -> Ok(Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_newsletter(
|
||||||
|
emails_path: String,
|
||||||
|
log_path: String,
|
||||||
|
send_email: fn(String) -> Result(Nil, Nil),
|
||||||
|
) -> Result(Nil, Nil) {
|
||||||
|
case create_log_file(log_path), read_emails(emails_path) {
|
||||||
|
Ok(_), Ok(emails) -> send_emails_and_log(emails, send_email, log_path)
|
||||||
|
_, _ -> Error(Nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_emails_and_log(
|
||||||
|
emails: List(String),
|
||||||
|
send_fn: fn(String) -> Result(Nil, Nil),
|
||||||
|
log_path: String,
|
||||||
|
) -> Result(Nil, Nil) {
|
||||||
|
let _ = case emails {
|
||||||
|
[] -> Ok(Nil)
|
||||||
|
[email, ..rest] -> {
|
||||||
|
let _ = case send_fn(email) {
|
||||||
|
Error(_) -> Error(Nil)
|
||||||
|
Ok(_) -> log_sent_email(log_path, email)
|
||||||
|
}
|
||||||
|
send_emails_and_log(rest, send_fn, log_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// It should read all the email addresses from the given file and attempt to send an email to every one of them. If the anonymous function that sends the email returns an `Ok` value, write the email address to the log file. Make sure to do it as soon as the email is sent.
|
||||||
85
newsletter/test/newsletter_test.gleam
Normal file
85
newsletter/test/newsletter_test.gleam
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import exercism/should
|
||||||
|
import exercism/test_runner
|
||||||
|
import newsletter
|
||||||
|
import simplifile
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
test_runner.main()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_emails_test() {
|
||||||
|
let emails =
|
||||||
|
"lucy@example.com
|
||||||
|
thomas@example.com
|
||||||
|
sid@example.com
|
||||||
|
"
|
||||||
|
let assert Ok(_) = simplifile.write("emails.txt", emails)
|
||||||
|
let assert Ok(emails) = newsletter.read_emails("emails.txt")
|
||||||
|
emails
|
||||||
|
|> should.equal(["lucy@example.com", "thomas@example.com", "sid@example.com"])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_log_file_test() {
|
||||||
|
let _ = simplifile.delete("log.txt")
|
||||||
|
let assert Ok(Nil) = newsletter.create_log_file("log.txt")
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_sent_email_test() {
|
||||||
|
let _ = simplifile.delete("log.txt")
|
||||||
|
let assert Ok(Nil) = newsletter.create_log_file("log.txt")
|
||||||
|
let assert Ok(Nil) =
|
||||||
|
newsletter.log_sent_email("log.txt", "janice@example.com")
|
||||||
|
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("janice@example.com\n")
|
||||||
|
|
||||||
|
let assert Ok(Nil) = newsletter.log_sent_email("log.txt", "joe@example.com")
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("janice@example.com\njoe@example.com\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_newsletter_test() {
|
||||||
|
let _ = simplifile.delete("log.txt")
|
||||||
|
let emails =
|
||||||
|
"bushra@example.com
|
||||||
|
abdi@example.com
|
||||||
|
bell@example.com
|
||||||
|
"
|
||||||
|
let assert Ok(Nil) = simplifile.write("emails.txt", emails)
|
||||||
|
|
||||||
|
let send_email = fn(email) {
|
||||||
|
case email {
|
||||||
|
"bushra@example.com" -> {
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("")
|
||||||
|
Ok(Nil)
|
||||||
|
}
|
||||||
|
"abdi@example.com" -> {
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("bushra@example.com\n")
|
||||||
|
Error(Nil)
|
||||||
|
}
|
||||||
|
"bell@example.com" -> {
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("bushra@example.com\n")
|
||||||
|
Ok(Nil)
|
||||||
|
}
|
||||||
|
_ -> panic as "Unexpected email given to send_email function"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let assert Ok(Nil) =
|
||||||
|
newsletter.send_newsletter("emails.txt", "log.txt", send_email)
|
||||||
|
|
||||||
|
let assert Ok(log) = simplifile.read("log.txt")
|
||||||
|
log
|
||||||
|
|> should.equal("bushra@example.com\nbell@example.com\n")
|
||||||
|
}
|
||||||
24
secure-treasure-chest/.exercism/config.json
Normal file
24
secure-treasure-chest/.exercism/config.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"lpil"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"src/secure_treasure_chest.gleam"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/secure_treasure_chest_test.gleam"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/example.gleam"
|
||||||
|
],
|
||||||
|
"invalidator": [
|
||||||
|
"gleam.toml",
|
||||||
|
"manifest.toml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"forked_from": [
|
||||||
|
"elm/secure-treasure-chest"
|
||||||
|
],
|
||||||
|
"blurb": "Learn about opaque types by making a more secure treasure chest"
|
||||||
|
}
|
||||||
1
secure-treasure-chest/.exercism/metadata.json
Normal file
1
secure-treasure-chest/.exercism/metadata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"track":"gleam","exercise":"secure-treasure-chest","id":"c5dbff3c2a1342d389388a95cc2a7f88","url":"https://exercism.org/tracks/gleam/exercises/secure-treasure-chest","handle":"fw353qwgs","is_requester":true,"auto_approve":false}
|
||||||
4
secure-treasure-chest/.gitignore
vendored
Normal file
4
secure-treasure-chest/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.beam
|
||||||
|
*.ez
|
||||||
|
build
|
||||||
|
erl_crash.dump
|
||||||
32
secure-treasure-chest/HELP.md
Normal file
32
secure-treasure-chest/HELP.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, run the command `gleam test` from within the exercise directory.
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit src/secure_treasure_chest.gleam` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Gleam track's documentation](https://exercism.org/docs/tracks/gleam)
|
||||||
|
- The [Gleam track's programming category on the forum](https://forum.exercism.org/c/programming/gleam)
|
||||||
|
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
To get help if you're having trouble, you can use one of the following resources:
|
||||||
|
|
||||||
|
- [gleam.run](https://gleam.run/documentation/) is the gleam official documentation.
|
||||||
|
- [Discord](https://discord.gg/Fm8Pwmy) is the discord channel.
|
||||||
|
- [StackOverflow](https://stackoverflow.com/questions/tagged/gleam) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
|
||||||
15
secure-treasure-chest/HINTS.md
Normal file
15
secure-treasure-chest/HINTS.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Hints
|
||||||
|
|
||||||
|
## 1. Define the `TreasureChest` opaque type.
|
||||||
|
|
||||||
|
- Opaque types are defined using the `opaque` keyword.
|
||||||
|
|
||||||
|
## 2. Define the `create` function.
|
||||||
|
|
||||||
|
- The [`string.length` function][length] can be used to get the length of a string.
|
||||||
|
|
||||||
|
## 3. Define `open` function.
|
||||||
|
|
||||||
|
- The `==` operator can be used to compare two strings for equality.
|
||||||
|
|
||||||
|
[length]: https://hexdocs.pm/gleam_stdlib/gleam/string.html#length
|
||||||
79
secure-treasure-chest/README.md
Normal file
79
secure-treasure-chest/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Secure Treasure Chest
|
||||||
|
|
||||||
|
Welcome to Secure Treasure Chest on Exercism's Gleam Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Opaque Types
|
||||||
|
|
||||||
|
Opaque types in Gleam are custom types where only the module that defines the type can construct or pattern match values of the type. This is useful for creating types that should only be used in a specific way.
|
||||||
|
|
||||||
|
Opaque types are defined using the `opaque` keyword.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
pub opaque type PositiveInt {
|
||||||
|
PositiveInt(inner: Int)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This `PositiveInt` type is to be used in situations where an int is wanted, but it has to be zero or greater. A regular Gleam `Int` could not be used as they can also be negative.
|
||||||
|
|
||||||
|
The module that defines this type can define a function to get the inner int value, and a function for creating the `PositiveInt` type which will return an error if the value is not positive.
|
||||||
|
|
||||||
|
```gleam
|
||||||
|
pub fn from_int(i: Int) -> Result(PositiveInt, String) {
|
||||||
|
case i {
|
||||||
|
_ if i < 0 -> Error("Value must be positive")
|
||||||
|
_ -> Ok(PositiveInt(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_int(i: PositiveInt) -> Int {
|
||||||
|
i.inner
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With this API other modules cannot construct a `PositiveInt` with a negative value, so any function that takes a `PositiveInt` can be sure that the value is positive.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Sharp eyed students will have noticed that the `TreasureChest` type in the previous exercise wasn't that secure!
|
||||||
|
|
||||||
|
If you used the `get_treasure` function you had to supply the password, but you could still destructure the `TreasureChest` type to get the treasure without having to know the password.
|
||||||
|
|
||||||
|
Let's fix that by using an Opaque Type.
|
||||||
|
|
||||||
|
## 1. Define the `TreasureChest` opaque type.
|
||||||
|
|
||||||
|
The `TreasureChest` contains two fields:
|
||||||
|
- A password that is a `String`.
|
||||||
|
- A treasure that is a generic type.
|
||||||
|
|
||||||
|
The `TreasureChest` type must be opaque.
|
||||||
|
|
||||||
|
## 2. Define the `create` function.
|
||||||
|
|
||||||
|
This function takes two arguments:
|
||||||
|
- A password `String`.
|
||||||
|
- A treasure value of any type.
|
||||||
|
|
||||||
|
The function returns a `TreasureChest` containing the password and the value.
|
||||||
|
|
||||||
|
If the password is shorter than 8 characters then the function should return an error saying `Password must be at least 8 characters long`.
|
||||||
|
|
||||||
|
## 3. Define `open` function.
|
||||||
|
|
||||||
|
This function takes two arguments:
|
||||||
|
|
||||||
|
- A `TreasureChest`.
|
||||||
|
- A password `String`.
|
||||||
|
|
||||||
|
If the password matches the password in the `TreasureChest` then the function should return the treasure, otherwise it should return an error saying `Incorrect password`.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @lpil
|
||||||
14
secure-treasure-chest/gleam.toml
Normal file
14
secure-treasure-chest/gleam.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name = "secure_treasure_chest"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gleam_otp = "~> 0.7 or ~> 1.0"
|
||||||
|
gleam_stdlib = ">= 0.54.0 or ~> 1.0"
|
||||||
|
simplifile = "~> 1.0"
|
||||||
|
gleam_erlang = ">= 0.25.0 and < 1.0.0"
|
||||||
|
gleam_yielder = ">= 1.1.0 and < 2.0.0"
|
||||||
|
gleam_regexp = ">= 1.1.0 and < 2.0.0"
|
||||||
|
gleam_deque = ">= 1.0.0 and < 2.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
exercism_test_runner = "~> 1.9"
|
||||||
31
secure-treasure-chest/manifest.toml
Normal file
31
secure-treasure-chest/manifest.toml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# This file was generated by Gleam
|
||||||
|
# You typically do not need to edit this file
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
|
||||||
|
{ name = "exercism_test_runner", version = "1.9.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "0B17BB25F2FF1E60266467C24FE0CA04005410306AA05E9A4B41B1852D72865C" },
|
||||||
|
{ name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" },
|
||||||
|
{ name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" },
|
||||||
|
{ name = "glance", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "E155BA1A787FD11827048355021C0390D2FE9A518485526F631A9D472858CC6D" },
|
||||||
|
{ name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" },
|
||||||
|
{ name = "gleam_community_colour", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "FDD6AC62C6EC8506C005949A4FCEF032038191D5EAAEC3C9A203CD53AE956ACA" },
|
||||||
|
{ name = "gleam_deque", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_deque", source = "hex", outer_checksum = "64D77068931338CF0D0CB5D37522C3E3CCA7CB7D6C5BACB41648B519CC0133C7" },
|
||||||
|
{ name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" },
|
||||||
|
{ name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" },
|
||||||
|
{ name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" },
|
||||||
|
{ name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" },
|
||||||
|
{ name = "gleam_stdlib", version = "0.55.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "32D8F4AE03771516950047813A9E359249BD9FBA5C33463FDB7B953D6F8E896B" },
|
||||||
|
{ name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" },
|
||||||
|
{ name = "glexer", version = "2.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "5C235CBDF4DA5203AD5EAB1D6D8B456ED8162C5424FE2309CFFB7EF438B7C269" },
|
||||||
|
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[requirements]
|
||||||
|
exercism_test_runner = { version = "~> 1.9" }
|
||||||
|
gleam_deque = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
|
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
|
||||||
|
gleam_otp = { version = "~> 0.7 or ~> 1.0" }
|
||||||
|
gleam_regexp = { version = ">= 1.1.0 and < 2.0.0" }
|
||||||
|
gleam_stdlib = { version = ">= 0.54.0 or ~> 1.0" }
|
||||||
|
gleam_yielder = { version = ">= 1.1.0 and < 2.0.0" }
|
||||||
|
simplifile = { version = "~> 1.0" }
|
||||||
25
secure-treasure-chest/src/secure_treasure_chest.gleam
Normal file
25
secure-treasure-chest/src/secure_treasure_chest.gleam
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import gleam/string
|
||||||
|
|
||||||
|
pub opaque type TreasureChest(a) {
|
||||||
|
TreasureChest(password: String, treasure: a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
password: String,
|
||||||
|
contents: treasure,
|
||||||
|
) -> Result(TreasureChest(treasure), String) {
|
||||||
|
case string.length(password) {
|
||||||
|
pass if pass >= 8 -> Ok(TreasureChest(password, contents))
|
||||||
|
_ -> Error("Password must be at least 8 characters long")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(
|
||||||
|
chest: TreasureChest(treasure),
|
||||||
|
password: String,
|
||||||
|
) -> Result(treasure, String) {
|
||||||
|
case chest.password == password {
|
||||||
|
False -> Error("Incorrect password")
|
||||||
|
True -> Ok(chest.treasure)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
secure-treasure-chest/test/secure_treasure_chest_test.gleam
Normal file
55
secure-treasure-chest/test/secure_treasure_chest_test.gleam
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import exercism/should
|
||||||
|
import exercism/test_runner
|
||||||
|
import gleam/string
|
||||||
|
import secure_treasure_chest
|
||||||
|
import simplifile
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
test_runner.main()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compact_whitespace(graphemes: List(String), acc: String) -> String {
|
||||||
|
case graphemes {
|
||||||
|
[] -> acc
|
||||||
|
[" ", " ", ..rest] -> compact_whitespace([" ", ..rest], acc)
|
||||||
|
[grapheme, ..rest] -> compact_whitespace(rest, acc <> grapheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_source() -> String {
|
||||||
|
let assert Ok(src) = simplifile.read("src/secure_treasure_chest.gleam")
|
||||||
|
compact_whitespace(string.to_graphemes(src), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_must_be_opaque_test() {
|
||||||
|
let src = read_source()
|
||||||
|
case string.contains(src, "pub opaque type TreasureChest") {
|
||||||
|
True -> Nil
|
||||||
|
False -> panic as "The TreasureChest type must exist and be opaque"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_is_ok_with_long_password_test() {
|
||||||
|
let assert Ok(_) = secure_treasure_chest.create("12345678", "My treasure")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_is_error_with_short_password_test() {
|
||||||
|
secure_treasure_chest.create("1234567", "My treasure")
|
||||||
|
|> should.equal(Error("Password must be at least 8 characters long"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_is_ok_with_the_correct_password_test() {
|
||||||
|
let assert Ok(chest) = secure_treasure_chest.create("wwwibble", 100)
|
||||||
|
secure_treasure_chest.open(chest, "wwwibble")
|
||||||
|
|> should.equal(Ok(100))
|
||||||
|
|
||||||
|
let assert Ok(chest) = secure_treasure_chest.create("wwwobble", 1.5)
|
||||||
|
secure_treasure_chest.open(chest, "wwwobble")
|
||||||
|
|> should.equal(Ok(1.5))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_is_an_error_with_an_incorrect_password_test() {
|
||||||
|
let assert Ok(chest) = secure_treasure_chest.create("wwwibble", 100)
|
||||||
|
secure_treasure_chest.open(chest, "wwwobble")
|
||||||
|
|> should.equal(Error("Incorrect password"))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user