{"id":25707336,"url":"https://github.com/shade40/celx","last_synced_at":"2025-02-25T08:06:06.812Z","repository":{"id":196600103,"uuid":"689450168","full_name":"shade40/celx","owner":"shade40","description":"A modern terminal UI framework powered by hypermedia served over HTTP.","archived":false,"fork":false,"pushed_at":"2024-03-26T07:59:55.000Z","size":112,"stargazers_count":20,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-04-26T09:41:15.961Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","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/shade40.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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-09-09T21:02:34.000Z","updated_at":"2024-04-26T09:41:15.962Z","dependencies_parsed_at":null,"dependency_job_id":"304a5c41-df81-43c5-be55-00f4d2ccccae","html_url":"https://github.com/shade40/celx","commit_stats":null,"previous_names":["shade40/celx"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shade40%2Fcelx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shade40%2Fcelx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shade40%2Fcelx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shade40%2Fcelx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shade40","download_url":"https://codeload.github.com/shade40/celx/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240627959,"owners_count":19831599,"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":[],"created_at":"2025-02-25T08:02:43.983Z","updated_at":"2025-02-25T08:06:06.762Z","avatar_url":"https://github.com/shade40.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"![celx](https://github.com/shade40/celx/blob/main/assets/header.png?raw=true)\n\n## celx\n\nA modern terminal UI framework powered by hypermedia served over HTTP.\n\n```\npip install sh40-celx\n```\n\nSee [/server](https://github.com/shade40/celx/tree/main/server/) for an example server \u0026 app.\n\n### Quickstart\n\n`celx` is a TUI application framework inspired by [htmx](htmx.org). It emphasizes the usage of hypermedia\nas the engine of application state (HATEOAS) as an alternative to reactive client-side frameworks. `celx` apps are\nwritten as XML fragments, and communicated through the Hypertext Transfer Protocol (HTTP).\n\nLet's start with a basic application index (running on `/`):\n\n```xml\n\u003ccelx version=\"0\"\u003e\n  \u003cpage\u003e\n    \u003ctower eid=\"root\"\u003e\n      \u003crow eid=\"header\"\u003e\n        \u003ctext\u003eHello World\u003c/text\u003e\n      \u003c/row\u003e\n      \u003ctower eid=\"body\"\u003e\n        \u003ctext\u003eThis is the app's body\u003c/text\u003e\n        \u003cbutton\u003eInsert content\u003c/button\u003e\n      \u003c/tower\u003e\n    \u003c/tower\u003e\n  \u003c/page\u003e\n\u003c/tower\u003e\n```\n\nAt the moment, both the header and body will take equal amounts of space on the page. You probably don't want this,\nso let's modify `header` to only take 1 cell of height:\n\n```xml\n\u003crow eid=\"header\"\u003e\n  \u003cstyle\u003e height: 1 \u003c/style\u003e\n\u003c/row\u003e\n```\n\nYou can insert a `\u003cstyle\u003e` tag everywhere, and it is always scoped to its parent widget. In effect, this means the above\nstyle gets converted to:\n\n```yaml\nRow#header:\n  height: 1\n```\n\nYou can insert `\u003cstyle\u003e` tags into the `page` and `celx` tags as well. We don't add a scoping header in such cases,\nas those objects aren't selectable. `page` styles are local to the current page object, `celx` (app) styles are global\nto all pages.\n\nWe use [Celadon](https://github.com/shade40/celadon) under the hood, so we inherit its selector \u0026 styling syntax. You can\nset any (already defined) attributes of any selected widget. Let's make the app's button span the whole width using a local\nstyle:\n\n```xml\n\u003cbutton\u003e\n  Insert content\n  \u003cstyle\u003e width: null \u003c/style\u003e\n\u003c/button\u003e\n```\n\nAlternatively, you can use a pre-defined group to do the same:\n\n```xml\n\u003cbutton group=\"width-fill\"\u003e\n  Insert content\n\u003c/button\u003e\n```\n\n#### Adding interactivity\n\nYou can press our button already, but you might notice it doesn't do anything. Let's fix that.\n\nFirst, add an on-submit event to the button:\n\n```xml\n\u003cbutton group=\"width-fill\" on-submit=\"GET /content; swap in #body\"\u003e\n  Insert content\n\u003c/button\u003e\n```\n\nAnd let's add the corresponding endpoint to the server (running on `/content`):\n\n```xml\n\u003ctext\u003eThis is some cool content\u003c/text\u003e\n```\n\nAfter this, our button will:\n\n- Send an HTTP `GET` request to `/content`, keeping its result\n- Parse the result as a widget, and swap `#body`'s children with it\n\nThe syntax used here is quite simple:\n\n`\u003ccommand\u003e \u003carg1\u003e \u003carg2\u003e ...`\n\n...where command is one of:\n\n- `GET`\n- `POST`\n- `insert`\n- `swap`\n- `append`\n\n`POST` optionally takes a selector for its first arg (`POST #parent /content`), which controls the\nwidget whos serialized result will be sent in the request. It defaults to the parent of the widget\nexecuting the request (our button, in this case).\n\n`insert`, `swap` and `append` take a location as their first argument, similar to `hx-swap`. Its\nvalue must be one of:\n\n- `in`: Add result into the targets children\n- `before`: Add result _before_ the target (by essentially executing the command on the target's parent, offset\n    by the target's offset)\n- `after`: Add result _after_ the target (the same way)\n- `None`: (only for `swap`) Replaces the target widget completely, deleting it from its parent and putting\n    result in its place.\n\nWhile `swap` replaces the target's (or its parent's) children completely (deleting previous content), `insert`\nand `append` add onto the current list\n\nSo in effect, our text and button will disappear and get replaced by whatever our server returns.\n\n![rule](https://singlecolorimage.com/get/707E8C/1600x3)\n\n### Features\n\n#### Hypermedia as the Engine of Application State\n\nSince applications are served over HTTP, you don't have to write _any_ client side code. So why is that\na good thing?\n\n- No client-side state duplication (your client doesn't even have to be _aware_ of state)\n\n  You cannot (and should never) trust client side code. If your application state mutates on the\n  client side, you must be able to validate it on the server, as the client could do _anything_\n  with that state. This essentially means you have to have duplicated state, and validation on both\n  sides.\n\n  Since all of your state is on the server, you avoid most of these issues.\n\n- You're free to choose your own server\n\n  Don't like Python? You can use any HTTP server, in ANY language. Python is more than fast enough\n  for our runtime, and this way all custom logic \u0026 slow operations happen on the server side in the\n  language of your choosing.\n\n- Instant usability, no need to install potentially dangerous application code\n\n  Your users only need the celx runtime to run your application. From that point on, trying out a new\n  app takes as much as writing in the URL its served at, and pressing enter. No further installation,\n  no 'clone my repo, download my build tool and execute these commands', not even a `pip install`.\n\n- Running a celx \u0026 html of the same backend on the same server\n\n  Since every bit of state is handled on the backend, you can simply send out different formats to represent\n  the same interfaces based on who is listening. In the (near) future celx will send a specific header\n  to tell the server to send celx' XML instead of HTML.\n\n#### A sophisticated styling engine\n\nAs shown above, each `\u003cstyle\u003e` tag is scoped to its parent widget. Think of this as a less error-prone\nversion of CSS' inline styles, or a more readable Tailwind. This approach doesn't fully replace the need\nfor Tailwind-like helper groups, so we have a few of those as well.\n\nOur (or rather [Celadon](https://github.com/shade40/Celadon)'s) styling system is also pretty neat in\nother ways. It supports nested styles, hierarchal queries for both direct and indirect parents, states\n(CSS' pseudoclasses), as well as your basic CSS stuff like types, ids and classes (named 'groups' in our case).\n\nHere is an example from Celadon's README. Most of this should feel fairly familiar if you're used to\nworking with CSS:\n\n```yaml\nButton:\n    fill_style: '@ui.primary'\n    # Automatically use either white or black, based on the W3C contrast\n    # guidelines\n    content_style: ''\n\n    # On hover, become a bit brighter\n    /hover: # Equivalent to 'Button/hover'\n        fill_style: '@ui.primary+1'\n\n    # Become a bit taller if in the 'big' group\n    .big:\n        height: 3\n\n    # If part of a Row in the 'button-row' group, fill available width.\n    # '\u0026' stands for the selector in the previous nesting layer, `Button`\n    # in this case.\n    Row.button-row \u003e \u0026:\n        width: null\n```\n\n![rule](https://singlecolorimage.com/get/4A7A9F/1600x3)\n\n### Documentation\n\nOnce the shade40 suite gets to a settled state (near 1.0), documentation will be\nhosted both online and as a celx application. Until then some of the widget references by using\n`python3 -m pydoc \u003cname\u003e`.\n\nWe will also create some example server applications to get you started with.\n\n![rule](https://singlecolorimage.com/get/AFE1AF/1600x3)\n\n### See also\n\n- [Hypermedia Systems](https://hypermedia.systems): The primary inspiration for the library. A great book, available\n    as hard-copy, ebook or even for free.\n- [Celadon](https://github.com/shade40/celadon): The core of the framework, providing the widget \u0026 styling\n    systems and the application runtime\n- [Zenith](https://github.com/shade40/zenith): The inline-markup language used by the framework, which can\n    be used either directly in widgets `\u003ctext\u003e[bold]Title\u003c/text\u003e` or in style definitions `\u003cstyle\u003e content_style: bold \u003c/style\u003e`.\n- [Slate](https://github.com/shade40/slate): The engine powering every interaction we make to the terminal and\n    its APIs, providing us with intelligent per-changed-character drawing and a way to color text (quite a useful\n    feature!) \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshade40%2Fcelx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshade40%2Fcelx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshade40%2Fcelx/lists"}