{"id":16711678,"url":"https://github.com/ymtszw/setem","last_synced_at":"2025-03-17T01:31:08.152Z","repository":{"id":43511983,"uuid":"306644069","full_name":"ymtszw/setem","owner":"ymtszw","description":"Set'em: Elm record setter generator","archived":false,"fork":false,"pushed_at":"2024-10-28T04:23:39.000Z","size":422,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-14T18:22:49.495Z","etag":null,"topics":["cli","elm","generator"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/ymtszw.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2020-10-23T13:22:58.000Z","updated_at":"2024-10-28T04:23:41.000Z","dependencies_parsed_at":"2022-09-26T16:21:28.610Z","dependency_job_id":"d786aadf-6f7d-4da2-904a-11511bcdb648","html_url":"https://github.com/ymtszw/setem","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymtszw%2Fsetem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymtszw%2Fsetem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymtszw%2Fsetem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ymtszw%2Fsetem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ymtszw","download_url":"https://codeload.github.com/ymtszw/setem/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243625037,"owners_count":20321220,"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","elm","generator"],"created_at":"2024-10-12T20:25:35.349Z","updated_at":"2025-03-17T01:31:07.766Z","avatar_url":"https://github.com/ymtszw.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🔸 setem 🔸\n\n[![sanity_check](https://github.com/ymtszw/setem/actions/workflows/sanity_check.yml/badge.svg)](https://github.com/ymtszw/setem/actions/workflows/sanity_check.yml)\n[![npm](https://img.shields.io/npm/v/setem)](https://www.npmjs.com/package/setem)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Twitter: gada\\_twt](https://img.shields.io/twitter/follow/gada\\_twt.svg?style=social)](https://twitter.com/gada\\_twt)\n\n\u003e Set'em: Elm record setter generator\n\n## What is this?\n\n\"Getters just get, setters just set!\"\n\n`setem` generates **all possible record setters** from your Elm source files into a single module.\n\n## Prerequisites\n\n- Reasonably new `nodejs`\n\n## Install\n\n```sh\nnpm install --save-dev setem\n# OR\n# yarn add --dev setem\n```\n\n## Usage\n\n```sh\n# Generates for an Elm project (including dependencies)\nnpm run setem --output src/                                 # For Elm project cwd. \"elm.json\" file must exist\nnpm run setem --output src/ --elm-json sub_project/elm.json # For non-cwd Elm project, targeted by \"elm.json\" file path\n\n# Only generates from specific files (NOT including dependencies)\nnpm run setem --output src/ src/Main.elm                    # From a single source file\nnpm run setem --output src/ src/Main.elm src/Data/User.elm  # From multiple source files\nnpm run setem --output src/ src/**/*.elm                    # From multiple source files resolved by glob in your shell\nnpm run setem --output src/ src/                            # From multiple source files in a specific directory, expanded recursively\n```\n\n(`npx` or global install also work)\n\nIt generates `src/RecordSetter.elm` file like this:\n\n```elm\n-- This module is generated by `setem` command. DO NOT edit manually!\n\n\nmodule RecordSetter exposing (..)\n\n\ns_f1 : a -\u003e { b | f1 : a } -\u003e { b | f1 : a }\ns_f1 value__ record__ =\n    { record__ | f1 = value__ }\n\n\ns_f2 : a -\u003e { b | f2 : a } -\u003e { b | f2 : a }\ns_f2 value__ record__ =\n    { record__ | f2 = value__ }\n\n...\n```\n\nAll `s_\u003cfield name\u003e` setter function works for any records with matching field names!\n\n## But Why?\n\nAs you all Elm developers have probably known, Elm innately provides getters for any record fields:\n\n```sh\n$ elm repl\n---- Elm 0.19.1 ----------------------------------------------------------------\nSay :help for help and :exit to exit! More at \u003chttps://elm-lang.org/0.19.1/repl\u003e\n--------------------------------------------------------------------------------\n\u003e .name\n\u003cfunction\u003e : { b | name : a } -\u003e a\n```\n\nThese getters are concise, pipeline-friendly (i.e. you can `x |\u003e doSomething |\u003e .name |\u003e doElse`), therefore composition-friendly (there are packages with \"lift\"-ing functions that work in tandem with getters, such as [elm-form-decoder] or [elm-monocle]).\n\n[elm-form-decoder]: https://gihtub.com/arowM/elm-form-decoder\n[elm-monocle]: https://github.com/arturopala/elm-monocle\n\nOn the other hand, it does not provide setters of the same characteristics.\nStandard record updating syntax is:\n\n```elm\n{ record | name = \"new name\" }\n```\n\n...which is,\n\n- Not pipeline-friendly. You have to combine it with anonymous function like:\n\n  ```elm\n  x |\u003e doSomething |\u003e (\\v -\u003e { record | name = v }) |\u003e doElse\n  ```\n\n- Not nest-friendly. You have to combine either `let in` or pattern matches:\n\n  ```elm\n  let\n      innerRecord =\n          record.inner\n  in\n  { record | inner = { innerRecord | name = doSomething innerRecord.name } }\n  ```\n\nNow, as discussed\n[many](https://groups.google.com/g/elm-discuss/c/46TJImv3LAg/m/4KPo8R6f5QEJ)\n[many](https://discourse.elm-lang.org/t/a-record-update-function-operator/4083)\n[times](https://discourse.elm-lang.org/t/proposal-record-setters/5920)\nin the community, it is somewhat deliberate choice in the language design,\nto NOT provide \"record setter/updater\" syntax.\n\n- It encourages to create nicely named top-level functions rather than relying on verbose anonymous functions.\n- Also it encourages to design flatter, simpler data structures (and possibly using custom types) to more precisely illustrate our requirements.\n- For unavoidable situations where setters are in strong demand, we can create \"data\" module with necessary setters exposed, which actually leads us to think about proper boundary of data and concerns. Never a bad thing!\n\nBut. A big BUT.\n\nIn our everyday programming we yearn for setters time to time.\n\n- When we work with foreign record data structures from, say, packages\n- When we have tons of records to work with, from code generation facilities such as [elm-graphql]\n- When it is more natural to work with nested records as-is.\n  For example when we use external/existing JSON APIs.\n- **Simply when we do not have much time**.\n  - Requirement of writing many setters in order to leverage composition-centric logics, is tedious.\n  - It is a discouragement for us before writing proper data modules, when we forsee many boilerplate works. Even if it is one time thing.\n\n[elm-graphql]: https://gihtub.com/dillonkearns/elm-graphql\n\nFor that, `setem` is born.\n\n## What you get\n\nAs the slogan says, \"setters just set!\"\n\n- `setem` just generates setters, and setters only\n  - It is up to you how you utilize those setters.\n    For instance, use `setem`-generated setters as building blocks for [Monocle][elm-monocle] definitions!\n- It does not provide \"updaters\" in the sense of `(a -\u003e a) -\u003e { b | name : a } -\u003e { b | name : a }`\n  - It might prove useful, though my current intuition says it sees less usages than setters.\n- A single importable module. Just `import RecordSetter exposing(..)` in your code and that's it! All setters are always available.\n\nWith generated setters it is possible to:\n\n- Pipeline-d set:\n\n  ```elm\n  x |\u003e doSomething |\u003e s_name v |\u003e s_anotherField (updater x.anotherField) |\u003e doElse\n  ```\n\n- Nested set:\n\n  ```elm\n  { record\n      | inner =\n          record.inner\n              |\u003e s_name (doSomething record.inner.name)\n              |\u003e s_anotherField v\n      , anotherInner =\n          record.anotherInner\n              |\u003e s_number 1\n              |\u003e s_moreNest\n                  (record.anotherInner.moreNest\n                      |\u003e s_yetAnotherField vv\n                      |\u003e s_howFarCanWeGet [ 2, 3, 4 ]\n                  )\n  }\n  ```\n\nSetters are ordinary functions. Pass them to any high-order functions as needed!\n\n## Tips\n\n### Q. Should we add setem-generated file to git?\n\nA. Up to you. Personally I do. If you are irritated by cluttered diffs every time you generated `setem`,\ncreate a `.gitattributes` file with a following entry:\n\n```ini\nsrc/RecordSetter.elm linguist-generated=true\n```\n\nPaths set as `linguist-generated=true` are collapsed by default on GitHub pull request diffs, reducing visual clutter.\nSee \u003chttps://github.com/github/linguist/blob/master/docs/overrides.md#generated-code\u003e\n\n## Implementation note\n\nIt is based on [tree-sitter-elm](https://github.com/Razzeee/tree-sitter-elm). Quite fast!\n\nThe generator looks for both record type *definitions* and record data *expressions* from your source files.\nIt generates setters of not yet used fields (or even, ones you are not going to use at all.)\nUnused ones are expected to be sorted out by Dead-Code Elimination feature of `elm make --optimize`.\n\nIf you do not give explicit `paths` as command line arguments, `setem` reads your `elm.json` file\nand generates setters not only from your `\"source-directories\"` but from your dependencies as well.\nIn this scenario, tokens from your dependencies are cached in your `elm-stuff/setem/` directory.\n\n## Development\n\nInstall reasonably new [Bun](https://bun.sh/). If you are using `mise` (successor of `asdf`, `rtx`), do the following:\n\n```sh\ngit clone git@github.com:ymtszw/setem.git\ncd setem/\ngit submodule update --init --recursive\nmise install # See .tool-versions for reference versions\nbun run lint\nbun run test # `bun test` still not supported; we use jest under the hood\nbun run test:cli\n```\n\nIn GitHub Actions, [sanity checks](https://github.com/ymtszw/setem/actions/workflows/sanity_check.yml) are performed against multiple runtimes such as recent LTS Node.js versions and Bun!\n\n\u003cdetails\u003e\n\u003csummary\u003eOr, you can still use Node.js\u003c/summary\u003e\n\n```sh\nmise install\nnpm run lint\nnpm run test\nnpm run test:cli\n```\n\n\u003c/details\u003e\n\n## Author \u0026 License\n\nMIT License (c) **Yu Matsuzawa**\n\n- Twitter: [@gada\\_twt](https://twitter.com/gada\\_twt)\n- Github: [@ymtszw](https://github.com/ymtszw)\n\n## Show your support\n\nGive a ⭐️ if this project helped you!\n\n***\n*This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fymtszw%2Fsetem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fymtszw%2Fsetem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fymtszw%2Fsetem/lists"}