{"id":22360314,"url":"https://github.com/dwyl/useful","last_synced_at":"2025-04-05T16:08:12.941Z","repository":{"id":39711510,"uuid":"300221006","full_name":"dwyl/useful","owner":"dwyl","description":"🇨🇭 A collection of useful functions for working in Elixir","archived":false,"fork":false,"pushed_at":"2025-03-17T23:16:13.000Z","size":163,"stargazers_count":34,"open_issues_count":10,"forks_count":6,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-03-29T15:07:46.858Z","etag":null,"topics":["elixir","useful","utility","utils"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/useful/Useful.html","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dwyl.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":"2020-10-01T09:25:03.000Z","updated_at":"2025-03-17T23:16:15.000Z","dependencies_parsed_at":"2023-02-18T16:15:50.807Z","dependency_job_id":"2e70ae76-7e0a-4fd7-bf97-bc3dfd3af478","html_url":"https://github.com/dwyl/useful","commit_stats":{"total_commits":71,"total_committers":7,"mean_commits":"10.142857142857142","dds":0.6901408450704225,"last_synced_commit":"096545f8ef695967f85ba9bc210e2e3d6a796a02"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fuseful","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fuseful/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fuseful/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwyl%2Fuseful/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dwyl","download_url":"https://codeload.github.com/dwyl/useful/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361689,"owners_count":20926643,"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":["elixir","useful","utility","utils"],"created_at":"2024-12-04T16:15:32.441Z","updated_at":"2025-04-05T16:08:12.902Z","avatar_url":"https://github.com/dwyl.png","language":"Elixir","readme":"\u003cdiv align=\"center\"\u003e\n\n# `useful`\n\nA collection of useful functions for building `Elixir` Apps.\n\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dwyl/useful/ci.yml?label=build\u0026style=flat-square\u0026branch=main)\n[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/gogs/main.svg?style=flat-square)](http://codecov.io/github/dwyl/auth?branch=main)\n[![Hex.pm](https://img.shields.io/hexpm/v/useful?color=brightgreen\u0026style=flat-square)](https://hex.pm/packages/useful)\n[![Libraries.io dependency status](https://img.shields.io/librariesio/release/hex/useful?logoColor=brightgreen\u0026style=flat-square)](https://libraries.io/hex/useful)\n[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/useful/issues)\n[![HitCount](http://hits.dwyl.com/dwyl/useful.svg)](http://hits.dwyl.com/dwyl/useful)\n\n![swiss-army-knife](https://user-images.githubusercontent.com/194400/94815682-b646e300-03f2-11eb-8069-46b9e10fac7e.png)\n\n\u003c/div\u003e\n\n# Why? 🤷\n\nWe found ourselves copy-pasting a few useful \"helper\" functions\nacross our Elixir projects ... \u003cbr /\u003e\nit wasn't\n[\"DRY\"](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself),\nso we created this library.\n\n# What? 💭\n\nA library of useful functions\nthat we reach for\nwhen building `Elixir` Apps.\n\n# Who? 👤\n\nThis library is used in our various `Elixir` / `Phoenix` apps. \u003cbr /\u003e\nAs with everything we do it's Open Source, Tested and Documented\nso that _anyone_ can benefit from it.\n\n# How? 💻\n\n## Install ⬇️\n\nInstall by adding `useful` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:useful, \"~\u003e 1.14.0\"}\n  ]\nend\n```\n\n## Function Reference\n\n### `atomize_map_keys/1`\n\nConverts a `Map` that has strings as keys (or mixed keys)\nto have only atom keys. e.g:\n\n```elixir\n# map that has different types of keys:\nmy_map = %{\"name\" =\u003e \"Alex\", id: 1}\nUseful.atomize_map_keys(my_map)\n%{name: Alex, id: 1}\n```\n\nWorks recursively for deeply nested maps:\n\n```elixir\nperson = %{\"name\" =\u003e \"Alex\", id: 1, details: %{\"age\" =\u003e 17, height: 185}}\nUseful.atomize_map_keys(person)\n%{name: Alex, id: 1, details: %{age: 17, height: 185}}\n```\n\n### `flatten_map/1`\n\nFlatten a `Map` of any depth/nesting:\n\n```elixir\niex\u003e map = %{name: \"alex\", data: %{age: 17, height: 185}}\niex\u003e Useful.flatten_map(map)\n%{data__age: 17, data__height: 185, name: \"alex\"}\n```\n\n**Note**: `flatten_map/1` converts all Map keys to `Atom`\nas it's easier to work with atoms as keys\ne.g: `map.person__name` instead of `map[\"person__name\"]`.\nWe use the `__` (_double underscore_)\nas the delimiter for the keys of nested maps,\nbecause if we attempt to use `.` (_period character_)\nwe get an error:\n\n```elixir\niex(1)\u003e :a.b\n** (UndefinedFunctionError) function :a.b/0 is undefined (module :a is not available)\n    :a.b()\n```\n\n### `get_in_default/1`\n\nGet a deeply nested value from a map.\n`get_in_default/3` Proxies `Kernel.get_in/2`\nbut allows setting a `default` value as the 3rd argument.\n\n```elixir\niex\u003e map = %{name: \"alex\", detail: %{age: 17, height: 185}}\niex\u003e Useful.get_in_default(map, [:data, :age])\n17\niex\u003e Useful.get_in_default(map, [:data, :everything], \"Awesome\")\n\"Awesome\"\niex\u003e Useful.get_in_default(conn, [:assigns, :person, :id], 0)\n0\n```\n\nWe needed this for getting `conn.assigns.person.id`\nin our [`App`](https://github.com/dwyl/mvp/)\nwithout having to write a bunch of boilerplate!\ne.g:\n\n```elixir\nperson_id =\n  case Map.has_key?(conn.assigns, :person) do\n    false -\u003e 0\n    true -\u003e Map.get(conn.assigns.person, :id)\n  end\n```\n\nis just:\n\n```elixir\nperson_id = Useful.get_in_default(conn, [:assigns, :person, :id], 0)\n```\n\n_Muuuuuuch cleaner/clearer_! 😍\nIf any of the keys in the list is not found\nit doesn't _explode_ with errors,\nsimply returns the `default` value `0`\nand continues!\n\n\u003e **Note**: Code inspired by:\n\u003e [stackoverflow.com/questions/48781427/optional-default-value-for-get-in](https://stackoverflow.com/questions/48781427/optional-default-value-for-get-in-access-behavior-elixir/48781493#48781493) \u003cbr /\u003e\n\u003e All credit to [**`@PatNowak`**](https://github.com/PatNowak) 🙌\n\nThe ideal syntax for this would be:\n\n```elixir\nperson_id = conn.assigns.person.id || 0\n```\n\nBut `Elixir` \"_Me no likey_\" ...\nSo this is what we have.\n\n### `list_tuple_to_unique_keys/1`\n\nTurns a list of tuples with the _same_ key\ninto a list of tuples with _unique_ keys.\nUseful when dealing with \"multipart\" forms\nthat upload multiple files. e.g:\n\n```elixir\nparts = [\n  {\"files\",[{\"content-type\", \"image/png\"},{\"content-disposition\",\"form-data; name=\\\"files\\\"; filename=\\\"first.png\\\"\"}],%Plug.Upload{path: \"..\", content_type: \"image/png\",filename: \"first.png\"}},\n  {\"files\",[{\"content-type\", \"image/webp\"},{\"content-disposition\",\"form-data; name=\\\"files\\\"; filename=\\\"second.webp\\\"\"}],%Plug.Upload{path: \"...\",content_type: \"image/webp\",filename: \"second.webp\"}}\n]\n\n\nUseful.list_tuples_to_unique_keys(parts) =\n[\n  {\"files-1\",[{\"content-type\", \"image/png\"},{\"content-disposition\",\"form-data; name=\\\"files\\\"; filename=\\\"first.png\\\"\"}],%Plug.Upload{path: \"..\", content_type: \"image/png\",filename: \"first.png\"}},\n  {\"files-2\",[{\"content-type\", \"image/webp\"},{\"content-disposition\",\"form-data; name=\\\"files\\\"; filename=\\\"second.webp\\\"\"}],%Plug.Upload{path: \"...\",content_type: \"image/webp\",filename: \"second.webp\"}}\n]\n```\n\n### `remove_item_from_list/2`\n\nRemove an `item` from a `list`.\n\nWith numbers:\n\n```elixir\nlist = [1, 2, 3, 4]\nUseful.remove_item_from_list(list, 3)\n[1, 2, 4]\n```\n\nWith a `List` of `Strings`:\n\n```elixir\nlist = [\"climate\", \"change\", \"is\", \"not\", \"real\"]\nUseful.remove_item_from_list(list, \"not\")\n[\"climate\", \"change\", \"is\", \"real\"]\n```\n\nThe `list` is the first argument to the function\nso it's easy to pipe:\n\n```elixir\nget_list_of_items(person_id)\n|\u003e Useful.remove_item_from_list(\"item_to_be_removed\")\n|\u003e etc.\n```\n\n### `stringify_map/1`\n\nStringify a `Map` e.g. to store it in a DB or log it stdout.\n\n```elixir\nmap = %{name: \"alex\", data: %{age: 17, height: 185}}\nUseful.stringify_map(map)\n\"data__age: 17, data__height: 185, name: alex\"\n```\n\n### `stringify_tuple/1`\n\nStringify a tuple of any length; useful in debugging.\n\n```elixir\niex\u003e tuple = {:ok, :example}\niex\u003e Useful.stringify_tuple(tuple)\n\"ok: example\"\n```\n\n### `truncate/3`\n\n\u003e **truncate**; To shorten (something) by, or as if by, cutting part of it off.\n\u003e [wiktionary.org/wiki/truncate](https://en.wiktionary.org/wiki/truncate)\n\nReturns a truncated version of the `String` according to the desired `length`.\n_Useful_ if your displaying an uncertain amount of text in an interface.\nE.g. the \"bio\" field on GitHub can be up **`160 characters`**.\n_Most_ `people` don't have a `bio` but some use every character.\nIf you're displaying profiles in an interface, you want a _predictable_ length.\nUsage:\n\n```elixir\niex\u003e input = \"You cannot lose what you never had.\"\niex\u003e Useful.truncate(input, 18)\n\"You cannot lose ...\"\n```\n\nThe **_optional_ third argument** `terminator`\nallows specify any `String` or an _empty_ `String` if you prefer\nas the terminator for your truncated text:\n\n```elixir\niex\u003e input = \"do or do not there is no try\"\niex\u003e Useful.truncate(input, 12, \"\") # no ellipsis\n\"do or do not\"\n\niex\u003e input = \"It was the best of times, it was the worst of times\"\niex\u003e Useful.trucate(input, 25, \"\")\n\"It was the best of times\"\n```\n\n### `typeof/1`\n\nReturns the type of a variable, e.g: \"function\" or \"integer\"\nInspired by\n[**`typeof`**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof)\nfrom `JavaScript` land.\n\n```elixir\niex\u003e myvar = 42\niex\u003e Useful.typeof(myvar)\n\"integer\"\n```\n\n### `empty_dir_contents/1`\n\nEmpties the directory\n(_deletes all files and any nested directories_)\nrecursively, but does _not_ delete the actual directory.\nThis is useful when you want to reset a directory,\ne.g. when testing.\n\n```elixir\niex\u003e dir = \"tmp\" # contains lots of sub directories and files\niex\u003e Useful.empty_dir_contents(dir)\n{:ok, dir}\n```\n\n\u003cbr /\u003e\n\n# Docs 📜\n\nDetailed docs available at:\nhttps://hexdocs.pm/useful/Useful.html\n\n\u003cbr /\u003e\n\n# Help Us Help You! 🙏\n\nIf you need a specific helper function or utility\n(e.g: something you found useful in a different programming language),\nplease\n[open an issue](https://github.com/dwyl/useful/issues)\nso that we can all benefit from useful functions.\n\nThanks!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fuseful","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdwyl%2Fuseful","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwyl%2Fuseful/lists"}