Better config files with node.js.
HConfig is a configuration file parser with JSON-like syntax but without all the cruft. It's not intended to be used for communication between computers, but rather to be written by humans.
Warning: HConfig expects input to be from a trusted source. While there's no extremely serious stuff like remote code execution, HConfig could expose information about your system to an attacker through expanding environment variables.
Here's an example config file for an imaginary web server:
conf.hcnf:
general {
port 8080
host localhost
index [ .html .htm ]
}
# This is where I host my cat pictures
virtual-host cats.example.com {
webroot /var/www/mycats
}
# Srs bsns
virtual-host resume.example.com {
webroot /var/www/resume
}
Parse it with this line:
hconfig.parseConfFile("conf.hcnf",
{ general: "once", "virtual-host": "many" });
and it returns this object:
{
"general": {
"port": 8080,
"host": "localhost",
"index": [ ".html", ".htm" ]
},
"virtual-host": [
{ "name": "cats.example.com", "webroot": "/var/www/mycats" },
{ "name": "resume.example.com", "webroot": "/var/www/resume" }
]
}
general
is an object, becasue we specified that it only exists once.
virtual-host
is an array, because we specified that it can exist many times.
Install:
npm install hconfig
Use:
var hconfig = require("hconfig");
hconfig.parseFile(filename);
// or
hconfig.parseString(string);
// or
hconfig.parseConfFile(filename);
// or
hconfig.parseConfString(string);
To run tests (requires mocha):
npm test
There are many JSON-like configuration formats with a prettier syntax out there, however those aren't really suitable for a lot of configuration files. Most config files for UNIX-style tools (e.g rc files, web servers, text editors) let you include other files, which is incredibly important in many situations, and neither JSON nor the JSON replacements out there are really built with that in mind.
With hconfig, you can create two files, say foo.hcnf and bar.hcnf, like this:
foo.hcnf:
virtual-host http://cats.example.com {
webroot /var/www/mycats
}
virtual-host http://resume.example.com {
webroot /var/www/resume
}
include bar.hcnf
bar.hcnf:
virtual-host https://webmail.example.com {
webroot /var/www/webmail
ssl-cert /etc/ssl/example.com.pem
ssl-key /etc/ssl/example.com.key
}
If our imaginary web server runs hcnf.parseConfFile("foo.hcnf")
, it will get
a data structure which looks like this:
{
"virtual-host": [
{
"name": "http://cats.example.com",
"webroot": "/var/www/mycats"
}, {
"name": "http://resume.example.com",
"webroot": "/var/www/resume"
}, {
"name": "https://webmail.example.com",
"webroot": "/var/www/webmail",
"ssl-cert": "/etc/ssl/example.com.pem",
"ssl-key": "/etc/ssl/example.co.key"
}
]
}
As you can imagine, this is very useful for a lot of general configuration stuff; there's a reason almost every traditional Linux and UNIX program has a feature for including other configuration files.
Parse a file, in the simple JSON.parse-style way.
By default, it will parse the file as if it was inside an object literal, but
without braces, and return an object. However, if includeRoot is set to
true, it will parse any value; {foo 10}
will be the object {foo: 10}
, but
"hello world"
will be a string, etc.
Like parseFile, but with a string instead of a file.
Parse a file in the mode detailed under the "UNIX style config files" heading.
If the sections parameter is an object, they will act as a whitelist, and an error will be thrown if the config file contains sections not in the object. This will throw an error, for example:
hconfig.parseConfFile("foo.hcnf", { "virtual-host": "many" });
foo.hcnf:
virtual-hots example.com { webroot /var/www }
You can also specify that a section should only exist once. This will thus throw an error:
hconfig.parseConfFile("foo.hcnf", { "general": "once" });
foo.hcnf:
general {
port 8080
}
general {
port 8081
}
Like parseConfFile, but with a string instead of a file.
If the parser encounters a problem with the file it's parsing, it will
throw an instance of the Error object. To differentiate parse errors from bugs
in HConfig, a hconfigParseError
property is set to true
. I would suggest
handling errors something like this:
let data;
try {
data = hconfig.parseFile("conf.hcnf");
} catch (err) {
if (err.hconfigParseError) {
console.error(err.message);
process.exit(1);
} else {
throw err;
}
}
It's useful for the user to get error messages whenever they've configured something incorrectly, so HConfig has a built-in way to specify the structure of your config files to give useful error messages.
Available types: string
, number
, array
, object
, bool
, null
, any
.
Note that a type specified as null
can be either null or undefined.
hconfig.parseConfFile("foo.hcnf", {
general: {
count: "once",
props: {
port: "number",
host: "string",
index: [ "array", "string" ]
}
},
"virtual-host": {
count: "many",
props: {
webroot: "string",
"ssl-cert": "string",
"ssl-key": "string"
}
}
});
You can also validate the name
property (the value between the section name
and the section block) like any other, but unlike other properties, it defaults
to [ "string", "null" ]
. but if you manually set it, it can be any type
except for objects (as that syntax would just be confusing). Let's say you want
to allow people to use an array of hostnames in virtual-host (but not allow it
to be unspecified):
hconfig.parseConfFile("foo.hcnf", {
"virtual-host": {
count: "many",
props: {
name: [ "string", "array" ],
webroot: "string"
}
}
});
foo.hcnf
:
virtual-host [ www.example.com example.com] {
webroot /var/www/example.com
}
You can also specify the default validation of properties by using *
. Unless
a default is specified, unknown properties will result in an error.
Here we allow unknown properties to be anything:
hconfig.parseConfFile("foo.hcnf", {
general: {
props: {
"*": "any"
}
}
})
HConfig contains the basic javascript data types; strings, numbers, objects, arrays, booleans, and null. In addition, it has the concept of sections, though those only exist when using parseConfFile and parseConfString.
-
A quoted string starts with a
"
, and continues until the next"
.$(FOO)
expands into the environment variable FOO\\
=>\
\"
=>"
\b
=> backspace\f
=> formfeed\n
=> newline\r
=> carriage return\t
=> tab\uXXXX
=> unicode character
-
A quoted string can also start with a
'
. It then continues until the next'
. This type of string doesn't expand escape sequences or environment variables. -
An unquoted string is a sequence of any characters other than whitespace,
[
,]
,[
, and}
, and which doesn't match any other syntax. Note that strings can start with numbers just fine, as long as they don't entirely match the syntax for numbers -200x400
will be interpreted as a string.
- Like javascript's numbers. Allows exponents with
e
andE
.
true
is the boolean true,false
is the boolean false, andnull
is null.
- An object starts with a
{
, followed by any number of key/value pairs, and is terminated with}
. - A key value pair is a string followed by a value. There is no separator between pairs, nor symbol between the key and value, other than whitespace.
{ port 8080 host example.com }
=> { port: 8080, host: "example.com" }
- An array starts with a
[
, followed by any number of values, and is terminated with]
. - Like with objects, there is no separator between values.
[ 10 20 50 ]
=> [ 10, 20 50 ]
- A "section" only exists when parsed with parseConfFile or parseConfString.
- The root of the file is a whitespace separated list of sections.
- They start with a mandatory string (the section name), followed by an optional string (the name property), followed by an object.
virtual-host example.com { foo 10 }
virtual-host blog.example.com { foo 15 }
becomes:
{
"virtual-host": [
{ "name": "example.com", "foo": 10 },
{ "name": "blog.example.com", "foo": 15 }
]
}
- An include is the unquoted string
include
, followed by another string, in the root of the file (e.g not inside any section). - It's only valid when parsed with parseConfFile or parseConfString.
- Includes the file at the specified path, which can be relative or absolute.
- Relative paths are relative to the file, or the current directory if parsed with parseConfString.
- Comments start with a #.
- Everything from # to the end of the line is a comment, and thus ignored.