Universal-Config

v1.0.0

What is Universal-Config?

This library tries to provide a layer for configuration files and storage that should be able to map any type of object to any configuration format and back.

How To

Accessing configuration works through the CONFIG-TREE function.

(uc:config-tree :access "a path" 2)

CONFIG-TREE internally uses the ACCESS function that tries to generically dispatch according to accessor and current object. By default it understands hash-tables, arrays, sequences, lists and slots on standard-objects. Support for alists and plists is not provided, as it isn't possible to properly distinguish them in a generic fashion. CONFIG-TREE and ACCESS are setf-able places. The former will try to augment missing objects in the configuration automatically upon setf if *AUGMENT-MISSING-PLACES* is non-NIL.

Saving and loading the configuration works with SAVE-CONFIGURATION and LOAD-CONFIGURATION respectively.

(uc:save-configuration #p"~/test.lisp")

Depending on what format is available (by default lisp and json), you can pass the format argument:

(uc:save-configuration #p"~/test.json" :format :json)

Due to having to save arbitrary types, universal-config has to serialize objects into a common structure. Depending on the amount of types the format supports, this will produce more or less gross output. Standard lisp format makes an effort to keep it readable though.

Extending Universal-Config

Universal-Config can be extended in three ways, the first being object accessors. In order to make Universal-Config support your own data structures, define methods for ACCESS and (SETF ACCESS). The ACCESS method should always return two values, the first being the actual value or the supplied default value and the second being T if the place was found or NIL otherwise.

(defmethod uc:access ((list list) (accessor keyword) &optional default)
    (let ((return (assoc accessor list)))
      (if return (values return T) (values default NIL))))
(defmethod (setf uc:access) (value (list list) (accessor keyword))
    (setf (cdr (assoc accessor list)) value))

Secondly, to make extra types like classes be serializable, you have to extend SERIALIZE and DESERIALIZE. The easiest way to do this is in a fashion similar to this:

(uc:define-serializer (my-class instance T)
    (make-array 2 :initial-contents (list (uc:serialize (first-slot instance)) (uc:serialize (other-slot instance)))))

The last optional argument to DEFINE-SERIALIZER is a minor optimization switch that should be set to T if the returned object is a vector.

(uc:define-deserializer (my-class vector T)
    (make-instance 'my-class :first-slot (uc:deserialize (aref vector 0)) :other-slot (uc:deserialize (aref vector 1))))

However, adding de/serializers for classes can be eased with the shorthand macro DEFINE-CLASS-DE/SERIALIZER:

(uc:define-class-de/serializer my-class first-slot other-slot)

When serializing, the output type should always be either an array, list, hash-map, string or number. Strings are handled specially though to allow for different types to be serialized into strings for output formats that don't support their canonical representations. The type the string is deserialized into is determined by its first character. Standard strings should therefore be preceded by #\- . If you have a data type that would fit better into a single string, you can define new de/serializing constructs for that through DEFINE-STRING-SERIALIZER and DEFINE-STRING-DESERIALIZER. By default string de/serializers for symbols, integers, ratios, floats and complex numbers are defined.

The last part is defining your own external formats. Universal-Config comes with two formats, LISP and JSON. Since differing formats support different types, there's a few switches that control the serialization process: *SERIALIZE-SYMBOLS* *SERIALIZE-NUMBERS* *SERIALIZE-HASH-TABLES* *SERIALIZE-LISTS*. The first two are string-serializer switches, the latter two decide whether these types should be wrapped in a vector, since most output formats don't support vectors and lists as different types, nor the different hash-table tests.

(uc:define-save-format my-format (stream object)
    (my-printer stream (serialize object)))
(uc:define-load-format my-format (stream)
    (deserialize (my-reader stream)))

Due to the way formats differ wildly, Universal-Config cannot assist further than providing the de/serialization facilities. It should not be too difficult to plug this into an existing format library though.

Other Guff

Universal-Config is licensed under the Artistic License 2.0 and ©2013 TymoonNET/NexT, Nicolas Hafner.
This library can be obtained via git on https://github.com/Shinmera/universal-config.git. For questions, patches or suggestions, please contact me via email or write a github issue.

Universal-Config Package Index