Project

ydl

0.0
The project is in a healthy, maintained state
Ydl provides a way to supply a ruby app with initialized objects by allowing the user to supply the data about the objects in a hierarchical series of "data definition files" with the extension .ydl.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
 Dependencies

Development

>= 1.0.0
>= 0.1.15
>= 0
>= 0
~> 3.0

Runtime

 Project Readme

Ydl

Ydl provides a way to supply a ruby app with initialized objects by allowing the user to supply the data about the objects in a hierarchical series of “data definition files” with the extension .ydl. In particular, the ydl command Ydl.load finds all files with the extension .ydl in the following locations and uses the data in them to instantiate Ruby objects:

  1. all .ydl files a configurable system-wide directory, by default /etc/ydl
  2. all .ydl files in the user’s directory /home/user/.ydl and all directories under that directory, recursively, and
  3. all .ydl files in each directory starting at the user’s home directory (or the root directory if the current directory is not directly or indirectly underneath the user’s home directory) and continuing to the current working directory,

After calling Ydl.load, you can access all the objects described by the .ydl files from a hierarchical tree of nested hashes via the global hash Ydl.data[<key] (or, more concisely, Ydl[<key]).

The .ydl files are in YAML format, with a couple of twists. One twist is that you can use cross-references of the form ydl:/path/to/other/node to set the value of one node to its value at another place in the data hierarchy. The second twist is that nodes having names of classes that you specify in your config are automatically instantiated into Ruby objects of that class and made available in the ydl data tree.

Installation

Add this line to your application’s Gemfile:

gem 'ydl'

And then execute:

$ bundle

Or install it yourself as:

$ gem install ydl

Usage

Ydl allows you to build a Hash, available as Ydl.data, containing all the items defined in the data definition files:

require 'ydl'

Ydl.load
Ydl.data.class     #=> Hash
Ydl.data[:persons] #=> { joe: {....}, mary: {....} }
Ydl.data[:lawyers] #=> { anne: {....}, will: {....} }

You can load only files with a given base name. Ydl.load then returns a Hash just of the objects from files with the given base name, but also adds them to the global Ydl.data variable as well.

require 'ydl'

people = Ydl.load('persons')
people.class        #=> Hash
people              #=> { joe: {....}, mary: {....} }
people[:joe].class  #=> Person
Ydl.data[:persons][:joe].class  #=> Person

lawyers = Ydl.load('lawyers')
lawyers.class        #=> Hash
lawyers              #=> { will: {....}, anne: {....} }
lawyers[:will].class  #=> Lawyer
Ydl.data[:lawyers][:will].class  #=> Lawyer

Data Definition Files

The data definition files have the extension .ydl and are read from the following places:

  • from a system wide directory that is by default, /etc/ydl, including all its subdirectories,
  • from a user-specific directory in the user’s HOME directory that is by default, ~/.ydl, including all its subdirectories,
  • in the current working directory and all directories above the current directory up to the user’s home directory or the root directory, whichever it hits first.

The data definitions are read in the same order given above, except that the third category reads files in the HOME directory first, then in each directory from there to the current directory in order. In other words, directories closer to the current directory take priority over those higher up in the directory hierarchy.

In each case, all files with the extension ‘.ydl’ are read and only those files. Definitions in files read later are merged into those read earlier, overwriting any data having the same key or appending to array data having the same key.

The base name of each file is used as an outer key for the contents of the file. For example, a file ~’persons.ydl’~ in the current directory will be treated as the value of a Hash with the key :persons.

Automatic Instantiation of Classes

If, when the files are read in, there is a class whose name is the camelized and singularized version of a hash key, and if the value of that key is itself a hash, an instance of the class is initialized using the hash values as initializers. So if there is a class defined whose last component is Person, the contents of the file person.ydl will instantiate objects of that class. This process is recursive, so values that are hashes with keys matching class names are instantiated as well. If there is more than one such class, an exception is raised.

You can restrict the classes searched for by setting the class_modules config setting to a string or a list of strings of class prefixes to be consulted. If class_modules is set to ‘Company::Engineers’, only the class Company::Engineers::Person will be instantiated for objects under the :persons hierarchy. By default all modules will be consulted.

In order for this to work, the initialize method for the classes must be able to take a Hash as an argument to .new. A different initializer method can be specified for each class with the class_init option in the configuration file.

If no class is found, the item is left as a Hash.

Cross References

String values can have the form ydl:/person/smith/address/city or ydl:person/smith/address/city (the presence of the leading ‘/’ is optional and has no meaning), that is, the ‘ydl:’ specifier followed by a “data path”, much like a file name path, will, upon resolution, look up the value of the given item and return it as the value of that element. Resolution of ydl: elements is deferred until all files have been read in so that forward references are possible. However, Ydl will not look outside the files being loaded to find a cross-reference, so if you selectively load files, you may not be able to resolve some cross-references. Circular cross-references raise a Ydl::CircularReference error.

Configuration

Ydl looks for a configuration file in .ydl/config.yaml of your HOME directory. Here is the sample configuration that explains the options available:

# You can set the system-wide ydl directory here; otherwise it defaults to
# /usr/local/share/ydl.

# system_ydl_dir: /usr/local/share/ydl

# For automatic instantiation, search for classes prefixed by the given modules
# in the order given. For example, if the key 'breed' is to be instantiated, you
# can restrict the search for classes named 'Breed' only in modules, 'Dog' and
# 'Cat' with this:
#
#   class_modules:
#     - Dog
#     - Cat
#
# then, only Dog::Breed and Cat::Breed will be searched for an existing breed
# class. Otherwise, any class ending in Breed could be used, and they will be
# searched in alphabetical order, and the first found will be used.
#
# A blank value means to consider classes in the main, global module level. You
# can always disambiguate the class selected with the class_map option below.

class_modules:
  -
  - LawDoc
  - Company::Employee

# By default, each key will be camelized and singularized to find the matching
# class. So, the key 'dogs' will look for a class named 'Dog', and 'dog_faces'
# will look for a class 'DogFace'. You can override this heuristic here by
# saying exactly which class a given key should map to.
class_map:
  address: LawDoc::Address
  persons: LawDoc::Person
  fax: LawDoc::Phone

# Specify constructors for classes whose .new method will not take a Hash as an
# argument to initialize the class.
class_init:
  LawDoc::Person: from_hash

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to [rubygems.org](https://rubygems.org).

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ddoherty03/ydl.