Household planning and management software meant to organise and streamline neurodivergent households.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Think this is a solid start

+403
+1
.gitignore
··· 1 + data
+26
mise.toml
··· 1 + [tools] 2 + erlang = "27" 3 + gleam = "1.14.0" 4 + rebar = "latest" 5 + 6 + [tasks.dev] 7 + description = "Run the app in development mode" 8 + tools.podman = "latest" 9 + depends = ["build_dashboard"] 10 + run = [ 11 + "mkdir -p ../data/postgres", 12 + "podman run --replace -v../data/postgres:/var/lib/postgresql:Z --name sweetnhome-dev-pg -p 5432:5432 -e POSTGRES_PASSWORD=sweetnhome_dev -e POSTGRES_USER=sweetnhome_dev -d docker.io/library/postgres:18.1", 13 + "gleam run" 14 + ] 15 + env = { PORT = 3005, DATABASE_URL = "postgres://sweetnhome_dev:sweetnhome_dev@localhost:5432/sweetnhome_dev"} 16 + dir = "sweetnhouse_server" 17 + [tasks.build_dashboard] 18 + description = "Build the dashboard static files" 19 + tools.bun = "latest" 20 + run = ["#bun install", "gleam build --target javascript", 21 + "echo 'import { main } from \"./sweetnhouse_dashboard.mjs\";document.addEventListener(\"DOMContentLoaded\", main())' > \"./build/dev/javascript/sweetnhouse_dashboard/sweetnhouse_dashboard.ts\"", 22 + "bun build ./build/dev/javascript/sweetnhouse_dashboard/sweetnhouse_dashboard.ts --minify --outfile ../sweetnhouse_server/priv/static/lumina_client.min.mjs --target=browser", 23 + "bun build ./build/dev/javascript/sweetnhouse_dashboard/sweetnhouse_dashboard.ts --outfile ../sweetnhouse_server/priv/static/lumina_client.mjs --target=browser", 24 + 25 + ] 26 + dir = "sweetnhouse_dashboard"
+4
sweetnhouse_dashboard/.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump
+19
sweetnhouse_dashboard/gleam.toml
··· 1 + name = "sweetnhouse_dashboard" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + 18 + [dev-dependencies] 19 + gleeunit = ">= 1.0.0 and < 2.0.0"
+11
sweetnhouse_dashboard/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "gleam_stdlib", version = "0.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, 6 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 7 + ] 8 + 9 + [requirements] 10 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 11 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
+5
sweetnhouse_dashboard/src/sweetnhouse_dashboard.gleam
··· 1 + import gleam/io 2 + 3 + pub fn main() -> Nil { 4 + io.println("Hello from sweetnhouse_dashboard!") 5 + }
+13
sweetnhouse_dashboard/test/sweetnhouse_dashboard_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }
+5
sweetnhouse_server/.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + priv/static/*js
+27
sweetnhouse_server/gleam.toml
··· 1 + name = "sweetnhouse_server" 2 + version = "1.0.0" 3 + target = "erlang" 4 + 5 + # Fill out these fields if you intend to generate HTML documentation or publish 6 + # your project to the Hex package manager. 7 + # 8 + # description = "" 9 + # licences = ["Apache-2.0"] 10 + # repository = { type = "github", user = "", repo = "" } 11 + # links = [{ title = "Website", href = "" }] 12 + # 13 + # For a full reference of all the available options, you can have a look at 14 + # https://gleam.run/writing-gleam/gleam-toml/. 15 + 16 + [dependencies] 17 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 18 + mist = ">= 2.0.0 and < 6.0.0" 19 + dot_env = ">= 1.2.0 and < 2.0.0" 20 + wisp = ">= 2.1.1 and < 3.0.0" 21 + gleam_erlang = ">= 1.3.0 and < 2.0.0" 22 + pog = ">= 4.1.0 and < 5.0.0" 23 + gleam_http = ">= 4.3.0 and < 5.0.0" 24 + 25 + 26 + [dev-dependencies] 27 + gleeunit = ">= 1.0.0 and < 2.0.0"
+45
sweetnhouse_server/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" }, 6 + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, 7 + { name = "dot_env", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "simplifile"], otp_app = "dot_env", source = "hex", outer_checksum = "F2B4815F1B5AF8F20A6EADBB393E715C4C35203EBD5BE8200F766EA83A0B18DE" }, 8 + { name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" }, 9 + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, 10 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 11 + { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, 12 + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 13 + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 14 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 15 + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 16 + { name = "gleam_stdlib", version = "0.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, 17 + { name = "gleam_time", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "0DF3834D20193F0A38D0EB21F0A78D48F2EC276C285969131B86DF8D4EF9E762" }, 18 + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 19 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 20 + { name = "glisten", version = "8.0.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "86B838196592D9EBDE7A1D2369AE3A51E568F7DD2D168706C463C42D17B95312" }, 21 + { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, 22 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 23 + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 24 + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 25 + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 26 + { name = "mist", version = "5.0.4", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7CED4B2D81FD547ADB093D97B9928B9419A7F58B8562A30A6CC17A252B31AD05" }, 27 + { name = "opentelemetry_api", version = "1.5.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "F53EC8A1337AE4A487D43AC89DA4BD3A3C99DDF576655D071DEED8B56A2D5DDA" }, 28 + { name = "pg_types", version = "0.6.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "9949A4849DD13408FA249AB7B745E0D2DFDB9532AEE2B9722326E33CD082A778" }, 29 + { name = "pgo", version = "0.20.0", build_tools = ["rebar3"], requirements = ["backoff", "opentelemetry_api", "pg_types"], otp_app = "pgo", source = "hex", outer_checksum = "2F11E6649CEB38E569EF56B16BE1D04874AE5B11A02867080A2817CE423C683B" }, 30 + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 31 + { name = "pog", version = "4.1.0", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_otp", "gleam_stdlib", "gleam_time", "pgo"], otp_app = "pog", source = "hex", outer_checksum = "E4AFBA39A5FAA2E77291836C9683ADE882E65A06AB28CA7D61AE7A3AD61EBBD5" }, 32 + { name = "simplifile", version = "2.3.2", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "E049B4DACD4D206D87843BCF4C775A50AE0F50A52031A2FFB40C9ED07D6EC70A" }, 33 + { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, 34 + { name = "wisp", version = "2.1.1", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "46E2E31DECD61A3748CF6CB317D9AC432BBC8D8A6E65655A9E787BDC69389DE0" }, 35 + ] 36 + 37 + [requirements] 38 + dot_env = { version = ">= 1.2.0 and < 2.0.0" } 39 + gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } 40 + gleam_http = { version = ">= 4.3.0 and < 5.0.0" } 41 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 42 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 43 + mist = { version = ">= 2.0.0 and < 6.0.0" } 44 + pog = { version = ">= 4.1.0 and < 5.0.0" } 45 + wisp = { version = ">= 2.1.1 and < 3.0.0" }
sweetnhouse_server/priv/index.html

This is a binary file and will not be displayed.

sweetnhouse_server/priv/static/.gitkeep

This is a binary file and will not be displayed.

+107
sweetnhouse_server/src/sweetnhouse_server.gleam
··· 1 + import dot_env/env 2 + import gleam/erlang/process 3 + import gleam/int 4 + import gleam/io 5 + import gleam/result 6 + import mist 7 + import pog 8 + import sweetnhouse_server/config 9 + import sweetnhouse_server/router 10 + import sweetnhouse_server/web 11 + import wisp 12 + import wisp/wisp_mist 13 + 14 + pub fn main() { 15 + case get_context() { 16 + Ok(ctx) -> { 17 + // Ensure the app_config table exists 18 + // This is where we will store key-value pairs like secret_key_base 19 + // and other configuration settings 20 + // We do this inside a {} (scope) to limit the lifetime of `conn` 21 + { 22 + let conn = pog.named_connection(ctx.db_pool_name) 23 + 24 + let setup_query = 25 + " 26 + CREATE TABLE IF NOT EXISTS app_config ( 27 + key TEXT PRIMARY KEY, 28 + value TEXT NOT NULL 29 + )" 30 + 31 + case pog.query(setup_query) |> pog.execute(conn) { 32 + Ok(_) -> Nil 33 + Error(_e) -> { 34 + panic as "Could not create app_config table." 35 + } 36 + } 37 + } 38 + // Retrieve or generate the secret_key_base 39 + let secret_key_base = case 40 + config.get_or(ctx, "secret_key_base", fn() { 41 + io.println("Generating new secret_key_base...") 42 + wisp.random_string(64) 43 + }) 44 + { 45 + #(key, False) -> { 46 + io.println("Using existing secret_key_base from database.") 47 + key 48 + } 49 + #(key, True) -> key 50 + } 51 + wisp.configure_logger() 52 + 53 + case 54 + wisp_mist.handler(router.handle_request(_, ctx), secret_key_base) 55 + |> mist.new 56 + |> mist.port(ctx.port) 57 + |> mist.after_start(fn(_, _, _) { Nil }) 58 + |> mist.start 59 + { 60 + Ok(_server) -> 61 + io.println("🚀 Server started on port " <> int.to_string(ctx.port)) 62 + Error(_error) -> io.println_error("Failed to start server!") 63 + } 64 + 65 + process.sleep_forever() 66 + } 67 + Error(e) -> io.println_error("Failed to start application: " <> e) 68 + } 69 + } 70 + 71 + fn get_context() -> Result(web.Context, String) { 72 + let app_name = env.get_string_or("APP_NAME", "SweetNHouse") 73 + let port = env.get_int_or("PORT", 3005) 74 + 75 + let pool_name = process.new_name("sweetnhouse_db_pool") 76 + 77 + use postgres_url <- result.try( 78 + env.get_string("DATABASE_URL") 79 + |> result.replace_error("DATABASE_URL not set in environment variables."), 80 + ) 81 + 82 + use config <- result.try( 83 + pog.url_config(pool_name, postgres_url) 84 + |> result.replace_error("Invalid DATABASE_URL format."), 85 + ) 86 + 87 + let config = config |> pog.pool_size(10) 88 + 89 + use _pool_pid <- result.try( 90 + pog.start(config) 91 + |> result.replace_error("Failed to start Pog database connection pool."), 92 + ) 93 + 94 + use priv_directory <- result.try( 95 + wisp.priv_directory("sweetnhouse_server") 96 + |> result.replace_error("Failed to get priv directory."), 97 + ) 98 + 99 + let static_directory = priv_directory <> "/static" 100 + 101 + Ok(web.Context( 102 + static_directory: static_directory, 103 + db_pool_name: pool_name, 104 + app_name: app_name, 105 + port: port, 106 + )) 107 + }
+38
sweetnhouse_server/src/sweetnhouse_server/config.gleam
··· 1 + import gleam/dynamic/decode 2 + import pog 3 + import sweetnhouse_server/web 4 + 5 + /// Retrieve a configuration value by key from the database, 6 + /// or compute, insert and return a default value if the key does not exist. 7 + /// Returns a tuple of the value and a boolean indicating whether the default function was ran. 8 + pub fn get_or( 9 + ctx: web.Context, 10 + key: String, 11 + default default: fn() -> String, 12 + ) -> #(String, Bool) { 13 + let conn = pog.named_connection(ctx.db_pool_name) 14 + 15 + case 16 + pog.query("SELECT value FROM app_config WHERE key = $1") 17 + |> pog.parameter(pog.text(key)) 18 + |> pog.returning(decode.at([0], decode.string)) 19 + |> pog.execute(conn) 20 + { 21 + Ok(res) if res.count > 0 -> { 22 + let z = "Could not retrieve " <> key <> " from database." 23 + let assert [val, ..] = res.rows as z 24 + #(val, False) 25 + } 26 + 27 + _ -> { 28 + let new_val = default() 29 + let assert Ok(_) = 30 + pog.query( 31 + "INSERT INTO app_config (key, value) VALUES ('secret_key_base', $1)", 32 + ) 33 + |> pog.parameter(pog.text(new_val)) 34 + |> pog.execute(conn) 35 + #(new_val, True) 36 + } 37 + } 38 + }
+20
sweetnhouse_server/src/sweetnhouse_server/router.gleam
··· 1 + import gleam/http.{Get, Post} 2 + import sweetnhouse_server/web.{type Context} 3 + import wisp.{type Request, type Response} 4 + 5 + pub fn handle_request(req: Request, ctx: Context) -> Response { 6 + use req <- web.middleware(req, ctx) 7 + 8 + case wisp.path_segments(req) { 9 + [] -> home_page(req) 10 + _ -> wisp.not_found() 11 + } 12 + } 13 + 14 + fn home_page(req: Request) -> Response { 15 + // Only allow GET requests 16 + use <- wisp.require_method(req, Get) 17 + 18 + wisp.ok() 19 + |> wisp.html_body("<h1>Todo</h1>") 20 + }
+28
sweetnhouse_server/src/sweetnhouse_server/web.gleam
··· 1 + import pog 2 + import gleam/erlang/process 3 + import wisp 4 + 5 + /// Context 6 + pub type Context { 7 + Context( 8 + static_directory: String, 9 + db_pool_name: process.Name(pog.Message), 10 + app_name: String, 11 + port: Int, 12 + ) 13 + } 14 + 15 + pub fn middleware( 16 + req: wisp.Request, 17 + ctx: Context, 18 + handle_request: fn(wisp.Request) -> wisp.Response, 19 + ) -> wisp.Response { 20 + let req = wisp.method_override(req) 21 + use <- wisp.log_request(req) 22 + use <- wisp.rescue_crashes 23 + use req <- wisp.handle_head(req) 24 + use req <- wisp.csrf_known_header_protection(req) 25 + use <- wisp.serve_static(req, under: "/static", from: ctx.static_directory) 26 + 27 + handle_request(req) 28 + }
+13
sweetnhouse_server/test/sweetnhouse_server_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }
+4
sweetnhouse_shared/.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump
+19
sweetnhouse_shared/gleam.toml
··· 1 + name = "sweetnhouse_shared" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + 18 + [dev-dependencies] 19 + gleeunit = ">= 1.0.0 and < 2.0.0"
+5
sweetnhouse_shared/src/sweetnhouse_shared.gleam
··· 1 + import gleam/io 2 + 3 + pub fn main() -> Nil { 4 + io.println("Hello from sweetnhouse_shared!") 5 + }
+13
sweetnhouse_shared/test/sweetnhouse_shared_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }