{"id":20601392,"url":"https://github.com/tmccarthy/tmm-scala-collections","last_synced_at":"2026-04-21T19:03:09.317Z","repository":{"id":49776894,"uuid":"268037874","full_name":"tmccarthy/tmm-scala-collections","owner":"tmccarthy","description":"Some collections I find useful","archived":false,"fork":false,"pushed_at":"2022-07-26T11:02:17.000Z","size":621,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-06T16:19:22.734Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tmccarthy.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}},"created_at":"2020-05-30T08:17:33.000Z","updated_at":"2024-12-09T19:54:17.000Z","dependencies_parsed_at":"2022-08-31T02:41:05.167Z","dependency_job_id":null,"html_url":"https://github.com/tmccarthy/tmm-scala-collections","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/tmccarthy/tmm-scala-collections","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmccarthy%2Ftmm-scala-collections","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmccarthy%2Ftmm-scala-collections/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmccarthy%2Ftmm-scala-collections/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmccarthy%2Ftmm-scala-collections/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tmccarthy","download_url":"https://codeload.github.com/tmccarthy/tmm-scala-collections/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tmccarthy%2Ftmm-scala-collections/sbom","scorecard":{"id":889771,"data":{"date":"2025-08-11","repo":{"name":"github.com/tmccarthy/tmm-scala-collections","commit":"9e995bb834fd10cb2a926289be197a7e1a502de3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Warn: no topLevel permission defined: .github/workflows/clean.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:147: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:154: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:161: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:170: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:177: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:186: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:193: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:200: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:212: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:222: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:232: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:242: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:56: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:65: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:72: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:81: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:88: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:95: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:130: update your workflow using https://app.stepsecurity.io/secureworkflow/tmccarthy/tmm-scala-collections/ci.yml/main?enable=pin","Info:   0 out of  15 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   6 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-24T11:20:46.327Z","repository_id":49776894,"created_at":"2025-08-24T11:20:46.328Z","updated_at":"2025-08-24T11:20:46.328Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32105868,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-21T11:25:29.218Z","status":"ssl_error","status_checked_at":"2026-04-21T11:25:28.499Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2024-11-16T09:09:52.343Z","updated_at":"2026-04-21T19:03:09.301Z","avatar_url":"https://github.com/tmccarthy.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `tmm-scala-collections`\n[![CircleCI](https://circleci.com/gh/tmccarthy/tmm-scala-collections.svg?style=svg)](https://app.circleci.com/pipelines/github/tmccarthy/tmm-scala-collections)\n[![Maven Central](https://img.shields.io/maven-central/v/au.id.tmm.tmm-scala-collections/tmm-scala-collections-core_2.13.svg)](https://repo.maven.apache.org/maven2/au/id/tmm/tmm-scala-collections/tmm-scala-collections-core_2.13/)\n\nThis is a set of libraries that contain Scala collections and collection-oriented utilities that I've found useful. It \nwas previously a sub-project of [tmccarthy/tmmUtils](https://github.com/tmccarthy/tmmUtils), but it has reached a level of\nmaturity that justifies splitting it out.\n\n## Usage\n\nThe libraries have been published to Maven central. They can be used by adding the following to your `build.sbt`\n\n```scala\nval tmmCollectionsVersion = \"0.0.1\"\n\nlibraryDependencies += \"au.id.tmm.tmm-scala-collections\" %% \"tmm-scala-collections-core\"       % tmmCollectionsVersion,\nlibraryDependencies += \"au.id.tmm.tmm-scala-collections\" %% \"tmm-scala-collections-circe\"      % tmmCollectionsVersion,\nlibraryDependencies += \"au.id.tmm.tmm-scala-collections\" %% \"tmm-scala-collections-cats\"       % tmmCollectionsVersion,\nlibraryDependencies += \"au.id.tmm.tmm-scala-collections\" %% \"tmm-scala-collections-scalacheck\" % tmmCollectionsVersion % Test, \n```\n\n## Collections\n\nThe following collections are provided by the `tmm-scala-collections-core` artefact:\n\n* [`DupelessSeq`](/core/src/main/scala/au/id/tmm/collections/DupelessSeq.scala): A (poorly-named) collection which can \n  be thought of as a sequence without duplicates, and with a constant-time `contains` check. It differs from [`ListSet`](https://www.scala-lang.org/api/current/scala/collection/immutable/ListSet.html)\n  (and other sets that retain insertion order) in that element order is considered for equality.\n* [`NonEmptyDupelessSeq`](/core/src/main/scala/au/id/tmm/collections/NonEmptyDupelessSeq.scala): A `DupelessSeq` that is\n  guaranteed to be non-empty.\n* [`NonEmptySet`](/core/src/main/scala/au/id/tmm/collections/NonEmptySet.scala): A set that is guaranteed to be \n  non-empty. Unlike the [`NonEmptySet`](https://typelevel.org/cats/api/cats/data/NonEmptySetOps.html) provided by [Cats](https://github.com/typelevel/cats),\n  this collection relies on universal equality in the same way as the Scala set. This makes it easier to use, as \n  elements do not require an `Order` instance, but comes at the cost of strict-lawfulness.\n* [`NonEmptyMap`](/core/src/main/scala/au/id/tmm/collections/NonEmptyMap.scala): A map that is guaranteed to be \n  non-empty. Again, like `NonEmptySet`, this differs from the Cats `NonEmptyMap` in that it relies on universal equality\n  rather than requiring an `Order` instance for keys.\n* [`NonEmptyArraySeq`](/core/src/main/scala/au/id/tmm/collections/NonEmptyArraySeq.scala): An immutable [`ArraySeq`](https://www.scala-lang.org/api/current/scala/collection/immutable/ArraySeq$.html)\n  that is guaranteed to be non-empty.\n\n#### Third-party library integration\n\nThe other modules in this project provide integration between the above collections and some popular FP libraries:\n\n* `tmm-scala-collections-scalacheck` provides integration with [ScalaCheck](https://github.com/typelevel/scalacheck) to\n  support property-based testing.\n* `tmm-scala-collections-circe` provides codecs for use with the [Circe](https://github.com/circe/circe) json library.\n* `tmm-scala-collections-cats` provides extensive lawful and unlawful instances for the collections above.\n\n## Syntax\n\n```scala\nimport au.id.tmm.collections.syntax._\n```\n\nThe `tmm-scala-collections-core` project provides a few useful syntax extensions via the above import. Some highlights \nare outlined below:\n\n#### Safe `groupBy`\n\nThe [`groupBy`](https://www.scala-lang.org/api/current/scala/collection/IterableOps.html#groupBy[K](f:A=%3EK):scala.collection.immutable.Map[K,C])\nmethod provided by the `IterableOps` class can be improved by reflecting the non-empty nature of the groups in the type\nsignature. The `safeGroupBy` syntax provides this:\n\n```scala\nimport au.id.tmm.collections.NonEmptySet\nimport au.id.tmm.collections.syntax._\nimport au.id.tmm.collections.cats.instances.all._\nimport cats.data.NonEmptyList\n\nval list = List(\"apple\", \"apricot\", \"banana\")\nval set = Set(\"apple\", \"apricot\", \"banana\")\n\n// ❌ BAD, return type doesn't indicate that groups cannot be empty\nval _: Map[Char, List[String]] = list.groupBy(_.head)\nval _: Map[Char, Set[String]]  = set.groupBy(_.head)\n\n// ✅ GOOD, return type indicates that groups are non-empty\nval _: Map[Char, NonEmptyList[String]] = list.safeGroupBy(_.head)\nval _: Map[Char, NonEmptySet[String]]  = set.safeGroupBy(_.head)\n```\n\n#### `orError` operations for `Iterable`\n\nThe `orError` syntaxes allow for easily extracting elements from `Iterable` collections based on the number of elements.\nI have found this valuable when processing and cleaning data from external sources:\n\n* `onlyElementOrException` returns a `Right` containing the element if the collection has exactly one element, `Left` \n  otherwise.\n* `emptyOrException` returns `Right(())` if the collection is empty, `Left` otherwise\n* `atMostOne` returns `Right(None)` for empty collections, `Right(Some)` for those with one element, and `Left` for two\n   or more elements.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmccarthy%2Ftmm-scala-collections","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftmccarthy%2Ftmm-scala-collections","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftmccarthy%2Ftmm-scala-collections/lists"}