Git Product home page Git Product logo

yaml_extend's Introduction

yaml_extend

Gem downloads License: MIT

Extends YAML to support file based inheritance.

That can be very handy to build a configuration hierarchy.

Basic support for ERB (embedded ruby) is included and automatically applied when config files are named *.erb or *.erb.*.

If you are just doing administration without knowing ruby, you can also just use the command line tool that is included to use this librarys features. Ensure, that ruby is installed on your system, before following further instructions.

Contents

Common information

It is possible to build inheritance trees like:

     defaults.yml   
  ________\_________    
  \        \        \                         
 dev.yml  int.yml  prod.yml                   

or like this:

fruits.yml   vegetables.yml          default.yml   extensions.yml       
         \    /                             \    /                
          food.yml                          merged.yml
                                              |
                                            another_extended.yml

A file can inherit from as many as you want. Trees can be nested as deep as you want.

YAML files are deep merged, the latest specified child file overwrites the former ones. Array values are merged as well by default. You can specifiy this with the 3rd Parameter.

The files to inherit from are specified by the key 'extends:' in the YAML file. That key can be customized if you prefer another one. See the examples below.

Installation

Ruby

Add this line to your application's Gemfile:

gem 'yaml_extend'

And then execute:

bundle install

Command line

If you just want to use the command line then run

gem install yaml_extend

Usage

yaml_extend adds the method YAML.ext_load_file to YAML.

This method works like the original YAML.load_file, by extending it with file inheritance.

Examples

Basic Inheritance

Given the following both files are defined:

# start.yml
extends: 'super.yml'
data:
    name: 'Mr. Superman'
    age: 134
    # using ERB templating with ruby code 
    foo: '<%= 'bar' %>'
    favorites:
        - 'Raspberrys'
# super.yml
data:
    name: 'Unknown'
    power: 2000
    favorites:
        - 'Bananas'
        - 'Apples'

When you then call ext_load_file

config = YAML.ext_load_file 'start.yml'

the returned YAML value results in

data:
    name: 'Mr. Superman'
    age: 134
    foo: 'bar'
    power: 2000
    favorites:
        - 'Bananas'
        - 'Apples'
        - 'Raspberrys'

Inherit from several files

If you want to inherit from several files, you can specify a list (Array) of files. They are merged from top to bottom, so the latest file "wins" - that means it overwrites duplicate values if they exist with the values in the latest file where they occur.

extends:
    - 'super_file.yml'
    - 'parent_file.yml'
...

Using custom extend key

If you don't want to use the default key 'extends:', you can specify your own custom key in two ways.

#custom1.yml
inherit_from:
    - 'super_file.yml'
foo: 'bar'
...
1. Specify by parameter

You can specify the key by parameter, this is the way to go if you want to use the different key only once or you use the ext_load_file method only once in your application.

config = YAML.ext_load_file 'custom1.yml', 'inherit_from'
2. Global configuration of the key

You can specify the key by configuration globally. So you only need to set the key once and not as parameter anymore

YAML.ext_load_key = 'inherit_from'
config = YAML.ext_load_file 'custom1.yml'
Reset the global key

To reset the global inheritance key, you can either set it to nil or call the reset_load_key method.

YAML.reset_load_key # more readable
YAML.ext_load_key = nil # more explicit

Using custom nested extend key

#custom2.yml
options:
    extend_file: 'super_file.yml'
    database: false
foo: 'bar'
...
config = YAML.ext_load_file 'custom2.yml', ['options','extend_file']

Command line

yaml_extend is also available on the command line after installation.

To parse and merge a prepared YAML file, just run

yaml_extend path/to/my/yaml_file.yml

Usually you might want to put the result into resulting YAML file. So just use the basic command line features to do so:

yaml_extend path/to/my/yaml_file.yml > combined_yaml.yml

Default options are used, custom options are not yet supported as parameters.

Documentation

YAML.ext_load_file(yaml_path, inheritance_key='extends', options = {})
  • yaml_path (String) relative or absolute path to yaml file to inherit from
  • inheritance_key (String) you can overwrite the default key, if you use the default 'extends' already as part of your configuration. The inheritance_key is NOT included, that means it will be deleted, in the final merged file. Default: 'extends'
  • options (Hash) collection of optional options, including all options of the based deep_merge gem
    • :preserve_inheritance_key (Boolean) Preserve inheritance key(s) from resulting yaml, does most time not make sense especially in multiple inheritance - DEFAULT: false
    • The following options are deep merge options that can be passed by - but the defaults differ from original https://github.com/danielsdeleo/deep_merge#options
    • :preserve_unmergeables (Boolean) Set to true to skip any unmergeable elements from source - DEFAULT: false
    • :knockout_prefix (String) Set to string value to signify prefix which deletes elements from existing element - DEFAULT: nil
    • :overwrite_arrays (Boolean) Set to true if you want to avoid merging arrays - DEFAULT: false
    • :sort_merged_arrays (Boolean) Set to true to sort all arrays that are merged together - DEFAULT: false
    • :unpack_arrays (String) Set to string value to run "Array::join" then "String::split" against all arrays - DEFAULT: nil
    • :merge_hash_arrays (Boolean) Set to true to merge hashes within arrays - DEFAULT: false
    • :extend_existing_arrays (Boolean) Set to true to extend existing arrays, instead of overwriting them - DEFAULT: true
    • :merge_nil_values (Boolean) Set to true to merge nil hash values, overwriting a possibly non-nil value - DEFAULT: false
    • :merge_debug (Boolean) Set to true to get console output of merge process for debugging - DEFAULT: false

See also rubydoc at https://www.rubydoc.info/gems/yaml_extend

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/magynhard/yaml_extend. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

yaml_extend's People

Contributors

ilude avatar jd-airship avatar jdpaton avatar magynhard avatar skyuchukov-teladoc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

yaml_extend's Issues

Boolean keys with value "true" in base YAML are not overwritten by "false" in extended YAMLs

For example,

Let the base YAML be:

# base.yml
alpha: true
beta: false
gamma: true
data:
    delta: false
    kappa: true
    theta: false

Let the extended YAML be:

# extended.yml
extends: 'base.yml'
alpha: false
beta: true
data:
    delta: true
    kappa: false

When you call

YAML.ext_load_file 'extended.yml'

the returned YAML value results in

alpha: true
beta: true
gamma: true
data:
    delta: true
    kappa: true
    theta: false

The values of alpha and kappa should actually be false since they are over-written in extended YAML

yaml_extend_issue

merged order wrong?

I thought this package would be a very nice addition to a mechanism I have in place to specify settings/configurations...but it seems the order in which merged files end up in the final result are giving not the results I would desire.

My settings syntax is as follows:

block:
- rule1:
   setting a
   setting b
   ....
- rule2:
  setting c 
  ...

The rules determine on what settings get applied. It is legal to list the same rule multiple times (that is why it is an array). If a setting is repeated, the 'last one wins' which feels most intuitive and follows what yaml does.

However in the following example:

# base.yaml
---
blockX:
- playground:
    macros:
      M_BASE: ja
      M_REPLACE: to_be_replaced
...
# settings.yaml
---
extends:
  - 'base.yaml'
blockX:
- playground:
    macros:
      M_DERIVE: ja
      M_REPLACE: replaced
...

Gives as result:

---
blockX:
- playground:
    macros:
      M_DERIVE: ja
      M_REPLACE: replaced
- playground:
    macros:
      M_BASE: ja
      M_REPLACE: to_be_replaced
...

In which it is unfortunate that the extended base comes last in the list, as a result when I process the file the macros result in:

M_DERIVE: ja
M_BASE: ja
M_REPLACE: to_be replaced ---> should haven bee 'replaced'

Would it be difficult to put the merged list in the order from base --> derived? (maybe optional)

Inheriting from several files does not merge them top to bottom

According to the docs:

If you want to inherit from several files, you can specify a list (Array) of files. They are merged from top to bottom, so the latest file "wins" - that means it overwrites duplicate values if they exist with the values in the latest file where they occur.

This however is not true. Actually the order seems to be exactly the opposite!
The later entries in the extends array have precedence over the earlier ones.
Additionally, the own keys of the file that extends multiple others tend to come first i.e. own entries first, then extended ones (the order is messed up, but they correctly overwrite the values of parent entries).

As an example, let's have the following 3 files:

# parent1.yml
parent1_key_overwritten: parent1
parent1_key_own: parent1
# parent2.yml
parent1_key_overwritten: parent2
parent2_key_own: parent2
# child.yml
extends:
  - parent1.yml
  - parent2.yml

some_other_key: child

The expected result of YAML.ext_load_file 'child.yml' should then be:

parent1_key_overwritten: parent2
parent1_key_own: parent1
parent2_key_own: parent2
some_other_key: child

but instead, what we get is:

some_other_key: child
parent1_key_overwritten: parent1
parent1_key_own: parent1
parent2_key_own: parent2

cannot find yaml files that are extended

if the yaml filesfile.ymland other.yml are in a directory called config

and the call YAML.ext_load_file 'config/file.yml' is issued with cwd one above config
then the other files that are extended are not found


# file.yml
extends: 
 - 'other.yml' # not found
 - 'config/other.yml'  # not found either

does not work with path relative to file.yml or relative to cwd

Need to pass options through to Psych

I've been using yaml_extend and like it. Thanks!

I now need to pass the symbolize_names option to Psych. Specifically I need to pass symbolize_names: true.
I don't see a way to do this. Is there a way to pass options thru to Psych? Am I overlooking something?

Emulating nested settings

Hi,

I was trying to use your gem in the following fashion -

nested_config.yml

options:
  extends: 'child_config.yml'

child_config.yml

blah: 'blah'

I want to be able to access the config like so config.options.blah rather than config.blah because I put the extends underneath the options key. Maybe I am doing something wrong? How do I achieve this?

Also, can you add multiple extends in a file?

Issues with the order extends work

As I understand, extends seems to add the extended files at the end of the main file, and not at the beginning.

This is a little counter intuitive (since everywhere else, extend or include means "first use the content of the included file"), and causes issues when using anchors.

Example use case:

This is a valid YAML:

one: &one
  hello: world

menu:
  <<: *one

While this is not a valid YAML:

menu:
  <<: *one

one: &one
  hello: world

So, if I wish to provide "blocks of YAML" that can be used by other blocks, I cannot use the extend functionality. The below example is broken:

# test.rb
require 'yaml_extend'
data = YAML.ext_load_file 'child.yml'
puts data.to_yaml
# child.yml
extends:
- parent.yml

menu:
  <<: *one
  <<: *two
# parent.yml
one: &one
  hello: world

two: &two
  hi: world
  hey: world

I know there are other ways around it, but my use case was more naturally implemented with anchors, and now I know they cannot be used this way.

Is there any reason for the included content to come last and not first?

Override only works for string values

Hi,
I've been looking to use this library and was wondering if it only works for values of type String.
Would it be possible to support at least other primitive types, like Integer, Boolean?

undefined method `start_with?' for #<Pathname:0x00000006327430>

Hi,

I've added the gem and followed the instructions, but I keep getting this error: undefined method `start_with?' for #Pathname:0x00000006327430

Method called: YAML.ext_load_file('/opt/config/test.yml')

Any ideas?

Thanks.

Details:
undefined method `start_with?' for #Pathname:0x00000006327430

/usr/local/rvm/gems/ruby-1.9.3-p551/gems/yaml_extend-1.1.1/lib/yaml_extend.rb:160:in `absolute_path?'

/usr/local/rvm/gems/ruby-1.9.3-p551/gems/yaml_extend-1.1.1/lib/yaml_extend.rb:142:in `make_absolute_path'

/usr/local/rvm/gems/ruby-1.9.3-p551/gems/yaml_extend-1.1.1/lib/yaml_extend.rb:107:in `ext_load_file_recursive'

/usr/local/rvm/gems/ruby-1.9.3-p551/gems/yaml_extend-1.1.1/lib/yaml_extend.rb:75:in `ext_load_file'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.