{"id":16068069,"url":"https://github.com/ged/configurability","last_synced_at":"2025-03-18T05:31:02.079Z","repository":{"id":65987989,"uuid":"1036433","full_name":"ged/configurability","owner":"ged","description":"A composable configuration system for Ruby (Github mirror)","archived":false,"fork":false,"pushed_at":"2020-12-29T00:32:33.000Z","size":233,"stargazers_count":12,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-28T07:32:42.015Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://bitbucket.org/ged/configurability","language":"Ruby","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ged.png","metadata":{"files":{"readme":"README.md","changelog":"History.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":"2010-10-30T00:56:44.000Z","updated_at":"2021-11-28T23:28:25.000Z","dependencies_parsed_at":null,"dependency_job_id":"d1e402fd-2213-4f0d-bd22-1f2948baa0a4","html_url":"https://github.com/ged/configurability","commit_stats":{"total_commits":208,"total_committers":2,"mean_commits":104.0,"dds":0.0625,"last_synced_commit":"3c85beb73f4d6f8a9e4e566e466d11f2e099242c"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ged%2Fconfigurability","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ged%2Fconfigurability/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ged%2Fconfigurability/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ged%2Fconfigurability/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ged","download_url":"https://codeload.github.com/ged/configurability/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243903159,"owners_count":20366432,"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":"2024-10-09T06:08:21.100Z","updated_at":"2025-03-18T05:31:02.068Z","avatar_url":"https://github.com/ged.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Configurability\n\nhome\n: https://configur.ability.guide/\n\ncode\n: https://hg.sr.ht/~ged/Configurability\n\ndocs\n: http://deveiate.org/code/configurability\n\ngithub\n: https://github.com/ged/configurability\n\n\n\n## Description\n\nConfigurability is a unified, non-intrusive, assume-nothing configuration system\nfor Ruby. It lets you keep the configuration for multiple objects in a single\nconfig file, load the file when it's convenient for you, and distribute the\nconfiguration when you're ready, sending it everywhere it needs to go with a\nsingle action.\n\n\n## Installation\n\n    gem install configurability\n\n\n## Example\n\n    # user.rb\n    require 'configurability'\n    \n    class User\n        extend Configurability\n\n        configurability( :users ) do\n            setting :min_password_length, default: 6\n        end\n\n    end\n\n\n    # config.yml\n    users:\n      min_password_length: 12\n\nIn pry:\n\n    [1] pry(main)\u003e require 'user'\n    =\u003e true\n    [2] pry(main)\u003e User.min_password_length\n    =\u003e 6\n    [3] pry(main)\u003e config = Configurability::Config.load( \"config.yml\" )\n    =\u003e #\u003cConfigurability::Config:0x7fd99a0f635816 loaded from config.yml ...\u003e\n    [4] pry(main)\u003e config.install\n    =\u003e [Loggability, User]\n    [5] pry(main)\u003e User.min_password_length\n    =\u003e 12\n\n\n## Usage\n\nTo add Configurability to your module or class, just `extend` it and declare\nsome settings:\n\n    require 'configurability'\n    class Database\n        extend Configurability\n\n        configurability( :db ) do\n            setting :url, default: 'sqlite:/'\n            setting :user\n            setting :password\n            setting :port do |value|\n                Integer( value ) if value\n            end\n        end\n    end\n\nThis sets up the class to use the `db` config key, and adds attributes for the\nthree subkey settings under it (with getters and setters) to the class. It also\nadds a `configure` class method that will set whichever of the settings are\npassed to it, defaulting the `url` to the provided value if it's not given.\n\nThe `setting` can include a block which is given the config value before it's\nset; from this block you can do validity-checking, cast it to something other\nthan a String, etc. The new value will be set to the return value of the block.\nNote that this will be called with the default for the setting when it's\ndeclared, so the block should be able to handle the default (even if it's\n`nil`).\n\nIf your config file (e.g., `config.yml`) looks like this:\n\n    --\n    db:\n        url: 'postgres:/acme'\n        user: tim\n        password: \"pXVvVY,YjWNRRi[yPWx4\"\n\nYou can configure the `Database` class (and all other objects extended with\nConfigurability) with it like so:\n\n    require 'configurability/config'\n    \n    config = Configurability::Config.load( 'config.yml' )\n    Configurability.configure_objects( config )\n\nAfter this happens you can access the configuration values like this:\n\n    Database.url\n    # =\u003e \"postgres:/acme\"\n    Database.user\n    # =\u003e \"tim\"\n    Database.password\n    # =\u003e \"pXVvVY,YjWNRRi[yPWx4\"\n\nYou can add helper methods inside the `configurability` block to eliminate\nrepeated code in your setting blocks:\n\n    configurability( :server ) do\n      def self::make_path( value )\n        return nil unless value\n        pn = Pathname( value )\n        raise \"Can't read from %s!\" % [ pn ] unless pn.readable?\n        return pn\n      end\n\n    \tsetting :template_path do |value|\n    \t\tmake_path( value )\n    \tend\n\n    \tsetting :plugin_path do |*values|\n    \t\tmake_path( value )\n    \tend\n    end\n\nIf a setting is a boolean, you can also have a predicate method created for it\nalongside its getter and setter:\n\n    class Mailer\n      extend Configurability\n      configurability( :db ) do\n        setting :use_whitelist, default: false, predicate: true\n      end\n    end\n\n    Mailer.use_whitelist?\n    # =\u003e false\n    Mailer.use_whitelist = true\n    Mailer.use_whitelist?\n    # =\u003e true\n\n\n### More Details\n\nConfigurability is implemented using `Module#extend`, so there's very little\nmagic going on. Every object (including Class and Module objects) that is\nextended with Configurability is registered in an Array of objects that will be\nconfigured when the config is loaded.\n\nYou extend a Class, of course, as in the above example:\n\n    class MyClass\n        extend Configurabtility\n    end\n\nBut you can also add it to individual instances of objects to configure them\nseparately:\n\n    user = User.new\n    user.extend( Configurability )\n\nWhen you call:\n\n    Configurability.configure_objects( config )\n\nthe specified `config` will be spliced up and sent to all the objects that have\nbeen registered. `Configurability` expects the configuration to be broken\nup into a number of sections, each of which is accessible via either a method\nwith the _section name_ or the index operator (`#[]`) that takes the _section\nname_ as a `Symbol` or a `String`:\n\n    config.section_name\n    config[:section_name]\n    config['section_name']\n\nThe section name is based on an object's _config key_, which is the argument\nyou specify when declaring your settings. If you don't provide one, it defaults\nto the name of the object that is being extended with all non-word characters\nconverted into underscores (`_`). It will also have any leading Ruby-style\nnamespaces stripped, e.g.,\n\n    MyClass            -\u003e :myclass\n    Acme::User         -\u003e :user\n    \"J. Random Hacker\" -\u003e :j_random_hacker\n\nIf the object responds to the `#name` method, then the return value of that\nmethod is used to derive the name. If it doesn't have a `#name` method, the\nname of its `Class` will be used instead. If its class is anonymous, then\nthe object's config key will be `:anonymous`.\n\nWhen the configuration is loaded, any attribute writers that correspond with\nkeys of the config will be called with the configured values, and an instance\nvariable called `@config` is set to the appropriate section of the config.\n\nAs you add more objects to your configuration, it may be useful to group\nrelated sections together. You can specify that your object's configuration\nis part of a group by specifying a config key with either a dot if it's a String or a double underscore if it's a Symbol. E.g.,\n\n    # Read from the `db` subsection of `myapp`\n    configurability( 'myapp.db' )\n\n    # Same thing, but with a symbol:\n    configurability( :myapp__db )\n\n\n## Customization\n\nThe default behavior above is just provided as a reasonable default; you may\nwant to customize one or two things about how configuration is handled in your\nobjects.\n\n\n### Setting a Custom Config Key\n\nIf you want to customize the config key without calling `configurability` you\ncan do that by declaring it with the `config_key` method:\n\n    class OutputFormatter\n        extend Configurability\n        config_key :format\n    end\n\nor by overriding the `#config_key` method youtself and returning the desired\nvalue as a Symbol:\n\n    class User\n        extend Configurability\n        def self::config_key\n            return :employees\n        end\n    end\n\n\n### Changing How an Object Is Configured\n\nYou can also change what happens when an object is configured by implementing\na `#configure` method that takes the config section as an argument:\n\n    class WebServer\n        extend Configurability\n\n        config_key :webserver\n\n        def self::configure( config )\n            @default_bind_addr = config[:host]\n            @default_port = config[:port]\n        end\n    end\n\nIf you still want the `@config` variable to be set, just `super` from your\nimplementation; don't if you don't want it to be set.\n\n\n## Configuration Objects\n\nConfigurability also includes `Configurability::Config`, a fairly simple\nconfiguration object class that can be used to load a YAML configuration file,\nand then present both a Hash-like and a Struct-like interface for reading\nconfiguration sections and values; it's meant to be used in tandem with\nConfigurability, but it's also useful on its own.\n\nHere's a quick example to demonstrate some of its features. Suppose you have a\nconfig file that looks like this:\n\n    ---\n    database:\n      development:\n        adapter: sqlite3\n        database: db/dev.db\n        pool: 5\n        timeout: 5000\n      testing:\n        adapter: sqlite3\n        database: db/testing.db\n        pool: 2\n        timeout: 5000\n      production:\n        adapter: postgres\n        database: fixedassets\n        pool: 25\n        timeout: 50\n    ldap:\n      uri: ldap://ldap.acme.com/dc=acme,dc=com\n      bind_dn: cn=web,dc=acme,dc=com\n      bind_pass: Mut@ge.Mix@ge\n    branding:\n      header: \"#333\"\n      title: \"#dedede\"\n      anchor: \"#9fc8d4\"\n\nYou can load this config like so:\n\n    require 'configurability/config'\n    config = Configurability::Config.load( 'examples/config.yml' )\n    # =\u003e #\u003cConfigurability::Config:0x1018a7c7016 loaded from\n        examples/config.yml; 3 sections: database, ldap, branding\u003e\n\nAnd then access it using struct-like methods:\n\n    config.database\n    # =\u003e #\u003cConfigurability::Config::Struct:101806fb816\n        {:development=\u003e{:adapter=\u003e\"sqlite3\", :database=\u003e\"db/dev.db\", :pool=\u003e5,\n        :timeout=\u003e5000}, :testing=\u003e{:adapter=\u003e\"sqlite3\",\n        :database=\u003e\"db/testing.db\", :pool=\u003e2, :timeout=\u003e5000},\n        :production=\u003e{:adapter=\u003e\"postgres\", :database=\u003e\"fixedassets\",\n        :pool=\u003e25, :timeout=\u003e50}}\u003e\n\n    config.database.development.adapter\n    # =\u003e \"sqlite3\"\n\n    config.ldap.uri\n    # =\u003e \"ldap://ldap.acme.com/dc=acme,dc=com\"\n\n    config.branding.title\n    # =\u003e \"#dedede\"\n\nor using a Hash-like interface using either `Symbol`s, `String`s, or a mix of\nboth:\n\n    config[:branding][:title]\n    # =\u003e \"#dedede\"\n\n    config['branding']['header']\n    # =\u003e \"#333\"\n\n    config['branding'][:anchor]\n    # =\u003e \"#9fc8d4\"\n\nYou can install it (i.e., configure your objects) via the Configurability\ninterface:\n\n    config.install\n\nIf you change the values in the config object, they won't propagate\nautomatically; you'll need to call `#install` on it again to send the changes to\nthe objects being configured.\n\nYou can check to see if the file the config was loaded from has changed since\nyou loaded it:\n\n    config.changed?\n    # =\u003e false\n\n    # Simulate changing the file by manually changing its mtime\n    File.utime( Time.now, Time.now, config.path )\n    config.changed?\n    # =\u003e true\n\nIf it has changed (or even if it hasn't), you can reload it, which\nautomatically re-installs it via the Configurability interface if it has:\n\n    config.reload\n\nYou can make modifications via the same Struct- or Hash-like interfaces and\nwrite the modified config back out to the same file:\n\n    config.database.testing.adapter = 'mysql'\n    config[:database]['testing'].database = 't_fixedassets'\n\nthen dump it to a YAML string:\n\n    config.dump\n    # =\u003e \"--- \\ndatabase: \\n  development: \\n    adapter: sqlite3\\n  \n      database: db/dev.db\\n    pool: 5\\n    timeout: 5000\\n  testing: \\n  \n      adapter: mysql\\n    database: t_fixedassets\\n    pool: 2\\n    timeout:\n      5000\\n  production: \\n    adapter: postgres\\n    database:\n      fixedassets\\n    pool: 25\\n    timeout: 50\\nldap: \\n  uri:\n      ldap://ldap.acme.com/dc=acme,dc=com\\n  bind_dn:\n      cn=web,dc=acme,dc=com\\n  bind_pass: Mut@ge.Mix@ge\\nbranding: \\n\n      header: \\\"#333\\\"\\n  title: \\\"#dedede\\\"\\n  anchor: \\\"#9fc8d4\\\"\\n\"\n\nor write it back to the file it was loaded from:\n\n    config.write\n\nNote that this is just using `YAML.dump`, so any comments, ordering, or other\nnice formatting you have in your config file will be clobbered if you rewrite\nit.\n\n\n## Configuration Defaults\n\nIt's a good idea to provide a set of reasonable defaults for any configured\nobject. The defaults for all settings are added to extended Classes and Modules\nas a constant named `CONFIG_DEFAULTS`. You can, of course set this constant\nyourself as well.\n\nConfigurability provides a `defaults` method that will return the hash of\nsettings and their default values from the `CONFIG_DEFAULTS` constant. You can\nalso override the `defaults` method yourself (and super to the original) if you\nwish to do something different.\n\nThere are also a couple of useful functions built on top of this method:\n\ngather_defaults\n: You can fetch a Hash of the default config values of all objects that have\n  been extended with Configurability by calling\n  `Configurabilty.gather_defaults`. You can also pass an object that responds\n  to `#merge!` to the method to merge the defaults into an existing\n  config.\n\ndefault_config\n: This will return a Configurability::Config object made from the results of\n  `gather_defaults`. This makes it easy to write a config file that contains the default\n  configuration: `Configurability.default_config.write( \"defaults.yml\" )`\n\n\n## Development\n\nYou can submit bug reports, suggestions, clone it with Mercurial, and\nread more about future plans at\n[the project page](http://hg.sr.ht/ged/configurability). If you\nprefer Git, there is also a\n[Github mirror](https://github.com/ged/configurability).\n\nAfter checking out the source, run:\n\n    $ rake newb\n\nThis task will install any missing dependencies, run the tests/specs,\nand generate the API documentation.\n\n\n## Authors\n\n- Michael Granger \u003cged@faeriemud.org\u003e\n- Mahlon E. Smith \u003cmahlon@martini.nu\u003e\n\n\n## License\n\nCopyright (c) 2010-2020 Michael Granger and Mahlon E. Smith\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the author/s, nor the names of the project's\n  contributors may be used to endorse or promote products derived from this\n  software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fged%2Fconfigurability","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fged%2Fconfigurability","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fged%2Fconfigurability/lists"}