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.
Accessing configuration works through the
(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.
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
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.
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
(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
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
(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-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,
JSON. Since differing formats support different types, there's a few switches that control the serialization process:
*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.
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.
If set to non-NIL, (SETF (CONFIG-TREE ..) ..) will attempt to augment missing places.
The global configuration storage variable.
Fallback function used when no deserializer method matched. Useful for applying deserializers specific to the input format.
Fallback function used when no serializer method matched. Useful for applying serializers specific to the output format.
The default output format to use.
Whether to serialize hash-tables into a vector and a hash-table. This is necessary for most output formats as there is no differentiation made between hash-table tests. Serializing them will ensure that the proper test can be restored upon deserializing.
Whether to serialize lists into vector representation. This is necessary for most output formats as there are no two representations of list- or vector-like structures.
Whether to serialize numbers into string representation. This is necessary for many output formats as they do not support the variety of number types lisp provides (ratios, floats, complex numbers).
Whether symbols should be serialized into string representation. Note that symbol plists are not serialized into strings.
Universal object accessor. Returns two values, the first being the accessed value or the supplied default, the second being T if the requested place was found or NIL if the default is returned.
Warning condition signalled when a place is augmented automatically.
Retrieve a value from the configuration.
Shorthand macro to define de/serializer methods for a class and the specified slots. CLASS --- A class type. SLOTDEFS ::= SLOTDEF* SLOTDEF ::= SLOT-SYMBOL | (SLOT-SYMBOL INITARG-SYMBOL)
Define a new OBJECT-TYPE to deserialize into a usable representation. If EXPECT-VECTOR is non-NIL, the bound OBJECT-VAR will be of type VECTOR.
Define a format of NAME to load an object from a stream.
Define a format of NAME to save an object to a stream.
Define an OBJECT-TYPE to be serialized. The expected value of this function should be one of HASH-TABLE, VECTOR, STRING or NUMBER. If RETURN-VECTOR is non-NIL, the object returned should be of type VECTOR.
Defines an OBJECT-TYPE to be serialized to a string. To discern string serialized objects, an IDENT-CHAR is needed.
Deserialize an OBJECT into a usable configuration object.
Escape all instances of CHAR in the string that match CHAR with a backslash.
Error condition signalled when attempting to set an inexistent place.
Load the configuration from PATH with the given FORMAT.
Attempts to create a fitting container for an accessor.
Save the configuration OBJECT to PATH using the given FORMAT.
Serialize the given object recursively into a format that is ready for outputting.
Sets the place indicated by the ACCESSORS list to VALUE if possible. See (SETF CONFIG-TREE)
Split the string by CHAR minding backslash escaped instances.
Unescape all backslash escaped instances of CHAR.
Establishes a configuration context.