diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..12b9772 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "26.0.2" + gleam-version: "1.1.0" + rebar3-version: "3" + # elixir-version: "1.15.4" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..f5fa5de --- /dev/null +++ b/gleam.toml @@ -0,0 +1,21 @@ +name = "tiny_cache" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "username", repo = "project" } +# links = [{ title = "Website", href = "https://gleam.run" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = ">= 0.34.0 and < 2.0.0" +gleam_erlang = ">= 0.25.0 and < 1.0.0" +gleam_otp = ">= 0.10.0 and < 1.0.0" + +[dev-dependencies] +gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..3850d59 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,15 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" }, + { name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" }, + { name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" }, + { name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" }, +] + +[requirements] +gleam_erlang = { version = ">= 0.25.0 and < 1.0.0"} +gleam_otp = { version = ">= 0.10.0 and < 1.0.0"} +gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } +gleeunit = { version = ">= 1.0.0 and < 2.0.0" } diff --git a/src/lib/cache.gleam b/src/lib/cache.gleam new file mode 100644 index 0000000..321782c --- /dev/null +++ b/src/lib/cache.gleam @@ -0,0 +1,65 @@ +import gleam/dict.{type Dict} +import gleam/erlang/process.{type Subject} +import gleam/otp/actor + +// Type alias for a Gleam dict +type TinyCacheDataStore(v) = + Dict(String, v) + +// Subject for TinyCache Messages +pub type TinyCacheSubject(v) = + Subject(Message(v)) + +/// Messages which the TinyCache actor understands +/// Keys -> send client all keys +/// Get -> send client value for key +/// Set -> insert pair +/// Shutdown -> stop actor process normally +pub type Message(v) { + Keys(reply_with: Subject(List(String))) + Get(reply_with: Subject(Result(v, Nil)), key: String) + Set(key: String, value: v) + Shutdown +} + +/// Loop function for actor +fn handle_message( + message: Message(v), + store: TinyCacheDataStore(v), +) -> actor.Next(Message(v), TinyCacheDataStore(v)) { + case message { + Keys(client) -> { + process.send(client, dict.keys(store)) + actor.continue(store) + } + Get(client, key) -> { + process.send(client, dict.get(store, key)) + actor.continue(store) + } + Set(key, value) -> { + let store = dict.insert(store, key, value) + actor.continue(store) + } + Shutdown -> actor.Stop(process.Normal) + } +} + +/// Start TinyCache +pub fn new() -> Result(TinyCacheSubject(v), actor.StartError) { + actor.start(dict.new(), handle_message) +} + +/// Retrieve all keys +pub fn keys(tiny_cache: TinyCacheSubject(v)) -> List(String) { + actor.call(tiny_cache, Keys, 1000) +} + +/// Get value for key +pub fn get(tiny_cache: TinyCacheSubject(v), key: String) -> Result(v, Nil) { + actor.call(tiny_cache, Get(_, key), 1000) +} + +/// Set pair +pub fn set(tiny_cache: TinyCacheSubject(v), key: String, value: v) { + process.send(tiny_cache, Set(key, value)) +} diff --git a/src/tiny_cache.gleam b/src/tiny_cache.gleam new file mode 100644 index 0000000..b3d352d --- /dev/null +++ b/src/tiny_cache.gleam @@ -0,0 +1,9 @@ +import lib/cache + +const tiny_cache_server_port = 6210 + +pub fn main() { + let assert _a = cache.new() + todo + // endpoints /keys, /set, and /get +} diff --git a/test/tiny_cache_test.gleam b/test/tiny_cache_test.gleam new file mode 100644 index 0000000..3831e7a --- /dev/null +++ b/test/tiny_cache_test.gleam @@ -0,0 +1,12 @@ +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +// gleeunit test functions end in `_test` +pub fn hello_world_test() { + 1 + |> should.equal(1) +}