Skip to content

Commit

Permalink
Merge branch 'main' into add-result-chaining-example
Browse files Browse the repository at this point in the history
  • Loading branch information
Anton-4 authored Sep 2, 2024
2 parents 6c8f842 + 629a6cb commit ba0a82b
Show file tree
Hide file tree
Showing 31 changed files with 373 additions and 58 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ examples/Tasks/main
examples/Tuples/main
examples/EncodeDecode/main
examples/SafeMath/main
examples/ElmWebApp/backend
examples/ElmWebApp/frontend/elm-stuff/*
examples/ElmWebApp/frontend/elm.js
examples/GoPlatform/platform/*.dylib
roc_nightly/

Expand Down
3 changes: 3 additions & 0 deletions ci_scripts/all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ $ROC build ./examples/SafeMath/main.roc
$ROC test ./examples/SafeMath/main.roc
expect ci_scripts/expect_scripts/SafeMath.exp

$ROC build ./examples/HelloWeb/main.roc --linker=legacy
expect ci_scripts/expect_scripts/HelloWeb.exp

$ROC build --lib ./examples/GoPlatform/main.roc --output examples/GoPlatform/platform/libapp.so
go build -C examples/GoPlatform/platform -buildmode=pie -o dynhost

Expand Down
25 changes: 25 additions & 0 deletions ci_scripts/expect_scripts/ElmFrontendRocBackend.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/expect

# uncomment line below for debugging
# exp_internal 1

set timeout 7

spawn ./examples/ElmWebApp/backend

cd ./examples/ElmWebApp/frontend
spawn elm reactor --port 8001
cd ../../..

# wait for elm to start up
sleep 3

set curlOutput [exec curl -sS localhost:8001/index.html]

# We don't actually run the elm js here, selenium or puppeteer could be used in the future for a complete test
if {[string match "*Elm.Main.init*" $curlOutput]} {
exit 0
} else {
puts "Error: curl output was different than expected: $curlOutput"
exit 1
}
25 changes: 25 additions & 0 deletions ci_scripts/expect_scripts/HelloWeb.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

#!/usr/bin/expect

# uncomment line below for debugging
# exp_internal 1

set timeout 7

spawn ./examples/HelloWeb/main

expect "Listening on <http://127.0.0.1:8000>\r\n" {
set curlOutput [exec curl -sS localhost:8000]

if {$curlOutput eq "<b>Hello, web!</b></br>"} {
expect "Z Get /\r\n" {
exit 0
}
} else {
puts "Error: curl output was different than expected: $curlOutput"
exit 1
}
}

puts stderr "\nError: output was different than expected."
exit 1
7 changes: 2 additions & 5 deletions examples/Arithmetic/main.roc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }

import pf.Stdout
import pf.Task
import pf.Arg

TaskErrors : [InvalidArg, InvalidNumStr]
Expand Down Expand Up @@ -29,9 +28,7 @@ main =

Task.ok results

taskResult <- Task.attempt task

when taskResult is
when Task.result! task is
Ok result -> Stdout.line result
Err InvalidArg -> Task.err (Exit 1 "Error: Please provide two integers between -1000 and 1000 as arguments.")
Err InvalidNumStr -> Task.err (Exit 1 "Error: Invalid number format. Please provide integers between -1000 and 1000.")
Expand Down
3 changes: 1 addition & 2 deletions examples/CommandLineArgs/main.roc
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Run with `roc ./examples/CommandLineArgs/main.roc some_argument`
# !! This currently does not work in combination with --linker=legacy, see https://github.com/roc-lang/basic-cli/issues/82
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

import pf.Stdout
import pf.Task
import pf.Arg

main =
Expand Down
3 changes: 1 addition & 2 deletions examples/CommandLineArgsFile/main.roc
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Run with `roc ./examples/CommandLineArgsFile/main.roc -- examples/CommandLineArgsFile/input.txt`
# This currently does not work in combination with --linker=legacy, see https://github.com/roc-lang/basic-cli/issues/82
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

import pf.Stdout
import pf.Task exposing [Task]
import pf.Path exposing [Path]
import pf.Arg

Expand Down
69 changes: 69 additions & 0 deletions examples/ElmWebApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Web App with Elm

A minimal web app with an Elm [frontend](https://chatgpt.com/share/93575daf-49ef-48ba-b39d-ab04672e4019) and Roc [backend](https://chatgpt.com/share/7ac35a32-dab5-46d0-bb17-9d584469556f). The Roc backend uses the [basic-webserver](https://github.com/roc-lang/basic-webserver) platform.

## Why Elm + Roc?

Roc was inspired by Elm, so it's nice to be able to use a similar language for the frontend. Elm also has a mature collection of re-usable packages.

## Alternatives

We've also enjoyed using [htmx with Roc](https://github.com/lukewilliamboswell/roc-htmx-playground). It allows you to use Roc for the frontend and the backend.

## Full Code

src/Main.elm:
```elm
file:frontend/src/Main.elm
```

elm.json:
```json
file:frontend/elm.json
```

index.html:
```html
file:frontend/index.html
```

backend.roc:
```roc
file:backend.roc
```
## Running

### Roc

You can change the port on which the Roc server runs with ROC_BASIC_WEBSERVER_PORT.
```
cd examples/ElmWebApp/
# development
roc backend.roc --linker=legacy
# production
roc build backend.roc --optimize --linker=legacy
./backend
```

### Elm

> Note: for non-trivial Elm development we recommend using [elm-watch](https://github.com/lydell/elm-watch).
Compile elm code to javascript:
```
cd examples/ElmWebApp/frontend
# development
elm make src/Main.elm --output elm.js
# production
elm make src/Main.elm --output elm.js --optimize
```

Serve the frontend:
```
elm reactor --port 8001 # Roc backend will be on 8000
```
For production; use a [battle-tested HTTP server](https://chatgpt.com/share/5809a606-10ea-4ee6-b821-732465016254) instead of elm reactor.

Open [localhost:8001/index.html](localhost:8001/index.html) in your browser.
36 changes: 36 additions & 0 deletions examples/ElmWebApp/backend.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
app [Model, server] {
pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.8.0/jz2EfGAtz_y06nN7f8tU9AvmzhKK-jnluXQQGa9rZoQ.tar.br",
}

import pf.Stdout
import pf.Http exposing [Request, Response]
import pf.Utc

# [backend](https://chatgpt.com/share/7ac35a32-dab5-46d0-bb17-9d584469556f) Roc server

# Model is produced by `init`.
Model : {}

# With `init` you can set up a database connection once at server startup,
# generate css by running `tailwindcss`,...
# In this case we don't have anything to initialize, so it is just `Task.ok {}`.

server = { init: Task.ok {}, respond }

respond : Request, Model -> Task Response [ServerErr Str]_
respond = \req, _ ->
# Log request datetime, method and url
datetime = Utc.now! |> Utc.toIso8601Str

Stdout.line! "$(datetime) $(Http.methodToStr req.method) $(req.url)"

Task.ok {
status: 200,
headers: [
# !!
# Change http://localhost:8001 to your domain for production usage
# !!
{ name: "Access-Control-Allow-Origin", value: "http://localhost:8001" },
],
body: Str.toUtf8 "Hi, Elm! This is from Roc: 🎁\n",
}
27 changes: 27 additions & 0 deletions examples/ElmWebApp/frontend/elm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3"
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.3"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
13 changes: 13 additions & 0 deletions examples/ElmWebApp/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head> </head>
<body>
<!-- app div is used by elm -->
<div id="app"></div>
<!-- elm is compiled to js -->
<script src="elm.js"></script>
<script>
Elm.Main.init({ node: document.getElementById("app") });
</script>
</body>
</html>
84 changes: 84 additions & 0 deletions examples/ElmWebApp/frontend/src/Main.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module Main exposing (..)

-- Importing necessary modules
import Browser
import Html exposing (Html, div, text, h1)
import Html.Attributes exposing (..)
import Http

-- MAIN

-- The main function is the entry point of an Elm application
main =
Browser.element
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}

-- MODEL

-- Model represents the state of our application
type Model
= Failure String
| Loading
| Success String

-- init function sets up the initial state and any commands to run on startup
init : () -> (Model, Cmd Msg)
init _ =
( Loading
, fetchData
)

-- UPDATE

-- Msg represents the different types of messages our app can receive
type Msg
= GotResponse (Result Http.Error String)

-- update function handles how the model changes in response to messages
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotResponse result ->
case result of
Ok body ->
(Success body, Cmd.none)
Err error ->
(Failure (Debug.toString error), Cmd.none)

-- VIEW

-- view function determines what to display in the browser based on the current model
view : Model -> Html Msg
view model =
case model of
Failure errorMsg ->
text ("Is the Roc webserver running? I hit an error: " ++ errorMsg)
Loading ->
text "Loading..."
Success body ->
div [ id "app" ]
[
h1 [] [ text body ]
]

-- HTTP

-- fetchData sends an HTTP GET request to the Roc backend
fetchData : Cmd Msg
fetchData =
Http.get
{ url = "http://localhost:8000/"
, expect = Http.expectString GotResponse
}

-- SUBSCRIPTIONS

-- subscriptions allow the app to listen for external input (e.g., time, websockets)
-- In this case, we're not using any subscriptions
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
11 changes: 5 additions & 6 deletions examples/EncodeDecode/main.roc
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br",
json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.8.0/BlWJJh_ouV7c_IwvecYpgpR3jOCzVO-oyk-7ISdl2S4.tar.br",
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.10.1/jozYCvOqoYa-cV6OdTcxw3uDGn61cLvzr5dK1iKf1ag.tar.br",
}

import pf.Task
import json.Core exposing [json]
import json.Json
import pf.Stdout
### start snippet impl

Expand Down Expand Up @@ -91,7 +90,7 @@ originalList = [

# encode them into JSON
encodedBytes : List U8
encodedBytes = Encode.toBytes originalList json
encodedBytes = Encode.toBytes originalList Json.utf8

# test we have encoded correctly
expect encodedBytes == originalBytes
Expand All @@ -102,7 +101,7 @@ originalBytes = "[1,2,3,4,5,6,7,8,9,10]" |> Str.toUtf8

# decode into a list of ItemKind's
decodedList : List ItemKind
decodedList = Decode.fromBytes originalBytes json |> Result.withDefault []
decodedList = Decode.fromBytes originalBytes Json.utf8 |> Result.withDefault []

# test we have decoded correctly
expect decodedList == originalList
Expand Down
3 changes: 1 addition & 2 deletions examples/FizzBuzz/main.roc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" }

import pf.Stdout
import pf.Task

main =
List.range { start: At 1, end: At 100 }
Expand Down
Loading

0 comments on commit ba0a82b

Please sign in to comment.