Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for modifying content after reading #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 84 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@

This is a one-stop shop for all your configuration needs:

* [Read](#216-read) and [write](#217-write) config files in YAML, JSON, TOML, INI, XML, HCL and Java Properties formats
* Add [custom marshallers](#222-register_marshaller) or override the built-in ones
* [Set](#21-set) and [read](#24-fetch) settings for deeply nested keys
* [Set](#21-set) defaults for undefined settings
* [Read](#24-fetch) settings with indifferent access
* [Merge](#25-merge) configuration settings from other hash objects
* Read values from [environment variables](#23-set_from_env)
- [Read](#216-read) and [write](#217-write) config files in YAML, JSON, TOML, INI, XML, HCL and Java Properties formats
- Add [custom marshallers](#222-register_marshaller) or override the built-in ones
- [Set](#21-set) and [read](#24-fetch) settings for deeply nested keys
- [Set](#21-set) defaults for undefined settings
- [Read](#24-fetch) settings with indifferent access
- [Merge](#25-merge) configuration settings from other hash objects
- Read values from [environment variables](#23-set_from_env)

## Installation

Expand All @@ -53,35 +53,35 @@ Or install it yourself as:

## Contents

* [1. Usage](#1-usage)
* [1.1 app](#11-app)
* [2. Interface](#2-interface)
* [2.1 set](#21-set)
* [2.2 set_if_empty](#22-set_if_empty)
* [2.3 set_from_env](#23-set_from_env)
* [2.4 fetch](#24-fetch)
* [2.5 merge](#25-merge)
* [2.6 coerce](#26-coerce)
* [2.7 append](#27-append)
* [2.8 remove](#28-remove)
* [2.9 delete](#29-delete)
* [2.10 alias_setting](#210-alias_setting)
* [2.11 validate](#211-validate)
* [2.12 filename=](#212-filename)
* [2.13 extname=](#213-extname)
* [2.14 append_path](#214-append_path)
* [2.15 prepend_path](#215-prepend_path)
* [2.16 read](#216-read)
* [2.17 write](#217-write)
* [2.18 exist?](#218-exist)
* [2.19 env_prefix=](#219-env_prefix)
* [2.20 env_separator=](#220-env_separator)
* [2.21 autoload_env](#221-autoload_env)
* [2.22 register_marshaller](#222-register_marshaller)
* [2.23 unregister_marshaller](#223-unregister_marshaller)
* [3. Examples](#3-examples)
* [3.1 Working with env vars](#31-working-with-env-vars)
* [3.2 Working with optparse](#32-working-with-optparse)
- [1. Usage](#1-usage)
- [1.1 app](#11-app)
- [2. Interface](#2-interface)
- [2.1 set](#21-set)
- [2.2 set_if_empty](#22-set_if_empty)
- [2.3 set_from_env](#23-set_from_env)
- [2.4 fetch](#24-fetch)
- [2.5 merge](#25-merge)
- [2.6 coerce](#26-coerce)
- [2.7 append](#27-append)
- [2.8 remove](#28-remove)
- [2.9 delete](#29-delete)
- [2.10 alias_setting](#210-alias_setting)
- [2.11 validate](#211-validate)
- [2.12 filename=](#212-filename)
- [2.13 extname=](#213-extname)
- [2.14 append_path](#214-append_path)
- [2.15 prepend_path](#215-prepend_path)
- [2.16 read](#216-read)
- [2.17 write](#217-write)
- [2.18 exist?](#218-exist)
- [2.19 env_prefix=](#219-env_prefix)
- [2.20 env_separator=](#220-env_separator)
- [2.21 autoload_env](#221-autoload_env)
- [2.22 register_marshaller](#222-register_marshaller)
- [2.23 unregister_marshaller](#223-unregister_marshaller)
- [3. Examples](#3-examples)
- [3.1 Working with env vars](#31-working-with-env-vars)
- [3.2 Working with optparse](#32-working-with-optparse)

## 1. Usage

Expand Down Expand Up @@ -509,13 +509,13 @@ There are two ways for reading configuration files and both use the `read` metho

Currently the supported file formats are:

* `yaml` for `.yaml`, `.yml` extensions
* `json` for `.json` extension
* `toml` for `.toml` extension
* `ini` for `.ini`, `.cnf`, `.conf`, `.cfg`, `.cf extensions`
* `hcl` for `.hcl` extensions
* `xml` for `.xml` extension
* `jprops` for `.properties`, `.props`, `.prop` extensions
- `yaml` for `.yaml`, `.yml` extensions
- `json` for `.json` extension
- `toml` for `.toml` extension
- `ini` for `.ini`, `.cnf`, `.conf`, `.cfg`, `.cf extensions`
- `hcl` for `.hcl` extensions
- `xml` for `.xml` extension
- `jprops` for `.properties`, `.props`, `.prop` extensions

Calling `read` without any arguments searches through provided locations to find configuration file and reads it. Therefore, you need to specify at least one search path that contains the configuration file together with actual filename. When filename is specified then all known extensions will be tried.

Expand Down Expand Up @@ -546,6 +546,43 @@ For example, if you have a configuration file formatted using `YAML` notation wi
config.read("investments.config", format: :yaml)
```

If you want to modify the contents of the file after reading, but before parsing, you can pass a block to `read`:

```ruby
config.read("investments.yaml") do |content|
content.concat <<~EOF
custom: true
EOF
end
```

For a more interesting example of how this can be useful, consider the following:

```ruby
require 'tty-config'
require 'erb'

TTY::Config.new do |config|
if config.exist?
config.read do |content|
if %w[<% %>].all? { |tag| content.include? tag }
content.replace ERB.new(content).result
end
end
end
end
```

Which would read the config, rendering any ERB templates before parsing the resulting file.

This can be useful for integrating with other tools, such as reading encrypted secrets from a Vault server.

```yaml
---
github:
access_token: %x{vault kv get -mount secret -field access-token github}
```

### 2.17 write

By default **TTY::Config**, persists configuration file in the current working directory with a `config.yml` name. However, you can change the default file name by specifying the `filename` and `extension` type:
Expand Down Expand Up @@ -664,7 +701,7 @@ Then we can make configuration aware of the above variable name in one of these
```ruby
config.set_from_env(:server, :port)
config.set_from_env("server.port")
````
```

And retrieve the value:

Expand Down Expand Up @@ -705,8 +742,8 @@ Currently supported formats out-of-the-box are: `YAML`, `JSON`, `TOML`, `INI`, `

To create your own marshaller use the `TTY::Config::Marshaller` interface. You need to provide the implementation for the following marshalling methods:

* `marshal`
* `unmarshal`
- `marshal`
- `unmarshal`

In addition, you will need to specify the extension types this marshaller will handle using the `extension` method. The method accepts a list of names preceded by a dot:

Expand Down Expand Up @@ -778,7 +815,7 @@ config.unregister_marshaller :yaml, :json, :toml, :ini, :xml, :hcl, :jprops

### 3.1 Working with env vars

*TTY::Config* fully supports working with environment variables. For example, there are couple of environment variables that your configuration is interested in, which normally would be set in terminal but for the sake of this example we assign them:
_TTY::Config_ fully supports working with environment variables. For example, there are couple of environment variables that your configuration is interested in, which normally would be set in terminal but for the sake of this example we assign them:

```ruby
ENV["MYTOOL_HOST"] = "192.168.1.17"
Expand Down
2 changes: 2 additions & 0 deletions lib/tty/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ def read(file = find_file, format: :auto)
ext = (format == :auto ? extname : ".#{format}")
content = ::File.read(file)

yield content if block_given?

merge(unmarshal(content, ext: ext))
end

Expand Down
14 changes: 14 additions & 0 deletions spec/unit/read_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
expect(config.to_hash).to eq({})
end

it "can modify content before parsing" do
file = fixtures_path("empty.yml")
config = TTY::Config.new

config.read(file) do |contents|
contents.concat <<~EOF
---
appended: true
EOF
end

expect(config.to_hash).to eq({ "appended" => true })
end

it "reads from a specified file" do
file = fixtures_path("investments.yml")
config = TTY::Config.new
Expand Down