{"id":16964984,"url":"https://github.com/propensive/ambience","last_synced_at":"2025-04-05T15:46:04.788Z","repository":{"id":80754890,"uuid":"600879683","full_name":"propensive/ambience","owner":"propensive","description":"Safely access environment variables and system properties in Scala","archived":false,"fork":false,"pushed_at":"2025-02-12T18:58:10.000Z","size":1220,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-28T20:49:18.056Z","etag":null,"topics":["environment-variables","java-system-properties","jvm","scala","system-properties"],"latest_commit_sha":null,"homepage":"https://soundness.dev/ambience/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"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-02-12T21:27:27.000Z","updated_at":"2025-02-12T18:58:15.000Z","dependencies_parsed_at":"2023-10-11T11:28:05.541Z","dependency_job_id":"ba78586f-819b-4272-96c4-582c789dfd28","html_url":"https://github.com/propensive/ambience","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fambience","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fambience/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fambience/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fambience/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/ambience/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247361598,"owners_count":20926642,"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":["environment-variables","java-system-properties","jvm","scala","system-properties"],"created_at":"2024-10-13T23:44:46.469Z","updated_at":"2025-04-05T15:46:04.776Z","avatar_url":"https://github.com/propensive.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/ambience/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/ambience/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Ambience\n\n__Safely access environment variables and system properties__\n\nThere are two common methods of passing textual key/value data into a JVM when\nit starts: environment variables and system properties. Environment variables\nare taken from the shell environment within which the JVM is started, and\nusually have uppercase names like `PATH` or `XDG_CONFIG_DIR`, while system\nproperties are specified as parameters to the `java` command, and have\nlowercase or camel-case, period-separated names like `default.log.directory`.\nThe format of the values of each is dependent on the variable or property, but\na program running on the JVM will have expectations about the format of the\nvariables and properties it accesses, and it is desirable to parse them into\nstructured types as early as possible. This is the role of Ambience.\n\n## Features\n\n- typesafe access of environment variables and system properties\n- types are inferred for known variables and properties, avoiding strings wherever possible\n- concise syntax with idiomatic renaming of environment variables\n- complete flexibility to use substitute environments\n\n\n## Availability\n\n\n\n\n\n\n\n## Getting Started\n\nAll Ambience terms and types are in the `ambience` package,\n```scala\nimport ambience.*\n````\nbut are exported to the `soundness` package, which is useful when using\nAmbience with other Soundness libraries:\n```scala\nimport soundness.*\n```\n\nAll other entities are imported explicitly.\n\n### Environment Variables\n\nAn environment variable, such as `XDG_DATA_DIRS`, can be accessed by applying\nit as a `Text` value, to the `Environment` object, like so:\n```scala\nimport gossamer.t\nimport environments.jvm\nimport contingency.strategies.throwUnsafely\n\nval xdgDataDirs = Environment(t\"XDG_DATA_DIRS\")\n```\n\nNote that we import `environments.jvm` because we want to use the environment\nfrom the running JVM. It's possible to use alternative environments, for\nexample when testing, but normally we want `environments.jvm`.\n\nEnvironment variables typically use a naming scheme that is incongruous with\nother identifiers in Scala. Environment variable names are typically in\nuppercase, with multiple words separated by underscores. So an identifier which\nwould be written `moduleDataHome` in Scala would be written `MODULE_DATA_HOME`\nas an environment variable. Ambience can perform this translation\nautomatically, just by accessing the variable name as a dynamic member of the\n`Environment` object, for example,\n```scala\nval dirs = Environment.moduleDataHome\n```\nwill access the environment variable `MODULE_DATA_HOME`.\n\nIf the variable is not defined in the environment, an `EnvironmentError` will\nbe raised.\n\nIt might be reasonably presumed that in the examples above, string values (as\n`Text`s) would be returned, and in general this is true, unless the variable is\n_known_ in which case, the variable will be parsed into a more appropriate\nrepresentation.\n\nFor a variable to be _known_, a contextual `EnvironmentVariable` instance,\nparameterized on the singleton type of the environment variable's name (in its\ncamel-case variant) and a result type, must be in scope. For example,\n`Environment.columns` (referring to the `COLUMNS` environment variable) will\nreturn an `Int` thanks to the presence of an\n`EnvironmentVariable[\"columns\", Int]` typeclass instance, which is provided by\nAmbience since `COLUMNS` is a standard POSIX environment variable name.\n\nHowever, the `moduleDataHome` example above will default to returning a `Text`\nvalue, since no `EnvironmentVariable` instance for the `\"moduleDataHome\"`\nsingleton literal type is provided by Ambience.\n\nWe can, of course, provide one. The `EnvironmentVariable` trait defines two methods:\n- `read`, which takes a `Text` value and returns a parsed value, and\n- `name`, which allows custom renamings from \"Scala style\" names to a\n  particular environment variable, with a default implementation that can be\n  overridden\n\nHere is an example implementation for a process ID:\n```scala\nimport anticipation.Text\nimport rudiments.Pid\nimport spectacular.decodeAs\n\ngiven EnvironmentVariable[\"child\", Pid] with\n  def read(value: Text): Pid = Pid(value.decodeAs[Int])\n  override def name: Text = t\"CHILD_PID\"\n```\n\nNow, accessing `Environment.child` will read the `CHILD_PID` environment\nvariable, parse it as an integer and construct a new `Pid` instance. We could\nalso omit the override of `name` and just access the value as\n`Environment.childPid`.\n\nNote that `value.decodeAs[Int]`, which is provided by\n[Spectacular](https://github.com/propensive/spectacular/), and uses a\n`Int is Decodable` instance may, given the definition of `Int is Decodable`, fail with\na `NumberError` if the `CHILD_PID` variable contains a value which isn't an\ninteger. The capability to raise this error will be aggregated into the\n`Environment.child` call, ensuring that it will be handled.\n\n#### Custom `Environment`s\n\nSometimes it is useful to specify a different source of environment variables\nthan the JVM. This may be because we need to test a method which has been\nwritten to get a value from the environment, but we don't want to introduce the\nnondeterminism that arises when running the tests on different machines with\ndifferent environments.\n\nAnother example presents itself if we want a method call (which takes an\n`Environment`) to see different values for certain environment variables, for\nexample, to ensure that the method accesses an SSH agent with a particular PID,\nrather than the SSH agent specified in the `SSH_AGENT_PID` environment\nvariable, captured when the JVM started.\n\nThe `Environment` typeclass defines just a single `apply` method for accessing\nenvironment variables, taking a `Text` value of the environment variable name,\nand returning a `Maybe[Text]`, with `Unset` indicating that the environment\nvariable is not specified. It is distinct from the empty string.\n\nFor example, given a `Map[Text, Text]` of environment variables, `vars`, we\ncould create a new `Environment` with the following `given` definition:\n```scala\nimport vacuous.Unset\n\nval vars = Map(t\"HOME\" -\u003e t\"/home/root\")\ngiven Environment = vars.getOrElse(_, Unset)\n```\n\n#### Summary\n\nTogether, these facilities provide access to environment variables which is\nidiomatic (with Scala-style identifiers), typesafe (using checked errors if\nparsing fails), concise (types can be inferred) and flexible (allowing\nsubstitution of entire environments).\n\n### System Properties\n\nSupport for system properties is provided in much the same way as for\nenvironment variables:\n- access is provided through the `Properties` object, by `Text` value or\n  dynamic member access\n- `systemProperties.jvm` provides the standard system properties from the JVM\n- alternative instances of `SystemProperties` can be defined to provide\n  substitute values\n- `SystemProperty` provides the same functionality as `EnvironmentVariable`\n- a `SystemPropertyError` will be raised instead of an `EnvironmentError`\n\nThere is no need to rename system properties, since they already follow the\nfamiliar Scala identifier style. Access through the `Properties` object is\nslightly different from `Environment`, though: since property names use a\n\"dotted\" format, they can be accessed as dynamic members of the `Properties`\nobject, for example,\n```scala\nimport systemProperties.jvm\n\nval home = Properties.user.home()\n```\nor,\n```scala\nval dir = Properties.db.user.cache.dir()\n```\nwhere the empty parentheses are necessary to signal that the path representing\nthe property name has been specified, and its value should be retrieved. The\nretrieval itself works in much the same way as for environment variables.\n\n\n## Status\n\nAmbience is classified as __fledgling__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nAmbience is designed to be _small_. Its entire source code currently consists\nof 300 lines of code.\n\n## Building\n\nAmbience will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Ambience?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Ambience's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Ambience and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `ambience`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Ambience's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Ambience are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/ambience/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Ambience\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nAmbience was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nAn _ambience_ is a sense derived from the surrounding environment, and _Ambience_ provides sensible access to the \"surrounding environment\" of a Scala program.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo depicts the upper atmosphere of an imagined planet, alluding to the synonymous meaning of \"ambience\".\n\n## License\n\nAmbience is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fambience","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fambience","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fambience/lists"}