{"id":15061162,"url":"https://github.com/leostera/minttea","last_synced_at":"2025-04-09T05:12:24.514Z","repository":{"id":212296841,"uuid":"731160255","full_name":"leostera/minttea","owner":"leostera","description":"A fun little TUI framework for OCaml","archived":false,"fork":false,"pushed_at":"2024-09-16T10:12:03.000Z","size":29743,"stargazers_count":393,"open_issues_count":17,"forks_count":28,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-02T04:05:02.496Z","etag":null,"topics":["cli","multicore","ocaml","terminal","the-elm-architecture","tui"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leostera.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-13T13:41:08.000Z","updated_at":"2025-03-28T02:36:04.000Z","dependencies_parsed_at":"2023-12-29T19:24:40.245Z","dependency_job_id":"ae1f881c-dc9e-4593-af2d-7f1ed9b08b6f","html_url":"https://github.com/leostera/minttea","commit_stats":null,"previous_names":["leostera/minttea"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fminttea","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fminttea/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fminttea/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leostera%2Fminttea/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leostera","download_url":"https://codeload.github.com/leostera/minttea/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247980844,"owners_count":21027808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","multicore","ocaml","terminal","the-elm-architecture","tui"],"created_at":"2024-09-24T23:10:32.747Z","updated_at":"2025-04-09T05:12:24.489Z","avatar_url":"https://github.com/leostera.png","language":"OCaml","readme":"# Mint Tea\n\u003cimg src=\"https://github.com/dmmulroy/minttea/assets/2755722/e9e96e73-1f7f-4b8f-8bb1-445308dfe8bd\" alt=\"Mint Tea Logo\" width=\"400\"/\u003e\n\nA fun, functional, and stateful way to build terminal apps in OCaml heavily\ninspired by [BubbleTea][bubbletea]. Mint Tea is built on Riot and uses The Elm\nArchitecture.\n\n[bubbletea]: https://github.com/charmbracelet/bubbletea\n\n\u003cimg src=\"https://github.com/leostera/minttea/raw/main/examples/views/demo.gif\"/\u003e\n\n## Tutorial\n\nMint Tea is based on the functional paradigm of [The Elm\nArchitecture][tea], which works great with OCaml. It's a delightful\nway to build applications.\n\nThis tutorial assumes you have a working knowledge of OCaml.\n\n[tea]: https://guide.elm-lang.org/architecture/\n\n### Getting Started\n\nFor this tutorial, we're making a shopping list.\n\nWe'll start by defining our `dune-project` file:\n\n```dune\n(lang dune 3.12)\n```\n\nAnd a `dune` file for our executable:\n\n```dune\n(executable\n  (name shop)\n  (libraries minttea))\n```\n\nThen we need to pin the `minttea` package to the github source:\n\n```\n$ opam install minttea\n```\n\nOpam will do some work installing `minttea` from the github source.\n\nWe can run `dune build` to validate the package has been installed correctly.\n\nGreat, now we can create a new `shop.ml` file and start by opening up `Minttea`:\n\n```ocaml\nopen Minttea\n```\n\nMint Tea programs are composed of a **model** that describes the application\nstate, and three simple functions:\n\n* `init`, a function that returns the initial commands for the application to\n  run\n* `update` a function that handles incoming events and updates the model\n  accordingly\n* `view`, a function that renders the UI based on the data in the model\n\n### The Model\n\nWe'll start by creating a type for our model. This type can be anything you\nwant, just remember that it must hold your entire application state.\n\n\u003c!-- $MDX file=./examples/basic/main.ml,part=model --\u003e\n```ocaml\ntype model = {\n  (* the choices that will be used and whether they are selected or unselected *)\n  choices : (string * [ `selected | `unselected ]) list;\n  (* the current position of the cursor *)\n  cursor : int;\n}\n```\n\n### Initialization\n\nNext up, we'll create our `initial_model` function. If creating this initial\nstate is too expensive, we could make it a function too, so we can call it when\nwe need to start the application.\n\n\u003c!-- $MDX file=./examples/basic/main.ml,part=initial_model --\u003e\n```ocaml\nlet initial_model =\n  {\n    cursor = 0;\n    choices =\n      [\n        (\"Buy empanadas 🥟\", `unselected);\n        (\"Buy carrots 🥕\", `unselected);\n        (\"Buy cupcakes 🧁\", `unselected);\n      ];\n  }\n```\n\nNext we will define our `init` function. This function takes the initial state\nand returns a Mint Tea `Command` that kicks off the application. This can be\ngoing into fullscreen, setting up timers, or just nothing. \n\nIn this case we do nothing:\n\n\u003c!-- $MDX file=./examples/basic/main.ml,part=init --\u003e\n```ocaml\nlet init _model = Command.Noop\n```\n\n### The Update Function\n\nThe interesting part of any TEA application is always how it updates the model\nbased off incoming events. In Mint Tea things aren't any different. The\n`update` function gets called whenever \"things happen\" – this could be a key\npress, a timer going off, or even every rendering frame. There is even the\npossibility of using custom events.\n\n\u003c!-- $MDX file=./examples/basic/main.ml,part=update --\u003e\n```ocaml\nlet update event model =\n  match event with\n  (* if we press `q` or the escape key, we exit *)\n  | Event.KeyDown ((Key \"q\" | Escape), _modifier) -\u003e (model, Command.Quit)\n  (* if we press up or `k`, we move up in the list *)\n  | Event.KeyDown ((Up | Key \"k\"), _modifier) -\u003e\n      let cursor =\n        if model.cursor = 0 then List.length model.choices - 1\n        else model.cursor - 1\n      in\n      ({ model with cursor }, Command.Noop)\n  (* if we press down or `j`, we move down in the list *)\n  | Event.KeyDown ((Down | Key \"j\"), _modifier) -\u003e\n      let cursor =\n        if model.cursor = List.length model.choices - 1 then 0\n        else model.cursor + 1\n      in\n      ({ model with cursor }, Command.Noop)\n  (* when we press enter or space we toggle the item in the list\n     that the cursor points to *)\n  | Event.KeyDown ((Enter | Space), _modifier) -\u003e\n      let toggle status =\n        match status with `selected -\u003e `unselected | `unselected -\u003e `selected\n      in\n      let choices =\n        List.mapi\n          (fun idx (name, status) -\u003e\n            let status = if idx = model.cursor then toggle status else status in\n            (name, status))\n          model.choices\n      in\n      ({ model with choices }, Command.Noop)\n  (* for all other events, we do nothing *)\n  | _ -\u003e (model, Command.Noop)\n```\n\nYou may have noticed the special command `Quit` up there. This command tells\nMint Tea that it's time for the application to shutdown.\n\n### The View Method\n\nFinally, we need to render our TUI. For that we define a little `view` method\nthat takes our model and creates a `string`. That string is our TUI!\n\nBecause the view describes the entire UI of your application, you don't have to\nworry about redrawing logic or things like that. Mint Tea takes care of it for\nyou.\n\n\u003c!-- $MDX file=./examples/basic/main.ml,part=view --\u003e\n```ocaml\nlet view model =\n  (* we create our options by mapping over them *)\n  let options =\n    model.choices\n    |\u003e List.mapi (fun idx (name, checked) -\u003e\n           let cursor = if model.cursor = idx then \"\u003e\" else \" \" in\n           let checked = if checked = `selected then \"x\" else \" \" in\n           Format.sprintf \"%s [%s] %s\" cursor checked name)\n    |\u003e String.concat \"\\n\"\n  in\n  (* and we send the UI for rendering! *)\n  Format.sprintf\n    {|\nWhat should we buy at the market?\n\n%s\n\nPress q to quit.\n\n  |} options\n```\n\n### All Together Now\n\nThe last step is to simply run our program. We build our Mint Tea application\nby calling `Minttea.app ~init ~update ~view ()` and we can start it by calling\n`Minttea.start app ~initial_model`\n\n\u003c!-- $MDX file=./examples/basic/main.ml,part=start --\u003e\n```ocaml\nlet app = Minttea.app ~init ~update ~view ()\nlet () = Minttea.start app ~initial_model\n```\n\nWe can now run our application:\n\n`$ dune exec ./shop.exe`\n\nAnd we get our lovely little TUI app:\n\n\u003cimg src=\"https://github.com/leostera/minttea/raw/main/examples/basic/demo.gif\"/\u003e\n\n## What's Next?\n\nThis tutorial covers the very basics of building an interactive terminal UI\nwith Mint Tea, but in the real world you'll also need to perform I/O.\n\nYou can also check our other [examples in GitHub](https://github.com/leostera/minttea/tree/main/examples) to see more ways in which\nyou can build your TUIs.\n\n## Libraries to use with Mint Tea\n\n* [Leaves](./leaves): Common Mint Tea components to get you started\n* [Spices](./spices): style, format, and layout tools for terminal applications\n","funding_links":[],"categories":["\u003ca name=\"OCaml\"\u003e\u003c/a\u003eOCaml"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleostera%2Fminttea","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleostera%2Fminttea","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleostera%2Fminttea/lists"}